diff --git a/examples/django_chat/chat/__init__.py b/examples/django_chat/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/django_chat/chat/admin.py b/examples/django_chat/chat/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/examples/django_chat/chat/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/examples/django_chat/chat/management/__init__.py b/examples/django_chat/chat/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/django_chat/chat/management/commands/__init__.py b/examples/django_chat/chat/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/django_chat/chat/management/commands/runserver_socketio.py b/examples/django_chat/chat/management/commands/runserver_socketio.py new file mode 100644 index 0000000..55a0a98 --- /dev/null +++ b/examples/django_chat/chat/management/commands/runserver_socketio.py @@ -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 diff --git a/examples/django_chat/chat/migrations/__init__.py b/examples/django_chat/chat/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/django_chat/chat/models.py b/examples/django_chat/chat/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/examples/django_chat/chat/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/examples/django_chat/chat/sockets.py b/examples/django_chat/chat/sockets.py new file mode 100644 index 0000000..664f98c --- /dev/null +++ b/examples/django_chat/chat/sockets.py @@ -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') diff --git a/examples/django_chat/chat/templates/base.html b/examples/django_chat/chat/templates/base.html new file mode 100644 index 0000000..19e11b6 --- /dev/null +++ b/examples/django_chat/chat/templates/base.html @@ -0,0 +1,91 @@ + + + + Django-SocketIO Test + + + + + +

Flask-SocketIO Test

+

Send:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ +
+

Receive:

+

+ + diff --git a/examples/django_chat/chat/tests.py b/examples/django_chat/chat/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/examples/django_chat/chat/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/examples/django_chat/chat/urls.py b/examples/django_chat/chat/urls.py new file mode 100644 index 0000000..8afd17e --- /dev/null +++ b/examples/django_chat/chat/urls.py @@ -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'), +] diff --git a/examples/django_chat/chat/views.py b/examples/django_chat/chat/views.py new file mode 100644 index 0000000..8e9a471 --- /dev/null +++ b/examples/django_chat/chat/views.py @@ -0,0 +1,6 @@ +from django.shortcuts import render + + +def socket_base(request, template="base.html"): + context={} + return render(request, template, context) diff --git a/examples/django_chat/django_chat/__init__.py b/examples/django_chat/django_chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/django_chat/django_chat/settings.py b/examples/django_chat/django_chat/settings.py new file mode 100644 index 0000000..8d3e4be --- /dev/null +++ b/examples/django_chat/django_chat/settings.py @@ -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/' diff --git a/examples/django_chat/django_chat/urls.py b/examples/django_chat/django_chat/urls.py new file mode 100644 index 0000000..d895120 --- /dev/null +++ b/examples/django_chat/django_chat/urls.py @@ -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')), +] diff --git a/examples/django_chat/django_chat/wsgi.py b/examples/django_chat/django_chat/wsgi.py new file mode 100644 index 0000000..3ed4e71 --- /dev/null +++ b/examples/django_chat/django_chat/wsgi.py @@ -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() diff --git a/examples/django_chat/manage.py b/examples/django_chat/manage.py new file mode 100755 index 0000000..0f0e198 --- /dev/null +++ b/examples/django_chat/manage.py @@ -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) diff --git a/examples/django_chat/readme.md b/examples/django_chat/readme.md new file mode 100644 index 0000000..4303bca --- /dev/null +++ b/examples/django_chat/readme.md @@ -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 diff --git a/examples/django_chat/requirement.txt b/examples/django_chat/requirement.txt new file mode 100644 index 0000000..57cfa47 --- /dev/null +++ b/examples/django_chat/requirement.txt @@ -0,0 +1,3 @@ +Django<1.9 +eventlet +python-socketio diff --git a/examples/django_chat/sdjango/__init__.py b/examples/django_chat/sdjango/__init__.py new file mode 100644 index 0000000..600aab4 --- /dev/null +++ b/examples/django_chat/sdjango/__init__.py @@ -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)) diff --git a/examples/django_chat/sdjango/sd_manager.py b/examples/django_chat/sdjango/sd_manager.py new file mode 100644 index 0000000..0e8bf86 --- /dev/null +++ b/examples/django_chat/sdjango/sd_manager.py @@ -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) diff --git a/examples/django_chat/sdjango/sd_middleware.py b/examples/django_chat/sdjango/sd_middleware.py new file mode 100644 index 0000000..1ab446e --- /dev/null +++ b/examples/django_chat/sdjango/sd_middleware.py @@ -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'] diff --git a/examples/README.rst b/examples/flask_chat/README.rst similarity index 100% rename from examples/README.rst rename to examples/flask_chat/README.rst diff --git a/examples/app.py b/examples/flask_chat/app.py similarity index 100% rename from examples/app.py rename to examples/flask_chat/app.py diff --git a/examples/latency.py b/examples/flask_chat/latency.py similarity index 100% rename from examples/latency.py rename to examples/flask_chat/latency.py diff --git a/examples/requirements.txt b/examples/flask_chat/requirements.txt similarity index 100% rename from examples/requirements.txt rename to examples/flask_chat/requirements.txt diff --git a/examples/static/style.css b/examples/flask_chat/static/style.css similarity index 100% rename from examples/static/style.css rename to examples/flask_chat/static/style.css diff --git a/examples/templates/index.html b/examples/flask_chat/templates/index.html similarity index 100% rename from examples/templates/index.html rename to examples/flask_chat/templates/index.html diff --git a/examples/templates/latency.html b/examples/flask_chat/templates/latency.html similarity index 100% rename from examples/templates/latency.html rename to examples/flask_chat/templates/latency.html