├── .gitignore
├── LICENSE
├── README.rst
├── dev-requirements.txt
├── examples
├── chat-asyncio
│ ├── chat.py
│ └── templates
│ │ └── index.html
├── chat-gevent
│ ├── chat.py
│ └── templates
│ │ └── index.html
├── echo-asyncio
│ ├── echo.py
│ └── templates
│ │ └── index.html
├── echo-gevent
│ ├── echo.py
│ └── templates
│ │ └── index.html
├── echo
│ ├── echo.py
│ └── templates
│ │ └── index.html
└── pubsub-asyncio
│ ├── pubsub.py
│ └── templates
│ └── index.html
├── flask_uwsgi_websocket
├── __init__.py
├── _async.py
├── _asyncio.py
├── _gevent.py
├── _uwsgi.py
└── websocket.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg-info
2 | *.pyc
3 | *.pyo
4 | dist/
5 | build/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014 Zach Kelling
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Flask-uWSGI-WebSocket
2 | =====================
3 | High-performance WebSockets for your Flask apps powered by `uWSGI
4 | `_. Low-level uWSGI WebSocket API
5 | access and flexible high-level abstractions for building complex WebSocket
6 | applications with Flask. Supports several different concurrency models
7 | including Gevent. Inspired by `Flask-Sockets
8 | `_.
9 |
10 | .. code-block:: python
11 |
12 | from flask import Flask
13 | from flask_uwsgi_websocket import GeventWebSocket
14 |
15 | app = Flask(__name__)
16 | websocket = GeventWebSocket(app)
17 |
18 | @websocket.route('/echo')
19 | def echo(ws):
20 | while True:
21 | msg = ws.receive()
22 | ws.send(msg)
23 |
24 | if __name__ == '__main__':
25 | app.run(gevent=100)
26 |
27 |
28 | Installation
29 | ------------
30 | Preferred method of installation is via pip::
31 |
32 | $ pip install Flask-uWSGI-WebSocket
33 |
34 | Installing uWSGI
35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 | Of course you'll also need uWSGI (with SSL support, at minimum). It can also be
37 | installed with pip::
38 |
39 | $ pip install uwsgi
40 |
41 | If that fails or you need to enable the asyncio plugin, read on.
42 |
43 | uWSGI on Mac OS X
44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45 | On some versions of Mac OS X, OpenSSL headers are no longer included. If you
46 | use Homebrew, install OpenSSL and ensure they are available::
47 |
48 | $ brew install openssl && brew link openssl --force
49 |
50 | This should ensure pip can install uWSGI::
51 |
52 | $ LDFLAGS="-L/usr/local/lib" pip install uwsgi --no-use-wheel
53 |
54 | If you plan to use the asyncio plugin, you'll need to ensure that it's enabled
55 | when uWSGI is compiled. You can use ``UWSGI_PROFILE`` to do this. With Homebrew Python 3.5 installed::
56 |
57 | $ LDFLAGS="-L/usr/local/lib" CFLAGS="-I/usr/local/include/python3.5m" UWSGI_PROFLILE="asyncio" pip3 install uwsgi --no-use-wheel
58 |
59 |
60 | uWSGI on Linux
61 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
62 | If your Linux distribution includes uWSGI with specific plugins, that is many
63 | times your best bet. If that fails or you'd prefer to compile uWSGI yourself,
64 | you'll need to ensure that the requisite build tools, OpenSSL headers, etc are
65 | installed::
66 |
67 | $ apt-get install build-essential libssl-dev python3-dev python3-venv
68 |
69 | According to the `uWSGI asyncio docs
70 | `_, ``UWSGI_PROFILE``
71 | and ``greenlet.h`` location should be specified.
72 |
73 | If you are installing uWSGI into a virtualenv, the process is::
74 |
75 | $ python3 -m venv pyvenv
76 | $ . pyvenv/bin/activate
77 | (pyvenv)$ pip install greenlet
78 |
79 | Now, ``greenlet.h`` should be available at ``$VIRTUAL_ENV/include/site/python3.5``. To build with pip::
80 |
81 | $ mkdir -p $VIRTUAL_ENV/include/site/python3.5/greenlet
82 | $ ln -s ../greenlet.h $VIRTUAL_ENV/include/site/python3.5/greenlet/
83 | $ CFLAGS="-I$VIRTUAL_ENV/include/site/python3.5" UWSGI_PROFILE="asyncio" pip install uwsgi --no-use-wheel
84 |
85 | Deployment
86 | ----------
87 | You can use uWSGI's built-in HTTP router to get up and running quickly::
88 |
89 | $ uwsgi --master --http :8080 --http-websockets --wsgi echo:app
90 |
91 | ...which is what ``app.run`` does after wrapping your Flask app::
92 |
93 | app.run(debug=True, host='localhost', port=8080, master=true, processes=8)
94 |
95 | uWSGI supports several concurrency models, in particular it has nice support
96 | for Gevent. If you want to use Gevent, import
97 | ``flask_uwsgi_websocket.GeventWebSocket`` and configure uWSGI to use the
98 | gevent loop engine::
99 |
100 | $ uwsgi --master --http :8080 --http-websockets --gevent 100 --wsgi echo:app
101 |
102 | ...or::
103 |
104 | app.run(debug=True, gevent=100)
105 |
106 | Note that you cannot use multiple threads with gevent loop engine.
107 |
108 | To enable asyncio instead::
109 |
110 | $ uwsgi --master --http :5000 --http-websockets --asyncio 100 --greenlet --wsgi chat:app
111 |
112 | ...or::
113 |
114 | app.run(debug=True, asyncio=100, greenlet=True)
115 |
116 | For production you'll probably want to run uWSGI behind Haproxy or Nginx,
117 | instead of using the built-int HTTP router. Explore the `uWSGI documentation
118 | `_ to learn more
119 | about the various concurrency and deployment options.
120 |
121 | Development
122 | -----------
123 | It's possible to take advantage of Flask's interactive debugger by installing
124 | Werkzeug's ``DebuggedApplication`` middleware::
125 |
126 | from werkzeug.debug import DebuggedApplication
127 | app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
128 |
129 | ...and running uWSGI with only a single worker::
130 |
131 | $ uwsgi --master --http :8080 --http-websockets --wsgi-file --workers 1 --threads 8 app.py
132 |
133 | If you use ``app.run(debug=True)`` or export ``FLASK_UWSGI_DEBUG``,
134 | Flask-uWSGI-Websocket will do this automatically for you.
135 |
136 |
137 | Examples
138 | --------
139 | There are several examples `available here `_.
140 |
141 | API
142 | ---
143 |
144 | ``WebSocket``
145 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
146 | Applies ``WebSocketMiddleware`` to your Flask App, allowing you to decorate
147 | routes with the ``route`` method, turning them into WebSocket handlers.
148 |
149 | Additionally monkey-patches ``app.run``, to run your app directly in uWSGI.
150 |
151 | ``route(url)``
152 |
153 | ``run(debug, host, port, **kwargs)``
154 | ``**kwargs`` are passed to uWSGI as command line arguments.
155 |
156 |
157 | ``WebSocketMiddleware``
158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159 | WebSocket Middleware which automatically performs WebSocket handshake and
160 | passes ``WebSocketClient`` instances to your route.
161 |
162 |
163 | ``WebSocketClient``
164 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165 | Exposes the `uWSGI WebSocket API
166 | `_.
167 |
168 | ``recv()`` (alias ``WebSocket.receive()``)
169 |
170 | ``recv_nb()``
171 |
172 | ``send(msg)``
173 |
174 | ``send_binary(msg)``
175 |
176 | ``recv_nb()``
177 |
178 | ``send_from_sharedarea(id, pos)``
179 |
180 | ``send_binary_from_sharedarea(id, pos)``
181 |
182 |
183 | ``GeventWebSocket``
184 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
185 | Fancier WebSocket abstraction that takes advantage of Gevent loop engine.
186 | Requires uWSGI to be run with ``--uwsgi`` option.
187 |
188 |
189 | ``GeventWebSocketMiddleware``
190 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
191 | Automatically performs WebSocket handshake and passes a
192 | ``GeventWebSocketClient`` instance to your route.
193 |
194 |
195 | ``GeventWebSocketClient``
196 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
197 | WebSocket client abstraction with fully non-blocking methods.
198 |
199 | ``receive()``
200 |
201 | ``send(msg)``
202 |
203 | ``close()``
204 |
205 | ``connected``
206 |
207 |
208 | ``AsyncioWebSocket``
209 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
210 | Fancier WebSocket abstraction that takes advantage of Asyncio loop engine.
211 | Requires uWSGI to be run with ``--asyncio`` and ``--greenlet`` option.
212 |
213 |
214 | ``AsyncioWebSocketMiddleware``
215 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
216 | Automatically performs WebSocket handshake and passes a ``AsyncioWebSocketClient`` instance to your route.
217 |
218 |
219 | ``AsyncioWebSocketClient``
220 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
221 | WebSocket client abstraction with asyncio coroutines.
222 |
223 | ``coroutine a_recv()`` (alias ``receive()``, ``recv()``)
224 |
225 | ``coroutine a_send(msg)`` (alias ``send()``)
226 |
227 | ``recv_nb()`` (should be useless)
228 |
229 | ``send_nb()`` (should be useless)
230 |
231 | ``close()``
232 |
233 | ``connected``
234 |
235 |
236 | Advanced Usage
237 | --------------
238 | Normally websocket routes happen outside of the normal request context. You can
239 | get a request context in your websocket handler by using
240 | ``app.request_context``::
241 |
242 | app = Flask(__name__)
243 | ws = GeventWebSocket(app)
244 |
245 | @ws.route('/websocket')
246 | def websocket(ws):
247 | with app.request_context(ws.environ):
248 | print request.args
249 |
--------------------------------------------------------------------------------
/dev-requirements.txt:
--------------------------------------------------------------------------------
1 | Flask
2 | uwsgi
3 | gevent
4 |
--------------------------------------------------------------------------------
/examples/chat-asyncio/chat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from collections import deque
3 | from flask import Flask, render_template
4 | from flask_uwsgi_websocket import AsyncioWebSocket
5 | from asyncio import coroutine
6 |
7 | app = Flask(__name__)
8 | ws = AsyncioWebSocket(app)
9 |
10 | users = {}
11 | backlog = deque(maxlen=10)
12 |
13 | @app.route('/')
14 | def index():
15 | return render_template('index.html')
16 |
17 | @ws.route('/websocket')
18 | @coroutine
19 | def chat(ws):
20 | users[ws.id] = ws
21 |
22 | for msg in backlog:
23 | yield from ws.send(msg)
24 |
25 | while True:
26 | msg = yield from ws.receive()
27 | if msg is not None:
28 | backlog.append(msg)
29 | for id in users:
30 | if id != ws.id:
31 | yield from users[id].send(msg)
32 | else:
33 | break
34 |
35 | del users[ws.id]
36 |
37 | if __name__ == '__main__':
38 | app.run(debug=True, asyncio=100, greenlet=True)
39 |
--------------------------------------------------------------------------------
/examples/chat-asyncio/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebSocket Chat Example
5 |
6 |
9 |
10 |
46 |
47 |
48 | WebSocket Chat Example
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |