mirror of
https://github.com/django/daphne.git
synced 2025-04-20 00:32:09 +03:00
Rename to plural "channels", start fleshing out req/resp cycle
This commit is contained in:
parent
821816f656
commit
6cd01e2bc1
|
@ -1,7 +0,0 @@
|
|||
from .consumer_registry import ConsumerRegistry
|
||||
|
||||
# Make a site-wide registry
|
||||
coreg = ConsumerRegistry()
|
||||
|
||||
# Load an implementation of Channel
|
||||
from .channels.memory import Channel
|
|
@ -1,18 +0,0 @@
|
|||
from django.core.handlers.base import BaseHandler
|
||||
from channel import Channel
|
||||
from .response import encode_response
|
||||
from .request import decode_request
|
||||
|
||||
|
||||
class DjangoUrlAdapter(object):
|
||||
"""
|
||||
Adapts the channel-style HTTP requests to the URL-router/handler style
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.handler = BaseHandler()
|
||||
self.handler.load_middleware()
|
||||
|
||||
def __call__(self, request, response_channel):
|
||||
response = self.handler.get_response(decode_request(request))
|
||||
Channel(response_channel).send(**encode_response(response))
|
11
channels/__init__.py
Normal file
11
channels/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from .consumer_registry import ConsumerRegistry
|
||||
|
||||
# Make a site-wide registry
|
||||
coreg = ConsumerRegistry()
|
||||
|
||||
# Load an implementation of Channel
|
||||
from .backends import InMemoryChannel as Channel
|
||||
|
||||
# Ensure monkeypatching
|
||||
from .hacks import monkeypatch_django
|
||||
monkeypatch_django()
|
47
channels/adapters.py
Normal file
47
channels/adapters.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from django.core.handlers.base import BaseHandler
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from channels import Channel, coreg
|
||||
|
||||
|
||||
class UrlConsumer(object):
|
||||
"""
|
||||
Dispatches channel HTTP requests into django's URL system.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.handler = BaseHandler()
|
||||
self.handler.load_middleware()
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
request = HttpRequest.channel_decode(kwargs)
|
||||
try:
|
||||
response = self.handler.get_response(request)
|
||||
except HttpResponse.ResponseLater:
|
||||
return
|
||||
Channel(request.response_channel).send(**response.channel_encode())
|
||||
|
||||
|
||||
def view_producer(channel_name):
|
||||
"""
|
||||
Returns a new view function that actually writes the request to a channel
|
||||
and abandons the response (with an exception the Worker will catch)
|
||||
"""
|
||||
def producing_view(request):
|
||||
Channel(channel_name).send(**request.channel_encode())
|
||||
raise HttpResponse.ResponseLater()
|
||||
return producing_view
|
||||
|
||||
|
||||
def view_consumer(channel_name):
|
||||
"""
|
||||
Decorates a normal Django view to be a channel consumer.
|
||||
Does not run any middleware.
|
||||
"""
|
||||
def inner(func):
|
||||
def consumer(**kwargs):
|
||||
request = HttpRequest.channel_decode(kwargs)
|
||||
response = func(request)
|
||||
Channel(request.response_channel).send(**response.channel_encode())
|
||||
coreg.add_consumer(consumer, [channel_name])
|
||||
return func
|
||||
return inner
|
2
channels/backends/__init__.py
Normal file
2
channels/backends/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from .base import BaseChannel
|
||||
from .memory import InMemoryChannel
|
|
@ -1,4 +1,4 @@
|
|||
class Channel(object):
|
||||
class BaseChannel(object):
|
||||
"""
|
||||
Base class for all channel layer implementations.
|
||||
"""
|
||||
|
@ -51,3 +51,12 @@ class Channel(object):
|
|||
response channels.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def as_view(self):
|
||||
"""
|
||||
Returns a view version of this channel - one that takes
|
||||
the request passed in and dispatches it to our channel,
|
||||
serialized.
|
||||
"""
|
||||
from channels.adapters import view_producer
|
||||
return view_producer(self.name)
|
|
@ -2,12 +2,12 @@ import time
|
|||
import string
|
||||
import random
|
||||
from collections import deque
|
||||
from .base import Channel as BaseChannel
|
||||
from .base import BaseChannel
|
||||
|
||||
queues = {}
|
||||
closed = set()
|
||||
|
||||
class Channel(BaseChannel):
|
||||
class InMemoryChannel(BaseChannel):
|
||||
"""
|
||||
In-memory channel implementation. Intended only for use with threading,
|
||||
in low-throughput development environments.
|
0
channel/consumer_registry.py → channels/consumer_registry.py
Executable file → Normal file
0
channel/consumer_registry.py → channels/consumer_registry.py
Executable file → Normal file
23
channels/docs/integration-changes.rst
Normal file
23
channels/docs/integration-changes.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
Message Standards
|
||||
=================
|
||||
|
||||
Some standardised message formats are used for common message types - they
|
||||
are detailed below.
|
||||
|
||||
HTTP Request
|
||||
------------
|
||||
|
||||
Represents a full-fledged, single HTTP request coming in from a client.
|
||||
Contains the following keys:
|
||||
|
||||
* request: An encoded Django HTTP request
|
||||
* response_channel: The channel name to write responses to
|
||||
|
||||
|
||||
HTTP Response
|
||||
-------------
|
||||
|
||||
Sends a whole response to a client.
|
||||
Contains the following keys:
|
||||
|
||||
* response: An encoded Django HTTP response
|
23
channels/docs/message-standards.rst
Normal file
23
channels/docs/message-standards.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
Message Standards
|
||||
=================
|
||||
|
||||
Some standardised message formats are used for common message types - they
|
||||
are detailed below.
|
||||
|
||||
HTTP Request
|
||||
------------
|
||||
|
||||
Represents a full-fledged, single HTTP request coming in from a client.
|
||||
Contains the following keys:
|
||||
|
||||
* request: An encoded Django HTTP request
|
||||
* response_channel: The channel name to write responses to
|
||||
|
||||
|
||||
HTTP Response
|
||||
-------------
|
||||
|
||||
Sends a whole response to a client.
|
||||
Contains the following keys:
|
||||
|
||||
* response: An encoded Django HTTP response
|
27
channels/hacks.py
Normal file
27
channels/hacks.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from django.http.request import HttpRequest
|
||||
from django.http.response import HttpResponseBase
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from .request import encode_request, decode_request
|
||||
from .response import encode_response, decode_response, ResponseLater
|
||||
|
||||
|
||||
def monkeypatch_django():
|
||||
"""
|
||||
Monkeypatches support for us into parts of Django.
|
||||
"""
|
||||
# Request encode/decode
|
||||
HttpRequest.channel_encode = encode_request
|
||||
HttpRequest.channel_decode = staticmethod(decode_request)
|
||||
# Response encode/decode
|
||||
HttpResponseBase.channel_encode = encode_response
|
||||
HttpResponseBase.channel_decode = staticmethod(decode_response)
|
||||
HttpResponseBase.ResponseLater = ResponseLater
|
||||
# Allow ResponseLater to propagate above handler
|
||||
BaseHandler.old_handle_uncaught_exception = BaseHandler.handle_uncaught_exception
|
||||
BaseHandler.handle_uncaught_exception = new_handle_uncaught_exception
|
||||
|
||||
|
||||
def new_handle_uncaught_exception(self, request, resolver, exc_info):
|
||||
if exc_info[0] is ResponseLater:
|
||||
raise
|
||||
return BaseHandler.old_handle_uncaught_exception(self, request, resolver, exc_info)
|
24
channel/management/commands/runinterfaceserver.py → channels/management/commands/runserver.py
Executable file → Normal file
24
channel/management/commands/runinterfaceserver.py → channels/management/commands/runserver.py
Executable file → Normal file
|
@ -2,11 +2,11 @@ import django
|
|||
import threading
|
||||
from django.core.management.commands.runserver import Command as RunserverCommand
|
||||
from django.core.handlers.wsgi import WSGIHandler
|
||||
from channel import Channel, coreg
|
||||
from channel.request import encode_request
|
||||
from channel.response import decode_response
|
||||
from channel.worker import Worker
|
||||
from channel.utils import auto_import_consumers
|
||||
from django.http import HttpResponse
|
||||
from channels import Channel, coreg
|
||||
from channels.worker import Worker
|
||||
from channels.utils import auto_import_consumers
|
||||
from channels.adapters import UrlConsumer
|
||||
|
||||
|
||||
class Command(RunserverCommand):
|
||||
|
@ -24,7 +24,8 @@ class Command(RunserverCommand):
|
|||
# Check a handler is registered for http reqs
|
||||
auto_import_consumers()
|
||||
if not coreg.consumer_for_channel("django.wsgi.request"):
|
||||
raise RuntimeError("No consumer registered for WSGI requests")
|
||||
# Register the default one
|
||||
coreg.add_consumer(UrlConsumer(), ["django.wsgi.request"])
|
||||
# Launch a worker thread
|
||||
worker = WorkerThread()
|
||||
worker.daemon = True
|
||||
|
@ -39,13 +40,10 @@ class WSGIInterfaceHandler(WSGIHandler):
|
|||
"""
|
||||
|
||||
def get_response(self, request):
|
||||
response_channel = Channel.new_name("django.wsgi.response")
|
||||
Channel("django.wsgi.request").send(
|
||||
request = encode_request(request),
|
||||
response_channel = response_channel,
|
||||
)
|
||||
channel, message = Channel.receive_many([response_channel])
|
||||
return decode_response(message)
|
||||
request.response_channel = Channel.new_name("django.wsgi.response")
|
||||
Channel("django.wsgi.request").send(**request.channel_encode())
|
||||
channel, message = Channel.receive_many([request.response_channel])
|
||||
return HttpResponse.channel_decode(message)
|
||||
|
||||
|
||||
class WorkerThread(threading.Thread):
|
2
channel/request.py → channels/request.py
Executable file → Normal file
2
channel/request.py → channels/request.py
Executable file → Normal file
|
@ -15,6 +15,7 @@ def encode_request(request):
|
|||
"path": request.path,
|
||||
"path_info": request.path_info,
|
||||
"method": request.method,
|
||||
"response_channel": request.response_channel,
|
||||
}
|
||||
return value
|
||||
|
||||
|
@ -31,4 +32,5 @@ def decode_request(value):
|
|||
request.path = value['path']
|
||||
request.method = value['method']
|
||||
request.path_info = value['path_info']
|
||||
request.response_channel = value['response_channel']
|
||||
return request
|
9
channel/response.py → channels/response.py
Executable file → Normal file
9
channel/response.py → channels/response.py
Executable file → Normal file
|
@ -27,3 +27,12 @@ def decode_response(value):
|
|||
)
|
||||
response._headers = {k.lower: (k, v) for k, v in value['headers']}
|
||||
return response
|
||||
|
||||
|
||||
class ResponseLater(Exception):
|
||||
"""
|
||||
Class that represents a response which will be sent doown the response
|
||||
channel later. Used to move a django view-based segment onto the next
|
||||
task, as otherwise we'd need to write some kind of fake response.
|
||||
"""
|
||||
pass
|
2
channel/utils.py → channels/utils.py
Executable file → Normal file
2
channel/utils.py → channels/utils.py
Executable file → Normal file
|
@ -10,5 +10,5 @@ def auto_import_consumers():
|
|||
try:
|
||||
__import__(consumer_module_name)
|
||||
except ImportError as e:
|
||||
if "no module named" not in str(e).lower():
|
||||
if "no module named consumers" not in str(e).lower():
|
||||
raise
|
0
channel/worker.py → channels/worker.py
Executable file → Normal file
0
channel/worker.py → channels/worker.py
Executable file → Normal file
Loading…
Reference in New Issue
Block a user