Skip to content

Instantly share code, notes, and snippets.

@jjmontesl
Last active July 9, 2018 20:55
Show Gist options
  • Save jjmontesl/203ac28b6690fb70de67818cb4d6f149 to your computer and use it in GitHub Desktop.
Save jjmontesl/203ac28b6690fb70de67818cb4d6f149 to your computer and use it in GitHub Desktop.
Draft of an integration of Django HTTP on top of Quart-Asyncio.
import logging
import asyncio
from cf.comp.component import Component
from quart import Quart, websocket, app, send_from_directory, redirect, request, url_for
from quart.serving import run_app, Server
from django.core.servers.basehttp import (
WSGIServer, get_internal_wsgi_application, run,
)
from django.core.handlers.wsgi import WSGIRequest
from cf.core.injection import Inject
from cf_django.django import DjangoService
import os
from http.cookies import SimpleCookie
import pdb
logger = logging.getLogger(__name__)
logger_quart = logging.getLogger("quart")
class DjangoHTTPService(Component):
'''
'''
http = None
django = Inject(DjangoService)
async def initialize(self):
logger.info("Starting Django HTTP bridge: %s", os.environ['DJANGO_SETTINGS_MODULE'])
self.handler = get_internal_wsgi_application()
#run(self.addr, int(self.port), handler,
# ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
@self.http.app.route('/<path:path>', methods=['GET', 'POST'])
async def cf_django(path):
headers = request.headers
data = await request.data
values = await request.values
body = await request.body
# As per: https://github.com/django/django/blob/master/django/core/handlers/wsgi.py
# and: https://www.python.org/dev/peps/pep-0333/
environ = {}
environ['SERVER_PROTOCOL'] = 'HTTP/1.1'
environ['SCRIPT_NAME'] = ''
environ['REQUEST_METHOD'] = request.method.upper()
environ['PATH_INFO'] = '/' + path if path else '/'
environ['SERVER_NAME'] = "localhost"
environ['SERVER_PORT'] = 8000
if request.content_type:
environ['CONTENT_TYPE'] = request.content_type
if request.cookies:
cookies = SimpleCookie({k: v for k, v in request.cookies.items()})
environ['HTTP_COOKIE'] = str(cookies).split(" ", 1)[1]
if headers:
for k, v in headers.items():
environ[k] = v
if data:
environ['QUERY_STRING'] = str(data)
environ['wsgi.input'] = body
logger.debug("Django Request: %s", environ)
logger.debug(headers)
logger.debug(data)
django_request = WSGIRequest(environ)
response = self.handler.get_response(django_request)
logger.debug(response)
#pdb.set_trace()
logger.debug(response.cookies)
resp_headers = {}
if response.cookies:
resp_headers['Set-Cookie'] = str(response.cookies).split(' ', 1)[1]
return (response, response.status_code, resp_headers)
@self.http.app.route('/', methods=['GET', 'POST'])
async def cf_django_root():
return await cf_django(None)
import logging
import asyncio
from cf.comp.component import Component
from quart import Quart, websocket, app, send_from_directory, redirect, request, url_for
from quart.serving import run_app, Server
import os
logger = logging.getLogger(__name__)
class DjangoService(Component):
'''
'''
settings = None
async def initialize(self):
logger.debug("Initializing Django (settings=%s)", self.settings)
os.environ['DJANGO_SETTINGS_MODULE'] = self.settings
from django.conf import settings
self._settings = settings
#settings.configure()
import logging
import asyncio
from cf.comp.component import Component
from quart import Quart, websocket, app, send_from_directory
from quart.serving import run_app, Server
logger = logging.getLogger(__name__)
logger_quart = logging.getLogger("quart")
class HTTPService(Component):
'''
'''
host = "127.0.0.1"
port = 8000
app = None
async def initialize(self):
logger.info("Starting HTTP service [host=%s, port=%s]", self.host, self.port)
self.app = Quart("olass")
'''
app.run(host=self.host,
port=self.port,
debug=self.ctx.olass.debug,
access_log_format="%(h)s %(r)s %(s)s %(b)s %(D)s",
keep_alive_timeout=5,
#use_reloader: bool=False,
loop=self.ctx.olass.loop)
'''
#@app.route('/')
#async def hello():
# return 'hello'
#@self.app.route('/ui/<path:path>')
#async def ui_static(path):
# return await send_from_directory('olass/webui/static', path)
#@self.app.websocket('/ws')
#async def ws():
# while True:
# await websocket.send('Test message')
# await asyncio.sleep(5)
# Create the asyncio server
await self.ctx.cf.loop.create_server(
lambda: Server(self.app, self.ctx.cf.loop, logger_quart, "HTTP: %(h)s %(r)s %(s)s %(b)s %(D)s", 5),
self.host, self.port, ssl=None)
@jjmontesl
Copy link
Author

jjmontesl commented Jul 9, 2018

This is experimental, not finished code and it has several known issues. Nor I am convinced that integrating via WSGI is the best option here.

This is an excerpt of a larger project and it's using features not shown here (dependency injection, configuration injection, lifecycle management...).

I have uploaded this gist to complement the SO question: https://stackoverflow.com/questions/51252760/how-to-integrate-django-app-with-tcp-server

@jjmontesl
Copy link
Author

jjmontesl commented Jul 9, 2018

Remember, when using Django for a service, that model instances lifecycle is usually not a problem because objects live only as long as the HTTPRequest that creates them. Within a service, this doesn't happen, and you need to synchronize access to model instances carefully. This involves avoiding usage of cached/older instances to do modifications, and refreshing model objects anytime other process might have changed them. Django CRM does not guarantee uniqueness of model objects that represent the same database record.

Also note that you can use a separate process if you are using Django Requests (HTTP) just to serve a few preconfigured templates, static views, admin views, or in general, stuff that is backed by the database (because the database serves a truth source). You only need the kind of integration discussed in this gist if you need to access the realtime in-memory state of your process from Django views and templates. If you can avoid it, an alternate approach is to provide a static web application and let the HTTP clients to use websockets to communicate with your process (this is something I'm actually using in production for another project and works well as Django is merely serving static and admin views, which only hit the database). I this case I prefer to use WAMP and a WAMP router (crossbar.io) to handle websockets, instead of listening to websockets directly from your application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment