mirror of
https://github.com/django/daphne.git
synced 2025-06-28 17:03:03 +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.
|
Base class for all channel layer implementations.
|
||||||
"""
|
"""
|
||||||
|
@ -51,3 +51,12 @@ class Channel(object):
|
||||||
response channels.
|
response channels.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
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 string
|
||||||
import random
|
import random
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from .base import Channel as BaseChannel
|
from .base import BaseChannel
|
||||||
|
|
||||||
queues = {}
|
queues = {}
|
||||||
closed = set()
|
closed = set()
|
||||||
|
|
||||||
class Channel(BaseChannel):
|
class InMemoryChannel(BaseChannel):
|
||||||
"""
|
"""
|
||||||
In-memory channel implementation. Intended only for use with threading,
|
In-memory channel implementation. Intended only for use with threading,
|
||||||
in low-throughput development environments.
|
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
|
import threading
|
||||||
from django.core.management.commands.runserver import Command as RunserverCommand
|
from django.core.management.commands.runserver import Command as RunserverCommand
|
||||||
from django.core.handlers.wsgi import WSGIHandler
|
from django.core.handlers.wsgi import WSGIHandler
|
||||||
from channel import Channel, coreg
|
from django.http import HttpResponse
|
||||||
from channel.request import encode_request
|
from channels import Channel, coreg
|
||||||
from channel.response import decode_response
|
from channels.worker import Worker
|
||||||
from channel.worker import Worker
|
from channels.utils import auto_import_consumers
|
||||||
from channel.utils import auto_import_consumers
|
from channels.adapters import UrlConsumer
|
||||||
|
|
||||||
|
|
||||||
class Command(RunserverCommand):
|
class Command(RunserverCommand):
|
||||||
|
@ -24,7 +24,8 @@ class Command(RunserverCommand):
|
||||||
# Check a handler is registered for http reqs
|
# Check a handler is registered for http reqs
|
||||||
auto_import_consumers()
|
auto_import_consumers()
|
||||||
if not coreg.consumer_for_channel("django.wsgi.request"):
|
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
|
# Launch a worker thread
|
||||||
worker = WorkerThread()
|
worker = WorkerThread()
|
||||||
worker.daemon = True
|
worker.daemon = True
|
||||||
|
@ -39,13 +40,10 @@ class WSGIInterfaceHandler(WSGIHandler):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_response(self, request):
|
def get_response(self, request):
|
||||||
response_channel = Channel.new_name("django.wsgi.response")
|
request.response_channel = Channel.new_name("django.wsgi.response")
|
||||||
Channel("django.wsgi.request").send(
|
Channel("django.wsgi.request").send(**request.channel_encode())
|
||||||
request = encode_request(request),
|
channel, message = Channel.receive_many([request.response_channel])
|
||||||
response_channel = response_channel,
|
return HttpResponse.channel_decode(message)
|
||||||
)
|
|
||||||
channel, message = Channel.receive_many([response_channel])
|
|
||||||
return decode_response(message)
|
|
||||||
|
|
||||||
|
|
||||||
class WorkerThread(threading.Thread):
|
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": request.path,
|
||||||
"path_info": request.path_info,
|
"path_info": request.path_info,
|
||||||
"method": request.method,
|
"method": request.method,
|
||||||
|
"response_channel": request.response_channel,
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -31,4 +32,5 @@ def decode_request(value):
|
||||||
request.path = value['path']
|
request.path = value['path']
|
||||||
request.method = value['method']
|
request.method = value['method']
|
||||||
request.path_info = value['path_info']
|
request.path_info = value['path_info']
|
||||||
|
request.response_channel = value['response_channel']
|
||||||
return request
|
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']}
|
response._headers = {k.lower: (k, v) for k, v in value['headers']}
|
||||||
return response
|
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:
|
try:
|
||||||
__import__(consumer_module_name)
|
__import__(consumer_module_name)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
if "no module named" not in str(e).lower():
|
if "no module named consumers" not in str(e).lower():
|
||||||
raise
|
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