committed by
GitHub
29 changed files with 643 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||
from django.contrib import admin |
|||
|
|||
# Register your models here. |
@ -0,0 +1,108 @@ |
|||
from re import match |
|||
from _thread import start_new_thread |
|||
from time import sleep |
|||
from os import getpid, kill, environ |
|||
from signal import SIGINT |
|||
import six |
|||
import copy |
|||
|
|||
from django.conf import settings |
|||
from django.core.handlers.wsgi import WSGIHandler |
|||
from django.core.management.base import BaseCommand, CommandError |
|||
from django.core.management.commands.runserver import naiveip_re, DEFAULT_PORT |
|||
from django.utils.autoreload import code_changed, restart_with_reloader |
|||
from django.core.wsgi import get_wsgi_application |
|||
|
|||
# from gevent import pywsgi |
|||
from sdjango import autodiscover |
|||
from sdjango import namespace |
|||
from sdjango.sd_manager import SdManager |
|||
from sdjango.sd_middleware import SdMiddleware |
|||
import socketio |
|||
import eventlet |
|||
|
|||
|
|||
RELOAD = False |
|||
|
|||
def reload_watcher(): |
|||
global RELOAD |
|||
while True: |
|||
RELOAD = code_changed() |
|||
if RELOAD: |
|||
kill(getpid(), SIGINT) |
|||
restart_with_reloader() |
|||
sleep(1) |
|||
|
|||
class Command(BaseCommand): |
|||
|
|||
def add_arguments(self, parser): |
|||
parser.add_argument('addrport', nargs='?', help='Optional port number, or ipaddr:port') |
|||
|
|||
def handle(self, *args, **options): |
|||
from django.utils import translation |
|||
from django.conf import settings |
|||
|
|||
translation.activate(settings.LANGUAGE_CODE) |
|||
addrport = options.get('addrport', None) |
|||
if addrport is None: |
|||
self.addr = '' |
|||
self.port = DEFAULT_PORT |
|||
else: |
|||
m = match(naiveip_re, addrport) |
|||
if m is None: |
|||
raise CommandError('"%s" is not a valid port number ' |
|||
'or address:port pair.' % options['addrport']) |
|||
self.addr, _ipv4, ipv6, _fqdn, self.port = m.groups() |
|||
|
|||
if not self.port.isdigit(): |
|||
raise CommandError('"%s" is not a valid port number' % self.port) |
|||
|
|||
if not self.addr: |
|||
self.addr = '127.0.0.1' |
|||
# Make the port available here for the path: |
|||
# socketio_tags.socketio -> |
|||
# socketio_scripts.html -> |
|||
# io.Socket JS constructor |
|||
# allowing the port to be set as the client-side default there. |
|||
environ["DJANGO_SOCKETIO_PORT"] = str(self.port) |
|||
|
|||
if settings.DEBUG is True: |
|||
start_new_thread(reload_watcher, ()) |
|||
|
|||
try: |
|||
bind = (self.addr, int(self.port)) |
|||
print("SocketIOServer running on %s:%s" % bind) |
|||
handler = self.get_handler(*args, **options) |
|||
|
|||
# sio = socketio.Server(client_manager=SdManager(), async_mode='gevent') |
|||
sio = socketio.Server(client_manager=SdManager(), async_mode='eventlet') |
|||
autodiscover() |
|||
namespace.insert_in_server(sio) |
|||
|
|||
app = get_wsgi_application() |
|||
app = SdMiddleware(sio, handler) |
|||
eventlet.wsgi.server(eventlet.listen(bind), app) |
|||
|
|||
except KeyboardInterrupt: |
|||
# eventlet server will handle exception |
|||
# server.stop() |
|||
# if RELOAD: |
|||
# print("Reloading...") |
|||
# restart_with_reloader() |
|||
pass |
|||
|
|||
def get_handler(self, *args, **options): |
|||
""" |
|||
Returns the django.contrib.staticfiles handler. |
|||
""" |
|||
handler = WSGIHandler() |
|||
try: |
|||
from django.contrib.staticfiles.handlers import StaticFilesHandler |
|||
except ImportError: |
|||
return handler |
|||
use_static_handler = options.get('use_static_handler', True) |
|||
insecure_serving = options.get('insecure_serving', False) |
|||
if (settings.DEBUG and use_static_handler or |
|||
(use_static_handler and insecure_serving)): |
|||
handler = StaticFilesHandler(handler) |
|||
return handler |
@ -0,0 +1,3 @@ |
|||
from django.db import models |
|||
|
|||
# Create your models here. |
@ -0,0 +1,64 @@ |
|||
import logging |
|||
from sdjango import namespace |
|||
|
|||
|
|||
online_user_num = 0 |
|||
|
|||
|
|||
@namespace('/test') |
|||
class TestNamespace: |
|||
|
|||
def __init__(self, name): |
|||
self.name = name |
|||
self.request = None # django request object |
|||
|
|||
def _get_socket(self, sid): |
|||
socket = namespace.server.eio._get_socket(sid) |
|||
return socket |
|||
|
|||
def _get_request(self, sid): |
|||
socket = self._get_socket(sid) |
|||
return socket._request |
|||
|
|||
def emit(self, *args, **kwargs): |
|||
if 'namespace' not in kwargs: |
|||
kwargs['namespace'] = self.name |
|||
|
|||
namespace.server.emit(*args, **kwargs) |
|||
|
|||
def on_my_event(self, sid, message): |
|||
self.emit('my response', {'data': message['data']}, room=sid) |
|||
|
|||
def on_my_broadcast_event(self, sid, message): |
|||
self.emit('my response', {'data': message['data']}) |
|||
|
|||
def on_join(self, sid, message): |
|||
namespace.server.enter_room(sid, message['room'], namespace='/test') |
|||
self.emit('my response', {'data': 'Entered room: '+message['room']}, room=sid) |
|||
|
|||
def on_leave(self, sid, message): |
|||
namespace.server.leave_room(sid, message['room'], namespace='/test') |
|||
self.emit('my response', {'data': 'Left room:'+message['room']}, room=sid) |
|||
|
|||
def on_close_room(self, sid, message): |
|||
self.emit('my response', {'data': 'Room '+message['room']+ ' is closing'}, |
|||
room=message['room']) |
|||
namespace.server.close_room(message['room'], namespace='/test') |
|||
|
|||
def on_my_room_event(self, sid, message): |
|||
self.emit('my response', {'data': message['data']}, room=message['room']) |
|||
|
|||
def on_disconnect_request(self, sid): |
|||
namespace.server.disconnect(sid, namespace='/test') |
|||
|
|||
# two method must have |
|||
def on_connect(self, sid, environ): |
|||
if 'django_request' in environ: |
|||
request = environ['django_request'] |
|||
socket = self._get_socket(sid) |
|||
socket._request = request |
|||
|
|||
self.emit('my response', {'data': "{} Connected".format(request.user), "count": 0}, room=sid) |
|||
|
|||
def on_disconnect(self, sid): |
|||
print('Client disconnected') |
@ -0,0 +1,91 @@ |
|||
<!DOCTYPE HTML> |
|||
<html> |
|||
<head> |
|||
<title>Django-SocketIO Test</title> |
|||
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.4.min.js"></script> |
|||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script> |
|||
<script type="text/javascript" charset="utf-8"> |
|||
$(document).ready(function(){ |
|||
namespace = '/test'; |
|||
var socket = io.connect('http://' + document.domain + ':' + location.port + namespace); |
|||
|
|||
socket.on('connect', function() { |
|||
socket.emit('my event', {data: 'I\'m connected!'}); |
|||
}); |
|||
socket.on('disconnect', function() { |
|||
$('#log').append('<br>Disconnected'); |
|||
}); |
|||
socket.on('my response', function(msg) { |
|||
$('#log').append('<br>Received: ' + msg.data); |
|||
}); |
|||
|
|||
// event handler for server sent data |
|||
// the data is displayed in the "Received" section of the page |
|||
// handlers for the different forms in the page |
|||
// these send data to the server in a variety of ways |
|||
$('form#emit').submit(function(event) { |
|||
socket.emit('my event', {data: $('#emit_data').val()}); |
|||
return false; |
|||
}); |
|||
$('form#broadcast').submit(function(event) { |
|||
socket.emit('my broadcast event', {data: $('#broadcast_data').val()}); |
|||
return false; |
|||
}); |
|||
$('form#join').submit(function(event) { |
|||
socket.emit('join', {room: $('#join_room').val()}); |
|||
return false; |
|||
}); |
|||
$('form#leave').submit(function(event) { |
|||
socket.emit('leave', {room: $('#leave_room').val()}); |
|||
return false; |
|||
}); |
|||
$('form#send_room').submit(function(event) { |
|||
socket.emit('my room event', {room: $('#room_name').val(), data: $('#room_data').val()}); |
|||
return false; |
|||
}); |
|||
$('form#close').submit(function(event) { |
|||
socket.emit('close room', {room: $('#close_room').val()}); |
|||
return false; |
|||
}); |
|||
$('form#disconnect').submit(function(event) { |
|||
socket.emit('disconnect request'); |
|||
return false; |
|||
}); |
|||
}); |
|||
</script> |
|||
</head> |
|||
<body> |
|||
<h1>Flask-SocketIO Test</h1> |
|||
<h2>Send:</h2> |
|||
<form id="emit" method="POST" action='#'> |
|||
<input type="text" name="emit_data" id="emit_data" placeholder="Message"> |
|||
<input type="submit" value="Echo"> |
|||
</form> |
|||
<form id="broadcast" method="POST" action='#'> |
|||
<input type="text" name="broadcast_data" id="broadcast_data" placeholder="Message"> |
|||
<input type="submit" value="Broadcast"> |
|||
</form> |
|||
<form id="join" method="POST" action='#'> |
|||
<input type="text" name="join_room" id="join_room" placeholder="Room Name"> |
|||
<input type="submit" value="Join Room"> |
|||
</form> |
|||
<form id="leave" method="POST" action='#'> |
|||
<input type="text" name="leave_room" id="leave_room" placeholder="Room Name"> |
|||
<input type="submit" value="Leave Room"> |
|||
</form> |
|||
<form id="send_room" method="POST" action='#'> |
|||
<input type="text" name="room_name" id="room_name" placeholder="Room Name"> |
|||
<input type="text" name="room_data" id="room_data" placeholder="Message"> |
|||
<input type="submit" value="Send to Room"> |
|||
</form> |
|||
<form id="close" method="POST" action="#"> |
|||
<input type="text" name="close_room" id="close_room" placeholder="Room Name"> |
|||
<input type="submit" value="Close Room"> |
|||
</form> |
|||
<form id="disconnect" method="POST" action="#"> |
|||
<input type="submit" value="Disconnect"> |
|||
</form> |
|||
<h2>Receive:</h2> |
|||
<div><p id="log"></p></div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,3 @@ |
|||
from django.test import TestCase |
|||
|
|||
# Create your tests here. |
@ -0,0 +1,13 @@ |
|||
from django.conf.urls import ( |
|||
url, patterns, include |
|||
) |
|||
|
|||
import sdjango |
|||
|
|||
from .views import socket_base |
|||
|
|||
|
|||
urlpatterns = [ |
|||
url(r'^socket\.io', include(sdjango.urls)), |
|||
url(r'^$', socket_base, name='socket_base'), |
|||
] |
@ -0,0 +1,6 @@ |
|||
from django.shortcuts import render |
|||
|
|||
|
|||
def socket_base(request, template="base.html"): |
|||
context={} |
|||
return render(request, template, context) |
@ -0,0 +1,105 @@ |
|||
""" |
|||
Django settings for django_chat project. |
|||
|
|||
Generated by 'django-admin startproject' using Django 1.8. |
|||
|
|||
For more information on this file, see |
|||
https://docs.djangoproject.com/en/1.8/topics/settings/ |
|||
|
|||
For the full list of settings and their values, see |
|||
https://docs.djangoproject.com/en/1.8/ref/settings/ |
|||
""" |
|||
|
|||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) |
|||
import os |
|||
|
|||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
|||
|
|||
|
|||
# Quick-start development settings - unsuitable for production |
|||
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ |
|||
|
|||
# SECURITY WARNING: keep the secret key used in production secret! |
|||
SECRET_KEY = 'n_&k)3!sn-79f5=g93(t$&a09*b@6w)4hf!2e%hbdp=3v-e(v9' |
|||
|
|||
# SECURITY WARNING: don't run with debug turned on in production! |
|||
DEBUG = True |
|||
|
|||
ALLOWED_HOSTS = [] |
|||
|
|||
|
|||
# Application definition |
|||
|
|||
INSTALLED_APPS = ( |
|||
'django.contrib.admin', |
|||
'django.contrib.auth', |
|||
'django.contrib.contenttypes', |
|||
'django.contrib.sessions', |
|||
'django.contrib.messages', |
|||
'django.contrib.staticfiles', |
|||
|
|||
# local app |
|||
'chat', |
|||
) |
|||
|
|||
MIDDLEWARE_CLASSES = ( |
|||
'django.contrib.sessions.middleware.SessionMiddleware', |
|||
'django.middleware.common.CommonMiddleware', |
|||
'django.middleware.csrf.CsrfViewMiddleware', |
|||
'django.contrib.auth.middleware.AuthenticationMiddleware', |
|||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', |
|||
'django.contrib.messages.middleware.MessageMiddleware', |
|||
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
|||
'django.middleware.security.SecurityMiddleware', |
|||
) |
|||
|
|||
ROOT_URLCONF = 'django_chat.urls' |
|||
|
|||
TEMPLATES = [ |
|||
{ |
|||
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
|||
'DIRS': [], |
|||
'APP_DIRS': True, |
|||
'OPTIONS': { |
|||
'context_processors': [ |
|||
'django.template.context_processors.debug', |
|||
'django.template.context_processors.request', |
|||
'django.contrib.auth.context_processors.auth', |
|||
'django.contrib.messages.context_processors.messages', |
|||
], |
|||
}, |
|||
}, |
|||
] |
|||
|
|||
WSGI_APPLICATION = 'django_chat.wsgi.application' |
|||
|
|||
|
|||
# Database |
|||
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases |
|||
|
|||
DATABASES = { |
|||
'default': { |
|||
'ENGINE': 'django.db.backends.sqlite3', |
|||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), |
|||
} |
|||
} |
|||
|
|||
|
|||
# Internationalization |
|||
# https://docs.djangoproject.com/en/1.8/topics/i18n/ |
|||
|
|||
LANGUAGE_CODE = 'en-us' |
|||
|
|||
TIME_ZONE = 'UTC' |
|||
|
|||
USE_I18N = True |
|||
|
|||
USE_L10N = True |
|||
|
|||
USE_TZ = True |
|||
|
|||
|
|||
# Static files (CSS, JavaScript, Images) |
|||
# https://docs.djangoproject.com/en/1.8/howto/static-files/ |
|||
|
|||
STATIC_URL = '/static/' |
@ -0,0 +1,11 @@ |
|||
from django.conf.urls import include, url |
|||
from django.contrib import admin |
|||
|
|||
urlpatterns = [ |
|||
# Examples: |
|||
# url(r'^$', 'django_chat.views.home', name='home'), |
|||
# url(r'^blog/', include('blog.urls')), |
|||
|
|||
url(r'^admin/', include(admin.site.urls)), |
|||
url('', include('chat.urls')), |
|||
] |
@ -0,0 +1,16 @@ |
|||
""" |
|||
WSGI config for django_chat project. |
|||
|
|||
It exposes the WSGI callable as a module-level variable named ``application``. |
|||
|
|||
For more information on this file, see |
|||
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ |
|||
""" |
|||
|
|||
import os |
|||
|
|||
from django.core.wsgi import get_wsgi_application |
|||
|
|||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_chat.settings") |
|||
|
|||
application = get_wsgi_application() |
@ -0,0 +1,10 @@ |
|||
#!/usr/bin/env python |
|||
import os |
|||
import sys |
|||
|
|||
if __name__ == "__main__": |
|||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_chat.settings") |
|||
|
|||
from django.core.management import execute_from_command_line |
|||
|
|||
execute_from_command_line(sys.argv) |
@ -0,0 +1,18 @@ |
|||
# python-socketio with django example |
|||
|
|||
This example is for who wants to use django with pyton-socketio. Some tricks used in this example is inspired by gevent-socketio. |
|||
|
|||
# How to Setup |
|||
|
|||
```sh |
|||
pip install -r requirement.txt |
|||
python manage.py migrate |
|||
``` |
|||
|
|||
# How to Run |
|||
|
|||
```sh |
|||
python manage.py runserver_socketio |
|||
``` |
|||
|
|||
open http://127.0.0.1:8000/ with your browser to see the result |
@ -0,0 +1,3 @@ |
|||
Django<1.9 |
|||
eventlet |
|||
python-socketio |
@ -0,0 +1,103 @@ |
|||
import logging |
|||
import inspect |
|||
|
|||
from django.http import HttpResponse |
|||
from django.views.decorators.csrf import csrf_exempt |
|||
from django.core.wsgi import get_wsgi_application |
|||
|
|||
try: |
|||
# Django version >= 1.9 |
|||
from django.utils.module_loading import import_module |
|||
except ImportError: |
|||
# Django version < 1.9 |
|||
from django.utils.importlib import import_module |
|||
|
|||
from django.conf.urls import patterns, url, include |
|||
|
|||
|
|||
LOADING_SOCKETIO = False |
|||
|
|||
def autodiscover(): |
|||
""" |
|||
Auto-discover INSTALLED_APPS sockets.py modules and fail silently when |
|||
not present. NOTE: socketio_autodiscover was inspired/copied from |
|||
django.contrib.admin autodiscover |
|||
""" |
|||
global LOADING_SOCKETIO |
|||
if LOADING_SOCKETIO: |
|||
return |
|||
LOADING_SOCKETIO = True |
|||
|
|||
import imp |
|||
from django.conf import settings |
|||
|
|||
for app in settings.INSTALLED_APPS: |
|||
|
|||
try: |
|||
app_path = import_module(app).__path__ |
|||
except AttributeError: |
|||
continue |
|||
|
|||
try: |
|||
imp.find_module('sockets', app_path) |
|||
except ImportError: |
|||
continue |
|||
|
|||
import_module("%s.sockets" % app) |
|||
|
|||
LOADING_SOCKETIO = False |
|||
|
|||
|
|||
class namespace: |
|||
|
|||
"""This is a event handler keeper for socketio event |
|||
|
|||
used as a decorators |
|||
""" |
|||
|
|||
handler_container = {} |
|||
server = None |
|||
|
|||
def __init__(self, name=''): |
|||
if not name.startswith('/'): |
|||
self.name = '/'+name |
|||
self.name = name |
|||
|
|||
def __call__(self, handler): |
|||
instance = handler(self.name) |
|||
|
|||
if self.name not in namespace.handler_container: |
|||
namespace.handler_container[self.name] = [] |
|||
|
|||
methods = inspect.getmembers(instance, predicate=inspect.ismethod) |
|||
|
|||
for key, value in methods: |
|||
if key.startswith('on_'): |
|||
namespace.handler_container[self.name].append(value) |
|||
|
|||
return True |
|||
|
|||
@classmethod |
|||
def insert_in_server(cls, server): |
|||
"""a special method to dynamic add event for socketio server |
|||
""" |
|||
namespace.server = server |
|||
|
|||
for name, handlers in namespace.handler_container.items(): |
|||
|
|||
for obj in handlers: |
|||
event_name = obj.__name__.replace('on_', '').replace('_', ' ') |
|||
server.on(event_name, obj, name) |
|||
|
|||
namespace.handler_container = {} # reset to empty dict |
|||
|
|||
|
|||
@csrf_exempt |
|||
def socketio(request): |
|||
try: |
|||
request.environ['django_request'] = request |
|||
except: |
|||
logging.getLogger("socketio").error("Exception while handling socketio connection", exc_info=True) |
|||
return HttpResponse(200) |
|||
|
|||
urls = patterns("", (r'', socketio)) |
@ -0,0 +1,22 @@ |
|||
from socketio.base_manager import BaseManager |
|||
|
|||
|
|||
class SdManager(BaseManager): |
|||
|
|||
""" |
|||
""" |
|||
|
|||
def initialize(self, server): |
|||
# import pdb; pdb.set_trace() |
|||
super().initialize(server) |
|||
|
|||
def connect(self, sid, namespace): |
|||
"""Register a client connection to a namespace. |
|||
and set the django request object? |
|||
""" |
|||
# TODO: process user authentication here? |
|||
# if 'django_request' in self.server.environ[sid]: |
|||
# print(self.server.environ[sid]['django_request'].user) |
|||
|
|||
self.enter_room(sid, namespace, None) |
|||
self.enter_room(sid, namespace, sid) |
@ -0,0 +1,64 @@ |
|||
import urllib |
|||
import engineio |
|||
|
|||
|
|||
class SdMiddleware(engineio.Middleware): |
|||
"""WSGI middleware for Socket.IO. |
|||
|
|||
This middleware dispatches traffic to a Socket.IO application, and |
|||
optionally forwards regular HTTP traffic to a WSGI application. |
|||
|
|||
:param socketio_app: The Socket.IO server. |
|||
:param wsgi_app: The WSGI app that receives all other traffic. |
|||
:param socketio_path: The endpoint where the Socket.IO application should |
|||
be installed. The default value is appropriate for |
|||
most cases. |
|||
|
|||
Example usage:: |
|||
|
|||
import socketio |
|||
import eventlet |
|||
from . import wsgi_app |
|||
|
|||
sio = socketio.Server() |
|||
app = socketio.Middleware(sio, wsgi_app) |
|||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
|||
""" |
|||
def __init__(self, socketio_app, wsgi_app=None, socketio_path='socket.io'): |
|||
super().__init__(socketio_app, wsgi_app, socketio_path) |
|||
|
|||
def __call__(self, environ, start_response): |
|||
if 'gunicorn.socket' in environ: |
|||
# gunicorn saves the socket under environ['gunicorn.socket'], while |
|||
# eventlet saves it under environ['eventlet.input']. Eventlet also |
|||
# stores the socket inside a wrapper class, while gunicon writes it |
|||
# directly into the environment. To give eventlet's WebSocket |
|||
# module access to this socket when running under gunicorn, here we |
|||
# copy the socket to the eventlet format. |
|||
class Input(object): |
|||
def __init__(self, socket): |
|||
self.socket = socket |
|||
|
|||
def get_socket(self): |
|||
return self.socket |
|||
|
|||
environ['eventlet.input'] = Input(environ['gunicorn.socket']) |
|||
|
|||
path = environ['PATH_INFO'] |
|||
if path is not None and \ |
|||
path.startswith('/{0}/'.format(self.engineio_path)): |
|||
|
|||
query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) |
|||
sid = query.get('sid', None) |
|||
|
|||
if sid is None: |
|||
self.wsgi_app(environ, start_response) |
|||
|
|||
engineio_res = self.engineio_app.handle_request(environ, start_response) |
|||
return engineio_res |
|||
|
|||
elif self.wsgi_app is not None: |
|||
return self.wsgi_app(environ, start_response) |
|||
else: |
|||
start_response("404 Not Found", [('Content-type', 'text/plain')]) |
|||
return ['Not Found'] |
Loading…
Reference in new issue