├── MANIFEST.in ├── docs ├── _static │ ├── logo.png │ └── index.html ├── _themes │ ├── flask │ │ ├── theme.conf │ │ ├── relations.html │ │ ├── layout.html │ │ └── static │ │ │ └── flasky.css_t │ ├── flask_small │ │ ├── theme.conf │ │ ├── layout.html │ │ └── static │ │ │ └── flasky.css_t │ ├── README │ ├── LICENSE │ └── flask_theme_support.py ├── Makefile ├── make.bat ├── conf.py └── index.rst ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── question.md │ ├── bug_report.md │ └── feature_request.md ├── .travis.yml ├── example ├── requirements.txt ├── sessions.py ├── app_namespace.py ├── app.py └── templates │ ├── index.html │ └── sessions.html ├── .gitignore ├── LICENSE ├── bin ├── release └── mkchangelog.py ├── README.md ├── setup.py ├── flask_socketio ├── namespace.py └── test_client.py ├── test_socketio.py └── CHANGES.md /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakebjorn/Flask-SocketIO/master/docs/_static/logo.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: miguelgrinberg 2 | patreon: miguelgrinberg 3 | custom: https://paypal.me/miguelgrinberg 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | - "pypy" 8 | - "pypy3" 9 | install: python setup.py install 10 | script: python setup.py test 11 | -------------------------------------------------------------------------------- /docs/_themes/flask/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | index_logo = '' 8 | index_logo_height = 120px 9 | touch_icon = 10 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0.2 2 | Flask-Login==0.4.1 3 | Flask-Session==0.3.1 4 | Flask_SocketIO 5 | itsdangerous==1.1.0 6 | Jinja2==2.10 7 | MarkupSafe==1.1.0 8 | python-engineio 9 | python-socketio 10 | six==1.11.0 11 | Werkzeug==0.14.1 12 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | var 14 | sdist 15 | develop-eggs 16 | .installed.cfg 17 | lib 18 | lib64 19 | __pycache__ 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /docs/_static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Flask-SocketIO documentation 5 | 6 | 7 | 8 | 9 | The Flask-SocketIO documentation is available at Read the Docs. 10 | If your browser does not automatically redirect you, please click here. 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/_themes/flask/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Your question** 11 | Enter your question here. Please be concise and include code if necessary. If you are getting errors, please include the complete error message, including the stack trace. 12 | 13 | **Logs** 14 | Please provide relevant logs from the server and the client. On the server, add the `logger=True` and `engineio_logger=True` arguments to your `SockertIO()` object to get logs dumped on your terminal. The same arguments can be used on the [python-socketio's client](https://github.com/miguelgrinberg/python-socketio). If you are using the JavaScript client, see [here](https://socket.io/docs/logging-and-debugging/) for how to enable logs. 15 | -------------------------------------------------------------------------------- /docs/_themes/flask/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 8 | {% endblock %} 9 | {%- block relbar2 %}{% endblock %} 10 | {% block header %} 11 | {{ super() }} 12 | {% if pagename == 'index' %} 13 |
14 | {% endif %} 15 | {% endblock %} 16 | {%- block footer %} 17 | 21 | {% if pagename == 'index' %} 22 |
23 | {% endif %} 24 | {%- endblock %} 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. If you are getting errors, please include the complete error message, including the stack trace. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Logs** 24 | Please provide relevant logs from the server and the client. On the server, add the `logger=True` and `engineio_logger=True` arguments to your `SockertIO()` object to get logs dumped on your terminal. The same arguments can be used on the [python-socketio's client](https://github.com/miguelgrinberg/python-socketio). If you are using the JavaScript client, see [here](https://socket.io/docs/logging-and-debugging/) for how to enable logs. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Miguel Grinberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Logs** 20 | Please provide relevant logs from the server and the client. On the server, add the `logger=True` and `engineio_logger=True` arguments to your `SockertIO()` object to get logs dumped on your terminal. The same arguments can be used on the [python-socketio's client](https://github.com/miguelgrinberg/python-socketio). If you are using the JavaScript client, see [here](https://socket.io/docs/logging-and-debugging/) for how to enable logs. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /docs/_themes/README: -------------------------------------------------------------------------------- 1 | Flask Sphinx Styles 2 | =================== 3 | 4 | This repository contains sphinx styles for Flask and Flask related 5 | projects. To use this style in your Sphinx documentation, follow 6 | this guide: 7 | 8 | 1. put this folder as _themes into your docs folder. Alternatively 9 | you can also use git submodules to check out the contents there. 10 | 2. add this to your conf.py: 11 | 12 | sys.path.append(os.path.abspath('_themes')) 13 | html_theme_path = ['_themes'] 14 | html_theme = 'flask' 15 | 16 | The following themes exist: 17 | 18 | - 'flask' - the standard flask documentation theme for large 19 | projects 20 | - 'flask_small' - small one-page theme. Intended to be used by 21 | very small addon libraries for flask. 22 | 23 | The following options exist for the flask_small theme: 24 | 25 | [options] 26 | index_logo = '' filename of a picture in _static 27 | to be used as replacement for the 28 | h1 in the index.rst file. 29 | index_logo_height = 120px height of the index logo 30 | github_fork = '' repository name on github for the 31 | "fork me" badge 32 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | VERSION="$1" 4 | VERSION_FILE=flask_socketio/__init__.py 5 | 6 | if [[ "$VERSION" == "" ]]; then 7 | echo "Usage: $0 " 8 | fi 9 | 10 | # update change log 11 | head -n 2 CHANGES.md > _CHANGES.md 12 | echo "**Release $VERSION** - $(date +%F)" >> _CHANGES.md 13 | echo "" >> _CHANGES.md 14 | pip install gitpython 15 | python bin/mkchangelog.py >> _CHANGES.md 16 | echo "" >> _CHANGES.md 17 | len=$(wc -l < CHANGES.md) 18 | tail -n $(expr $len - 2) CHANGES.md >> _CHANGES.md 19 | vim _CHANGES.md 20 | set +e 21 | grep -q ABORT _CHANGES.md 22 | if [[ "$?" == "0" ]]; then 23 | rm _CHANGES.md 24 | echo "Aborted." 25 | exit 1 26 | fi 27 | set -e 28 | mv _CHANGES.md CHANGES.md 29 | 30 | sed -i "" "s/^__version__ = '.*'$/__version__ = '$VERSION'/" $VERSION_FILE 31 | rm -rf dist 32 | pip install --upgrade pip wheel twine 33 | python setup.py sdist bdist_wheel --universal 34 | 35 | git add $VERSION_FILE CHANGES.md 36 | git commit -m "Release $VERSION" 37 | git tag -f v$VERSION 38 | git push -f --tags origin master 39 | 40 | read -p "Press any key to submit to PyPI or Ctrl-C to abort..." -n1 -s 41 | twine upload dist/* 42 | 43 | NEW_VERSION="${VERSION%.*}.$((${VERSION##*.}+1))dev" 44 | sed -i "" "s/^__version__ = '.*'$/__version__ = '$NEW_VERSION'/" $VERSION_FILE 45 | git add $VERSION_FILE 46 | git commit -m "Version $NEW_VERSION" 47 | git push origin master 48 | echo "Development is now open on version $NEW_VERSION!" 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Flask-SocketIO 2 | ============== 3 | 4 | [![Build Status](https://travis-ci.org/miguelgrinberg/Flask-SocketIO.png?branch=master)](https://travis-ci.org/miguelgrinberg/Flask-SocketIO) 5 | 6 | Socket.IO integration for Flask applications. 7 | 8 | Installation 9 | ------------ 10 | 11 | You can install this package as usual with pip: 12 | 13 | pip install flask-socketio 14 | 15 | Example 16 | ------- 17 | 18 | ```py 19 | from flask import Flask, render_template 20 | from flask_socketio import SocketIO, emit 21 | 22 | app = Flask(__name__) 23 | app.config['SECRET_KEY'] = 'secret!' 24 | socketio = SocketIO(app) 25 | 26 | @app.route('/') 27 | def index(): 28 | return render_template('index.html') 29 | 30 | @socketio.on('my event') 31 | def test_message(message): 32 | emit('my response', {'data': 'got it!'}) 33 | 34 | if __name__ == '__main__': 35 | socketio.run(app) 36 | ``` 37 | 38 | Resources 39 | --------- 40 | 41 | - [Tutorial](http://blog.miguelgrinberg.com/post/easy-websockets-with-flask-and-gevent) 42 | - [Documentation](http://flask-socketio.readthedocs.io/en/latest/) 43 | - [PyPI](https://pypi.python.org/pypi/Flask-SocketIO) 44 | - [Change Log](https://github.com/miguelgrinberg/Flask-SocketIO/blob/master/CHANGES.md) 45 | - Questions? See the [questions](https://stackoverflow.com/questions/tagged/flask-socketio) others have asked on Stack Overflow, or [ask](https://stackoverflow.com/questions/ask?tags=python+flask-socketio+python-socketio) your own question. 46 | 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flask-SocketIO 3 | -------------- 4 | 5 | Socket.IO integration for Flask applications. 6 | """ 7 | import re 8 | from setuptools import setup 9 | 10 | with open('flask_socketio/__init__.py', 'r') as f: 11 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 12 | f.read(), re.MULTILINE).group(1) 13 | 14 | setup( 15 | name='Flask-SocketIO', 16 | version=version, 17 | url='http://github.com/miguelgrinberg/Flask-SocketIO/', 18 | license='MIT', 19 | author='Miguel Grinberg', 20 | author_email='miguelgrinberg50@gmail.com', 21 | description='Socket.IO integration for Flask applications', 22 | long_description=__doc__, 23 | packages=['flask_socketio'], 24 | zip_safe=False, 25 | include_package_data=True, 26 | platforms='any', 27 | install_requires=[ 28 | 'Flask>=0.9', 29 | 'python-socketio>=4.3.0' 30 | ], 31 | tests_require=[ 32 | 'coverage' 33 | ], 34 | test_suite='test_socketio', 35 | classifiers=[ 36 | 'Environment :: Web Environment', 37 | 'Intended Audience :: Developers', 38 | 'License :: OSI Approved :: MIT License', 39 | 'Operating System :: OS Independent', 40 | 'Programming Language :: Python', 41 | 'Programming Language :: Python :: 2', 42 | 'Programming Language :: Python :: 3', 43 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 44 | 'Topic :: Software Development :: Libraries :: Python Modules' 45 | ] 46 | ) 47 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the theme, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | We kindly ask you to only use these themes in an unmodified manner just 22 | for Flask and Flask-related products, not for unrelated projects. If you 23 | like the visual style and want to use it for your own projects, please 24 | consider making some larger changes to the themes (such as changing 25 | font faces, sizes, colors or margins). 26 | 27 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 31 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 37 | POSSIBILITY OF SUCH DAMAGE. 38 | -------------------------------------------------------------------------------- /bin/mkchangelog.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import re 3 | import sys 4 | import git 5 | 6 | URL = 'https://github.com/miguelgrinberg/flask-socketio' 7 | merges = {} 8 | 9 | 10 | def format_message(commit): 11 | if commit.message.startswith('Version '): 12 | return '' 13 | if '#nolog' in commit.message: 14 | return '' 15 | if commit.message.startswith('Merge pull request'): 16 | pr = commit.message.split('#')[1].split(' ')[0] 17 | message = ' '.join([line for line in [line.strip() for line in commit.message.split('\n')[1:]] if line]) 18 | merges[message] = pr 19 | return '' 20 | if commit.message.startswith('Release '): 21 | return '\n**{message}** - {date}\n'.format( 22 | message=commit.message.strip(), 23 | date=datetime.datetime.fromtimestamp(commit.committed_date).strftime('%Y-%m-%d')) 24 | message = ' '.join([line for line in [line.strip() for line in commit.message.split('\n')] if line]) 25 | if message in merges: 26 | message += ' #' + merges[message] 27 | message = re.sub('\\(.*(#[0-9]+)\\)', '\\1', message) 28 | message = re.sub('Fixes (#[0-9]+)', '\\1', message) 29 | message = re.sub('fixes (#[0-9]+)', '\\1', message) 30 | message = re.sub('#([0-9]+)', '[#\\1]({url}/issues/\\1)'.format(url=URL), message) 31 | message += ' ([commit]({url}/commit/{sha}))'.format(url=URL, sha=str(commit)) 32 | if commit.author.name != 'Miguel Grinberg': 33 | message += ' (thanks **{name}**!)'.format(name=commit.author.name) 34 | return '- ' + message 35 | 36 | 37 | def main(all=False): 38 | repo = git.Repo() 39 | 40 | for commit in repo.iter_commits(): 41 | if not all and commit.message.startswith('Release '): 42 | break 43 | message = format_message(commit) 44 | if message: 45 | print(message) 46 | 47 | 48 | if __name__ == '__main__': 49 | main(all=len(sys.argv) > 1 and sys.argv[1] == 'all') 50 | -------------------------------------------------------------------------------- /example/sessions.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, session, request, jsonify 2 | from flask_login import LoginManager, UserMixin, current_user, login_user, \ 3 | logout_user 4 | from flask_session import Session 5 | from flask_socketio import SocketIO, emit 6 | 7 | app = Flask(__name__) 8 | app.config['SECRET_KEY'] = 'top-secret!' 9 | app.config['SESSION_TYPE'] = 'filesystem' 10 | login = LoginManager(app) 11 | Session(app) 12 | socketio = SocketIO(app, manage_session=False) 13 | 14 | 15 | class User(UserMixin, object): 16 | def __init__(self, id=None): 17 | self.id = id 18 | 19 | 20 | @login.user_loader 21 | def load_user(id): 22 | return User(id) 23 | 24 | 25 | @app.route('/') 26 | def index(): 27 | return render_template('sessions.html') 28 | 29 | 30 | @app.route('/session', methods=['GET', 'POST']) 31 | def session_access(): 32 | if request.method == 'GET': 33 | return jsonify({ 34 | 'session': session.get('value', ''), 35 | 'user': current_user.id 36 | if current_user.is_authenticated else 'anonymous' 37 | }) 38 | data = request.get_json() 39 | if 'session' in data: 40 | session['value'] = data['session'] 41 | elif 'user' in data: 42 | if data['user']: 43 | login_user(User(data['user'])) 44 | else: 45 | logout_user() 46 | return '', 204 47 | 48 | 49 | @socketio.on('get-session') 50 | def get_session(): 51 | emit('refresh-session', { 52 | 'session': session.get('value', ''), 53 | 'user': current_user.id 54 | if current_user.is_authenticated else 'anonymous' 55 | }) 56 | 57 | 58 | @socketio.on('set-session') 59 | def set_session(data): 60 | if 'session' in data: 61 | session['value'] = data['session'] 62 | elif 'user' in data: 63 | if data['user'] is not None: 64 | login_user(User(data['user'])) 65 | else: 66 | logout_user() 67 | 68 | 69 | if __name__ == '__main__': 70 | socketio.run(app) 71 | -------------------------------------------------------------------------------- /flask_socketio/namespace.py: -------------------------------------------------------------------------------- 1 | from socketio import Namespace as _Namespace 2 | 3 | 4 | class Namespace(_Namespace): 5 | def __init__(self, namespace=None): 6 | super(Namespace, self).__init__(namespace) 7 | self.socketio = None 8 | 9 | def _set_socketio(self, socketio): 10 | self.socketio = socketio 11 | 12 | def trigger_event(self, event, *args): 13 | """Dispatch an event to the proper handler method. 14 | 15 | In the most common usage, this method is not overloaded by subclasses, 16 | as it performs the routing of events to methods. However, this 17 | method can be overriden if special dispatching rules are needed, or if 18 | having a single method that catches all events is desired. 19 | """ 20 | handler_name = 'on_' + event 21 | if not hasattr(self, handler_name): 22 | # there is no handler for this event, so we ignore it 23 | return 24 | handler = getattr(self, handler_name) 25 | return self.socketio._handle_event(handler, event, self.namespace, 26 | *args) 27 | 28 | def emit(self, event, data=None, room=None, include_self=True, 29 | namespace=None, callback=None): 30 | """Emit a custom event to one or more connected clients.""" 31 | return self.socketio.emit(event, data, room=room, 32 | include_self=include_self, 33 | namespace=namespace or self.namespace, 34 | callback=callback) 35 | 36 | def send(self, data, room=None, include_self=True, namespace=None, 37 | callback=None): 38 | """Send a message to one or more connected clients.""" 39 | return self.socketio.send(data, room=room, include_self=include_self, 40 | namespace=namespace or self.namespace, 41 | callback=callback) 42 | 43 | def close_room(self, room, namespace=None): 44 | """Close a room.""" 45 | return self.socketio.close_room(room=room, 46 | namespace=namespace or self.namespace) 47 | 48 | -------------------------------------------------------------------------------- /example/app_namespace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from threading import Lock 3 | from flask import Flask, render_template, session, request 4 | from flask_socketio import SocketIO, Namespace, emit, join_room, leave_room, \ 5 | close_room, rooms, disconnect 6 | 7 | # Set this variable to "threading", "eventlet" or "gevent" to test the 8 | # different async modes, or leave it set to None for the application to choose 9 | # the best option based on installed packages. 10 | async_mode = None 11 | 12 | app = Flask(__name__) 13 | app.config['SECRET_KEY'] = 'secret!' 14 | socketio = SocketIO(app, async_mode=async_mode) 15 | thread = None 16 | thread_lock = Lock() 17 | 18 | 19 | def background_thread(): 20 | """Example of how to send server generated events to clients.""" 21 | count = 0 22 | while True: 23 | socketio.sleep(10) 24 | count += 1 25 | socketio.emit('my_response', 26 | {'data': 'Server generated event', 'count': count}, 27 | namespace='/test') 28 | 29 | 30 | @app.route('/') 31 | def index(): 32 | return render_template('index.html', async_mode=socketio.async_mode) 33 | 34 | 35 | class MyNamespace(Namespace): 36 | def on_my_event(self, message): 37 | session['receive_count'] = session.get('receive_count', 0) + 1 38 | emit('my_response', 39 | {'data': message['data'], 'count': session['receive_count']}) 40 | 41 | def on_my_broadcast_event(self, message): 42 | session['receive_count'] = session.get('receive_count', 0) + 1 43 | emit('my_response', 44 | {'data': message['data'], 'count': session['receive_count']}, 45 | broadcast=True) 46 | 47 | def on_join(self, message): 48 | join_room(message['room']) 49 | session['receive_count'] = session.get('receive_count', 0) + 1 50 | emit('my_response', 51 | {'data': 'In rooms: ' + ', '.join(rooms()), 52 | 'count': session['receive_count']}) 53 | 54 | def on_leave(self, message): 55 | leave_room(message['room']) 56 | session['receive_count'] = session.get('receive_count', 0) + 1 57 | emit('my_response', 58 | {'data': 'In rooms: ' + ', '.join(rooms()), 59 | 'count': session['receive_count']}) 60 | 61 | def on_close_room(self, message): 62 | session['receive_count'] = session.get('receive_count', 0) + 1 63 | emit('my_response', {'data': 'Room ' + message['room'] + ' is closing.', 64 | 'count': session['receive_count']}, 65 | room=message['room']) 66 | close_room(message['room']) 67 | 68 | def on_my_room_event(self, message): 69 | session['receive_count'] = session.get('receive_count', 0) + 1 70 | emit('my_response', 71 | {'data': message['data'], 'count': session['receive_count']}, 72 | room=message['room']) 73 | 74 | def on_disconnect_request(self): 75 | session['receive_count'] = session.get('receive_count', 0) + 1 76 | emit('my_response', 77 | {'data': 'Disconnected!', 'count': session['receive_count']}) 78 | disconnect() 79 | 80 | def on_my_ping(self): 81 | emit('my_pong') 82 | 83 | def on_connect(self): 84 | global thread 85 | with thread_lock: 86 | if thread is None: 87 | thread = socketio.start_background_task(background_thread) 88 | emit('my_response', {'data': 'Connected', 'count': 0}) 89 | 90 | def on_disconnect(self): 91 | print('Client disconnected', request.sid) 92 | 93 | 94 | socketio.on_namespace(MyNamespace('/test')) 95 | 96 | 97 | if __name__ == '__main__': 98 | socketio.run(app, debug=True) 99 | -------------------------------------------------------------------------------- /example/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from threading import Lock 3 | from flask import Flask, render_template, session, request, \ 4 | copy_current_request_context 5 | from flask_socketio import SocketIO, emit, join_room, leave_room, \ 6 | close_room, rooms, disconnect 7 | 8 | # Set this variable to "threading", "eventlet" or "gevent" to test the 9 | # different async modes, or leave it set to None for the application to choose 10 | # the best option based on installed packages. 11 | async_mode = None 12 | 13 | app = Flask(__name__) 14 | app.config['SECRET_KEY'] = 'secret!' 15 | socketio = SocketIO(app, async_mode=async_mode) 16 | thread = None 17 | thread_lock = Lock() 18 | 19 | 20 | def background_thread(): 21 | """Example of how to send server generated events to clients.""" 22 | count = 0 23 | while True: 24 | socketio.sleep(10) 25 | count += 1 26 | socketio.emit('my_response', 27 | {'data': 'Server generated event', 'count': count}, 28 | namespace='/test') 29 | 30 | 31 | @app.route('/') 32 | def index(): 33 | return render_template('index.html', async_mode=socketio.async_mode) 34 | 35 | 36 | @socketio.on('my_event', namespace='/test') 37 | def test_message(message): 38 | session['receive_count'] = session.get('receive_count', 0) + 1 39 | emit('my_response', 40 | {'data': message['data'], 'count': session['receive_count']}) 41 | 42 | 43 | @socketio.on('my_broadcast_event', namespace='/test') 44 | def test_broadcast_message(message): 45 | session['receive_count'] = session.get('receive_count', 0) + 1 46 | emit('my_response', 47 | {'data': message['data'], 'count': session['receive_count']}, 48 | broadcast=True) 49 | 50 | 51 | @socketio.on('join', namespace='/test') 52 | def join(message): 53 | join_room(message['room']) 54 | session['receive_count'] = session.get('receive_count', 0) + 1 55 | emit('my_response', 56 | {'data': 'In rooms: ' + ', '.join(rooms()), 57 | 'count': session['receive_count']}) 58 | 59 | 60 | @socketio.on('leave', namespace='/test') 61 | def leave(message): 62 | leave_room(message['room']) 63 | session['receive_count'] = session.get('receive_count', 0) + 1 64 | emit('my_response', 65 | {'data': 'In rooms: ' + ', '.join(rooms()), 66 | 'count': session['receive_count']}) 67 | 68 | 69 | @socketio.on('close_room', namespace='/test') 70 | def close(message): 71 | session['receive_count'] = session.get('receive_count', 0) + 1 72 | emit('my_response', {'data': 'Room ' + message['room'] + ' is closing.', 73 | 'count': session['receive_count']}, 74 | room=message['room']) 75 | close_room(message['room']) 76 | 77 | 78 | @socketio.on('my_room_event', namespace='/test') 79 | def send_room_message(message): 80 | session['receive_count'] = session.get('receive_count', 0) + 1 81 | emit('my_response', 82 | {'data': message['data'], 'count': session['receive_count']}, 83 | room=message['room']) 84 | 85 | 86 | @socketio.on('disconnect_request', namespace='/test') 87 | def disconnect_request(): 88 | @copy_current_request_context 89 | def can_disconnect(): 90 | disconnect() 91 | 92 | session['receive_count'] = session.get('receive_count', 0) + 1 93 | # for this emit we use a callback function 94 | # when the callback function is invoked we know that the message has been 95 | # received and it is safe to disconnect 96 | emit('my_response', 97 | {'data': 'Disconnected!', 'count': session['receive_count']}, 98 | callback=can_disconnect) 99 | 100 | 101 | @socketio.on('my_ping', namespace='/test') 102 | def ping_pong(): 103 | emit('my_pong') 104 | 105 | 106 | @socketio.on('connect', namespace='/test') 107 | def test_connect(): 108 | global thread 109 | with thread_lock: 110 | if thread is None: 111 | thread = socketio.start_background_task(background_thread) 112 | emit('my_response', {'data': 'Connected', 'count': 0}) 113 | 114 | 115 | @socketio.on('disconnect', namespace='/test') 116 | def test_disconnect(): 117 | print('Client disconnected', request.sid) 118 | 119 | 120 | if __name__ == '__main__': 121 | socketio.run(app, debug=True) 122 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | color: #000; 20 | background: white; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 40px auto 0 auto; 32 | width: 700px; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | } 44 | 45 | img.floatingflask { 46 | padding: 0 0 10px 10px; 47 | float: right; 48 | } 49 | 50 | div.footer { 51 | text-align: right; 52 | color: #888; 53 | padding: 10px; 54 | font-size: 14px; 55 | width: 650px; 56 | margin: 0 auto 40px auto; 57 | } 58 | 59 | div.footer a { 60 | color: #888; 61 | text-decoration: underline; 62 | } 63 | 64 | div.related { 65 | line-height: 32px; 66 | color: #888; 67 | } 68 | 69 | div.related ul { 70 | padding: 0 0 0 10px; 71 | } 72 | 73 | div.related a { 74 | color: #444; 75 | } 76 | 77 | /* -- body styles ----------------------------------------------------------- */ 78 | 79 | a { 80 | color: #004B6B; 81 | text-decoration: underline; 82 | } 83 | 84 | a:hover { 85 | color: #6D4100; 86 | text-decoration: underline; 87 | } 88 | 89 | div.body { 90 | padding-bottom: 40px; /* saved for footer */ 91 | } 92 | 93 | div.body h1, 94 | div.body h2, 95 | div.body h3, 96 | div.body h4, 97 | div.body h5, 98 | div.body h6 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | font-weight: normal; 101 | margin: 30px 0px 10px 0px; 102 | padding: 0; 103 | } 104 | 105 | {% if theme_index_logo %} 106 | div.indexwrapper h1 { 107 | text-indent: -999999px; 108 | background: url({{ theme_index_logo }}) no-repeat center center; 109 | height: {{ theme_index_logo_height }}; 110 | } 111 | {% endif %} 112 | 113 | div.body h2 { font-size: 180%; } 114 | div.body h3 { font-size: 150%; } 115 | div.body h4 { font-size: 130%; } 116 | div.body h5 { font-size: 100%; } 117 | div.body h6 { font-size: 100%; } 118 | 119 | a.headerlink { 120 | color: white; 121 | padding: 0 4px; 122 | text-decoration: none; 123 | } 124 | 125 | a.headerlink:hover { 126 | color: #444; 127 | background: #eaeaea; 128 | } 129 | 130 | div.body p, div.body dd, div.body li { 131 | line-height: 1.4em; 132 | } 133 | 134 | div.admonition { 135 | background: #fafafa; 136 | margin: 20px -30px; 137 | padding: 10px 30px; 138 | border-top: 1px solid #ccc; 139 | border-bottom: 1px solid #ccc; 140 | } 141 | 142 | div.admonition p.admonition-title { 143 | font-family: 'Garamond', 'Georgia', serif; 144 | font-weight: normal; 145 | font-size: 24px; 146 | margin: 0 0 10px 0; 147 | padding: 0; 148 | line-height: 1; 149 | } 150 | 151 | div.admonition p.last { 152 | margin-bottom: 0; 153 | } 154 | 155 | div.highlight{ 156 | background-color: white; 157 | } 158 | 159 | dt:target, .highlight { 160 | background: #FAF3E8; 161 | } 162 | 163 | div.note { 164 | background-color: #eee; 165 | border: 1px solid #ccc; 166 | } 167 | 168 | div.seealso { 169 | background-color: #ffc; 170 | border: 1px solid #ff6; 171 | } 172 | 173 | div.topic { 174 | background-color: #eee; 175 | } 176 | 177 | div.warning { 178 | background-color: #ffe4e4; 179 | border: 1px solid #f66; 180 | } 181 | 182 | p.admonition-title { 183 | display: inline; 184 | } 185 | 186 | p.admonition-title:after { 187 | content: ":"; 188 | } 189 | 190 | pre, tt { 191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 192 | font-size: 0.85em; 193 | } 194 | 195 | img.screenshot { 196 | } 197 | 198 | tt.descname, tt.descclassname { 199 | font-size: 0.95em; 200 | } 201 | 202 | tt.descname { 203 | padding-right: 0.08em; 204 | } 205 | 206 | img.screenshot { 207 | -moz-box-shadow: 2px 2px 4px #eee; 208 | -webkit-box-shadow: 2px 2px 4px #eee; 209 | box-shadow: 2px 2px 4px #eee; 210 | } 211 | 212 | table.docutils { 213 | border: 1px solid #888; 214 | -moz-box-shadow: 2px 2px 4px #eee; 215 | -webkit-box-shadow: 2px 2px 4px #eee; 216 | box-shadow: 2px 2px 4px #eee; 217 | } 218 | 219 | table.docutils td, table.docutils th { 220 | border: 1px solid #888; 221 | padding: 0.25em 0.7em; 222 | } 223 | 224 | table.field-list, table.footnote { 225 | border: none; 226 | -moz-box-shadow: none; 227 | -webkit-box-shadow: none; 228 | box-shadow: none; 229 | } 230 | 231 | table.footnote { 232 | margin: 15px 0; 233 | width: 100%; 234 | border: 1px solid #eee; 235 | } 236 | 237 | table.field-list th { 238 | padding: 0 0.8em 0 0; 239 | } 240 | 241 | table.field-list td { 242 | padding: 0; 243 | } 244 | 245 | table.footnote td { 246 | padding: 0.5em; 247 | } 248 | 249 | dl { 250 | margin: 0; 251 | padding: 0; 252 | } 253 | 254 | dl dd { 255 | margin-left: 30px; 256 | } 257 | 258 | pre { 259 | padding: 0; 260 | margin: 15px -30px; 261 | padding: 8px; 262 | line-height: 1.3em; 263 | padding: 7px 30px; 264 | background: #eee; 265 | border-radius: 2px; 266 | -moz-border-radius: 2px; 267 | -webkit-border-radius: 2px; 268 | } 269 | 270 | dl pre { 271 | margin-left: -60px; 272 | padding-left: 60px; 273 | } 274 | 275 | tt { 276 | background-color: #ecf0f3; 277 | color: #222; 278 | /* padding: 1px 2px; */ 279 | } 280 | 281 | tt.xref, a tt { 282 | background-color: #FBFBFB; 283 | } 284 | 285 | a:hover tt { 286 | background: #EEE; 287 | } 288 | -------------------------------------------------------------------------------- /example/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Flask-SocketIO Test 5 | 6 | 7 | 94 | 95 | 96 |

Flask-SocketIO Test

97 |

Async mode is: {{ async_mode }}

98 |

Average ping/pong latency: ms

99 |

Send:

100 |
101 | 102 | 103 |
104 |
105 | 106 | 107 |
108 |
109 | 110 | 111 |
112 |
113 | 114 | 115 |
116 |
117 | 118 | 119 | 120 |
121 |
122 | 123 | 124 |
125 |
126 | 127 |
128 |

Receive:

129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /example/templates/sessions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-SocketIO Sessions example 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

Flask-SocketIO Sessions example

14 | 15 |
16 |

HTTP

17 |
18 |

Session

19 |
20 |

Current session:

21 |

22 | 23 | 24 |

25 |
26 |

User

27 |
28 |

Current user:

29 |

30 | 31 | 32 | 33 |

34 |
35 |
36 |
37 |

Socket.IO

38 |

39 | 60 |
61 |
62 |
63 |
64 | 65 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-SocketIO.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-SocketIO.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-SocketIO" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-SocketIO" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-SocketIO.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-SocketIO.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Flask-SocketIO documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Feb 9 12:36:23 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | sys.path.insert(0, os.path.abspath('..')) 22 | sys.path.append(os.path.abspath('_themes')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = ['sphinx.ext.autodoc'] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix of source filenames. 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = u'Flask-SocketIO' 48 | copyright = u'2014, Miguel Grinberg' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | #version = '0.1.0' 56 | # The full version, including alpha/beta/rc tags. 57 | #release = '0.1.0' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | exclude_patterns = ['_build'] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all 74 | # documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | # If true, keep warnings as "system message" paragraphs in the built documents. 95 | #keep_warnings = False 96 | 97 | 98 | # -- Options for HTML output ---------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = 'flask_small' 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | html_theme_options = { 108 | 'index_logo': 'logo.png', 109 | 'github_fork': 'miguelgrinberg/Flask-SocketIO' 110 | } 111 | 112 | # Add any paths that contain custom themes here, relative to this directory. 113 | html_theme_path = ['_themes'] 114 | 115 | # The name for this set of Sphinx documents. If None, it defaults to 116 | # " v documentation". 117 | #html_title = None 118 | 119 | # A shorter title for the navigation bar. Default is the same as html_title. 120 | #html_short_title = None 121 | 122 | # The name of an image file (relative to this directory) to place at the top 123 | # of the sidebar. 124 | #html_logo = None 125 | 126 | # The name of an image file (within the static path) to use as favicon of the 127 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 128 | # pixels large. 129 | #html_favicon = None 130 | 131 | # Add any paths that contain custom static files (such as style sheets) here, 132 | # relative to this directory. They are copied after the builtin static files, 133 | # so a file named "default.css" will overwrite the builtin "default.css". 134 | html_static_path = ['_static'] 135 | 136 | # Add any extra paths that contain custom files (such as robots.txt or 137 | # .htaccess) here, relative to this directory. These files are copied 138 | # directly to the root of the documentation. 139 | #html_extra_path = [] 140 | 141 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 142 | # using the given strftime format. 143 | #html_last_updated_fmt = '%b %d, %Y' 144 | 145 | # If true, SmartyPants will be used to convert quotes and dashes to 146 | # typographically correct entities. 147 | #html_use_smartypants = True 148 | 149 | # Custom sidebar templates, maps document names to template names. 150 | #html_sidebars = {} 151 | 152 | # Additional templates that should be rendered to pages, maps page names to 153 | # template names. 154 | #html_additional_pages = {} 155 | 156 | # If false, no module index is generated. 157 | #html_domain_indices = True 158 | 159 | # If false, no index is generated. 160 | #html_use_index = True 161 | 162 | # If true, the index is split into individual pages for each letter. 163 | #html_split_index = False 164 | 165 | # If true, links to the reST sources are added to the pages. 166 | #html_show_sourcelink = True 167 | 168 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 169 | #html_show_sphinx = True 170 | 171 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 172 | #html_show_copyright = True 173 | 174 | # If true, an OpenSearch description file will be output, and all pages will 175 | # contain a tag referring to it. The value of this option must be the 176 | # base URL from which the finished HTML is served. 177 | #html_use_opensearch = '' 178 | 179 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 180 | #html_file_suffix = None 181 | 182 | # Output file base name for HTML help builder. 183 | htmlhelp_basename = 'Flask-SocketIOdoc' 184 | 185 | 186 | # -- Options for LaTeX output --------------------------------------------- 187 | 188 | latex_elements = { 189 | # The paper size ('letterpaper' or 'a4paper'). 190 | #'papersize': 'letterpaper', 191 | 192 | # The font size ('10pt', '11pt' or '12pt'). 193 | #'pointsize': '10pt', 194 | 195 | # Additional stuff for the LaTeX preamble. 196 | #'preamble': '', 197 | } 198 | 199 | # Grouping the document tree into LaTeX files. List of tuples 200 | # (source start file, target name, title, 201 | # author, documentclass [howto, manual, or own class]). 202 | latex_documents = [ 203 | ('index', 'Flask-SocketIO.tex', u'Flask-SocketIO Documentation', 204 | u'Miguel Grinberg', 'manual'), 205 | ] 206 | 207 | # The name of an image file (relative to this directory) to place at the top of 208 | # the title page. 209 | #latex_logo = None 210 | 211 | # For "manual" documents, if this is true, then toplevel headings are parts, 212 | # not chapters. 213 | #latex_use_parts = False 214 | 215 | # If true, show page references after internal links. 216 | #latex_show_pagerefs = False 217 | 218 | # If true, show URL addresses after external links. 219 | #latex_show_urls = False 220 | 221 | # Documents to append as an appendix to all manuals. 222 | #latex_appendices = [] 223 | 224 | # If false, no module index is generated. 225 | #latex_domain_indices = True 226 | 227 | 228 | # -- Options for manual page output --------------------------------------- 229 | 230 | # One entry per manual page. List of tuples 231 | # (source start file, name, description, authors, manual section). 232 | man_pages = [ 233 | ('index', 'flask-socketio', u'Flask-SocketIO Documentation', 234 | [u'Miguel Grinberg'], 1) 235 | ] 236 | 237 | # If true, show URL addresses after external links. 238 | #man_show_urls = False 239 | 240 | 241 | # -- Options for Texinfo output ------------------------------------------- 242 | 243 | # Grouping the document tree into Texinfo files. List of tuples 244 | # (source start file, target name, title, author, 245 | # dir menu entry, description, category) 246 | texinfo_documents = [ 247 | ('index', 'Flask-SocketIO', u'Flask-SocketIO Documentation', 248 | u'Miguel Grinberg', 'Flask-SocketIO', 'One line description of project.', 249 | 'Miscellaneous'), 250 | ] 251 | 252 | # Documents to append as an appendix to all manuals. 253 | #texinfo_appendices = [] 254 | 255 | # If false, no module index is generated. 256 | #texinfo_domain_indices = True 257 | 258 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 259 | #texinfo_show_urls = 'footnote' 260 | 261 | # If true, do not generate a @detailmenu in the "Top" node's menu. 262 | #texinfo_no_detailmenu = False 263 | 264 | autodoc_member_order = 'bysource' 265 | -------------------------------------------------------------------------------- /flask_socketio/test_client.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from socketio import packet 4 | from socketio.pubsub_manager import PubSubManager 5 | from werkzeug.test import EnvironBuilder 6 | 7 | 8 | class SocketIOTestClient(object): 9 | """ 10 | This class is useful for testing a Flask-SocketIO server. It works in a 11 | similar way to the Flask Test Client, but adapted to the Socket.IO server. 12 | 13 | :param app: The Flask application instance. 14 | :param socketio: The application's ``SocketIO`` instance. 15 | :param namespace: The namespace for the client. If not provided, the client 16 | connects to the server on the global namespace. 17 | :param query_string: A string with custom query string arguments. 18 | :param headers: A dictionary with custom HTTP headers. 19 | :param flask_test_client: The instance of the Flask test client 20 | currently in use. Passing the Flask test 21 | client is optional, but is necessary if you 22 | want the Flask user session and any other 23 | cookies set in HTTP routes accessible from 24 | Socket.IO events. 25 | """ 26 | queue = {} 27 | acks = {} 28 | 29 | def __init__(self, app, socketio, namespace=None, query_string=None, 30 | headers=None, flask_test_client=None): 31 | def _mock_send_packet(sid, pkt): 32 | if pkt.packet_type == packet.EVENT or \ 33 | pkt.packet_type == packet.BINARY_EVENT: 34 | if sid not in self.queue: 35 | self.queue[sid] = [] 36 | if pkt.data[0] == 'message' or pkt.data[0] == 'json': 37 | self.queue[sid].append({'name': pkt.data[0], 38 | 'args': pkt.data[1], 39 | 'namespace': pkt.namespace or '/'}) 40 | else: 41 | self.queue[sid].append({'name': pkt.data[0], 42 | 'args': pkt.data[1:], 43 | 'namespace': pkt.namespace or '/'}) 44 | elif pkt.packet_type == packet.ACK or \ 45 | pkt.packet_type == packet.BINARY_ACK: 46 | self.acks[sid] = {'args': pkt.data, 47 | 'namespace': pkt.namespace or '/'} 48 | elif pkt.packet_type in [packet.DISCONNECT, packet.ERROR]: 49 | self.connected[pkt.namespace or '/'] = False 50 | 51 | self.app = app 52 | self.flask_test_client = flask_test_client 53 | self.sid = uuid.uuid4().hex 54 | self.queue[self.sid] = [] 55 | self.acks[self.sid] = None 56 | self.callback_counter = 0 57 | self.socketio = socketio 58 | self.connected = {} 59 | socketio.server._send_packet = _mock_send_packet 60 | socketio.server.environ[self.sid] = {} 61 | socketio.server.async_handlers = False # easier to test when 62 | socketio.server.eio.async_handlers = False # events are sync 63 | if isinstance(socketio.server.manager, PubSubManager): 64 | raise RuntimeError('Test client cannot be used with a message ' 65 | 'queue. Disable the queue on your test ' 66 | 'configuration.') 67 | socketio.server.manager.initialize() 68 | self.connect(namespace=namespace, query_string=query_string, 69 | headers=headers) 70 | 71 | def is_connected(self, namespace=None): 72 | """Check if a namespace is connected. 73 | 74 | :param namespace: The namespace to check. The global namespace is 75 | assumed if this argument is not provided. 76 | """ 77 | return self.connected.get(namespace or '/', False) 78 | 79 | def connect(self, namespace=None, query_string=None, headers=None): 80 | """Connect the client. 81 | 82 | :param namespace: The namespace for the client. If not provided, the 83 | client connects to the server on the global 84 | namespace. 85 | :param query_string: A string with custom query string arguments. 86 | :param headers: A dictionary with custom HTTP headers. 87 | 88 | Note that it is usually not necessary to explicitly call this method, 89 | since a connection is automatically established when an instance of 90 | this class is created. An example where it this method would be useful 91 | is when the application accepts multiple namespace connections. 92 | """ 93 | url = '/socket.io' 94 | if query_string: 95 | if query_string[0] != '?': 96 | query_string = '?' + query_string 97 | url += query_string 98 | environ = EnvironBuilder(url, headers=headers).get_environ() 99 | environ['flask.app'] = self.app 100 | if self.flask_test_client: 101 | # inject cookies from Flask 102 | self.flask_test_client.cookie_jar.inject_wsgi(environ) 103 | self.connected['/'] = True 104 | self.socketio.server._handle_eio_connect(self.sid, environ) 105 | if namespace is not None and namespace != '/': 106 | self.connected[namespace] = True 107 | pkt = packet.Packet(packet.CONNECT, namespace=namespace) 108 | with self.app.app_context(): 109 | self.socketio.server._handle_eio_message(self.sid, 110 | pkt.encode()) 111 | 112 | def disconnect(self, namespace=None): 113 | """Disconnect the client. 114 | 115 | :param namespace: The namespace to disconnect. The global namespace is 116 | assumed if this argument is not provided. 117 | """ 118 | if not self.is_connected(namespace): 119 | raise RuntimeError('not connected') 120 | pkt = packet.Packet(packet.DISCONNECT, namespace=namespace) 121 | with self.app.app_context(): 122 | self.socketio.server._handle_eio_message(self.sid, pkt.encode()) 123 | del self.connected[namespace or '/'] 124 | 125 | def emit(self, event, *args, **kwargs): 126 | """Emit an event to the server. 127 | 128 | :param event: The event name. 129 | :param *args: The event arguments. 130 | :param callback: ``True`` if the client requests a callback, ``False`` 131 | if not. Note that client-side callbacks are not 132 | implemented, a callback request will just tell the 133 | server to provide the arguments to invoke the 134 | callback, but no callback is invoked. Instead, the 135 | arguments that the server provided for the callback 136 | are returned by this function. 137 | :param namespace: The namespace of the event. The global namespace is 138 | assumed if this argument is not provided. 139 | """ 140 | namespace = kwargs.pop('namespace', None) 141 | if not self.is_connected(namespace): 142 | raise RuntimeError('not connected') 143 | callback = kwargs.pop('callback', False) 144 | id = None 145 | if callback: 146 | self.callback_counter += 1 147 | id = self.callback_counter 148 | pkt = packet.Packet(packet.EVENT, data=[event] + list(args), 149 | namespace=namespace, id=id) 150 | with self.app.app_context(): 151 | encoded_pkt = pkt.encode() 152 | if isinstance(encoded_pkt, list): 153 | for epkt in encoded_pkt: 154 | self.socketio.server._handle_eio_message(self.sid, epkt) 155 | else: 156 | self.socketio.server._handle_eio_message(self.sid, encoded_pkt) 157 | ack = self.acks.pop(self.sid, None) 158 | if ack is not None: 159 | return ack['args'][0] if len(ack['args']) == 1 \ 160 | else ack['args'] 161 | 162 | def send(self, data, json=False, callback=False, namespace=None): 163 | """Send a text or JSON message to the server. 164 | 165 | :param data: A string, dictionary or list to send to the server. 166 | :param json: ``True`` to send a JSON message, ``False`` to send a text 167 | message. 168 | :param callback: ``True`` if the client requests a callback, ``False`` 169 | if not. Note that client-side callbacks are not 170 | implemented, a callback request will just tell the 171 | server to provide the arguments to invoke the 172 | callback, but no callback is invoked. Instead, the 173 | arguments that the server provided for the callback 174 | are returned by this function. 175 | :param namespace: The namespace of the event. The global namespace is 176 | assumed if this argument is not provided. 177 | """ 178 | if json: 179 | msg = 'json' 180 | else: 181 | msg = 'message' 182 | return self.emit(msg, data, callback=callback, namespace=namespace) 183 | 184 | def get_received(self, namespace=None): 185 | """Return the list of messages received from the server. 186 | 187 | Since this is not a real client, any time the server emits an event, 188 | the event is simply stored. The test code can invoke this method to 189 | obtain the list of events that were received since the last call. 190 | 191 | :param namespace: The namespace to get events from. The global 192 | namespace is assumed if this argument is not 193 | provided. 194 | """ 195 | if not self.is_connected(namespace): 196 | raise RuntimeError('not connected') 197 | namespace = namespace or '/' 198 | r = [pkt for pkt in self.queue[self.sid] 199 | if pkt['namespace'] == namespace] 200 | self.queue[self.sid] = [pkt for pkt in self.queue[self.sid] 201 | if pkt not in r] 202 | return r 203 | -------------------------------------------------------------------------------- /docs/_themes/flask/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 18px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0 0 20px 0; 95 | margin: 0; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #444; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input { 136 | border: 1px solid #ccc; 137 | font-family: 'Georgia', serif; 138 | font-size: 1em; 139 | } 140 | 141 | /* -- body styles ----------------------------------------------------------- */ 142 | 143 | a { 144 | color: #004B6B; 145 | text-decoration: underline; 146 | } 147 | 148 | a:hover { 149 | color: #6D4100; 150 | text-decoration: underline; 151 | } 152 | 153 | div.body h1, 154 | div.body h2, 155 | div.body h3, 156 | div.body h4, 157 | div.body h5, 158 | div.body h6 { 159 | font-family: 'Garamond', 'Georgia', serif; 160 | font-weight: normal; 161 | margin: 30px 0px 10px 0px; 162 | padding: 0; 163 | } 164 | 165 | {% if theme_index_logo %} 166 | div.indexwrapper h1 { 167 | text-indent: -999999px; 168 | background: url({{ theme_index_logo }}) no-repeat center center; 169 | height: {{ theme_index_logo_height }}; 170 | } 171 | {% endif %} 172 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 173 | div.body h2 { font-size: 180%; } 174 | div.body h3 { font-size: 150%; } 175 | div.body h4 { font-size: 130%; } 176 | div.body h5 { font-size: 100%; } 177 | div.body h6 { font-size: 100%; } 178 | 179 | a.headerlink { 180 | color: #ddd; 181 | padding: 0 4px; 182 | text-decoration: none; 183 | } 184 | 185 | a.headerlink:hover { 186 | color: #444; 187 | background: #eaeaea; 188 | } 189 | 190 | div.body p, div.body dd, div.body li { 191 | line-height: 1.4em; 192 | } 193 | 194 | div.admonition { 195 | background: #fafafa; 196 | margin: 20px -30px; 197 | padding: 10px 30px; 198 | border-top: 1px solid #ccc; 199 | border-bottom: 1px solid #ccc; 200 | } 201 | 202 | div.admonition tt.xref, div.admonition a tt { 203 | border-bottom: 1px solid #fafafa; 204 | } 205 | 206 | dd div.admonition { 207 | margin-left: -60px; 208 | padding-left: 60px; 209 | } 210 | 211 | div.admonition p.admonition-title { 212 | font-family: 'Garamond', 'Georgia', serif; 213 | font-weight: normal; 214 | font-size: 24px; 215 | margin: 0 0 10px 0; 216 | padding: 0; 217 | line-height: 1; 218 | } 219 | 220 | div.admonition p.last { 221 | margin-bottom: 0; 222 | } 223 | 224 | div.highlight { 225 | background-color: white; 226 | } 227 | 228 | dt:target, .highlight { 229 | background: #FAF3E8; 230 | } 231 | 232 | div.note { 233 | background-color: #eee; 234 | border: 1px solid #ccc; 235 | } 236 | 237 | div.seealso { 238 | background-color: #ffc; 239 | border: 1px solid #ff6; 240 | } 241 | 242 | div.topic { 243 | background-color: #eee; 244 | } 245 | 246 | p.admonition-title { 247 | display: inline; 248 | } 249 | 250 | p.admonition-title:after { 251 | content: ":"; 252 | } 253 | 254 | pre, tt { 255 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 256 | font-size: 0.9em; 257 | } 258 | 259 | img.screenshot { 260 | } 261 | 262 | tt.descname, tt.descclassname { 263 | font-size: 0.95em; 264 | } 265 | 266 | tt.descname { 267 | padding-right: 0.08em; 268 | } 269 | 270 | img.screenshot { 271 | -moz-box-shadow: 2px 2px 4px #eee; 272 | -webkit-box-shadow: 2px 2px 4px #eee; 273 | box-shadow: 2px 2px 4px #eee; 274 | } 275 | 276 | table.docutils { 277 | border: 1px solid #888; 278 | -moz-box-shadow: 2px 2px 4px #eee; 279 | -webkit-box-shadow: 2px 2px 4px #eee; 280 | box-shadow: 2px 2px 4px #eee; 281 | } 282 | 283 | table.docutils td, table.docutils th { 284 | border: 1px solid #888; 285 | padding: 0.25em 0.7em; 286 | } 287 | 288 | table.field-list, table.footnote { 289 | border: none; 290 | -moz-box-shadow: none; 291 | -webkit-box-shadow: none; 292 | box-shadow: none; 293 | } 294 | 295 | table.footnote { 296 | margin: 15px 0; 297 | width: 100%; 298 | border: 1px solid #eee; 299 | background: #fdfdfd; 300 | font-size: 0.9em; 301 | } 302 | 303 | table.footnote + table.footnote { 304 | margin-top: -15px; 305 | border-top: none; 306 | } 307 | 308 | table.field-list th { 309 | padding: 0 0.8em 0 0; 310 | } 311 | 312 | table.field-list td { 313 | padding: 0; 314 | } 315 | 316 | table.footnote td.label { 317 | width: 0px; 318 | padding: 0.3em 0 0.3em 0.5em; 319 | } 320 | 321 | table.footnote td { 322 | padding: 0.3em 0.5em; 323 | } 324 | 325 | dl { 326 | margin: 0; 327 | padding: 0; 328 | } 329 | 330 | dl dd { 331 | margin-left: 30px; 332 | } 333 | 334 | blockquote { 335 | margin: 0 0 0 30px; 336 | padding: 0; 337 | } 338 | 339 | ul, ol { 340 | margin: 10px 0 10px 30px; 341 | padding: 0; 342 | } 343 | 344 | pre { 345 | background: #eee; 346 | padding: 7px 30px; 347 | margin: 15px -30px; 348 | line-height: 1.3em; 349 | } 350 | 351 | dl pre, blockquote pre, li pre { 352 | margin-left: -60px; 353 | padding-left: 60px; 354 | } 355 | 356 | dl dl pre { 357 | margin-left: -90px; 358 | padding-left: 90px; 359 | } 360 | 361 | tt { 362 | background-color: #ecf0f3; 363 | color: #222; 364 | /* padding: 1px 2px; */ 365 | } 366 | 367 | tt.xref, a tt { 368 | background-color: #FBFBFB; 369 | border-bottom: 1px solid white; 370 | } 371 | 372 | a.reference { 373 | text-decoration: none; 374 | border-bottom: 1px dotted #004B6B; 375 | } 376 | 377 | a.reference:hover { 378 | border-bottom: 1px solid #6D4100; 379 | } 380 | 381 | a.footnote-reference { 382 | text-decoration: none; 383 | font-size: 0.7em; 384 | vertical-align: top; 385 | border-bottom: 1px dotted #004B6B; 386 | } 387 | 388 | a.footnote-reference:hover { 389 | border-bottom: 1px solid #6D4100; 390 | } 391 | 392 | a:hover tt { 393 | background: #EEE; 394 | } 395 | 396 | 397 | @media screen and (max-width: 870px) { 398 | 399 | div.sphinxsidebar { 400 | display: none; 401 | } 402 | 403 | div.document { 404 | width: 100%; 405 | 406 | } 407 | 408 | div.documentwrapper { 409 | margin-left: 0; 410 | margin-top: 0; 411 | margin-right: 0; 412 | margin-bottom: 0; 413 | } 414 | 415 | div.bodywrapper { 416 | margin-top: 0; 417 | margin-right: 0; 418 | margin-bottom: 0; 419 | margin-left: 0; 420 | } 421 | 422 | ul { 423 | margin-left: 0; 424 | } 425 | 426 | .document { 427 | width: auto; 428 | } 429 | 430 | .footer { 431 | width: auto; 432 | } 433 | 434 | .bodywrapper { 435 | margin: 0; 436 | } 437 | 438 | .footer { 439 | width: auto; 440 | } 441 | 442 | .github { 443 | display: none; 444 | } 445 | 446 | 447 | 448 | } 449 | 450 | 451 | 452 | @media screen and (max-width: 875px) { 453 | 454 | body { 455 | margin: 0; 456 | padding: 20px 30px; 457 | } 458 | 459 | div.documentwrapper { 460 | float: none; 461 | background: white; 462 | } 463 | 464 | div.sphinxsidebar { 465 | display: block; 466 | float: none; 467 | width: 102.5%; 468 | margin: 50px -30px -20px -30px; 469 | padding: 10px 20px; 470 | background: #333; 471 | color: white; 472 | } 473 | 474 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 475 | div.sphinxsidebar h3 a { 476 | color: white; 477 | } 478 | 479 | div.sphinxsidebar a { 480 | color: #aaa; 481 | } 482 | 483 | div.sphinxsidebar p.logo { 484 | display: none; 485 | } 486 | 487 | div.document { 488 | width: 100%; 489 | margin: 0; 490 | } 491 | 492 | div.related { 493 | display: block; 494 | margin: 0; 495 | padding: 10px 0 20px 0; 496 | } 497 | 498 | div.related ul, 499 | div.related ul li { 500 | margin: 0; 501 | padding: 0; 502 | } 503 | 504 | div.footer { 505 | display: none; 506 | } 507 | 508 | div.bodywrapper { 509 | margin: 0; 510 | } 511 | 512 | div.body { 513 | min-height: 0; 514 | padding: 0; 515 | } 516 | 517 | .rtd_doc_footer { 518 | display: none; 519 | } 520 | 521 | .document { 522 | width: auto; 523 | } 524 | 525 | .footer { 526 | width: auto; 527 | } 528 | 529 | .footer { 530 | width: auto; 531 | } 532 | 533 | .github { 534 | display: none; 535 | } 536 | } 537 | 538 | 539 | /* scrollbars */ 540 | 541 | ::-webkit-scrollbar { 542 | width: 6px; 543 | height: 6px; 544 | } 545 | 546 | ::-webkit-scrollbar-button:start:decrement, 547 | ::-webkit-scrollbar-button:end:increment { 548 | display: block; 549 | height: 10px; 550 | } 551 | 552 | ::-webkit-scrollbar-button:vertical:increment { 553 | background-color: #fff; 554 | } 555 | 556 | ::-webkit-scrollbar-track-piece { 557 | background-color: #eee; 558 | -webkit-border-radius: 3px; 559 | } 560 | 561 | ::-webkit-scrollbar-thumb:vertical { 562 | height: 50px; 563 | background-color: #ccc; 564 | -webkit-border-radius: 3px; 565 | } 566 | 567 | ::-webkit-scrollbar-thumb:horizontal { 568 | width: 50px; 569 | background-color: #ccc; 570 | -webkit-border-radius: 3px; 571 | } 572 | 573 | /* misc. */ 574 | 575 | .revsys-inline { 576 | display: none!important; 577 | } -------------------------------------------------------------------------------- /test_socketio.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | import coverage 4 | 5 | cov = coverage.coverage(branch=True) 6 | cov.start() 7 | 8 | from flask import Flask, session, request, json as flask_json 9 | from flask_socketio import SocketIO, send, emit, join_room, leave_room, \ 10 | Namespace, disconnect 11 | 12 | app = Flask(__name__) 13 | app.config['SECRET_KEY'] = 'secret' 14 | socketio = SocketIO(app) 15 | disconnected = None 16 | 17 | 18 | @socketio.on('connect') 19 | def on_connect(): 20 | if request.args.get('fail'): 21 | return False 22 | send('connected') 23 | send(json.dumps(request.args.to_dict(flat=False))) 24 | send(json.dumps({h: request.headers[h] for h in request.headers.keys() 25 | if h not in ['Host', 'Content-Type', 'Content-Length']})) 26 | 27 | 28 | @socketio.on('disconnect') 29 | def on_disconnect(): 30 | global disconnected 31 | disconnected = '/' 32 | 33 | 34 | @socketio.on('connect', namespace='/test') 35 | def on_connect_test(): 36 | send('connected-test') 37 | send(json.dumps(request.args.to_dict(flat=False))) 38 | send(json.dumps({h: request.headers[h] for h in request.headers.keys() 39 | if h not in ['Host', 'Content-Type', 'Content-Length']})) 40 | 41 | 42 | @socketio.on('disconnect', namespace='/test') 43 | def on_disconnect_test(): 44 | global disconnected 45 | disconnected = '/test' 46 | 47 | 48 | @socketio.on('message') 49 | def on_message(message): 50 | send(message) 51 | if message == 'test session': 52 | session['a'] = 'b' 53 | if message not in "test noackargs": 54 | return message 55 | 56 | 57 | @socketio.on('json') 58 | def on_json(data): 59 | send(data, json=True, broadcast=True) 60 | if not data.get('noackargs'): 61 | return data 62 | 63 | 64 | @socketio.on('message', namespace='/test') 65 | def on_message_test(message): 66 | send(message) 67 | 68 | 69 | @socketio.on('json', namespace='/test') 70 | def on_json_test(data): 71 | send(data, json=True, namespace='/test') 72 | 73 | 74 | @socketio.on('my custom event') 75 | def on_custom_event(data): 76 | emit('my custom response', data) 77 | if not data.get('noackargs'): 78 | return data 79 | 80 | 81 | @socketio.on('other custom event') 82 | @socketio.on('and another custom event') 83 | def get_request_event(data): 84 | global request_event_data 85 | request_event_data = request.event 86 | emit('my custom response', data) 87 | 88 | 89 | def get_request_event2(data): 90 | global request_event_data 91 | request_event_data = request.event 92 | emit('my custom response', data) 93 | 94 | socketio.on_event('yet another custom event', get_request_event2) 95 | 96 | 97 | @socketio.on('my custom namespace event', namespace='/test') 98 | def on_custom_event_test(data): 99 | emit('my custom namespace response', data, namespace='/test') 100 | 101 | 102 | def on_custom_event_test2(data): 103 | emit('my custom namespace response', data, namespace='/test') 104 | 105 | socketio.on_event('yet another custom namespace event', on_custom_event_test2, 106 | namespace='/test') 107 | 108 | 109 | @socketio.on('my custom broadcast event') 110 | def on_custom_event_broadcast(data): 111 | emit('my custom response', data, broadcast=True) 112 | 113 | 114 | @socketio.on('my custom broadcast namespace event', namespace='/test') 115 | def on_custom_event_broadcast_test(data): 116 | emit('my custom namespace response', data, namespace='/test', 117 | broadcast=True) 118 | 119 | 120 | @socketio.on('join room') 121 | def on_join_room(data): 122 | join_room(data['room']) 123 | 124 | 125 | @socketio.on('leave room') 126 | def on_leave_room(data): 127 | leave_room(data['room']) 128 | 129 | 130 | @socketio.on('join room', namespace='/test') 131 | def on_join_room_namespace(data): 132 | join_room(data['room']) 133 | 134 | 135 | @socketio.on('leave room', namespace='/test') 136 | def on_leave_room_namespace(data): 137 | leave_room(data['room']) 138 | 139 | 140 | @socketio.on('my room event') 141 | def on_room_event(data): 142 | room = data.pop('room') 143 | emit('my room response', data, room=room) 144 | 145 | 146 | @socketio.on('my room namespace event', namespace='/test') 147 | def on_room_namespace_event(data): 148 | room = data.pop('room') 149 | send('room message', room=room) 150 | 151 | 152 | @socketio.on_error() 153 | def error_handler(value): 154 | if isinstance(value, AssertionError): 155 | global error_testing 156 | error_testing = True 157 | else: 158 | raise value 159 | return value 160 | 161 | 162 | @socketio.on('error testing') 163 | def raise_error(data): 164 | raise AssertionError() 165 | 166 | 167 | @socketio.on_error('/test') 168 | def error_handler_namespace(value): 169 | if isinstance(value, AssertionError): 170 | global error_testing_namespace 171 | error_testing_namespace = True 172 | else: 173 | raise value 174 | return value 175 | 176 | 177 | @socketio.on("error testing", namespace='/test') 178 | def raise_error_namespace(data): 179 | raise AssertionError() 180 | 181 | 182 | @socketio.on_error_default 183 | def error_handler_default(value): 184 | if isinstance(value, AssertionError): 185 | global error_testing_default 186 | error_testing_default = True 187 | else: 188 | raise value 189 | return value 190 | 191 | 192 | @socketio.on("error testing", namespace='/unused_namespace') 193 | def raise_error_default(data): 194 | raise AssertionError() 195 | 196 | 197 | class MyNamespace(Namespace): 198 | def on_connect(self): 199 | send('connected-ns') 200 | send(json.dumps(request.args.to_dict(flat=False))) 201 | send(json.dumps( 202 | {h: request.headers[h] for h in request.headers.keys() 203 | if h not in ['Host', 'Content-Type', 'Content-Length']})) 204 | 205 | def on_disconnect(self): 206 | global disconnected 207 | disconnected = '/ns' 208 | 209 | def on_message(self, message): 210 | send(message) 211 | if message == 'test session': 212 | session['a'] = 'b' 213 | if message not in "test noackargs": 214 | return message 215 | 216 | def on_json(self, data): 217 | send(data, json=True, broadcast=True) 218 | if not data.get('noackargs'): 219 | return data 220 | 221 | def on_exit(self, data): 222 | disconnect() 223 | 224 | def on_my_custom_event(self, data): 225 | emit('my custom response', data) 226 | if not data.get('noackargs'): 227 | return data 228 | 229 | def on_other_custom_event(self, data): 230 | global request_event_data 231 | request_event_data = request.event 232 | emit('my custom response', data) 233 | 234 | 235 | socketio.on_namespace(MyNamespace('/ns')) 236 | 237 | 238 | @app.route('/session') 239 | def session_route(): 240 | session['foo'] = 'bar' 241 | return '' 242 | 243 | 244 | class TestSocketIO(unittest.TestCase): 245 | @classmethod 246 | def setUpClass(cls): 247 | pass 248 | 249 | @classmethod 250 | def tearDownClass(cls): 251 | cov.stop() 252 | cov.report(include='flask_socketio/*', show_missing=True) 253 | 254 | def setUp(self): 255 | pass 256 | 257 | def tearDown(self): 258 | pass 259 | 260 | def test_connect(self): 261 | client = socketio.test_client(app) 262 | self.assertTrue(client.is_connected()) 263 | received = client.get_received() 264 | self.assertEqual(len(received), 3) 265 | self.assertEqual(received[0]['args'], 'connected') 266 | self.assertEqual(received[1]['args'], '{}') 267 | self.assertEqual(received[2]['args'], '{}') 268 | client.disconnect() 269 | self.assertFalse(client.is_connected()) 270 | 271 | def test_connect_query_string_and_headers(self): 272 | client = socketio.test_client( 273 | app, query_string='?foo=bar&foo=baz', 274 | headers={'Authorization': 'Bearer foobar'}) 275 | received = client.get_received() 276 | self.assertEqual(len(received), 3) 277 | self.assertEqual(received[0]['args'], 'connected') 278 | self.assertEqual(received[1]['args'], '{"foo": ["bar", "baz"]}') 279 | self.assertEqual(received[2]['args'], 280 | '{"Authorization": "Bearer foobar"}') 281 | client.disconnect() 282 | 283 | def test_connect_namespace(self): 284 | client = socketio.test_client(app, namespace='/test') 285 | self.assertTrue(client.is_connected('/test')) 286 | received = client.get_received('/test') 287 | self.assertEqual(len(received), 3) 288 | self.assertEqual(received[0]['args'], 'connected-test') 289 | self.assertEqual(received[1]['args'], '{}') 290 | self.assertEqual(received[2]['args'], '{}') 291 | client.disconnect(namespace='/test') 292 | self.assertFalse(client.is_connected('/test')) 293 | 294 | def test_connect_namespace_query_string_and_headers(self): 295 | client = socketio.test_client( 296 | app, namespace='/test', query_string='foo=bar', 297 | headers={'My-Custom-Header': 'Value'}) 298 | received = client.get_received('/test') 299 | self.assertEqual(len(received), 3) 300 | self.assertEqual(received[0]['args'], 'connected-test') 301 | self.assertEqual(received[1]['args'], '{"foo": ["bar"]}') 302 | self.assertEqual(received[2]['args'], '{"My-Custom-Header": "Value"}') 303 | client.disconnect(namespace='/test') 304 | 305 | def test_connect_rejected(self): 306 | client = socketio.test_client(app, query_string='fail=1') 307 | self.assertFalse(client.is_connected()) 308 | 309 | def test_disconnect(self): 310 | global disconnected 311 | disconnected = None 312 | client = socketio.test_client(app) 313 | client.disconnect() 314 | self.assertEqual(disconnected, '/') 315 | 316 | def test_disconnect_namespace(self): 317 | global disconnected 318 | disconnected = None 319 | client = socketio.test_client(app, namespace='/test') 320 | client.disconnect('/test') 321 | self.assertEqual(disconnected, '/test') 322 | 323 | def test_send(self): 324 | client = socketio.test_client(app) 325 | client.get_received() 326 | client.send('echo this message back') 327 | received = client.get_received() 328 | self.assertEqual(len(received), 1) 329 | self.assertEqual(received[0]['args'], 'echo this message back') 330 | 331 | def test_send_json(self): 332 | client1 = socketio.test_client(app) 333 | client2 = socketio.test_client(app) 334 | client1.get_received() 335 | client2.get_received() 336 | client1.send({'a': 'b'}, json=True) 337 | received = client1.get_received() 338 | self.assertEqual(len(received), 1) 339 | self.assertEqual(received[0]['args']['a'], 'b') 340 | received = client2.get_received() 341 | self.assertEqual(len(received), 1) 342 | self.assertEqual(received[0]['args']['a'], 'b') 343 | 344 | def test_send_namespace(self): 345 | client = socketio.test_client(app, namespace='/test') 346 | client.get_received('/test') 347 | client.send('echo this message back', namespace='/test') 348 | received = client.get_received('/test') 349 | self.assertTrue(len(received) == 1) 350 | self.assertTrue(received[0]['args'] == 'echo this message back') 351 | 352 | def test_send_json_namespace(self): 353 | client = socketio.test_client(app, namespace='/test') 354 | client.get_received('/test') 355 | client.send({'a': 'b'}, json=True, namespace='/test') 356 | received = client.get_received('/test') 357 | self.assertEqual(len(received), 1) 358 | self.assertEqual(received[0]['args']['a'], 'b') 359 | 360 | def test_emit(self): 361 | client = socketio.test_client(app) 362 | client.get_received() 363 | client.emit('my custom event', {'a': 'b'}) 364 | received = client.get_received() 365 | self.assertEqual(len(received), 1) 366 | self.assertEqual(len(received[0]['args']), 1) 367 | self.assertEqual(received[0]['name'], 'my custom response') 368 | self.assertEqual(received[0]['args'][0]['a'], 'b') 369 | 370 | def test_emit_binary(self): 371 | client = socketio.test_client(app) 372 | client.get_received() 373 | client.emit('my custom event', {u'a': b'\x01\x02\x03'}) 374 | received = client.get_received() 375 | self.assertEqual(len(received), 1) 376 | self.assertEqual(len(received[0]['args']), 1) 377 | self.assertEqual(received[0]['name'], 'my custom response') 378 | self.assertEqual(received[0]['args'][0]['a'], b'\x01\x02\x03') 379 | 380 | def test_request_event_data(self): 381 | client = socketio.test_client(app) 382 | client.get_received() 383 | global request_event_data 384 | request_event_data = None 385 | client.emit('other custom event', 'foo') 386 | expected_data = {'message': 'other custom event', 'args': ('foo',)} 387 | self.assertEqual(request_event_data, expected_data) 388 | client.emit('and another custom event', 'bar') 389 | expected_data = {'message': 'and another custom event', 390 | 'args': ('bar',)} 391 | self.assertEqual(request_event_data, expected_data) 392 | 393 | def test_emit_namespace(self): 394 | client = socketio.test_client(app, namespace='/test') 395 | client.get_received('/test') 396 | client.emit('my custom namespace event', {'a': 'b'}, namespace='/test') 397 | received = client.get_received('/test') 398 | self.assertEqual(len(received), 1) 399 | self.assertEqual(len(received[0]['args']), 1) 400 | self.assertEqual(received[0]['name'], 'my custom namespace response') 401 | self.assertEqual(received[0]['args'][0]['a'], 'b') 402 | 403 | def test_broadcast(self): 404 | client1 = socketio.test_client(app) 405 | client2 = socketio.test_client(app) 406 | client3 = socketio.test_client(app, namespace='/test') 407 | client2.get_received() 408 | client3.get_received('/test') 409 | client1.emit('my custom broadcast event', {'a': 'b'}, broadcast=True) 410 | received = client2.get_received() 411 | self.assertEqual(len(received), 1) 412 | self.assertEqual(len(received[0]['args']), 1) 413 | self.assertEqual(received[0]['name'], 'my custom response') 414 | self.assertEqual(received[0]['args'][0]['a'], 'b') 415 | self.assertEqual(len(client3.get_received('/test')), 0) 416 | 417 | def test_broadcast_namespace(self): 418 | client1 = socketio.test_client(app, namespace='/test') 419 | client2 = socketio.test_client(app, namespace='/test') 420 | client3 = socketio.test_client(app) 421 | client2.get_received('/test') 422 | client3.get_received() 423 | client1.emit('my custom broadcast namespace event', {'a': 'b'}, 424 | namespace='/test') 425 | received = client2.get_received('/test') 426 | self.assertEqual(len(received), 1) 427 | self.assertEqual(len(received[0]['args']), 1) 428 | self.assertEqual(received[0]['name'], 'my custom namespace response') 429 | self.assertEqual(received[0]['args'][0]['a'], 'b') 430 | self.assertEqual(len(client3.get_received()), 0) 431 | 432 | def test_session(self): 433 | flask_client = app.test_client() 434 | flask_client.get('/session') 435 | client = socketio.test_client(app, flask_test_client=flask_client) 436 | client.get_received() 437 | client.send('echo this message back') 438 | self.assertEqual(socketio.server.environ[client.sid]['saved_session'], 439 | {'foo': 'bar'}) 440 | client.send('test session') 441 | self.assertEqual(socketio.server.environ[client.sid]['saved_session'], 442 | {'a': 'b', 'foo': 'bar'}) 443 | 444 | def test_room(self): 445 | client1 = socketio.test_client(app) 446 | client2 = socketio.test_client(app) 447 | client3 = socketio.test_client(app, namespace='/test') 448 | client1.get_received() 449 | client2.get_received() 450 | client3.get_received('/test') 451 | client1.emit('join room', {'room': 'one'}) 452 | client2.emit('join room', {'room': 'one'}) 453 | client3.emit('join room', {'room': 'one'}, namespace='/test') 454 | client1.emit('my room event', {'a': 'b', 'room': 'one'}) 455 | received = client1.get_received() 456 | self.assertEqual(len(received), 1) 457 | self.assertEqual(len(received[0]['args']), 1) 458 | self.assertEqual(received[0]['name'], 'my room response') 459 | self.assertEqual(received[0]['args'][0]['a'], 'b') 460 | self.assertEqual(received, client2.get_received()) 461 | received = client3.get_received('/test') 462 | self.assertEqual(len(received), 0) 463 | client1.emit('leave room', {'room': 'one'}) 464 | client1.emit('my room event', {'a': 'b', 'room': 'one'}) 465 | received = client1.get_received() 466 | self.assertEqual(len(received), 0) 467 | received = client2.get_received() 468 | self.assertEqual(len(received), 1) 469 | self.assertEqual(len(received[0]['args']), 1) 470 | self.assertEqual(received[0]['name'], 'my room response') 471 | self.assertEqual(received[0]['args'][0]['a'], 'b') 472 | client2.disconnect() 473 | socketio.emit('my room event', {'a': 'b'}, room='one') 474 | received = client1.get_received() 475 | self.assertEqual(len(received), 0) 476 | received = client3.get_received('/test') 477 | self.assertEqual(len(received), 0) 478 | client3.emit('my room namespace event', {'room': 'one'}, 479 | namespace='/test') 480 | received = client3.get_received('/test') 481 | self.assertEqual(len(received), 1) 482 | self.assertEqual(received[0]['name'], 'message') 483 | self.assertEqual(received[0]['args'], 'room message') 484 | socketio.close_room('one', namespace='/test') 485 | client3.emit('my room namespace event', {'room': 'one'}, 486 | namespace='/test') 487 | received = client3.get_received('/test') 488 | self.assertEqual(len(received), 0) 489 | 490 | def test_error_handling(self): 491 | client = socketio.test_client(app) 492 | client.get_received() 493 | global error_testing 494 | error_testing = False 495 | client.emit("error testing", "") 496 | self.assertTrue(error_testing) 497 | 498 | def test_error_handling_namespace(self): 499 | client = socketio.test_client(app, namespace='/test') 500 | client.get_received('/test') 501 | global error_testing_namespace 502 | error_testing_namespace = False 503 | client.emit("error testing", "", namespace='/test') 504 | self.assertTrue(error_testing_namespace) 505 | 506 | def test_error_handling_default(self): 507 | client = socketio.test_client(app, namespace='/unused_namespace') 508 | client.get_received('/unused_namespace') 509 | global error_testing_default 510 | error_testing_default = False 511 | client.emit("error testing", "", namespace='/unused_namespace') 512 | self.assertTrue(error_testing_default) 513 | 514 | def test_ack(self): 515 | client1 = socketio.test_client(app) 516 | client2 = socketio.test_client(app) 517 | client3 = socketio.test_client(app) 518 | ack = client1.send('echo this message back', callback=True) 519 | self.assertEqual(ack, 'echo this message back') 520 | ack = client1.send('test noackargs', callback=True) 521 | # python-socketio releases before 1.5 did not correctly implement 522 | # callbacks with no arguments. Here we check for [] (the correct, 1.5 523 | # and up response) and None (the wrong pre-1.5 response). 524 | self.assertTrue(ack == [] or ack is None) 525 | ack2 = client2.send({'a': 'b'}, json=True, callback=True) 526 | self.assertEqual(ack2, {'a': 'b'}) 527 | ack3 = client3.emit('my custom event', {'a': 'b'}, callback=True) 528 | self.assertEqual(ack3, {'a': 'b'}) 529 | 530 | def test_noack(self): 531 | client1 = socketio.test_client(app) 532 | client2 = socketio.test_client(app) 533 | client3 = socketio.test_client(app) 534 | no_ack_dict = {'noackargs': True} 535 | noack = client1.send("test noackargs", callback=False) 536 | self.assertIsNone(noack) 537 | noack2 = client2.send(no_ack_dict, json=True, callback=False) 538 | self.assertIsNone(noack2) 539 | noack3 = client3.emit('my custom event', no_ack_dict) 540 | self.assertIsNone(noack3) 541 | 542 | def test_error_handling_ack(self): 543 | client1 = socketio.test_client(app) 544 | client2 = socketio.test_client(app, namespace='/test') 545 | client3 = socketio.test_client(app, namespace='/unused_namespace') 546 | errorack = client1.emit("error testing", "", callback=True) 547 | self.assertIsNotNone(errorack) 548 | errorack_namespace = client2.emit("error testing", "", 549 | namespace='/test', callback=True) 550 | self.assertIsNotNone(errorack_namespace) 551 | errorack_default = client3.emit("error testing", "", 552 | namespace='/unused_namespace', 553 | callback=True) 554 | self.assertIsNotNone(errorack_default) 555 | 556 | def test_on_event(self): 557 | client = socketio.test_client(app) 558 | client.get_received() 559 | global request_event_data 560 | request_event_data = None 561 | client.emit('yet another custom event', 'foo') 562 | expected_data = {'message': 'yet another custom event', 563 | 'args': ('foo',)} 564 | self.assertEqual(request_event_data, expected_data) 565 | 566 | client = socketio.test_client(app, namespace='/test') 567 | client.get_received('/test') 568 | client.emit('yet another custom namespace event', {'a': 'b'}, 569 | namespace='/test') 570 | received = client.get_received('/test') 571 | self.assertEqual(len(received), 1) 572 | self.assertEqual(len(received[0]['args']), 1) 573 | self.assertEqual(received[0]['name'], 'my custom namespace response') 574 | self.assertEqual(received[0]['args'][0]['a'], 'b') 575 | 576 | def test_connect_class_based(self): 577 | client = socketio.test_client(app, namespace='/ns') 578 | received = client.get_received('/ns') 579 | self.assertEqual(len(received), 3) 580 | self.assertEqual(received[0]['args'], 'connected-ns') 581 | self.assertEqual(received[1]['args'], '{}') 582 | self.assertEqual(received[2]['args'], '{}') 583 | client.disconnect('/ns') 584 | 585 | def test_connect_class_based_query_string_and_headers(self): 586 | client = socketio.test_client( 587 | app, namespace='/ns', query_string='foo=bar', 588 | headers={'Authorization': 'Basic foobar'}) 589 | received = client.get_received('/ns') 590 | self.assertEqual(len(received), 3) 591 | self.assertEqual(received[0]['args'], 'connected-ns') 592 | self.assertEqual(received[1]['args'], '{"foo": ["bar"]}') 593 | self.assertEqual(received[2]['args'], 594 | '{"Authorization": "Basic foobar"}') 595 | client.disconnect('/ns') 596 | 597 | def test_disconnect_class_based(self): 598 | global disconnected 599 | disconnected = None 600 | client = socketio.test_client(app, namespace='/ns') 601 | client.disconnect('/ns') 602 | self.assertEqual(disconnected, '/ns') 603 | 604 | def test_send_class_based(self): 605 | client = socketio.test_client(app, namespace='/ns') 606 | client.get_received('/ns') 607 | client.send('echo this message back', namespace='/ns') 608 | received = client.get_received('/ns') 609 | self.assertTrue(len(received) == 1) 610 | self.assertTrue(received[0]['args'] == 'echo this message back') 611 | 612 | def test_send_json_class_based(self): 613 | client = socketio.test_client(app, namespace='/ns') 614 | client.get_received('/ns') 615 | client.send({'a': 'b'}, json=True, namespace='/ns') 616 | received = client.get_received('/ns') 617 | self.assertEqual(len(received), 1) 618 | self.assertEqual(received[0]['args']['a'], 'b') 619 | 620 | def test_server_disconnected(self): 621 | client = socketio.test_client(app, namespace='/ns') 622 | client.get_received('/ns') 623 | client.emit('exit', {}, namespace='/ns') 624 | self.assertFalse(client.is_connected('/ns')) 625 | with self.assertRaises(RuntimeError): 626 | client.emit('hello', {}, namespace='/ns') 627 | 628 | def test_emit_class_based(self): 629 | client = socketio.test_client(app, namespace='/ns') 630 | client.get_received('/ns') 631 | client.emit('my_custom_event', {'a': 'b'}, namespace='/ns') 632 | received = client.get_received('/ns') 633 | self.assertEqual(len(received), 1) 634 | self.assertEqual(len(received[0]['args']), 1) 635 | self.assertEqual(received[0]['name'], 'my custom response') 636 | self.assertEqual(received[0]['args'][0]['a'], 'b') 637 | 638 | def test_request_event_data_class_based(self): 639 | client = socketio.test_client(app, namespace='/ns') 640 | client.get_received('/ns') 641 | global request_event_data 642 | request_event_data = None 643 | client.emit('other_custom_event', 'foo', namespace='/ns') 644 | expected_data = {'message': 'other_custom_event', 'args': ('foo',)} 645 | self.assertEqual(request_event_data, expected_data) 646 | 647 | def test_delayed_init(self): 648 | app = Flask(__name__) 649 | socketio = SocketIO(allow_upgrades=False, json=flask_json) 650 | 651 | @socketio.on('connect') 652 | def on_connect(): 653 | send({'connected': 'foo'}, json=True) 654 | 655 | socketio.init_app(app, cookie='foo') 656 | self.assertFalse(socketio.server.eio.allow_upgrades) 657 | self.assertEqual(socketio.server.eio.cookie, 'foo') 658 | 659 | client = socketio.test_client(app) 660 | received = client.get_received() 661 | self.assertEqual(len(received), 1) 662 | self.assertEqual(received[0]['args'], {'connected': 'foo'}) 663 | 664 | 665 | if __name__ == '__main__': 666 | unittest.main() 667 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Flask-SocketIO documentation master file, created by 2 | sphinx-quickstart on Sun Feb 9 12:36:23 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Flask-SocketIO's documentation! 7 | ========================================== 8 | 9 | **Flask-SocketIO** gives Flask applications access to low latency 10 | bi-directional communications between the clients and the server. The 11 | client-side application can use any of the `SocketIO `_ 12 | official clients libraries in Javascript, C++, Java and Swift, or any 13 | compatible client to establish a permanent connection to the server. 14 | 15 | Installation 16 | ------------ 17 | 18 | You can install this package in the usual way using ``pip``:: 19 | 20 | pip install flask-socketio 21 | 22 | Requirements 23 | ------------ 24 | 25 | Flask-SocketIO is compatible with both Python 2.7 and Python 3.3+. The 26 | asynchronous services that this package relies on can be selected among three 27 | choices: 28 | 29 | - `eventlet `_ is the best performant option, with 30 | support for long-polling and WebSocket transports. 31 | - `gevent `_ is supported in a number of different 32 | configurations. The long-polling transport is fully supported with the 33 | gevent package, but unlike eventlet, gevent does not have native WebSocket 34 | support. To add support for WebSocket there are currently two options. 35 | Installing the `gevent-websocket `_ 36 | package adds WebSocket support to gevent or one can use the `uWSGI 37 | `_ web server, which 38 | comes with WebSocket functionality. The use of gevent is also a performant 39 | option, but slightly lower than eventlet. 40 | - The Flask development server based on Werkzeug can be used as well, with the 41 | caveat that it lacks the performance of the other two options, so it should 42 | only be used to simplify the development workflow. This option only supports 43 | the long-polling transport. 44 | 45 | The extension automatically detects which asynchronous framework to use based 46 | on what is installed. Preference is given to eventlet, followed by gevent. 47 | For WebSocket support in gevent, uWSGI is preferred, followed by 48 | gevent-websocket. If neither eventlet nor gevent are installed, then the Flask 49 | development server is used. 50 | 51 | If using multiple processes, a message queue service is used by the processes 52 | to coordinate operations such as broadcasting. The supported queues are 53 | `Redis `_, `RabbitMQ `_, 54 | `Kafka `_, and any 55 | other message queues supported by the 56 | `Kombu `_ package. 57 | 58 | On the client-side, the official Socket.IO Javascript client library can be 59 | used to establish a connection to the server. There are also official clients 60 | written in Swift, Java and C++. Unofficial clients may also work, as long as 61 | they implement the 62 | `Socket.IO protocol `_. 63 | 64 | Initialization 65 | -------------- 66 | 67 | The following code example shows how to add Flask-SocketIO to a Flask 68 | application:: 69 | 70 | from flask import Flask, render_template 71 | from flask_socketio import SocketIO 72 | 73 | app = Flask(__name__) 74 | app.config['SECRET_KEY'] = 'secret!' 75 | socketio = SocketIO(app) 76 | 77 | if __name__ == '__main__': 78 | socketio.run(app) 79 | 80 | The ``init_app()`` style of initialization is also supported. To start the 81 | web server simply execute your script. Note the way the web server is started. 82 | The ``socketio.run()`` function encapsulates the start up of the web server and 83 | replaces the ``app.run()`` standard Flask development server start up. When the 84 | application is in debug mode the Werkzeug development server is still used and 85 | configured properly inside ``socketio.run()``. In production mode the eventlet 86 | web server is used if available, else the gevent web server is used. If 87 | eventlet and gevent are not installed, the Werkzeug development web server is 88 | used. 89 | 90 | The ``flask run`` command introduced in Flask 0.11 can be used to start a 91 | Flask-SocketIO development server based on Werkzeug, but this method of starting 92 | the Flask-SocketIO server is not recommended due to lack of WebSocket support. 93 | Previous versions of this package included a customized version of the 94 | ``flask run`` command that allowed the use of WebSocket on eventlet and gevent 95 | production servers, but this functionality has been discontinued in favor of the 96 | ``socketio.run(app)`` startup method shown above which is more robust. 97 | 98 | The application must serve a page to the client that loads the Socket.IO 99 | library and establishes a connection:: 100 | 101 | 102 | 108 | 109 | Receiving Messages 110 | ------------------ 111 | 112 | When using SocketIO, messages are received by both parties as events. On the 113 | client side Javascript callbacks are used. With Flask-SocketIO the server 114 | needs to register handlers for these events, similarly to how routes are 115 | handled by view functions. 116 | 117 | The following example creates a server-side event handler for an unnamed 118 | event:: 119 | 120 | @socketio.on('message') 121 | def handle_message(message): 122 | print('received message: ' + message) 123 | 124 | The above example uses string messages. Another type of unnamed events use 125 | JSON data:: 126 | 127 | @socketio.on('json') 128 | def handle_json(json): 129 | print('received json: ' + str(json)) 130 | 131 | The most flexible type of event uses custom event names. The message data for 132 | these events can be string, bytes, int, or JSON:: 133 | 134 | @socketio.on('my event') 135 | def handle_my_custom_event(json): 136 | print('received json: ' + str(json)) 137 | 138 | Custom named events can also support multiple arguments:: 139 | 140 | @socketio.on('my event') 141 | def handle_my_custom_event(arg1, arg2, arg3): 142 | print('received args: ' + arg1 + arg2 + arg3) 143 | 144 | 145 | Named events are the most flexible, as they eliminate the need to include 146 | additional metadata to describe the message type. The names ``message``, 147 | ``json``, ``connect`` and ``disconnect`` are reserved and cannot be used for 148 | named events. 149 | 150 | Flask-SocketIO also supports SocketIO namespaces, which allow the client to 151 | multiplex several independent connections on the same physical socket:: 152 | 153 | @socketio.on('my event', namespace='/test') 154 | def handle_my_custom_namespace_event(json): 155 | print('received json: ' + str(json)) 156 | 157 | When a namespace is not specified a default global namespace with the name 158 | ``'/'`` is used. 159 | 160 | For cases when a decorator syntax isn't convenient, the ``on_event`` method 161 | can be used:: 162 | 163 | def my_function_handler(data): 164 | pass 165 | 166 | socketio.on_event('my event', my_function_handler, namespace='/test') 167 | 168 | Clients may request an acknowledgement callback that confirms receipt of a 169 | message they sent. Any values returned from the handler function will be 170 | passed to the client as arguments in the callback function:: 171 | 172 | @socketio.on('my event') 173 | def handle_my_custom_event(json): 174 | print('received json: ' + str(json)) 175 | return 'one', 2 176 | 177 | In the above example, the client callback function will be invoked with 178 | two arguments, ``'one'`` and ``2``. If a handler function does not return any 179 | values, the client callback function will be invoked without arguments. 180 | 181 | Sending Messages 182 | ---------------- 183 | 184 | SocketIO event handlers defined as shown in the previous section can send 185 | reply messages to the connected client using the ``send()`` and ``emit()`` 186 | functions. 187 | 188 | The following examples bounce received events back to the client that sent 189 | them:: 190 | 191 | from flask_socketio import send, emit 192 | 193 | @socketio.on('message') 194 | def handle_message(message): 195 | send(message) 196 | 197 | @socketio.on('json') 198 | def handle_json(json): 199 | send(json, json=True) 200 | 201 | @socketio.on('my event') 202 | def handle_my_custom_event(json): 203 | emit('my response', json) 204 | 205 | Note how ``send()`` and ``emit()`` are used for unnamed and named events 206 | respectively. 207 | 208 | When working with namespaces, ``send()`` and ``emit()`` use the namespace of 209 | the incoming message by default. A different namespace can be specified with 210 | the optional ``namespace`` argument:: 211 | 212 | @socketio.on('message') 213 | def handle_message(message): 214 | send(message, namespace='/chat') 215 | 216 | @socketio.on('my event') 217 | def handle_my_custom_event(json): 218 | emit('my response', json, namespace='/chat') 219 | 220 | To send an event with multiple arguments, send a tuple:: 221 | 222 | @socketio.on('my event') 223 | def handle_my_custom_event(json): 224 | emit('my response', ('foo', 'bar', json), namespace='/chat') 225 | 226 | SocketIO supports acknowledgment callbacks that confirm that a message was 227 | received by the client:: 228 | 229 | def ack(): 230 | print 'message was received!' 231 | 232 | @socketio.on('my event') 233 | def handle_my_custom_event(json): 234 | emit('my response', json, callback=ack) 235 | 236 | When using callbacks, the Javascript client receives a callback function to 237 | invoke upon receipt of the message. After the client application invokes the 238 | callback function the server invokes the corresponding server-side callback. 239 | If the client-side callback is invoked with arguments, these are provided as 240 | arguments to the server-side callback as well. 241 | 242 | Broadcasting 243 | ------------ 244 | 245 | Another very useful feature of SocketIO is the broadcasting of messages. 246 | Flask-SocketIO supports this feature with the ``broadcast=True`` optional 247 | argument to ``send()`` and ``emit()``:: 248 | 249 | @socketio.on('my event') 250 | def handle_my_custom_event(data): 251 | emit('my response', data, broadcast=True) 252 | 253 | When a message is sent with the broadcast option enabled, all clients 254 | connected to the namespace receive it, including the sender. When namespaces 255 | are not used, the clients connected to the global namespace receive the 256 | message. Note that callbacks are not invoked for broadcast messages. 257 | 258 | In all the examples shown until this point the server responds to an event 259 | sent by the client. But for some applications, the server needs to be the 260 | originator of a message. This can be useful to send notifications to clients 261 | of events that originated in the server, for example in a background thread. 262 | The ``socketio.send()`` and ``socketio.emit()`` methods can be used to 263 | broadcast to all connected clients:: 264 | 265 | def some_function(): 266 | socketio.emit('some event', {'data': 42}) 267 | 268 | Note that ``socketio.send()`` and ``socketio.emit()`` are not the same 269 | functions as the context-aware ``send()`` and ``emit()``. Also note that in the 270 | above usage there is no client context, so ``broadcast=True`` is assumed and 271 | does not need to be specified. 272 | 273 | Rooms 274 | ----- 275 | 276 | For many applications it is necessary to group users into subsets that can be 277 | addressed together. The best example is a chat application with multiple rooms, 278 | where users receive messages from the room or rooms they are in, but not from 279 | other rooms where other users are. Flask-SocketIO supports this concept of 280 | rooms through the ``join_room()`` and ``leave_room()`` functions:: 281 | 282 | from flask_socketio import join_room, leave_room 283 | 284 | @socketio.on('join') 285 | def on_join(data): 286 | username = data['username'] 287 | room = data['room'] 288 | join_room(room) 289 | send(username + ' has entered the room.', room=room) 290 | 291 | @socketio.on('leave') 292 | def on_leave(data): 293 | username = data['username'] 294 | room = data['room'] 295 | leave_room(room) 296 | send(username + ' has left the room.', room=room) 297 | 298 | The ``send()`` and ``emit()`` functions accept an optional ``room`` argument 299 | that cause the message to be sent to all the clients that are in the given 300 | room. 301 | 302 | All clients are assigned a room when they connect, named with the session ID 303 | of the connection, which can be obtained from ``request.sid``. A given client 304 | can join any rooms, which can be given any names. When a client disconnects it 305 | is removed from all the rooms it was in. The context-free ``socketio.send()`` 306 | and ``socketio.emit()`` functions also accept a ``room`` argument to broadcast 307 | to all clients in a room. 308 | 309 | Since all clients are assigned a personal room, to address a message to a 310 | single client, the session ID of the client can be used as the room argument. 311 | 312 | Connection Events 313 | ----------------- 314 | 315 | Flask-SocketIO also dispatches connection and disconnection events. The 316 | following example shows how to register handlers for them:: 317 | 318 | @socketio.on('connect') 319 | def test_connect(): 320 | emit('my response', {'data': 'Connected'}) 321 | 322 | @socketio.on('disconnect') 323 | def test_disconnect(): 324 | print('Client disconnected') 325 | 326 | The connection event handler can return ``False`` to reject the connection, or 327 | it can also raise `ConectionRefusedError`. This is so that the client can be 328 | authenticated at this point. When using the exception, any arguments passed to 329 | the exception are returned to the client in the error packet. Examples:: 330 | 331 | from flask_socketio import ConnectionRefusedError 332 | 333 | @socketio.on('connect') 334 | def connect(): 335 | if not self.authenticate(request.args): 336 | raise ConnectionRefusedError('unauthorized!') 337 | 338 | Note that connection and disconnection events are sent individually on each 339 | namespace used. 340 | 341 | Class-Based Namespaces 342 | ---------------------- 343 | 344 | As an alternative to the decorator-based event handlers described above, the 345 | event handlers that belong to a namespace can be created as methods of a 346 | class. The :class:`flask_socketio.Namespace` is provided as a base class to 347 | create class-based namespaces:: 348 | 349 | from flask_socketio import Namespace, emit 350 | 351 | class MyCustomNamespace(Namespace): 352 | def on_connect(self): 353 | pass 354 | 355 | def on_disconnect(self): 356 | pass 357 | 358 | def on_my_event(self, data): 359 | emit('my_response', data) 360 | 361 | socketio.on_namespace(MyCustomNamespace('/test')) 362 | 363 | When class-based namespaces are used, any events received by the server are 364 | dispatched to a method named as the event name with the ``on_`` prefix. For 365 | example, event ``my_event`` will be handled by a method named ``on_my_event``. 366 | If an event is received for which there is no corresponding method defined in 367 | the namespace class, then the event is ignored. All event names used in 368 | class-based namespaces must use characters that are legal in method names. 369 | 370 | As a convenience to methods defined in a class-based namespace, the namespace 371 | instance includes versions of several of the methods in the 372 | :class:`flask_socketio.SocketIO` class that default to the proper namespace 373 | when the ``namespace`` argument is not given. 374 | 375 | If an event has a handler in a class-based namespace, and also a 376 | decorator-based function handler, only the decorated function handler is 377 | invoked. 378 | 379 | Error Handling 380 | -------------- 381 | 382 | Flask-SocketIO can also deal with exceptions:: 383 | 384 | @socketio.on_error() # Handles the default namespace 385 | def error_handler(e): 386 | pass 387 | 388 | @socketio.on_error('/chat') # handles the '/chat' namespace 389 | def error_handler_chat(e): 390 | pass 391 | 392 | @socketio.on_error_default # handles all namespaces without an explicit error handler 393 | def default_error_handler(e): 394 | pass 395 | 396 | Error handler functions take the exception object as an argument. 397 | 398 | The message and data arguments of the current request can also be inspected 399 | with the ``request.event`` variable, which is useful for error logging and 400 | debugging outside the event handler:: 401 | 402 | from flask import request 403 | 404 | @socketio.on("my error event") 405 | def on_my_event(data): 406 | raise RuntimeError() 407 | 408 | @socketio.on_error_default 409 | def default_error_handler(e): 410 | print(request.event["message"]) # "my error event" 411 | print(request.event["args"]) # (data,) 412 | 413 | Access to Flask's Context Globals 414 | --------------------------------- 415 | 416 | Handlers for SocketIO events are different than handlers for routes and that 417 | introduces a lot of confusion around what can and cannot be done in a SocketIO 418 | handler. The main difference is that all the SocketIO events generated for a 419 | client occur in the context of a single long running request. 420 | 421 | In spite of the differences, Flask-SocketIO attempts to make working with 422 | SocketIO event handlers easier by making the environment similar to that of a 423 | regular HTTP request. The following list describes what works and what doesn't: 424 | 425 | - An application context is pushed before invoking an event handler making 426 | ``current_app`` and ``g`` available to the handler. 427 | - A request context is also pushed before invoking a handler, also making 428 | ``request`` and ``session`` available. But note that WebSocket events do not 429 | have individual requests associated with them, so the request context that 430 | started the connection is pushed for all the events that are dispatched 431 | during the life of the connection. 432 | - The ``request`` context global is enhanced with a ``sid`` member that is set 433 | to a unique session ID for the connection. This value is used as an initial 434 | room where the client is added. 435 | - The ``request`` context global is enhanced with ``namespace`` and ``event`` 436 | members that contain the currently handled namespace and event arguments. 437 | The ``event`` member is a dictionary with ``message`` and ``args`` keys. 438 | - The ``session`` context global behaves in a different way than in regular 439 | requests. A copy of the user session at the time the SocketIO connection is 440 | established is made available to handlers invoked in the context of that 441 | connection. If a SocketIO handler modifies the session, the modified session 442 | will be preserved for future SocketIO handlers, but regular HTTP route 443 | handlers will not see these changes. Effectively, when a SocketIO handler 444 | modifies the session, a "fork" of the session is created exclusively for 445 | these handlers. The technical reason for this limitation is that to save the 446 | user session a cookie needs to be sent to the client, and that requires HTTP 447 | request and response, which do not exist in a SocketIO connection. When 448 | using server-side sessions such as those provided by the Flask-Session or 449 | Flask-KVSession extensions, changes made to the session in HTTP route 450 | handlers can be seen by SocketIO handlers, as long as the session is not 451 | modified in the SocketIO handlers. 452 | - The ``before_request`` and ``after_request`` hooks are not invoked for 453 | SocketIO event handlers. 454 | - SocketIO handlers can take custom decorators, but most Flask decorators will 455 | not be appropriate to use for a SocketIO handler, given that there is no 456 | concept of a ``Response`` object during a SocketIO connection. 457 | 458 | Authentication 459 | -------------- 460 | 461 | A common need of applications is to validate the identity of their users. The 462 | traditional mechanisms based on web forms and HTTP requests cannot be used in 463 | a SocketIO connection, since there is no place to send HTTP requests and 464 | responses. If necessary, an application can implement a customized login form 465 | that sends credentials to the server as a SocketIO message when the submit 466 | button is pressed by the user. 467 | 468 | However, in most cases it is more convenient to perform the traditional 469 | authentication process before the SocketIO connection is established. The 470 | user's identity can then be recorded in the user session or in a cookie, and 471 | later when the SocketIO connection is established that information will be 472 | accessible to SocketIO event handlers. 473 | 474 | Using Flask-Login with Flask-SocketIO 475 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 476 | 477 | Flask-SocketIO can access login information maintained by 478 | `Flask-Login `_. After a 479 | regular Flask-Login authentication is performed and the ``login_user()`` 480 | function is called to record the user in the user session, any SocketIO 481 | connections will have access to the ``current_user`` context variable:: 482 | 483 | @socketio.on('connect') 484 | def connect_handler(): 485 | if current_user.is_authenticated: 486 | emit('my response', 487 | {'message': '{0} has joined'.format(current_user.name)}, 488 | broadcast=True) 489 | else: 490 | return False # not allowed here 491 | 492 | Note that the ``login_required`` decorator cannot be used with SocketIO event 493 | handlers, but a custom decorator that disconnects non-authenticated users can 494 | be created as follows:: 495 | 496 | import functools 497 | from flask import request 498 | from flask_login import current_user 499 | from flask_socketio import disconnect 500 | 501 | def authenticated_only(f): 502 | @functools.wraps(f) 503 | def wrapped(*args, **kwargs): 504 | if not current_user.is_authenticated: 505 | disconnect() 506 | else: 507 | return f(*args, **kwargs) 508 | return wrapped 509 | 510 | @socketio.on('my event') 511 | @authenticated_only 512 | def handle_my_custom_event(data): 513 | emit('my response', {'message': '{0} has joined'.format(current_user.name)}, 514 | broadcast=True) 515 | 516 | Deployment 517 | ---------- 518 | 519 | There are many options to deploy a Flask-SocketIO server, ranging from simple 520 | to the insanely complex. In this section, the most commonly used options are 521 | described. 522 | 523 | Embedded Server 524 | ~~~~~~~~~~~~~~~ 525 | 526 | The simplest deployment strategy is to have eventlet or gevent installed, and 527 | start the web server by calling ``socketio.run(app)`` as shown in examples 528 | above. This will run the application on the eventlet or gevent web servers, 529 | whichever is installed. 530 | 531 | Note that ``socketio.run(app)`` runs a production ready server when eventlet 532 | or gevent are installed. If neither of these are installed, then the 533 | application runs on Flask's development web server, which is not appropriate 534 | for production use. 535 | 536 | Unfortunately this option is not available when using gevent with uWSGI. See 537 | the uWSGI section below for information on this option. 538 | 539 | Gunicorn Web Server 540 | ~~~~~~~~~~~~~~~~~~~ 541 | 542 | An alternative to ``socketio.run(app)`` is to use 543 | `gunicorn `_ as web server, using the eventlet or gevent 544 | workers. For this option, eventlet or gevent need to be installed, in addition 545 | to gunicorn. The command line that starts the eventlet server via gunicorn is:: 546 | 547 | gunicorn --worker-class eventlet -w 1 module:app 548 | 549 | If you prefer to use gevent, the command to start the server is:: 550 | 551 | gunicorn -k gevent -w 1 module:app 552 | 553 | When using gunicorn with the gevent worker and the WebSocket support provided 554 | by gevent-websocket, the command that starts the server must be changed to 555 | select a custom gevent web server that supports the WebSocket protocol. The 556 | modified command is:: 557 | 558 | gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module:app 559 | 560 | In all these commands, ``module`` is the Python module or package that defines 561 | the application instance, and ``app`` is the application instance itself. 562 | 563 | Due to the limited load balancing algorithm used by gunicorn, it is not possible 564 | to use more than one worker process when using this web server. For that reason, 565 | all the examples above include the ``-w 1`` option. 566 | 567 | uWSGI Web Server 568 | ~~~~~~~~~~~~~~~~ 569 | 570 | When using the uWSGI server in combination with gevent, the Socket.IO server 571 | can take advantage of uWSGI’s native WebSocket support. 572 | 573 | A complete explanation of the configuration and usage of the uWSGI server is 574 | beyond the scope of this documentation. The uWSGI server is a fairly complex 575 | package that provides a large and comprehensive set of options. It must be 576 | compiled with WebSocket and SSL support for the WebSocket transport to be 577 | available. As way of an introduction, the following command starts a uWSGI 578 | server for the example application app.py on port 5000:: 579 | 580 | $ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file app.py --callable app 581 | 582 | Using nginx as a WebSocket Reverse Proxy 583 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 584 | 585 | It is possible to use nginx as a front-end reverse proxy that passes requests 586 | to the application. However, only releases of nginx 1.4 and newer support 587 | proxying of the WebSocket protocol. Below is a basic nginx configuration that 588 | proxies HTTP and WebSocket requests:: 589 | 590 | server { 591 | listen 80; 592 | server_name _; 593 | 594 | location / { 595 | include proxy_params; 596 | proxy_pass http://127.0.0.1:5000; 597 | } 598 | 599 | location /static { 600 | alias /static; 601 | expires 30d; 602 | } 603 | 604 | location /socket.io { 605 | include proxy_params; 606 | proxy_http_version 1.1; 607 | proxy_buffering off; 608 | proxy_set_header Upgrade $http_upgrade; 609 | proxy_set_header Connection "Upgrade"; 610 | proxy_pass http://127.0.0.1:5000/socket.io; 611 | } 612 | } 613 | 614 | The next example adds the support for load balancing multiple Socket.IO 615 | servers:: 616 | 617 | upstream socketio_nodes { 618 | ip_hash; 619 | 620 | server 127.0.0.1:5000; 621 | server 127.0.0.1:5001; 622 | server 127.0.0.1:5002; 623 | # to scale the app, just add more nodes here! 624 | } 625 | 626 | server { 627 | listen 80; 628 | server_name _; 629 | 630 | location / { 631 | include proxy_params; 632 | proxy_pass http://127.0.0.1:5000; 633 | } 634 | 635 | locaton /static { 636 | alias /static; 637 | expires 30d; 638 | } 639 | 640 | location /socket.io { 641 | include proxy_params; 642 | proxy_http_version 1.1; 643 | proxy_buffering off; 644 | proxy_set_header Upgrade $http_upgrade; 645 | proxy_set_header Connection "Upgrade"; 646 | proxy_pass http://socketio_nodes/socket.io; 647 | } 648 | } 649 | 650 | While the above examples can work as an initial configuration, be aware that a 651 | production install of nginx will need a more complete configuration covering 652 | other deployment aspects such as serving static file assets and SSL support. 653 | 654 | Using Multiple Workers 655 | ~~~~~~~~~~~~~~~~~~~~~~ 656 | 657 | Flask-SocketIO supports multiple workers behind a load balancer starting with 658 | release 2.0. Deploying multiple workers gives applications that use 659 | Flask-SocketIO the ability to spread the client connections among multiple 660 | processes and hosts, and in this way scale to support very large numbers of 661 | concurrent clients. 662 | 663 | There are two requirements to use multiple Flask-SocketIO workers: 664 | 665 | - The load balancer must be configured to forward all HTTP requests from a 666 | given client always to the same worker. This is sometimes referenced as 667 | "sticky sessions". For nginx, use the ``ip_hash`` directive to achieve this. 668 | Gunicorn cannot be used with multiple workers because its load balancer 669 | algorithm does not support sticky sessions. 670 | 671 | - Since each of the servers owns only a subset of the client connections, a 672 | message queue such as Redis or RabbitMQ is used by the servers to coordinate 673 | complex operations such as broadcasting and rooms. 674 | 675 | When working with a message queue, there are additional dependencies that need to 676 | be installed: 677 | 678 | - For Redis, the package ``redis`` must be installed (``pip install redis``). 679 | - For RabbitMQ, the package ``kombu`` must be installed (``pip install kombu``). 680 | - For Kafka, the package ``kafka-python`` must be installed (``pip install kafka-python``). 681 | - For other message queues supported by Kombu, see the `Kombu documentation 682 | `_ 683 | to find out what dependencies are needed. 684 | - If eventlet or gevent are used, then monkey patching the Python standard 685 | library is normally required to force the message queue package to use 686 | coroutine friendly functions and classes. 687 | 688 | For eventlet, monkey patching is done with:: 689 | 690 | import eventlet 691 | eventlet.monkey_patch() 692 | 693 | For gevent, you can monkey patch the standard library with:: 694 | 695 | from gevent import monkey 696 | monkey.patch_all() 697 | 698 | In both cases it is recommended that you apply the monkey patching at the top 699 | of your main script, even above your imports. 700 | 701 | To start multiple Flask-SocketIO servers, you must first ensure you have the 702 | message queue service running. To start a Socket.IO server and have it connect to 703 | the message queue, add the ``message_queue`` argument to the ``SocketIO`` 704 | constructor:: 705 | 706 | socketio = SocketIO(app, message_queue='redis://') 707 | 708 | The value of the ``message_queue`` argument is the connection URL of the 709 | queue service that is used. For a redis queue running on the same host as the 710 | server, the ``'redis://'`` URL can be used. Likewise, for a default RabbitMQ 711 | queue the ``'amqp://'`` URL can be used. For Kafka, use a ``kafka://`` URL. 712 | The Kombu package has a `documentation 713 | section `_ 714 | that describes the format of the URLs for all the supported queues. 715 | 716 | Emitting from an External Process 717 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 718 | 719 | For many types of applications, it is necessary to emit events from a process 720 | that is not the SocketIO server, for an example a Celery worker. If the 721 | SocketIO server or servers are configured to listen on a message queue as 722 | shown in the previous section, then any other process can create its own 723 | ``SocketIO`` instance and use it to emit events in the same way the server 724 | does. 725 | 726 | For example, for an application that runs on an eventlet web server and uses 727 | a Redis message queue, the following Python script broadcasts an event to 728 | all clients:: 729 | 730 | socketio = SocketIO(message_queue='redis://') 731 | socketio.emit('my event', {'data': 'foo'}, namespace='/test') 732 | 733 | When using the ``SocketIO`` instance in this way, the Flask application 734 | instance is not passed to the constructor. 735 | 736 | The ``channel`` argument to ``SocketIO`` can be used to select a specific 737 | channel of communication through the message queue. Using a custom channel 738 | name is necessary when there are multiple independent SocketIO services 739 | sharing the same queue. 740 | 741 | Flask-SocketIO does not apply monkey patching when eventlet or gevent are 742 | used. But when working with a message queue, it is very likely that the Python 743 | package that talks to the message queue service will hang if the Python 744 | standard library is not monkey patched. 745 | 746 | It is important to note that an external process that wants to connect to 747 | a SocketIO server does not need to use eventlet or gevent like the main 748 | server. Having a server use a coroutine framework, while an external process 749 | is not a problem. For example, Celery workers do not need to be 750 | configured to use eventlet or gevent just because the main server does. But if 751 | your external process does use a coroutine framework for whatever reason, then 752 | monkey patching is likely required, so that the message queue accesses 753 | coroutine friendly functions and classes. 754 | 755 | Upgrading to Flask-SocketIO 1.x and 2.x from the 0.x releases 756 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 757 | 758 | Older versions of Flask-SocketIO had a completely different set of 759 | requirements. Those old versions had a dependency on 760 | `gevent-socketio `_ and 761 | `gevent-websocket `_, which 762 | are not required in release 1.0. 763 | 764 | In spite of the change in dependencies, there aren't many significant 765 | changes introduced in version 1.0. Below is a detailed list of 766 | the actual differences: 767 | 768 | - Release 1.0 drops support for Python 2.6, and adds support for Python 3.3, 769 | Python 3.4, and pypy. 770 | - Releases 0.x required an old version of the Socket.IO Javascript client. 771 | Starting with release 1.0, the current releases of Socket.IO and Engine.IO 772 | are supported. Releases of the Socket.IO client prior to 1.0 are no 773 | supported. The Swift and C++ official Socket.IO clients are now supported 774 | as well. 775 | - The 0.x releases depended on gevent, gevent-socketio and gevent-websocket. 776 | In release 1.0 gevent-socketio is not used anymore, and gevent is one of 777 | three options for backend web server, with eventlet and any regular 778 | multi-threaded WSGI server, including Flask's development web server. 779 | - The Socket.IO server options have changed in release 1.0. They can be 780 | provided in the SocketIO constructor, or in the ``run()`` call. The options 781 | provided in these two are merged before they are used. 782 | - The 0.x releases exposed the gevent-socketio connection as 783 | ``request.namespace``. In release 1.0 this is not available anymore. The 784 | request object defines ``request.namespace`` as the name of the namespace 785 | being handled, and adds ``request.sid``, defined as the unique session ID 786 | for the client connection, and ``request.event``, which contains the event 787 | name and arguments. 788 | - To get the list of rooms a client was in the 0.x release required the 789 | application to use a private structure of gevent-socketio, with the 790 | expression ``request.namespace.rooms``. This is not available in release 791 | 1.0, which includes a proper ``rooms()`` function. 792 | - The recommended "trick" to send a message to an individual client was to 793 | put each client in a separate room, then address messages to the desired 794 | room. This was formalized in release 1.0, where clients are assigned a room 795 | automatically when they connect. 796 | - The ``'connect'`` event for the global namespace did not fire on releases 797 | prior to 1.0. This has been fixed and now this event fires as expected. 798 | - Support for client-side callbacks was introduced in release 1.0. 799 | 800 | To upgrade to the newer Flask-SocketIO releases, you need to upgrade your 801 | Socket.IO client to a client that is compatible with the Socket.IO 1.0 802 | protocol. For the JavaScript client, the 1.3.x and 1.4.x releases have been 803 | extensively tested and found compatible. 804 | 805 | On the server side, there are a few points to consider: 806 | 807 | - If you wish to continue using gevent, then uninstall gevent-socketio from 808 | your virtual environment, as this package is not used anymore and may 809 | collide with its replacement, python-socketio. 810 | - If you want to have slightly better performance and stability, then it is 811 | recommended that you switch to eventlet. To do this, uninstall gevent, 812 | gevent-socketio and gevent-websocket, and install eventlet. 813 | - If your application uses monkey patching and you switched to eventlet, call 814 | `eventlet.monkey_patch()` instead of gevent's `monkey.patch_all()`. Also, 815 | any calls to gevent must be replaced with equivalent calls to eventlet. 816 | - Any uses of `request.namespace` must be replaced with direct calls into the 817 | Flask-SocketIO functions. For example, `request.namespace.rooms` must be 818 | replaced with the `rooms()` function. 819 | - Any uses of internal gevent-socketio objects must be removed, as this 820 | package is not a dependency anymore. 821 | 822 | Cross-Origin Controls 823 | --------------------- 824 | 825 | For security reasons, this server enforces a same-origin policy by default. In 826 | practical terms, this means the following: 827 | 828 | - If an incoming HTTP or WebSocket request includes the ``Origin`` header, 829 | this header must match the scheme and host of the connection URL. In case 830 | of a mismatch, a 400 status code response is returned and the connection is 831 | rejected. 832 | - No restrictions are imposed on incoming requests that do not include the 833 | ``Origin`` header. 834 | 835 | If necessary, the ``cors_allowed_origins`` option can be used to allow other 836 | origins. This argument can be set to a string to set a single allowed origin, or 837 | to a list to allow multiple origins. A special value of ``'*'`` can be used to 838 | instruct the server to allow all origins, but this should be done with care, as 839 | this could make the server vulnerable to Cross-Site Request Forgery (CSRF) 840 | attacks. 841 | 842 | API Reference 843 | ------------- 844 | 845 | .. module:: flask_socketio 846 | .. autoclass:: SocketIO 847 | :members: 848 | .. autofunction:: emit 849 | .. autofunction:: send 850 | .. autofunction:: join_room 851 | .. autofunction:: leave_room 852 | .. autofunction:: close_room 853 | .. autofunction:: rooms 854 | .. autofunction:: disconnect 855 | .. autoclass:: Namespace 856 | :members: 857 | .. autoclass:: SocketIOTestClient 858 | :members: 859 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Flask-SocketIO change log 2 | 3 | **Release 4.2.1** - 2019-08-05 4 | 5 | - Add support for Apache Kafka message queue [#700](https://github.com/miguelgrinberg/flask-socketio/issues/700) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f20268a3ec14af3e8d6681c2ffd01e299dc4f6df)) (thanks **Vincent Mézino**!) 6 | - Update CORS documentation ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d9cd34a86dedf96ca5232cf981b54b4c1c6e362d)) 7 | 8 | **Release 4.2.0** - 2019-07-29 9 | 10 | - Address potential websocket cross-origin attacks [#128](https://github.com/miguelgrinberg/python-engineio/issues/128) ([commit](https://github.com/miguelgrinberg/python-engineio/commit/7548f704a0a3000b7ac8a6c88796c4ae58aa9c37)) 11 | - Documentation for the Same Origin security policy ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/42d4e02055f5936f1322982bf44a845987d75144)) 12 | 13 | **Release 4.1.1** - 2019-07-29 14 | 15 | - Fix typo in "Using nginx" section [#1007](https://github.com/miguelgrinberg/flask-socketio/issues/1007) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9e152b24ec30dd712886c4da3ec6a3aded855a00)) (thanks **Steffen Schneider**!) 16 | - updated python-socketio min version requirement to 4.0.0 [#1006](https://github.com/miguelgrinberg/flask-socketio/issues/1006) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/40a34c119a6b7f7e00d2a476d26e985cbd10ec19)) (thanks **Shantanu Hazari**!) 17 | 18 | **Release 4.1.0** - 2019-06-09 19 | 20 | - Add ConnectionRefusedError exception from python-socketio [#989](https://github.com/miguelgrinberg/flask-socketio/issues/989) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/3f9fda8f0de551567834400ff72a95c10c7d42b4)) 21 | - Invoke Socket.IO callbacks with app and request context [#262](https://github.com/miguelgrinberg/flask-socketio/issues/262) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/75a75d47cb20fca8a8b2b2818a7602d43b4cea1f)) 22 | - Copy handler's name and docstring to handler wrapper [#573](https://github.com/miguelgrinberg/flask-socketio/issues/573) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4e0329b59e653edc19b31f05823b47bd63c0bc72)) 23 | - Less aggressive monkey patching for gevent [#413](https://github.com/miguelgrinberg/flask-socketio/issues/413) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/55d02d97708bd91b7d8f761ab57aba8d946039ff)) 24 | - Updates jquery and socket.io in example application [#988](https://github.com/miguelgrinberg/flask-socketio/issues/988) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/67da0d6627149c27da4ddc9b675aacf946dc3588)) (thanks **sillyfrog**!) 25 | 26 | **Release 4.0.0** - 2019-05-19 27 | 28 | - move to the latest python-socketio 4.x releases ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/686abcaf69c27f6fb6084d0948832ffff8177755)) 29 | - SocketIOTestClient can handle disconnect event from server [#967](https://github.com/miguelgrinberg/flask-socketio/issues/967) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/611fa68e102b7b49ac77d48406a034f23aca4998)) (thanks **Jack**!) 30 | - example app: disconnect in callback function [#453](https://github.com/miguelgrinberg/flask-socketio/issues/453) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f8ee01f938afcd13d4cae282976946660db8e982)) 31 | - update documentation for skip_sid argument supporting lists ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/932b0296afea789f51c0b7f39f99a561593fb257)) 32 | - add /static block to nginx configuration example [#222](https://github.com/miguelgrinberg/flask-socketio/issues/222) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/e052ae6d7b865929bfb7dca4b936902d204ccdb6)) 33 | - add notes on monkey patching [#383](https://github.com/miguelgrinberg/flask-socketio/issues/383) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/5dd2bca681cc826fa60558ec244633437bfebaf4)) 34 | - note the event names that are reserved in the documentation ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/0c9d1b32499ca5091cf04833c32f3ade803be6e7)) 35 | - minor doc improvements [#960](https://github.com/miguelgrinberg/flask-socketio/issues/960) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a0c29a94b1d53306550e8c5ffc4fcb71f6fb20b7)) 36 | - updated some requirements ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4bef800d5e7ba7d98a6f4cd94191ff0b4496c334)) 37 | - add link to stack overflow for questions ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/db46f062b2d13c7c464f625d5e1976d9625a0f37)) 38 | - helper release script ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/1fd43a3fc86be8848fc6d70d80638db688f4eb97)) 39 | 40 | **Release 3.3.2** - 2019-03-09 41 | 42 | - update dependencies ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b65e92b0b97bcbe2f047d508ed520cecd9765f32)) 43 | 44 | **Release 3.3.1** - 2019-02-16 45 | 46 | - keep connected status in test client ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/5e399d544317a741234d3260c04aca4ba2e18e5a)) 47 | 48 | **Release 3.3.0** - 2019-02-16 49 | 50 | - added flask_test_client option to Socket.IO test client ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/074278808e9a732f5f9fcb9273a312fbb6e279bd)) 51 | 52 | **Release 3.2.2** - 2019-02-12 53 | 54 | - suppress web server warning when in write-only mode ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ca582618863070657f0565d614552a7f65c9fb6d)) 55 | 56 | **Release 3.2.1** - 2019-01-24 57 | 58 | - remove error about eventlet/gevent used with flask run ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9a717733a1a1a93c155d8b41402a5b5527b6eee1)) 59 | 60 | **Release 3.2.0** - 2019-01-23 61 | 62 | - discontinue the customized flask run command ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9bde4beda523d539e2113151e8c4d3f88765a5ea)) 63 | - spelling corrected [#869](https://github.com/miguelgrinberg/flask-socketio/issues/869) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/afbd83ef75ed948bc5814c668b73f1be9573f0e4)) (thanks **Muhammad Hamza**!) 64 | 65 | **Release 3.1.2** - 2018-12-21 66 | 67 | - make unit tests compatible with Python 3.7.1 ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f84a7dfbe34110dc604a7e2df9098e4348248d55)) 68 | 69 | **Release 3.1.1** - 2018-12-08 70 | 71 | - fix dependency version ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/65f29ce16e386d5a0718de7e78ce39a77bd732d6)) 72 | 73 | **Release 3.1.0** - 2018-11-26 74 | 75 | - move to the new WSGIApp class in python-socketio ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/db74fa385e9f508fff25029f2a3bfd1b86916960)) 76 | - remove misleading target keyword arg in examples ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7463d340581bff18be09b989ab938d367e2cf408)) 77 | - Fix test client [call]back returning [#732](https://github.com/miguelgrinberg/flask-socketio/issues/732) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4b0648cb195be3f94344d9b0b7f77d9b312256d8)) (thanks **Alex Pilon**!) 78 | 79 | **Release 3.0.2** - 2018-09-12 80 | 81 | - undoing fix for [#713](https://github.com/miguelgrinberg/flask-socketio/issues/713) as it breaks the reloader for regular apps ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b20224b968fd99ee69e3cefc78d07e989c833064)) 82 | - README.md: Add syntax highlighting to python code [#723](https://github.com/miguelgrinberg/flask-socketio/issues/723) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/cc49b24b1aabcfe976dc402f798717939664e378)) (thanks **Meet Mangukiya**!) 83 | - Fix typo in docs [#717](https://github.com/miguelgrinberg/flask-socketio/issues/717) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/cec3986a5ba05f84df4894eeeb715d5c1a290abf)) (thanks **Grey Li**!) 84 | 85 | **Release 3.0.1** - 2018-06-03 86 | 87 | - reloader fix [#713](https://github.com/miguelgrinberg/flask-socketio/issues/713) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/549cad6307c066c24146590a5c3427fa4b9a8fa3)) 88 | 89 | **Release 3.0.0** - 2018-04-30 90 | 91 | - minor fix for Flask 1.0 ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/39649c83393da5b69d26e5e410a49c86af71a0f9)) 92 | - add pypy3 target to travis builds ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9d4a5a46edc359ceda873f3f984216946a8051c2)) 93 | - remove outdated warning about gunicorn R19 [#563](https://github.com/miguelgrinberg/flask-socketio/issues/563) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a917674006932088eea46311886d3e8c44d5984a)) 94 | - improved documentation for disconnect() [#673](https://github.com/miguelgrinberg/flask-socketio/issues/673) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/73f17fa3d24122ea4ece098a3c9bdb1898024eeb)) 95 | 96 | **Release 2.9.6** - 2018-03-10 97 | 98 | - support --with[#659](https://github.com/miguelgrinberg/flask-socketio/issues/659) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f08cc232fc1313c2d8a423d78a6444c127d116ae)) (thanks **Kareem Zidane**!) 99 | - add optional namespace argument to disconnect function ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9cfa8e117129841e8f060ca53f412985a65d4f54)) 100 | 101 | **Release 2.9.5** - 2018-03-09 102 | 103 | - add optional sid argument to disconnect function ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ff9f385bfed394c026fd9bb69e7ec803e0c53b5f)) 104 | - Fix typo in index.rst [#650](https://github.com/miguelgrinberg/flask-socketio/issues/650) Fix typographical error in the Authentication section of the file. ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/8d64c781eb7c5d51103ef766a6cc5ab2af08dc77)) (thanks **Michael Obi**!) 105 | 106 | **Release 2.9.4** - 2018-02-25 107 | 108 | - make managed session more like a real session ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/fdf8edbfdeaaf909bb59b1dc86822f27aa06df2d)) 109 | - Update docs link [#613](https://github.com/miguelgrinberg/flask-socketio/issues/613) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4d1c3cc29990226a85ba0f05d811043b42d0b1d9)) (thanks **Grey Li**!) 110 | 111 | **Release 2.9.3** - 2017-12-11 112 | 113 | - Support binary data in the test client [#601](https://github.com/miguelgrinberg/flask-socketio/issues/601) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ad0001dc04dc37f439bd52b82b02fc15434b38b8)) 114 | - Update docs now gevent-websocket is available for python3 [#599](https://github.com/miguelgrinberg/flask-socketio/issues/599) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/96cf8ee275fb3fd2e25c9dd1490d850ec0044c6d)) (thanks **Andrew Burrows**!) 115 | - fix param name in doc string [#585](https://github.com/miguelgrinberg/flask-socketio/issues/585) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/84da99af88b537e68ec3862017a8b48ca68f810d)) (thanks **Grey Li**!) 116 | - Add missing json argument in send[#587](https://github.com/miguelgrinberg/flask-socketio/issues/587) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/07284632727429e2cc174d68bad641f9060769e9)) (thanks **Grey Li**!) 117 | - improved documentation on acknowledgements ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/e5a4bfd33cbff1e5c2f39d1588e0e71abc4745e7)) 118 | - Support Redis SSL connection string [#569](https://github.com/miguelgrinberg/flask-socketio/issues/569) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/20d766d34672cfbae65f908231bd2338ee8643e3)) (thanks **Ján Koščo**!) 119 | - updated requirements [#534](https://github.com/miguelgrinberg/flask-socketio/issues/534) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/466bbc3be7e53025f24ea45d3f68b9db332393d6)) 120 | - prevent race conditions with thread start [#493](https://github.com/miguelgrinberg/flask-socketio/issues/493) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/5c7d14bd602475c2b4a5be618badb3b00c96cc3c)) 121 | - Documented some protocol defaults. [#516](https://github.com/miguelgrinberg/flask-socketio/issues/516) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/61c179d337428224b58f7c062b139db3df843152)) 122 | 123 | **Release 2.9.2** - 2017-08-05 124 | 125 | - some more unit tests ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/50ab42a3705bb03182735c70f83d81020b2037e1)) 126 | - Support custom headers and query string in test client [#520](https://github.com/miguelgrinberg/flask-socketio/issues/520) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ecf5925827a916ef52361856290f16087c4e36e9)) 127 | - added **kwarg to pywsgi.WSGIServer when import WebSocketHandler failed [#518](https://github.com/miguelgrinberg/flask-socketio/issues/518) there is any reason to not pass **kwarg pywsgi.WSGIServer in the case WebSocketHandler fail to import? ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/8a09692873ed6dea35599be7f1f607e1aa58170f)) (thanks **simus81**!) 128 | 129 | **Release 2.9.1** - 2017-07-16 130 | 131 | - also add ignore_queue to send function ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/1d37649fbdcfd15b98cadc524580f6d4d8d80bb6)) 132 | - expose ignore_queue param in the emit method [#505](https://github.com/miguelgrinberg/flask-socketio/issues/505) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/00a8de06d0cbc2051412729ad050669aef2085f2)) (thanks **hsvlz**!) 133 | 134 | **Release 2.9.0** - 2017-06-26 135 | 136 | - added flask-login to sessions example ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a51a3ba688c27b5d0294268d8fece6f79806bd02)) 137 | - updated example requirements file ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ece16238c91349585dce144038d2af0f807ef2af)) 138 | - better support for user sessions ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/742c5ba23e53b5bdaf0530ed483dd808e50dca08)) 139 | - remove unused code related to previous commit ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/3924544e0e6db77cee8bc9d461ab825be9606738)) 140 | - Support beaker (and possibly other) sessions ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/2625426499e3b375336ef78244b5df5571c2c57f)) 141 | - Fix typo in top level doc [#452](https://github.com/miguelgrinberg/flask-socketio/issues/452) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/8d3d72952456662352e3e54ecb006a7e36238b2f)) (thanks **Ben Harack**!) 142 | - Added an optional IPv6 support in eventlet ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d050d688bcdd7f99550d8a26138896b58a081191)) (thanks **Pavel Pushkarev**!) 143 | - fix KeyError ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/31e06d4472b27dbbac91f03319e53d46d8130b07)) 144 | - cleanup previous merge ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/5d432c784b19532171ef2dd6334b4e802d41eb8c)) 145 | - Fix 'path' or 'resource' doesn't work. ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a1d771940ba11b9802684c53f6bd34e6d2819170)) (thanks **Water Zheng**!) 146 | 147 | **Release 2.8.6** - 2017-03-21 148 | 149 | - add documentation for [#433](https://github.com/miguelgrinberg/flask-socketio/issues/433) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/3dac05157f75c84ed016230a32463f1bbc16cdcf)) 150 | - specify namespace in room related functions ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a41e6cb7766b8807a05a723edc279c3c39f5367a)) (thanks **Samuel Kortchmar**!) 151 | 152 | **Release 2.8.5** - 2017-03-02 153 | 154 | - specify sid in room related functions [#420](https://github.com/miguelgrinberg/flask-socketio/issues/420) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/6a3462d726be38b463361f681f01a78a5d771153)) 155 | 156 | **Release 2.8.4** - 2017-02-17 157 | 158 | - allow @socket.on decorators to be chained [#408](https://github.com/miguelgrinberg/flask-socketio/issues/408) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4a01d816e411f31b445b876ee2b0b8ac57502ef6)) 159 | - add 3.6 builds, remove 3.3 ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/32affb9d1ba6f15e445ad876cb9cd2f3c60538f1)) 160 | 161 | **Release 2.8.3** - 2017-02-13 162 | 163 | - updated test client to work with latest python-socketio release [#405](https://github.com/miguelgrinberg/flask-socketio/issues/405) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/6d5fba300f032613fdfc1315edf784da83b153ea)) 164 | - add support for using zmq as the message queue transport ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4ee08fffdbb87a78681b00bcb3d40d0dcee3baae)) (thanks **Eric Seidler**!) 165 | - Fix a typo [#400](https://github.com/miguelgrinberg/flask-socketio/issues/400) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/551f81e80cad85b7cdcb1ff4d38dffe412fdc6bd)) (thanks **Jordan Suchow**!) 166 | 167 | **Release 2.8.2** - 2016-12-16 168 | 169 | - make a copy of the environ dict ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/be088580f94d1528ead53ea8c3784ca76da5ecff)) 170 | 171 | **Release 2.8.1** - 2016-11-27 172 | 173 | - Do not call init_app when an app or message_queue aren't given [#367](https://github.com/miguelgrinberg/flask-socketio/issues/367) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/37279e270547293ac284dbedc53b90d24bd978db)) 174 | - Improved nginx section of the documentation [#334](https://github.com/miguelgrinberg/flask-socketio/issues/334) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/2f7790c6538bf9de0cb795ad2cedba9cc1304aa7)) 175 | 176 | **Release 2.8.0** - 2016-11-26 177 | 178 | - Pass client manager specific arguments in emit and send calls ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/10426ddf4e1e9fd8192f9636da32c471d9de4856)) 179 | - Support for "skip_sid" option. [#365](https://github.com/miguelgrinberg/flask-socketio/issues/365) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a8ae4791af0a573e83c48a19686aae107498ebd4)) 180 | - Make sure the test client is not used with a message queue [#366](https://github.com/miguelgrinberg/flask-socketio/issues/366) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/e14e5de75b617271de75a1a964702bdf48e4c814)) 181 | - Update custom namespace doc example [#364](https://github.com/miguelgrinberg/flask-socketio/issues/364) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/c1ba02cf52891b161bfc85e4b7f8e082aea77fd3)) (thanks **LikeMyBread**!) 182 | 183 | **Release 2.7.2** - 2016-11-04 184 | 185 | - Use standard run command if flask-socketio isn't instantiated [#347](https://github.com/miguelgrinberg/flask-socketio/issues/347) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7af6740b8332e65b49b065ab458a1deec73f0b2f)) 186 | - Improve socketio connect function in example for http[s] ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/391e793153dfee861795693ad5bdefb6d1367c33)) (thanks **Kyle Lawlor**!) 187 | - preserve options given in constructor when init_app is called [#321](https://github.com/miguelgrinberg/flask-socketio/issues/321) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f97673980f28cb3c809763e949aa1e20ddff21cc)) 188 | - include license and readme in the package [#326](https://github.com/miguelgrinberg/flask-socketio/issues/326) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/032a28103617e264f0ceff27ec3125696b2c24ab)) 189 | 190 | **Release 2.7.1** - 2016-09-04 191 | 192 | - add `__version__` to package ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/200424160e1f28b40e82daa5e427d8953614d612)) 193 | 194 | **Release 2.7.0** - 2016-09-01 195 | 196 | - uwsgi support, class-based namespaces ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a009636020417eb2f8be83c99a7bca6050c57353)) 197 | - fix failing unit test ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/78b884cd73a0114db4f95f166bc5c90195653b6e)) 198 | - Add on_event(), the non-decorator version of on() ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/39f8795e8118a01596f74e3e34df8c9ddd93645b)) (thanks **Stefan Otte**!) 199 | - improved callback handling on test client ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/6077d7a22a9460b58464e6ccd8e0d185310da6cc)) 200 | - add explicit eventlet.wsgi import [#309](https://github.com/miguelgrinberg/flask-socketio/issues/309) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d604a7e854c31b20db0e01ed3c8baebd90642dd9)) 201 | - fix document typos: messaque -> message [#304](https://github.com/miguelgrinberg/flask-socketio/issues/304) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4469cce5b77605effc76386a2811fd3524dbb262)) (thanks **朱✖️: (ง •_•)ง木犀**!) 202 | 203 | **Release 2.6.2** - 2016-08-09 204 | 205 | - ensure Flask's json is called with an app context [#297](https://github.com/miguelgrinberg/flask-socketio/issues/297) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7aa06118e68f65f0b9e37030f008f93a76be6bb4)) 206 | 207 | **Release 2.6.1** - 2016-08-02 208 | 209 | - Initialize received queue in test client [#295](https://github.com/miguelgrinberg/flask-socketio/issues/295) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/35399c57d0bc309220d20bcbf443c5483171ca1f)) 210 | - improved documentation on custom json encoder/decoder [#274](https://github.com/miguelgrinberg/flask-socketio/issues/274) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/6d4acee2dc324d43eedc063ea5807c38d0a3a0ea)) 211 | 212 | **Release 2.6** - 2016-07-24 213 | 214 | - flask 0.11 cli support [#289](https://github.com/miguelgrinberg/flask-socketio/issues/289) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/91bc084de8c818e14d7ebb0894750f5657a65bf9)) 215 | - documentation for the test client class ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b5cf4dfcedc2e5c7ec039f1a48f754a6375044d0)) 216 | - send should not require flask request ctx [#283](https://github.com/miguelgrinberg/flask-socketio/issues/283) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9ff7c392d43867ddce9b82a9d5e8ea2945708b43)) (thanks **yofreke**!) 217 | - add path as an argument to Socket.IO, as an alias to resource ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/aa34521f35cedce2c9ff5e57a6dec82c7a9b1c81)) 218 | 219 | **Release 2.5** - 2016-06-28 220 | 221 | - Improvements to example application ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9086588452078024d0ed2f9532de63bf16f5194f)) 222 | - expose async_mode, start_background_task and sleep from python-socketio ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/03dfe3344eaceb3db268d5a15332560d898f785f)) 223 | 224 | **release 2.4** - 2016-05-31 225 | 226 | - more robustness in dealing with bad connections ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/5a1c70762a5f4e949fe8e2ddc7e209257281ad54)) 227 | - do not rause KeyError for unknown clients ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/82ffcfbd8e43d2a575b403ef2fae3f1d2ae19afc)) 228 | 229 | **Release 2.3** - 2016-05-15 230 | 231 | - initialize client manager in test client ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f999a70711c1812b2bbd69f270db46b04e639818)) 232 | - Switch to `flask_*` from deprecated `flask.ext.*` [#258](https://github.com/miguelgrinberg/flask-socketio/issues/258) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/76105fe1cfaf6419d431a76198ab901d3bfb977b)) (thanks **Jeff Widman**!) 233 | - Fix typo in documentation Fix “ithreading” to “threading” ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/73932fdaf87defbadf3a9b3cf9a4905645a899de)) (thanks **whiteUnicorn**!) 234 | 235 | **Release 2.2** - 2016-03-06 236 | 237 | - Add notes regarding the need to monkey patch ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/21d6446000e6adc7e9c0488f8c4941ac7226e2aa)) 238 | - Added missing Python version classifiers to package ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/965b30a6dc83b570c05925ef9acee68ae57aadff)) 239 | 240 | **Release 2.1** - 2016-02-08 241 | 242 | - Added reference of `_SocketIOMiddleware` instance to the SocketIO class ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/8c9c853eac93daecc41be0b0ad47611c4ca4f509)) (thanks **Grant**!) 243 | - request context should not be needed when calling emit() with namespace [#213](https://github.com/miguelgrinberg/flask-socketio/issues/213) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/cfb7958e20466a6ed7fa44607585b5730a4c1e37)) (thanks **Tamas Nepusz**!) 244 | - fixed a missed word in deployment docs [#210](https://github.com/miguelgrinberg/flask-socketio/issues/210) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d528dc21a4e56106501af895e401a2542059a75c)) (thanks **George Lejnine**!) 245 | - Documentation improvements on handling multiple arguments with tuples ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/000623e396ba7854ba58794735d6c5339f3ffdb4)) 246 | - Fix typo in documentation [#207](https://github.com/miguelgrinberg/flask-socketio/issues/207) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d1a79a8e2207103969fa2550b3d0152f6e0b743d)) (thanks **Logan Chien**!) 247 | - More documentation improvement and fixes ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a2a3c6296e7b37765db8cdb4cd716fa702faee9d)) 248 | 249 | **Release 2.0** - 2016-01-10 250 | 251 | - Added documentation for uWSGI deployments ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/370628a1d9496701f0967e29dc5eb4f81871022f)) 252 | - support write_only flag for external processes ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7d3c8ffb3d6d1270fd19cff04bf0e3bf7eb97122)) 253 | - Additional work and documentation for message queues ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f9f54f922e1728897e42a037ac56facd12151f90)) 254 | - Added SSL enablement for eventlet ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/0618f75734e189f857fd644e500a1957092283dd)) (thanks **Chip Senkbeil**!) 255 | - fixed ssl configuration issue fixed [#188](https://github.com/miguelgrinberg/flask-socketio/issues/188) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9cd254c5fdc45a2aa8f03ec372eddb2f74e8a7bb)) (thanks **muatik**!) 256 | - message queue documentation ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/20f68f2298a6abb304f75b5f20b87af8180c5998)) 257 | - escaping of user provided input [#185](https://github.com/miguelgrinberg/flask-socketio/issues/185) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/68efeacd830ce07f8456df0478ad9a20e5399ab5)) 258 | - typo in __init__.py [#182](https://github.com/miguelgrinberg/flask-socketio/issues/182) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9831e77292ca44220a59efde56966fd909d839e5)) (thanks **Luke Yeager**!) 259 | - Updated dependencies ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4d50b84c01f7710e1b76e216c7eb85f70737f041)) 260 | - Integrate message queue backend ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/51b28409a58c1a57cd4f20be758e80d31e5451d9)) 261 | - Update index.rst Fix missprint ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a749af27d9cb0921e713431be75dffefed07d93f)) (thanks **Dmitry Zhuravlev-Nevsky**!) 262 | - A few small documentation updates ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/11073c23622b3976d95dec50af85e119abe45a4d)) 263 | - Fix spelling mistake [#178](https://github.com/miguelgrinberg/flask-socketio/issues/178) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/faed71c0ceaf9dccebf08a7e7764ce3351ce5b5b)) (thanks **Liam Stanley**!) 264 | 265 | **Release 1.2** - 2015-12-03 266 | 267 | - Install the Werkzeug debugger middleware in the correct place ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ed2eaeb6570bcafd0dec7eb8b5698dec3794fac0)) 268 | - Replace assertTrue with assertEqual where possible. ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/0018c072dfec51de821cb32b1179d7aa9eca5a60)) (thanks **jwg4**!) 269 | - added python 3.5 to the build ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/02085f027c1b9e5964786ef355edc96e68c181b5)) 270 | 271 | **Release 1.1** - 2015-11-19 272 | 273 | - recommend gunicorn 18 in documentation [#171](https://github.com/miguelgrinberg/flask-socketio/issues/171) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b8f1a02026fbcc38ae3c8044606a91c3ad108600)) 274 | - Add installation instructions [#74](https://github.com/miguelgrinberg/flask-socketio/issues/74) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/07ef6110a913db9a1950a85580356729d2cae3a2)) 275 | - Minor documentation fixes ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/588406b6765c9869577c41727906186871ea5624)) 276 | - Add a close[#162](https://github.com/miguelgrinberg/flask-socketio/issues/162) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/cbfacad11f448e52fe6bfcf7472066da6f632297)) 277 | - exit with error if gevent-socketio is installed ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f6610d3967817fef943960c9f6816b271387c344)) 278 | 279 | **Release 1.0** - 2015-10-29 280 | 281 | - Merge branch 'v1.0' ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d6baa817d06d805044f0e2ea2ab10a73c15fbfa9)) 282 | - daemonize background thread ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/89873a859aff3559bba2e6794c3f37beb42260b7)) 283 | - warn about performance when eventlet/gevent are not installed ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/6a3bb765d42120d03baa472b57da27195c7cf453)) 284 | - Reset async_mode to default ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b935954b671665ea2b2354ce739cdaaad2050342)) 285 | - Fix socket.on decorator when using delayed app initialization ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/658945c5fd58913ee408945bea13a507d66901a7)) 286 | 287 | **Release 1.0b4** - 2015-10-18 288 | 289 | - Do not fork the session unless it is modified ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/26172f41d889cd82860cfd76e39fc3b9e6d8b8db)) 290 | - Pass kwargs options to Werkzeug server ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/be900a87a70000974bebece3ada72e4a01cf83b8)) 291 | - Added section on upgrading from the 0.x releases ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d92545088e47ee71885793fb25c6d50f348fba18)) 292 | - Avoid argument collisions in run() method ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/c2e21c65dff87f00c698e9f876f83849a6933672)) 293 | - Updated requirements ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/794fcb0ff224ec4ed24b8daa27ec082d167aea32)) 294 | - Removed old code that isn't needed anymore ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/1e2dc14cd04433f58542215641cbd2af33eb2c3d)) 295 | - Fix custom resource path. [#157](https://github.com/miguelgrinberg/flask-socketio/issues/157) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/dd0784897d7ab5d599ea164a3cd93bb44d0e6aca)) (thanks **Bekt**!) 296 | 297 | **Release 1.0b3** - 2015-10-16 298 | 299 | - automatically pick the best async_mode ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/81110ccbd40465acee0431a1ac711eccd05b1b29)) 300 | - Addressed additional problems with multi-application support ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/37316d7d1bf1453856956413505eeb72ef9c7296)) 301 | 302 | **Release 1.0b2** - 2015-10-15 303 | 304 | - Allow more than one application per socketio instance [#146](https://github.com/miguelgrinberg/flask-socketio/issues/146) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/5f0dfbebfd3a8a8b9d94de3e5809efdaedf2f026)) 305 | - Moved server creation outside of socketio.run() ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/1f61ea0d175f8dfff71d4675e77698d4d5493e4f)) 306 | - added missing decorator return values [#149](https://github.com/miguelgrinberg/flask-socketio/issues/149) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/376a1327f528fad87647d3df7c9a44cb79f5dda2)) 307 | - documentation improvements ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/8284a2d6d84501f421eb01438e4bfab9df773a64)) 308 | - Support all async modes when app.debug is set ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d79e27b640587e7f4a1cdcc0a9899f76ac22277c)) 309 | 310 | **Release v1.0b1** - 2015-09-20 311 | 312 | - Replaced gevent, gevent-socketio and gevent-websocket with eventlet, python-socketio and python-engineio, gaining support for Python 3 and the latest versions of the Socket.IO Javascript client. 313 | - Add include_self option to emit and send ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ee77a52f206e7903c618695a17fa9ed235a9e1f0)) 314 | - Pass along extra_files param to run_with_reloader [#121](https://github.com/miguelgrinberg/flask-socketio/issues/121) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/096350a374a25cd1045328b6fc013238720a330b)) (thanks **bjamil**!) 315 | - tests for ack ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4ffd77145c033b5c142e34989edb63acd5e3823d)) (thanks **Patrick Jahns**!) 316 | - return value from handler ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/db6b8ab50402ba4a404a3a49d0cb78ddd7758705)) (thanks **Patrick Jahns**!) 317 | - Document how to use custom JSON encoder/decoder ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7cf23c3b69e602d75af453e28335dad766c0bf83)) 318 | - Remove executable bit from regular files ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7d8f8fbc11ec31ded91608a2895da57a57dad62c)) 319 | - Remove Python 2.6 from supported releases. ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ac5b05a05007eb93594b9c62746548a0c1c56ce4)) 320 | 321 | **Release 0.6.0** - 2015-03-15 322 | 323 | - Add event information in flask request variable [#101](https://github.com/miguelgrinberg/flask-socketio/issues/101) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a78432af5549f9b2e48c5f6f9fba31fce46debdc)) (thanks **Romain Paulus**!) 324 | - Change README to reflect deprecated .ext import format [#98](https://github.com/miguelgrinberg/flask-socketio/issues/98) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/ba04c22c6f82b031264ba1c02b5a06825d2b5f56)) (thanks **Keyan Pishdadian**!) 325 | - remove tag it is html bug. So I removed. ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a7c702815f9eaf07ece1df43cb20af737c8cbefd)) (thanks **shinriyo**!) 326 | 327 | **Release 0.5.0** - 2015-01-05 328 | 329 | - close_room[#84](https://github.com/miguelgrinberg/flask-socketio/issues/84) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/558297da694f624cf5a8987a72eb2e3770dc0bca)) 330 | - added API section to docs ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/d8c7316759807ab3cd984fd059b71fdb49789d0f)) 331 | - add use_reloader option to socketio.run[#59](https://github.com/miguelgrinberg/flask-socketio/issues/59) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/760b61c533dc24bb2ae41e64d37690e5ec4fadeb)) 332 | 333 | **Release 0.4.3** - 2014-12-16 334 | 335 | - allow clients to specify a custom socket.io resource name ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/3f6744f0692563494deea664eb6868fdd52a9b91)) 336 | - documentation improvements ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/6996e66bf9de9b1b5e9707c7637d31184dd2b4c2)) 337 | - Fix typo on front doc page [#77](https://github.com/miguelgrinberg/flask-socketio/issues/77) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b017356ee2d4c59214e7b5cace42d263c520a79a)) (thanks **Andrejs Cainikovs**!) 338 | 339 | **Release 0.4.2** - 2014-11-30 340 | 341 | - use gevent monkey-patching when the reloader is enabled ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/2e7e050f13d1be23bd7c60c3192ae9107b67552c)) 342 | 343 | **Release 0.4.1** - 2014-10-23 344 | 345 | - [#55](https://github.com/miguelgrinberg/flask-socketio/issues/55), no need to monkey patch ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/22d42481630fd2edfce46b445a42e42cbef2e0f4)) 346 | 347 | **Release 0.4** - 2014-09-23 348 | 349 | - Add error handlers (on_error and on_default_error). ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/27c439574b779deca52b3af51be139d37e806d9a)) (thanks **Alan Du**!) 350 | - Update index.rst fixed broken link to Flask-KVSession documentation. ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4bd1f2a2d5aa03be00827692d99b969903a5d3df)) 351 | 352 | **Release 0.3.8** - 2014-06-15 353 | 354 | - [#37](https://github.com/miguelgrinberg/flask-socketio/issues/37): broadcast without namespace ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9844dfe7a614257215e8c20919dcf2eb7afe3584)) 355 | - documented use of server-side sessions ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/2a08c09fc170945c2be9a4a790790a92c2967372)) 356 | - some more doc improvements ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/5fbaf8a78e0fc211bf0aeb001eb5ade204d37878)) 357 | - added client-side example code snippet ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/1bf7ed5c231b642844a184777a965b7b3269b659)) 358 | - documented the currently unsupported Socket.IO 1.x client library ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/db9342b195cc56e0b035982182413aead83da399)) 359 | - [#22](https://github.com/miguelgrinberg/flask-socketio/issues/22): document use of nginx as a reverse proxy ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9ed9b070cccd98be88ce3b3de0f684e4601924db)) 360 | - [#28](https://github.com/miguelgrinberg/flask-socketio/issues/28): example app did not start background thread when running under a production server ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/6c995a56b400f165fde9be63b29310fde17974fc)) 361 | 362 | **Release 0.3.7** - 2014-05-21 363 | 364 | - [#31](https://github.com/miguelgrinberg/flask-socketio/issues/31): show host and port on startup ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/43bc6782ec73cce90262ede5bec579f8d0b967c0)) 365 | 366 | **Release 0.3.6** - 2014-05-13 367 | 368 | - [#26](https://github.com/miguelgrinberg/flask-socketio/issues/26): threading error during exit ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/aa6f65fafd55568d12de75362e8726cde52a7ba2)) 369 | - added gevent dependency ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/52bcba3e39e00475e002e28c7733e43071d7f344)) 370 | 371 | **Release 0.3.5** - 2014-05-07 372 | 373 | - [#23](https://github.com/miguelgrinberg/flask-socketio/issues/23): incorrect use of run_with_reloader ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a0c3072f5948d1dac60653c0d8fce5c37a33843f)) 374 | 375 | **Release 0.3.4** - 2014-04-27 376 | 377 | - show a more friendly error when a server that is not compatible with gevent-socketio is used ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a58879f3d4db7e4e1e9b9a0cede2371b3cb1979f)) 378 | - added short deployment section to docs ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/93008853131e86a80737fb1a3defa38a763f0f35)) 379 | - correct syntax for js imports ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/f3491f6ea9d327ada68de7b0f7fe1c76f2767642)) 380 | - [#18](https://github.com/miguelgrinberg/flask-socketio/issues/18): server initiated communication does not work on the global namespace ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/21695aba5930e17b09cfb23bea1a273737b647fb)) 381 | 382 | **Release 0.3.3** - 2014-04-22 383 | 384 | - [#19](https://github.com/miguelgrinberg/flask-socketio/issues/19): use Flask's JSON serializers in gevent-socketio ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/44c1453170345c7a032bede3b1ccc0ffba18b92d)) 385 | - Correct URL to socket.io.min.js Fails in IE otherwise ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7e1542852a7e9c307ef0600d51bcf09e2ce642ba)) (thanks **Richard Morrison**!) 386 | 387 | **Release 0.3.2** - 2014-03-31 388 | 389 | - [#14](https://github.com/miguelgrinberg/flask-socketio/issues/14): access to server object when using gunicorn ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/1fb766c89f09dd814f4a62c693bf0c3b8318aecc)) 390 | 391 | **Release 0.3.1** - 2014-03-24 392 | 393 | - Cleanup of kwargs passthrough ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/8c8232853e9451a6f51825b2c3240605941038e8)) (thanks **ijustdrankwhat**!) 394 | - pop[] => pop('resource', None) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9b82e0716141be53badc8f208a647470c975055a)) (thanks **ijustdrankwhat**!) 395 | - Allow SocketIOServer keywords to get passed through Allow keyword passthrough at run() ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/378d75c5166a12920d1dd225ba8f75c88fc6d542)) (thanks **Shep.Walker**!) 396 | - added server pushed events to example app ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/3c06500d933d16120c9f38027abe6959fe83f08c)) 397 | - monkey patch early ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/4f9d0fade14272f4f896e2fdd0f513393b4c5361)) 398 | - travis ci builds ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/c5f60e012926a76a1cc9f7507f07f1f088b9d362)) 399 | - travis ci builds ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b0d14b0b20dde79a705ff5ebafb4f40dfe368826)) 400 | 401 | **Release 0.3** - 2014-03-08 402 | 403 | - Support for rooms ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/764932e5cae52883b7d2d8884e380e8b25f3d68f)) 404 | - update example requirements ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/a817068511d0ed8fd50141d31b83a7256ef56094)) 405 | - more tests, 83% coverage ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/386c9a9b1881a6a239165f0f8394202f9c1fa4f3)) 406 | - more tests ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/fa7816298ce5fb87a41f91227443e5eba5d910db)) 407 | 408 | **Release 0.2.2** - 2014-02-19 409 | 410 | - forgot self ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/8e76e6de7983ec9c990e2aea6d263a569915156a)) (thanks **Mark McGuire**!) 411 | - Return handler response (for client requested ack) ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/78682ef75788b97badf8f4c4c0c89db65c89fed3)) (thanks **Mark McGuire**!) 412 | - Use counter for sessid ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/fb11792f87ebf78576f6be859a9a019dfadeb211)) (thanks **Mark McGuire**!) 413 | - Add returns for methods ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/7663d9f108edf2580f81342eaaf9fc9a66312ea9)) (thanks **Mark McGuire**!) 414 | - Actually import random ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/239c2b713c791b41509ee95171b2c648b026f499)) (thanks **Mark McGuire**!) 415 | - Add sessid to socket ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/898447f7d07afa751bb7e7b8374a18c6751df140)) (thanks **Mark McGuire**!) 416 | - unit testing framework ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9633aba98d4b54325d1e33f0e3329e168b3b2445)) 417 | - [#2](https://github.com/miguelgrinberg/flask-socketio/issues/2): removed old code not intended for release ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b21e23ab3595cb4203aee6325f43a80e1adccb3b)) 418 | - [#6](https://github.com/miguelgrinberg/flask-socketio/issues/6): save session variables correctly ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/173876eb31f03e6b99419e9b6cc7f3a2b1463074)) 419 | 420 | **Release 0.2** - 2014-02-13 421 | 422 | - Updated documentation ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/2c3d883c3027c2c3663d32b60129e10886fba60e)) (thanks **Axel Haustant**!) 423 | - Run the sample app in debug mode ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/b71bfa7ad02d20dad1a3557455135f5b7403c931)) (thanks **Axel Haustant**!) 424 | - Added werkzeug debugger support ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/9b1716aa35c81d374d902d49f36d7ed341cb8bd1)) (thanks **Axel Haustant**!) 425 | - first release ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/be1d74829f3a8f2c29898181bdf81d78748c8e3b)) 426 | - Initial commit ([commit](https://github.com/miguelgrinberg/flask-socketio/commit/63db33d193e651b5df8a0ae305098dbb33832a58)) 427 | --------------------------------------------------------------------------------