Skip to content

Commit

Permalink
ASGI support
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Nov 25, 2018
1 parent 015ea20 commit b214380
Show file tree
Hide file tree
Showing 13 changed files with 376 additions and 16 deletions.
39 changes: 39 additions & 0 deletions examples/asgi/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Socket.IO aiohttp Examples
==========================

This directory contains example Socket.IO applications that are compatible with
asyncio and the aiohttp framework. These applications require Python 3.5 or
later.

app.py
------

A basic "kitchen sink" type application that allows the user to experiment
with most of the available features of the Socket.IO server.

latency.py
----------

A port of the latency application included in the official Engine.IO
Javascript server. In this application the client sends *ping* messages to
the server, which are responded by the server with a *pong*. The client
measures the time it takes for each of these exchanges and plots these in real
time to the page.

This is an ideal application to measure the performance of the different
asynchronous modes supported by the Socket.IO server.

Running the Examples
--------------------

To run these examples, create a virtual environment, install the requirements
and then run::

$ python app.py

or::

$ python latency.py

You can then access the application from your web browser at
``http://localhost:8080``.
91 changes: 91 additions & 0 deletions examples/asgi/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!DOCTYPE HTML>
<html>
<head>
<title>python-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/2.0.4/socket.io.slim.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>python-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>
82 changes: 82 additions & 0 deletions examples/asgi/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import asyncio

import uvicorn
from uvicorn.loops.auto import auto_loop_setup

import socketio

sio = socketio.AsyncServer(async_mode='asgi')
app = socketio.ASGIApp(sio, static_files={
'/': {'content_type': 'text/html', 'filename': 'app.html'},
})


async def background_task():
"""Example of how to send server generated events to clients."""
count = 0
while True:
await sio.sleep(10)
count += 1
await sio.emit('my response', {'data': 'Server generated event'},
namespace='/test')


@sio.on('my event', namespace='/test')
async def test_message(sid, message):
await sio.emit('my response', {'data': message['data']}, room=sid,
namespace='/test')


@sio.on('my broadcast event', namespace='/test')
async def test_broadcast_message(sid, message):
await sio.emit('my response', {'data': message['data']}, namespace='/test')


@sio.on('join', namespace='/test')
async def join(sid, message):
sio.enter_room(sid, message['room'], namespace='/test')
await sio.emit('my response', {'data': 'Entered room: ' + message['room']},
room=sid, namespace='/test')


@sio.on('leave', namespace='/test')
async def leave(sid, message):
sio.leave_room(sid, message['room'], namespace='/test')
await sio.emit('my response', {'data': 'Left room: ' + message['room']},
room=sid, namespace='/test')


@sio.on('close room', namespace='/test')
async def close(sid, message):
await sio.emit('my response',
{'data': 'Room ' + message['room'] + ' is closing.'},
room=message['room'], namespace='/test')
await sio.close_room(message['room'], namespace='/test')


@sio.on('my room event', namespace='/test')
async def send_room_message(sid, message):
await sio.emit('my response', {'data': message['data']},
room=message['room'], namespace='/test')


@sio.on('disconnect request', namespace='/test')
async def disconnect_request(sid):
await sio.disconnect(sid, namespace='/test')


@sio.on('connect', namespace='/test')
async def test_connect(sid, environ):
await sio.emit('my response', {'data': 'Connected', 'count': 0}, room=sid,
namespace='/test')


@sio.on('disconnect', namespace='/test')
def test_disconnect(sid):
print('Client disconnected')


if __name__ == '__main__':
loop = auto_loop_setup()
sio.start_background_task(background_task)
uvicorn.run(app, '127.0.0.1', 5000, loop=loop)
64 changes: 64 additions & 0 deletions examples/asgi/latency.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!doctype html>
<html>
<head>
<title>Socket.IO Latency</title>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<h1>Socket.IO Latency <span id="latency"></span></h1>
<h2 id="transport">(connecting)</h2>
<canvas id="chart" height="200"></canvas>

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/smoothie/1.27.0/smoothie.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.slim.js"></script>
<script>
// socket
var socket = io.connect('http://' + document.domain + ':' + location.port);
var char = $('chart').get(0);
socket.on('connect', function() {
if (chart.getContext) {
render();
window.onresize = render;
}
send();
});
socket.on('pong_from_server', function() {
var latency = new Date - last;
$('#latency').text(latency + 'ms');
if (time)
time.append(+new Date, latency);
setTimeout(send, 100);
});
socket.on('disconnect', function() {
if (smoothie)
smoothie.stop();
$('#transport').text('(disconnected)');
});

var last;
function send() {
last = new Date;
socket.emit('ping_from_client');
$('#transport').text(socket.io.engine.transport.name);
}

// chart
var smoothie;
var time;
function render() {
if (smoothie)
smoothie.stop();
chart.width = document.body.clientWidth;
smoothie = new SmoothieChart();
smoothie.streamTo(chart, 1000);
time = new TimeSeries();
smoothie.addTimeSeries(time, {
strokeStyle: 'rgb(255, 0, 0)',
fillStyle: 'rgba(255, 0, 0, 0.4)',
lineWidth: 2
});
}
</script>
</body>
</html>
19 changes: 19 additions & 0 deletions examples/asgi/latency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import uvicorn

import socketio

sio = socketio.AsyncServer(async_mode='asgi')
app = socketio.ASGIApp(sio, static_files={
'/': {'content_type': 'text/html', 'filename': 'latency.html'},
'/static/style.css': {'content_type': 'text/css',
'filename': 'static/style.css'},
})


@sio.on('ping_from_client')
async def ping(sid):
await sio.emit('pong_from_server', room=sid)


if __name__ == '__main__':
uvicorn.run(app, '127.0.0.1', 5000)
8 changes: 8 additions & 0 deletions examples/asgi/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
aiohttp==1.3.1

This comment has been minimized.

Copy link
@Serkan-devel

Serkan-devel Nov 25, 2018

Have you maybe considered using pipenv for dependency management?

This comment has been minimized.

Copy link
@miguelgrinberg

miguelgrinberg Nov 25, 2018

Author Owner

I'm not a big fan of pipenv, but in any case, it is for users, not project maintainers, it does not help my workflow at all.

async-timeout==1.1.0
chardet==2.3.0
multidict==2.1.4
python-engineio
python_socketio
six==1.10.0
yarl==0.9.2
4 changes: 4 additions & 0 deletions examples/asgi/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
body { margin: 0; padding: 0; font-family: Helvetica Neue; }
h1 { margin: 100px 100px 10px; }
h2 { color: #999; margin: 0 100px 30px; font-weight: normal; }
#latency { color: red; }
2 changes: 1 addition & 1 deletion examples/wsgi/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

sio = socketio.Server(logger=True, async_mode=async_mode)
app = Flask(__name__)
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app)
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app)
app.config['SECRET_KEY'] = 'secret!'
thread = None

Expand Down
4 changes: 2 additions & 2 deletions examples/wsgi/django_example/django_example/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
import os

from django.core.wsgi import get_wsgi_application
from socketio import Middleware
import socketio

from socketio_app.views import sio

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_example.settings")

django_app = get_wsgi_application()
application = Middleware(sio, django_app)
application = socketio.WSGIApp(sio, django_app)
2 changes: 1 addition & 1 deletion examples/wsgi/latency.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

sio = socketio.Server(async_mode=async_mode)
app = Flask(__name__)
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app)
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app)


@app.route('/')
Expand Down
11 changes: 6 additions & 5 deletions socketio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import sys

from .middleware import Middleware
from .base_manager import BaseManager
from .pubsub_manager import PubSubManager
from .kombu_manager import KombuManager
from .redis_manager import RedisManager
from .zmq_manager import ZmqManager
from .server import Server
from .namespace import Namespace
from .middleware import WSGIApp, Middleware
from .tornado import get_tornado_handler
if sys.version_info >= (3, 5): # pragma: no cover
from .asyncio_server import AsyncServer
from .asyncio_manager import AsyncManager
from .asyncio_namespace import AsyncNamespace
from .asyncio_redis_manager import AsyncRedisManager
from .asgi import ASGIApp
else: # pragma: no cover
AsyncServer = None
AsyncManager = None
Expand All @@ -22,9 +23,9 @@

__version__ = '2.0.0'

__all__ = ['__version__', 'Middleware', 'Server', 'BaseManager',
'PubSubManager', 'KombuManager', 'RedisManager', 'ZmqManager',
'Namespace']
__all__ = ['__version__', 'Server', 'BaseManager', 'PubSubManager',
'KombuManager', 'RedisManager', 'ZmqManager', 'Namespace',
'WSGIApp', 'Middleware']
if AsyncServer is not None: # pragma: no cover
__all__ += ['AsyncServer', 'AsyncNamespace', 'AsyncManager',
'AsyncRedisManager', 'get_tornado_handler']
'AsyncRedisManager', 'ASGIApp', 'get_tornado_handler']
Loading

0 comments on commit b214380

Please sign in to comment.