22 changed files with 1013 additions and 2506 deletions
@ -1,192 +1,19 @@ |
|||||
# Makefile for Sphinx documentation
|
# Minimal makefile for Sphinx documentation
|
||||
#
|
#
|
||||
|
|
||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||
SPHINXOPTS = |
SPHINXOPTS = |
||||
SPHINXBUILD = sphinx-build |
SPHINXBUILD = sphinx-build |
||||
PAPER = |
SOURCEDIR = . |
||||
BUILDDIR = _build |
BUILDDIR = _build |
||||
|
|
||||
# User-friendly check for sphinx-build
|
# Put it first so that "make" without argument is like "make help".
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) |
|
||||
$(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/) |
|
||||
endif |
|
||||
|
|
||||
# Internal variables.
|
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4 |
|
||||
PAPEROPT_letter = -D latex_paper_size=letter |
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . |
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . |
|
||||
|
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext |
|
||||
|
|
||||
help: |
help: |
||||
@echo "Please use \`make <target>' where <target> is one of" |
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) |
||||
@echo " html to make standalone HTML files" |
|
||||
@echo " dirhtml to make HTML files named index.html in directories" |
|
||||
@echo " singlehtml to make a single large HTML file" |
|
||||
@echo " pickle to make pickle files" |
|
||||
@echo " json to make JSON files" |
|
||||
@echo " htmlhelp to make HTML files and a HTML help project" |
|
||||
@echo " qthelp to make HTML files and a qthelp project" |
|
||||
@echo " applehelp to make an Apple Help Book" |
|
||||
@echo " devhelp to make HTML files and a Devhelp project" |
|
||||
@echo " epub to make an epub" |
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" |
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex" |
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" |
|
||||
@echo " text to make text files" |
|
||||
@echo " man to make manual pages" |
|
||||
@echo " texinfo to make Texinfo files" |
|
||||
@echo " info to make Texinfo files and run them through makeinfo" |
|
||||
@echo " gettext to make PO message catalogs" |
|
||||
@echo " changes to make an overview of all changed/added/deprecated items" |
|
||||
@echo " xml to make Docutils-native XML files" |
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes" |
|
||||
@echo " linkcheck to check all external links for integrity" |
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)" |
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)" |
|
||||
|
|
||||
clean: |
|
||||
rm -rf $(BUILDDIR)/* |
|
||||
|
|
||||
html: |
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html |
|
||||
@echo |
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." |
|
||||
|
|
||||
dirhtml: |
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml |
|
||||
@echo |
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." |
|
||||
|
|
||||
singlehtml: |
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml |
|
||||
@echo |
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." |
|
||||
|
|
||||
pickle: |
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle |
|
||||
@echo |
|
||||
@echo "Build finished; now you can process the pickle files." |
|
||||
|
|
||||
json: |
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json |
|
||||
@echo |
|
||||
@echo "Build finished; now you can process the JSON files." |
|
||||
|
|
||||
htmlhelp: |
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp |
|
||||
@echo |
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp." |
|
||||
|
|
||||
qthelp: |
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp |
|
||||
@echo |
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:" |
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/socketio.qhcp" |
|
||||
@echo "To view the help file:" |
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/socketio.qhc" |
|
||||
|
|
||||
applehelp: |
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp |
|
||||
@echo |
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp." |
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
|
||||
"bundle." |
|
||||
|
|
||||
devhelp: |
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp |
|
||||
@echo |
|
||||
@echo "Build finished." |
|
||||
@echo "To view the help file:" |
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/socketio" |
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/socketio" |
|
||||
@echo "# devhelp" |
|
||||
|
|
||||
epub: |
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub |
|
||||
@echo |
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub." |
|
||||
|
|
||||
latex: |
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex |
|
||||
@echo |
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." |
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||
"(use \`make latexpdf' here to do that automatically)." |
|
||||
|
|
||||
latexpdf: |
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex |
|
||||
@echo "Running LaTeX files through pdflatex..." |
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf |
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." |
|
||||
|
|
||||
latexpdfja: |
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex |
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..." |
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja |
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." |
|
||||
|
|
||||
text: |
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text |
|
||||
@echo |
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text." |
|
||||
|
|
||||
man: |
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man |
|
||||
@echo |
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man." |
|
||||
|
|
||||
texinfo: |
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo |
|
||||
@echo |
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." |
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
|
||||
"(use \`make info' here to do that automatically)." |
|
||||
|
|
||||
info: |
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo |
|
||||
@echo "Running Texinfo files through makeinfo..." |
|
||||
make -C $(BUILDDIR)/texinfo info |
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." |
|
||||
|
|
||||
gettext: |
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale |
|
||||
@echo |
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." |
|
||||
|
|
||||
changes: |
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes |
|
||||
@echo |
|
||||
@echo "The overview file is in $(BUILDDIR)/changes." |
|
||||
|
|
||||
linkcheck: |
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck |
|
||||
@echo |
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt." |
|
||||
|
|
||||
doctest: |
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest |
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||
"results in $(BUILDDIR)/doctest/output.txt." |
|
||||
|
|
||||
coverage: |
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage |
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
|
||||
"results in $(BUILDDIR)/coverage/python.txt." |
|
||||
|
|
||||
xml: |
.PHONY: help Makefile |
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml |
|
||||
@echo |
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml." |
|
||||
|
|
||||
pseudoxml: |
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml |
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
@echo |
%: Makefile |
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." |
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) |
@ -0,0 +1 @@ |
|||||
|
Place static files used by the documentation here. |
@ -1,12 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html> |
|
||||
<head> |
|
||||
<title>python-socketio documentation</title> |
|
||||
<meta http-equiv="refresh" content="0; URL=http://python-socketio.readthedocs.org/"> |
|
||||
<meta name="keywords" content="automatic redirection"> |
|
||||
</head> |
|
||||
<body> |
|
||||
The python-socketio documentation is available at <a href="http://python-socketio.readthedocs.org">Read the Docs</a>. |
|
||||
If your browser does not automatically redirect you, please <a href="http://python-socketio.readthedocs.org">click here</a>. |
|
||||
</body> |
|
||||
</html> |
|
Before Width: | Height: | Size: 54 KiB |
@ -1,37 +0,0 @@ |
|||||
Copyright (c) 2010 by Armin Ronacher. |
|
||||
|
|
||||
Some rights reserved. |
|
||||
|
|
||||
Redistribution and use in source and binary forms of the theme, with or |
|
||||
without modification, are permitted provided that the following conditions |
|
||||
are met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above copyright |
|
||||
notice, this list of conditions and the following disclaimer. |
|
||||
|
|
||||
* Redistributions in binary form must reproduce the above |
|
||||
copyright notice, this list of conditions and the following |
|
||||
disclaimer in the documentation and/or other materials provided |
|
||||
with the distribution. |
|
||||
|
|
||||
* The names of the contributors may not be used to endorse or |
|
||||
promote products derived from this software without specific |
|
||||
prior written permission. |
|
||||
|
|
||||
We kindly ask you to only use these themes in an unmodified manner just |
|
||||
for Flask and Flask-related products, not for unrelated projects. If you |
|
||||
like the visual style and want to use it for your own projects, please |
|
||||
consider making some larger changes to the themes (such as changing |
|
||||
font faces, sizes, colors or margins). |
|
||||
|
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE |
|
||||
POSSIBILITY OF SUCH DAMAGE. |
|
@ -1,31 +0,0 @@ |
|||||
Flask Sphinx Styles |
|
||||
=================== |
|
||||
|
|
||||
This repository contains sphinx styles for Flask and Flask related |
|
||||
projects. To use this style in your Sphinx documentation, follow |
|
||||
this guide: |
|
||||
|
|
||||
1. put this folder as _themes into your docs folder. Alternatively |
|
||||
you can also use git submodules to check out the contents there. |
|
||||
2. add this to your conf.py: |
|
||||
|
|
||||
sys.path.append(os.path.abspath('_themes')) |
|
||||
html_theme_path = ['_themes'] |
|
||||
html_theme = 'flask' |
|
||||
|
|
||||
The following themes exist: |
|
||||
|
|
||||
- 'flask' - the standard flask documentation theme for large |
|
||||
projects |
|
||||
- 'flask_small' - small one-page theme. Intended to be used by |
|
||||
very small addon libraries for flask. |
|
||||
|
|
||||
The following options exist for the flask_small theme: |
|
||||
|
|
||||
[options] |
|
||||
index_logo = '' filename of a picture in _static |
|
||||
to be used as replacement for the |
|
||||
h1 in the index.rst file. |
|
||||
index_logo_height = 120px height of the index logo |
|
||||
github_fork = '' repository name on github for the |
|
||||
"fork me" badge |
|
@ -1,24 +0,0 @@ |
|||||
{%- extends "basic/layout.html" %} |
|
||||
{%- block extrahead %} |
|
||||
{{ super() }} |
|
||||
{% if theme_touch_icon %} |
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" /> |
|
||||
{% endif %} |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9"> |
|
||||
{% endblock %} |
|
||||
{%- block relbar2 %}{% endblock %} |
|
||||
{% block header %} |
|
||||
{{ super() }} |
|
||||
{% if pagename == 'index' %} |
|
||||
<div class=indexwrapper> |
|
||||
{% endif %} |
|
||||
{% endblock %} |
|
||||
{%- block footer %} |
|
||||
<div class="footer"> |
|
||||
© Copyright {{ copyright }}. |
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>. |
|
||||
</div> |
|
||||
{% if pagename == 'index' %} |
|
||||
</div> |
|
||||
{% endif %} |
|
||||
{%- endblock %} |
|
@ -1,19 +0,0 @@ |
|||||
<h3>Related Topics</h3> |
|
||||
<ul> |
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul> |
|
||||
{%- for parent in parents %} |
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul> |
|
||||
{%- endfor %} |
|
||||
{%- if prev %} |
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter') |
|
||||
}}">{{ prev.title }}</a></li> |
|
||||
{%- endif %} |
|
||||
{%- if next %} |
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter') |
|
||||
}}">{{ next.title }}</a></li> |
|
||||
{%- endif %} |
|
||||
{%- for parent in parents %} |
|
||||
</ul></li> |
|
||||
{%- endfor %} |
|
||||
</ul></li> |
|
||||
</ul> |
|
@ -1,577 +0,0 @@ |
|||||
/* |
|
||||
* flasky.css_t |
|
||||
* ~~~~~~~~~~~~ |
|
||||
* |
|
||||
* :copyright: Copyright 2010 by Armin Ronacher. |
|
||||
* :license: Flask Design License, see LICENSE for details. |
|
||||
*/ |
|
||||
|
|
||||
{% set page_width = '940px' %} |
|
||||
{% set sidebar_width = '220px' %} |
|
||||
|
|
||||
@import url("basic.css"); |
|
||||
|
|
||||
/* -- page layout ----------------------------------------------------------- */ |
|
||||
|
|
||||
body { |
|
||||
font-family: 'Georgia', serif; |
|
||||
font-size: 17px; |
|
||||
background-color: white; |
|
||||
color: #000; |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
div.document { |
|
||||
width: {{ page_width }}; |
|
||||
margin: 30px auto 0 auto; |
|
||||
} |
|
||||
|
|
||||
div.documentwrapper { |
|
||||
float: left; |
|
||||
width: 100%; |
|
||||
} |
|
||||
|
|
||||
div.bodywrapper { |
|
||||
margin: 0 0 0 {{ sidebar_width }}; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar { |
|
||||
width: {{ sidebar_width }}; |
|
||||
} |
|
||||
|
|
||||
hr { |
|
||||
border: 1px solid #B1B4B6; |
|
||||
} |
|
||||
|
|
||||
div.body { |
|
||||
background-color: #ffffff; |
|
||||
color: #3E4349; |
|
||||
padding: 0 30px 0 30px; |
|
||||
} |
|
||||
|
|
||||
img.floatingflask { |
|
||||
padding: 0 0 10px 10px; |
|
||||
float: right; |
|
||||
} |
|
||||
|
|
||||
div.footer { |
|
||||
width: {{ page_width }}; |
|
||||
margin: 20px auto 30px auto; |
|
||||
font-size: 14px; |
|
||||
color: #888; |
|
||||
text-align: right; |
|
||||
} |
|
||||
|
|
||||
div.footer a { |
|
||||
color: #888; |
|
||||
} |
|
||||
|
|
||||
div.related { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar a { |
|
||||
color: #444; |
|
||||
text-decoration: none; |
|
||||
border-bottom: 1px dotted #999; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar a:hover { |
|
||||
border-bottom: 1px solid #999; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar { |
|
||||
font-size: 14px; |
|
||||
line-height: 1.5; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebarwrapper { |
|
||||
padding: 18px 10px; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebarwrapper p.logo { |
|
||||
padding: 0 0 20px 0; |
|
||||
margin: 0; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar h3, |
|
||||
div.sphinxsidebar h4 { |
|
||||
font-family: 'Garamond', 'Georgia', serif; |
|
||||
color: #444; |
|
||||
font-size: 24px; |
|
||||
font-weight: normal; |
|
||||
margin: 0 0 5px 0; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar h4 { |
|
||||
font-size: 20px; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar h3 a { |
|
||||
color: #444; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar p.logo a, |
|
||||
div.sphinxsidebar h3 a, |
|
||||
div.sphinxsidebar p.logo a:hover, |
|
||||
div.sphinxsidebar h3 a:hover { |
|
||||
border: none; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar p { |
|
||||
color: #555; |
|
||||
margin: 10px 0; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar ul { |
|
||||
margin: 10px 0; |
|
||||
padding: 0; |
|
||||
color: #000; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar input { |
|
||||
border: 1px solid #ccc; |
|
||||
font-family: 'Georgia', serif; |
|
||||
font-size: 1em; |
|
||||
} |
|
||||
|
|
||||
/* -- body styles ----------------------------------------------------------- */ |
|
||||
|
|
||||
a { |
|
||||
color: #004B6B; |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
|
|
||||
a:hover { |
|
||||
color: #6D4100; |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
|
|
||||
div.body h1, |
|
||||
div.body h2, |
|
||||
div.body h3, |
|
||||
div.body h4, |
|
||||
div.body h5, |
|
||||
div.body h6 { |
|
||||
font-family: 'Garamond', 'Georgia', serif; |
|
||||
font-weight: normal; |
|
||||
margin: 30px 0px 10px 0px; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
{% if theme_index_logo %} |
|
||||
div.indexwrapper h1 { |
|
||||
text-indent: -999999px; |
|
||||
background: url({{ theme_index_logo }}) no-repeat center center; |
|
||||
height: {{ theme_index_logo_height }}; |
|
||||
} |
|
||||
{% endif %} |
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } |
|
||||
div.body h2 { font-size: 180%; } |
|
||||
div.body h3 { font-size: 150%; } |
|
||||
div.body h4 { font-size: 130%; } |
|
||||
div.body h5 { font-size: 100%; } |
|
||||
div.body h6 { font-size: 100%; } |
|
||||
|
|
||||
a.headerlink { |
|
||||
color: #ddd; |
|
||||
padding: 0 4px; |
|
||||
text-decoration: none; |
|
||||
} |
|
||||
|
|
||||
a.headerlink:hover { |
|
||||
color: #444; |
|
||||
background: #eaeaea; |
|
||||
} |
|
||||
|
|
||||
div.body p, div.body dd, div.body li { |
|
||||
line-height: 1.4em; |
|
||||
} |
|
||||
|
|
||||
div.admonition { |
|
||||
background: #fafafa; |
|
||||
margin: 20px -30px; |
|
||||
padding: 10px 30px; |
|
||||
border-top: 1px solid #ccc; |
|
||||
border-bottom: 1px solid #ccc; |
|
||||
} |
|
||||
|
|
||||
div.admonition tt.xref, div.admonition a tt { |
|
||||
border-bottom: 1px solid #fafafa; |
|
||||
} |
|
||||
|
|
||||
dd div.admonition { |
|
||||
margin-left: -60px; |
|
||||
padding-left: 60px; |
|
||||
} |
|
||||
|
|
||||
div.admonition p.admonition-title { |
|
||||
font-family: 'Garamond', 'Georgia', serif; |
|
||||
font-weight: normal; |
|
||||
font-size: 24px; |
|
||||
margin: 0 0 10px 0; |
|
||||
padding: 0; |
|
||||
line-height: 1; |
|
||||
} |
|
||||
|
|
||||
div.admonition p.last { |
|
||||
margin-bottom: 0; |
|
||||
} |
|
||||
|
|
||||
div.highlight { |
|
||||
background-color: white; |
|
||||
} |
|
||||
|
|
||||
dt:target, .highlight { |
|
||||
background: #FAF3E8; |
|
||||
} |
|
||||
|
|
||||
div.note { |
|
||||
background-color: #eee; |
|
||||
border: 1px solid #ccc; |
|
||||
} |
|
||||
|
|
||||
div.seealso { |
|
||||
background-color: #ffc; |
|
||||
border: 1px solid #ff6; |
|
||||
} |
|
||||
|
|
||||
div.topic { |
|
||||
background-color: #eee; |
|
||||
} |
|
||||
|
|
||||
p.admonition-title { |
|
||||
display: inline; |
|
||||
} |
|
||||
|
|
||||
p.admonition-title:after { |
|
||||
content: ":"; |
|
||||
} |
|
||||
|
|
||||
pre, tt { |
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; |
|
||||
font-size: 0.9em; |
|
||||
} |
|
||||
|
|
||||
img.screenshot { |
|
||||
} |
|
||||
|
|
||||
tt.descname, tt.descclassname { |
|
||||
font-size: 0.95em; |
|
||||
} |
|
||||
|
|
||||
tt.descname { |
|
||||
padding-right: 0.08em; |
|
||||
} |
|
||||
|
|
||||
img.screenshot { |
|
||||
-moz-box-shadow: 2px 2px 4px #eee; |
|
||||
-webkit-box-shadow: 2px 2px 4px #eee; |
|
||||
box-shadow: 2px 2px 4px #eee; |
|
||||
} |
|
||||
|
|
||||
table.docutils { |
|
||||
border: 1px solid #888; |
|
||||
-moz-box-shadow: 2px 2px 4px #eee; |
|
||||
-webkit-box-shadow: 2px 2px 4px #eee; |
|
||||
box-shadow: 2px 2px 4px #eee; |
|
||||
} |
|
||||
|
|
||||
table.docutils td, table.docutils th { |
|
||||
border: 1px solid #888; |
|
||||
padding: 0.25em 0.7em; |
|
||||
} |
|
||||
|
|
||||
table.field-list, table.footnote { |
|
||||
border: none; |
|
||||
-moz-box-shadow: none; |
|
||||
-webkit-box-shadow: none; |
|
||||
box-shadow: none; |
|
||||
} |
|
||||
|
|
||||
table.footnote { |
|
||||
margin: 15px 0; |
|
||||
width: 100%; |
|
||||
border: 1px solid #eee; |
|
||||
background: #fdfdfd; |
|
||||
font-size: 0.9em; |
|
||||
} |
|
||||
|
|
||||
table.footnote + table.footnote { |
|
||||
margin-top: -15px; |
|
||||
border-top: none; |
|
||||
} |
|
||||
|
|
||||
table.field-list th { |
|
||||
padding: 0 0.8em 0 0; |
|
||||
} |
|
||||
|
|
||||
table.field-list td { |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
table.footnote td.label { |
|
||||
width: 0px; |
|
||||
padding: 0.3em 0 0.3em 0.5em; |
|
||||
} |
|
||||
|
|
||||
table.footnote td { |
|
||||
padding: 0.3em 0.5em; |
|
||||
} |
|
||||
|
|
||||
dl { |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
dl dd { |
|
||||
margin-left: 30px; |
|
||||
} |
|
||||
|
|
||||
blockquote { |
|
||||
margin: 0 0 0 30px; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
ul, ol { |
|
||||
margin: 10px 0 10px 30px; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
pre { |
|
||||
background: #eee; |
|
||||
padding: 7px 30px; |
|
||||
margin: 15px -30px; |
|
||||
line-height: 1.3em; |
|
||||
} |
|
||||
|
|
||||
dl pre, blockquote pre, li pre { |
|
||||
margin-left: -60px; |
|
||||
padding-left: 60px; |
|
||||
} |
|
||||
|
|
||||
dl dl pre { |
|
||||
margin-left: -90px; |
|
||||
padding-left: 90px; |
|
||||
} |
|
||||
|
|
||||
tt { |
|
||||
background-color: #ecf0f3; |
|
||||
color: #222; |
|
||||
/* padding: 1px 2px; */ |
|
||||
} |
|
||||
|
|
||||
tt.xref, a tt { |
|
||||
background-color: #FBFBFB; |
|
||||
border-bottom: 1px solid white; |
|
||||
} |
|
||||
|
|
||||
a.reference { |
|
||||
text-decoration: none; |
|
||||
border-bottom: 1px dotted #004B6B; |
|
||||
} |
|
||||
|
|
||||
a.reference:hover { |
|
||||
border-bottom: 1px solid #6D4100; |
|
||||
} |
|
||||
|
|
||||
a.footnote-reference { |
|
||||
text-decoration: none; |
|
||||
font-size: 0.7em; |
|
||||
vertical-align: top; |
|
||||
border-bottom: 1px dotted #004B6B; |
|
||||
} |
|
||||
|
|
||||
a.footnote-reference:hover { |
|
||||
border-bottom: 1px solid #6D4100; |
|
||||
} |
|
||||
|
|
||||
a:hover tt { |
|
||||
background: #EEE; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
@media screen and (max-width: 870px) { |
|
||||
|
|
||||
div.sphinxsidebar { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
div.document { |
|
||||
width: 100%; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
div.documentwrapper { |
|
||||
margin-left: 0; |
|
||||
margin-top: 0; |
|
||||
margin-right: 0; |
|
||||
margin-bottom: 0; |
|
||||
} |
|
||||
|
|
||||
div.bodywrapper { |
|
||||
margin-top: 0; |
|
||||
margin-right: 0; |
|
||||
margin-bottom: 0; |
|
||||
margin-left: 0; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
margin-left: 0; |
|
||||
} |
|
||||
|
|
||||
.document { |
|
||||
width: auto; |
|
||||
} |
|
||||
|
|
||||
.footer { |
|
||||
width: auto; |
|
||||
} |
|
||||
|
|
||||
.bodywrapper { |
|
||||
margin: 0; |
|
||||
} |
|
||||
|
|
||||
.footer { |
|
||||
width: auto; |
|
||||
} |
|
||||
|
|
||||
.github { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
@media screen and (max-width: 875px) { |
|
||||
|
|
||||
body { |
|
||||
margin: 0; |
|
||||
padding: 20px 30px; |
|
||||
} |
|
||||
|
|
||||
div.documentwrapper { |
|
||||
float: none; |
|
||||
background: white; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar { |
|
||||
display: block; |
|
||||
float: none; |
|
||||
width: 102.5%; |
|
||||
margin: 50px -30px -20px -30px; |
|
||||
padding: 10px 20px; |
|
||||
background: #333; |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, |
|
||||
div.sphinxsidebar h3 a { |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar a { |
|
||||
color: #aaa; |
|
||||
} |
|
||||
|
|
||||
div.sphinxsidebar p.logo { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
div.document { |
|
||||
width: 100%; |
|
||||
margin: 0; |
|
||||
} |
|
||||
|
|
||||
div.related { |
|
||||
display: block; |
|
||||
margin: 0; |
|
||||
padding: 10px 0 20px 0; |
|
||||
} |
|
||||
|
|
||||
div.related ul, |
|
||||
div.related ul li { |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
div.footer { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
div.bodywrapper { |
|
||||
margin: 0; |
|
||||
} |
|
||||
|
|
||||
div.body { |
|
||||
min-height: 0; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
.rtd_doc_footer { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
.document { |
|
||||
width: auto; |
|
||||
} |
|
||||
|
|
||||
.footer { |
|
||||
width: auto; |
|
||||
} |
|
||||
|
|
||||
.footer { |
|
||||
width: auto; |
|
||||
} |
|
||||
|
|
||||
.github { |
|
||||
display: none; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/* scrollbars */ |
|
||||
|
|
||||
::-webkit-scrollbar { |
|
||||
width: 6px; |
|
||||
height: 6px; |
|
||||
} |
|
||||
|
|
||||
::-webkit-scrollbar-button:start:decrement, |
|
||||
::-webkit-scrollbar-button:end:increment { |
|
||||
display: block; |
|
||||
height: 10px; |
|
||||
} |
|
||||
|
|
||||
::-webkit-scrollbar-button:vertical:increment { |
|
||||
background-color: #fff; |
|
||||
} |
|
||||
|
|
||||
::-webkit-scrollbar-track-piece { |
|
||||
background-color: #eee; |
|
||||
-webkit-border-radius: 3px; |
|
||||
} |
|
||||
|
|
||||
::-webkit-scrollbar-thumb:vertical { |
|
||||
height: 50px; |
|
||||
background-color: #ccc; |
|
||||
-webkit-border-radius: 3px; |
|
||||
} |
|
||||
|
|
||||
::-webkit-scrollbar-thumb:horizontal { |
|
||||
width: 50px; |
|
||||
background-color: #ccc; |
|
||||
-webkit-border-radius: 3px; |
|
||||
} |
|
||||
|
|
||||
/* misc. */ |
|
||||
|
|
||||
.revsys-inline { |
|
||||
display: none!important; |
|
||||
} |
|
@ -1,9 +0,0 @@ |
|||||
[theme] |
|
||||
inherit = basic |
|
||||
stylesheet = flasky.css |
|
||||
pygments_style = flask_theme_support.FlaskyStyle |
|
||||
|
|
||||
[options] |
|
||||
index_logo = '' |
|
||||
index_logo_height = 120px |
|
||||
touch_icon = |
|
@ -1,22 +0,0 @@ |
|||||
{% extends "basic/layout.html" %} |
|
||||
{% block header %} |
|
||||
{{ super() }} |
|
||||
{% if pagename == 'index' %} |
|
||||
<div class=indexwrapper> |
|
||||
{% endif %} |
|
||||
{% endblock %} |
|
||||
{% block footer %} |
|
||||
{% if pagename == 'index' %} |
|
||||
</div> |
|
||||
{% endif %} |
|
||||
{% endblock %} |
|
||||
{# do not display relbars #} |
|
||||
{% block relbar1 %}{% endblock %} |
|
||||
{% block relbar2 %} |
|
||||
{% if theme_github_fork %} |
|
||||
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;" |
|
||||
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a> |
|
||||
{% endif %} |
|
||||
{% endblock %} |
|
||||
{% block sidebar1 %}{% endblock %} |
|
||||
{% block sidebar2 %}{% endblock %} |
|
@ -1,291 +0,0 @@ |
|||||
/* |
|
||||
* flasky.css_t |
|
||||
* ~~~~~~~~~~~~ |
|
||||
* |
|
||||
* Sphinx stylesheet -- flasky theme based on nature theme. |
|
||||
* |
|
||||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. |
|
||||
* :license: BSD, see LICENSE for details. |
|
||||
* |
|
||||
*/ |
|
||||
|
|
||||
@import url("basic.css"); |
|
||||
|
|
||||
/* -- page layout ----------------------------------------------------------- */ |
|
||||
|
|
||||
body { |
|
||||
font-family: 'Georgia', serif; |
|
||||
font-size: 17px; |
|
||||
color: #000; |
|
||||
background: white; |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
div.documentwrapper { |
|
||||
float: left; |
|
||||
width: 100%; |
|
||||
} |
|
||||
|
|
||||
div.bodywrapper { |
|
||||
margin: 40px auto 0 auto; |
|
||||
width: 700px; |
|
||||
} |
|
||||
|
|
||||
hr { |
|
||||
border: 1px solid #B1B4B6; |
|
||||
} |
|
||||
|
|
||||
div.body { |
|
||||
background-color: #ffffff; |
|
||||
color: #3E4349; |
|
||||
padding: 0 30px 30px 30px; |
|
||||
} |
|
||||
|
|
||||
img.floatingflask { |
|
||||
padding: 0 0 10px 10px; |
|
||||
float: right; |
|
||||
} |
|
||||
|
|
||||
div.footer { |
|
||||
text-align: right; |
|
||||
color: #888; |
|
||||
padding: 10px; |
|
||||
font-size: 14px; |
|
||||
width: 650px; |
|
||||
margin: 0 auto 40px auto; |
|
||||
} |
|
||||
|
|
||||
div.footer a { |
|
||||
color: #888; |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
|
|
||||
div.related { |
|
||||
line-height: 32px; |
|
||||
color: #888; |
|
||||
} |
|
||||
|
|
||||
div.related ul { |
|
||||
padding: 0 0 0 10px; |
|
||||
} |
|
||||
|
|
||||
div.related a { |
|
||||
color: #444; |
|
||||
} |
|
||||
|
|
||||
/* -- body styles ----------------------------------------------------------- */ |
|
||||
|
|
||||
a { |
|
||||
color: #004B6B; |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
|
|
||||
a:hover { |
|
||||
color: #6D4100; |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
|
|
||||
div.body { |
|
||||
padding-bottom: 40px; /* saved for footer */ |
|
||||
} |
|
||||
|
|
||||
div.body h1, |
|
||||
div.body h2, |
|
||||
div.body h3, |
|
||||
div.body h4, |
|
||||
div.body h5, |
|
||||
div.body h6 { |
|
||||
font-family: 'Garamond', 'Georgia', serif; |
|
||||
font-weight: normal; |
|
||||
margin: 30px 0px 10px 0px; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
{% if theme_index_logo %} |
|
||||
div.indexwrapper h1 { |
|
||||
text-indent: -999999px; |
|
||||
background: url({{ theme_index_logo }}) no-repeat center center; |
|
||||
height: {{ theme_index_logo_height }}; |
|
||||
} |
|
||||
{% endif %} |
|
||||
|
|
||||
div.body h2 { font-size: 180%; } |
|
||||
div.body h3 { font-size: 150%; } |
|
||||
div.body h4 { font-size: 130%; } |
|
||||
div.body h5 { font-size: 100%; } |
|
||||
div.body h6 { font-size: 100%; } |
|
||||
|
|
||||
a.headerlink { |
|
||||
color: white; |
|
||||
padding: 0 4px; |
|
||||
text-decoration: none; |
|
||||
} |
|
||||
|
|
||||
a.headerlink:hover { |
|
||||
color: #444; |
|
||||
background: #eaeaea; |
|
||||
} |
|
||||
|
|
||||
div.body p, div.body dd, div.body li { |
|
||||
line-height: 1.4em; |
|
||||
} |
|
||||
|
|
||||
div.admonition { |
|
||||
background: #fafafa; |
|
||||
margin: 20px -30px; |
|
||||
padding: 10px 30px; |
|
||||
border-top: 1px solid #ccc; |
|
||||
border-bottom: 1px solid #ccc; |
|
||||
} |
|
||||
|
|
||||
div.admonition p.admonition-title { |
|
||||
font-family: 'Garamond', 'Georgia', serif; |
|
||||
font-weight: normal; |
|
||||
font-size: 24px; |
|
||||
margin: 0 0 10px 0; |
|
||||
padding: 0; |
|
||||
line-height: 1; |
|
||||
} |
|
||||
|
|
||||
div.admonition p.last { |
|
||||
margin-bottom: 0; |
|
||||
} |
|
||||
|
|
||||
div.highlight{ |
|
||||
background-color: white; |
|
||||
} |
|
||||
|
|
||||
dt:target, .highlight { |
|
||||
background: #FAF3E8; |
|
||||
} |
|
||||
|
|
||||
div.note { |
|
||||
background-color: #eee; |
|
||||
border: 1px solid #ccc; |
|
||||
} |
|
||||
|
|
||||
div.seealso { |
|
||||
background-color: #ffc; |
|
||||
border: 1px solid #ff6; |
|
||||
} |
|
||||
|
|
||||
div.topic { |
|
||||
background-color: #eee; |
|
||||
} |
|
||||
|
|
||||
div.warning { |
|
||||
background-color: #ffe4e4; |
|
||||
border: 1px solid #f66; |
|
||||
} |
|
||||
|
|
||||
p.admonition-title { |
|
||||
display: inline; |
|
||||
} |
|
||||
|
|
||||
p.admonition-title:after { |
|
||||
content: ":"; |
|
||||
} |
|
||||
|
|
||||
pre, tt { |
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; |
|
||||
font-size: 0.85em; |
|
||||
} |
|
||||
|
|
||||
img.screenshot { |
|
||||
} |
|
||||
|
|
||||
tt.descname, tt.descclassname { |
|
||||
font-size: 0.95em; |
|
||||
} |
|
||||
|
|
||||
tt.descname { |
|
||||
padding-right: 0.08em; |
|
||||
} |
|
||||
|
|
||||
img.screenshot { |
|
||||
-moz-box-shadow: 2px 2px 4px #eee; |
|
||||
-webkit-box-shadow: 2px 2px 4px #eee; |
|
||||
box-shadow: 2px 2px 4px #eee; |
|
||||
} |
|
||||
|
|
||||
table.docutils { |
|
||||
border: 1px solid #888; |
|
||||
-moz-box-shadow: 2px 2px 4px #eee; |
|
||||
-webkit-box-shadow: 2px 2px 4px #eee; |
|
||||
box-shadow: 2px 2px 4px #eee; |
|
||||
} |
|
||||
|
|
||||
table.docutils td, table.docutils th { |
|
||||
border: 1px solid #888; |
|
||||
padding: 0.25em 0.7em; |
|
||||
} |
|
||||
|
|
||||
table.field-list, table.footnote { |
|
||||
border: none; |
|
||||
-moz-box-shadow: none; |
|
||||
-webkit-box-shadow: none; |
|
||||
box-shadow: none; |
|
||||
} |
|
||||
|
|
||||
table.footnote { |
|
||||
margin: 15px 0; |
|
||||
width: 100%; |
|
||||
border: 1px solid #eee; |
|
||||
} |
|
||||
|
|
||||
table.field-list th { |
|
||||
padding: 0 0.8em 0 0; |
|
||||
} |
|
||||
|
|
||||
table.field-list td { |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
table.footnote td { |
|
||||
padding: 0.5em; |
|
||||
} |
|
||||
|
|
||||
dl { |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
dl dd { |
|
||||
margin-left: 30px; |
|
||||
} |
|
||||
|
|
||||
pre { |
|
||||
padding: 0; |
|
||||
margin: 15px -30px; |
|
||||
padding: 8px; |
|
||||
line-height: 1.3em; |
|
||||
padding: 7px 30px; |
|
||||
background: #eee; |
|
||||
border-radius: 2px; |
|
||||
-moz-border-radius: 2px; |
|
||||
-webkit-border-radius: 2px; |
|
||||
} |
|
||||
|
|
||||
dl pre { |
|
||||
margin-left: -60px; |
|
||||
padding-left: 60px; |
|
||||
} |
|
||||
|
|
||||
dl.class { |
|
||||
margin-bottom: 50px; |
|
||||
} |
|
||||
|
|
||||
tt { |
|
||||
background-color: #ecf0f3; |
|
||||
color: #222; |
|
||||
/* padding: 1px 2px; */ |
|
||||
} |
|
||||
|
|
||||
tt.xref, a tt { |
|
||||
background-color: #FBFBFB; |
|
||||
} |
|
||||
|
|
||||
a:hover tt { |
|
||||
background: #EEE; |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
[theme] |
|
||||
inherit = basic |
|
||||
stylesheet = flasky.css |
|
||||
nosidebar = true |
|
||||
pygments_style = flask_theme_support.FlaskyStyle |
|
||||
|
|
||||
[options] |
|
||||
index_logo = '' |
|
||||
index_logo_height = 120px |
|
||||
github_fork = '' |
|
@ -1,86 +0,0 @@ |
|||||
# flasky extensions. flasky pygments style based on tango style |
|
||||
from pygments.style import Style |
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \ |
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal |
|
||||
|
|
||||
|
|
||||
class FlaskyStyle(Style): |
|
||||
background_color = "#f8f8f8" |
|
||||
default_style = "" |
|
||||
|
|
||||
styles = { |
|
||||
# No corresponding class for the following: |
|
||||
#Text: "", # class: '' |
|
||||
Whitespace: "underline #f8f8f8", # class: 'w' |
|
||||
Error: "#a40000 border:#ef2929", # class: 'err' |
|
||||
Other: "#000000", # class 'x' |
|
||||
|
|
||||
Comment: "italic #8f5902", # class: 'c' |
|
||||
Comment.Preproc: "noitalic", # class: 'cp' |
|
||||
|
|
||||
Keyword: "bold #004461", # class: 'k' |
|
||||
Keyword.Constant: "bold #004461", # class: 'kc' |
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd' |
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn' |
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp' |
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr' |
|
||||
Keyword.Type: "bold #004461", # class: 'kt' |
|
||||
|
|
||||
Operator: "#582800", # class: 'o' |
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords |
|
||||
|
|
||||
Punctuation: "bold #000000", # class: 'p' |
|
||||
|
|
||||
# because special names such as Name.Class, Name.Function, etc. |
|
||||
# are not recognized as such later in the parsing, we choose them |
|
||||
# to look the same as ordinary variables. |
|
||||
Name: "#000000", # class: 'n' |
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised |
|
||||
Name.Builtin: "#004461", # class: 'nb' |
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp' |
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised |
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised |
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised |
|
||||
Name.Entity: "#ce5c00", # class: 'ni' |
|
||||
Name.Exception: "bold #cc0000", # class: 'ne' |
|
||||
Name.Function: "#000000", # class: 'nf' |
|
||||
Name.Property: "#000000", # class: 'py' |
|
||||
Name.Label: "#f57900", # class: 'nl' |
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised |
|
||||
Name.Other: "#000000", # class: 'nx' |
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword |
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised |
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised |
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised |
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised |
|
||||
|
|
||||
Number: "#990000", # class: 'm' |
|
||||
|
|
||||
Literal: "#000000", # class: 'l' |
|
||||
Literal.Date: "#000000", # class: 'ld' |
|
||||
|
|
||||
String: "#4e9a06", # class: 's' |
|
||||
String.Backtick: "#4e9a06", # class: 'sb' |
|
||||
String.Char: "#4e9a06", # class: 'sc' |
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment |
|
||||
String.Double: "#4e9a06", # class: 's2' |
|
||||
String.Escape: "#4e9a06", # class: 'se' |
|
||||
String.Heredoc: "#4e9a06", # class: 'sh' |
|
||||
String.Interpol: "#4e9a06", # class: 'si' |
|
||||
String.Other: "#4e9a06", # class: 'sx' |
|
||||
String.Regex: "#4e9a06", # class: 'sr' |
|
||||
String.Single: "#4e9a06", # class: 's1' |
|
||||
String.Symbol: "#4e9a06", # class: 'ss' |
|
||||
|
|
||||
Generic: "#000000", # class: 'g' |
|
||||
Generic.Deleted: "#a40000", # class: 'gd' |
|
||||
Generic.Emph: "italic #000000", # class: 'ge' |
|
||||
Generic.Error: "#ef2929", # class: 'gr' |
|
||||
Generic.Heading: "bold #000080", # class: 'gh' |
|
||||
Generic.Inserted: "#00A000", # class: 'gi' |
|
||||
Generic.Output: "#888", # class: 'go' |
|
||||
Generic.Prompt: "#745334", # class: 'gp' |
|
||||
Generic.Strong: "bold #000000", # class: 'gs' |
|
||||
Generic.Subheading: "bold #800080", # class: 'gu' |
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt' |
|
||||
} |
|
@ -0,0 +1,88 @@ |
|||||
|
API Reference |
||||
|
============= |
||||
|
|
||||
|
.. toctree:: |
||||
|
:maxdepth: 3 |
||||
|
|
||||
|
.. module:: socketio |
||||
|
|
||||
|
``Server`` class |
||||
|
---------------- |
||||
|
|
||||
|
.. autoclass:: Server |
||||
|
:members: |
||||
|
|
||||
|
``AsyncServer`` class |
||||
|
--------------------- |
||||
|
|
||||
|
.. autoclass:: AsyncServer |
||||
|
:members: |
||||
|
:inherited-members: |
||||
|
|
||||
|
``WSGIApp`` class |
||||
|
----------------- |
||||
|
|
||||
|
.. autoclass:: WSGIApp |
||||
|
:members: |
||||
|
|
||||
|
``ASGIApp`` class |
||||
|
----------------- |
||||
|
|
||||
|
.. autoclass:: ASGIApp |
||||
|
:members: |
||||
|
|
||||
|
``Middleware`` class (deprecated) |
||||
|
--------------------------------- |
||||
|
|
||||
|
.. autoclass:: Middleware |
||||
|
:members: |
||||
|
|
||||
|
``Namespace`` class |
||||
|
------------------- |
||||
|
|
||||
|
.. autoclass:: Namespace |
||||
|
:members: |
||||
|
|
||||
|
``AsyncNamespace`` class |
||||
|
------------------------ |
||||
|
|
||||
|
.. autoclass:: AsyncNamespace |
||||
|
:members: |
||||
|
:inherited-members: |
||||
|
|
||||
|
``BaseManager`` class |
||||
|
--------------------- |
||||
|
|
||||
|
.. autoclass:: BaseManager |
||||
|
:members: |
||||
|
|
||||
|
``PubSubManager`` class |
||||
|
----------------------- |
||||
|
|
||||
|
.. autoclass:: PubSubManager |
||||
|
:members: |
||||
|
|
||||
|
``KombuManager`` class |
||||
|
---------------------- |
||||
|
|
||||
|
.. autoclass:: KombuManager |
||||
|
:members: |
||||
|
|
||||
|
``RedisManager`` class |
||||
|
---------------------- |
||||
|
|
||||
|
.. autoclass:: RedisManager |
||||
|
:members: |
||||
|
|
||||
|
``AsyncManager`` class |
||||
|
---------------------- |
||||
|
|
||||
|
.. autoclass:: AsyncManager |
||||
|
:members: |
||||
|
:inherited-members: |
||||
|
|
||||
|
``AsyncRedisManager`` class |
||||
|
--------------------------- |
||||
|
|
||||
|
.. autoclass:: AsyncRedisManager |
||||
|
:members: |
@ -0,0 +1,277 @@ |
|||||
|
Deployment |
||||
|
========== |
||||
|
|
||||
|
The following sections describe a variety of deployment strategies for |
||||
|
Socket.IO servers. |
||||
|
|
||||
|
aiohttp |
||||
|
------- |
||||
|
|
||||
|
`Aiohttp <http://aiohttp.readthedocs.io/>`_ is a framework with support for HTTP |
||||
|
and WebSocket, based on asyncio. Support for this framework is limited to Python |
||||
|
3.5 and newer. |
||||
|
|
||||
|
Instances of class ``socketio.AsyncServer`` will automatically use aiohttp |
||||
|
for asynchronous operations if the library is installed. To request its use |
||||
|
explicitly, the ``async_mode`` option can be given in the constructor:: |
||||
|
|
||||
|
sio = socketio.AsyncServer(async_mode='aiohttp') |
||||
|
|
||||
|
A server configured for aiohttp must be attached to an existing application:: |
||||
|
|
||||
|
app = web.Application() |
||||
|
sio.attach(app) |
||||
|
|
||||
|
The aiohttp application can define regular routes that will coexist with the |
||||
|
Socket.IO server. A typical pattern is to add routes that serve a client |
||||
|
application and any associated static files. |
||||
|
|
||||
|
The aiohttp application is then executed in the usual manner:: |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
web.run_app(app) |
||||
|
|
||||
|
Tornado |
||||
|
------- |
||||
|
|
||||
|
`Tornado <http://www.tornadoweb.org//>`_ is a web framework with support |
||||
|
for HTTP and WebSocket. Support for this framework requires Python 3.5 and |
||||
|
newer. Only Tornado version 5 and newer are supported, thanks to its tight |
||||
|
integration with asyncio. |
||||
|
|
||||
|
Instances of class ``socketio.AsyncServer`` will automatically use tornado |
||||
|
for asynchronous operations if the library is installed. To request its use |
||||
|
explicitly, the ``async_mode`` option can be given in the constructor:: |
||||
|
|
||||
|
sio = socketio.AsyncServer(async_mode='tornado') |
||||
|
|
||||
|
A server configured for tornado must include a request handler for |
||||
|
Engine.IO:: |
||||
|
|
||||
|
app = tornado.web.Application( |
||||
|
[ |
||||
|
(r"/socketio.io/", socketio.get_tornado_handler(sio)), |
||||
|
], |
||||
|
# ... other application options |
||||
|
) |
||||
|
|
||||
|
The tornado application can define other routes that will coexist with the |
||||
|
Socket.IO server. A typical pattern is to add routes that serve a client |
||||
|
application and any associated static files. |
||||
|
|
||||
|
The tornado application is then executed in the usual manner:: |
||||
|
|
||||
|
app.listen(port) |
||||
|
tornado.ioloop.IOLoop.current().start() |
||||
|
|
||||
|
Sanic |
||||
|
----- |
||||
|
|
||||
|
`Sanic <http://sanic.readthedocs.io/>`_ is a very efficient asynchronous web |
||||
|
server for Python 3.5 and newer. |
||||
|
|
||||
|
Instances of class ``socketio.AsyncServer`` will automatically use Sanic for |
||||
|
asynchronous operations if the framework is installed. To request its use |
||||
|
explicitly, the ``async_mode`` option can be given in the constructor:: |
||||
|
|
||||
|
sio = socketio.AsyncServer(async_mode='sanic') |
||||
|
|
||||
|
A server configured for aiohttp must be attached to an existing application:: |
||||
|
|
||||
|
app = web.Application() |
||||
|
sio.attach(app) |
||||
|
|
||||
|
The Sanic application can define regular routes that will coexist with the |
||||
|
Socket.IO server. A typical pattern is to add routes that serve a client |
||||
|
application and any associated static files. |
||||
|
|
||||
|
The Sanic application is then executed in the usual manner:: |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
app.run() |
||||
|
|
||||
|
Uvicorn, Daphne, and other ASGI servers |
||||
|
--------------------------------------- |
||||
|
|
||||
|
The ``socketio.ASGIApp`` class is an ASGI compatible application that can |
||||
|
forward Socket.IO traffic to an ``socketio.AsyncServer`` instance:: |
||||
|
|
||||
|
sio = socketio.AsyncServer(async_mode='asgi') |
||||
|
app = socketio.ASGIApp(sio) |
||||
|
|
||||
|
The application can then be deployed with any ASGI compatible web server. |
||||
|
|
||||
|
Eventlet |
||||
|
-------- |
||||
|
|
||||
|
`Eventlet <http://eventlet.net/>`_ is a high performance concurrent networking |
||||
|
library for Python 2 and 3 that uses coroutines, enabling code to be written in |
||||
|
the same style used with the blocking standard library functions. An Socket.IO |
||||
|
server deployed with eventlet has access to the long-polling and WebSocket |
||||
|
transports. |
||||
|
|
||||
|
Instances of class ``socketio.Server`` will automatically use eventlet for |
||||
|
asynchronous operations if the library is installed. To request its use |
||||
|
explicitly, the ``async_mode`` option can be given in the constructor:: |
||||
|
|
||||
|
sio = socketio.Server(async_mode='eventlet') |
||||
|
|
||||
|
A server configured for eventlet is deployed as a regular WSGI application, |
||||
|
using the provided ``socketio.Middleware``:: |
||||
|
|
||||
|
app = socketio.Middleware(sio) |
||||
|
import eventlet |
||||
|
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
||||
|
|
||||
|
Using Gunicorn with Eventlet |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
An alternative to running the eventlet WSGI server as above is to use |
||||
|
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
||||
|
command to launch the application under gunicorn is shown below:: |
||||
|
|
||||
|
$ gunicorn -k eventlet -w 1 module:app |
||||
|
|
||||
|
Due to limitations in its load balancing algorithm, gunicorn can only be used |
||||
|
with one worker process, so the ``-w`` option cannot be set to a value higher |
||||
|
than 1. A single eventlet worker can handle a large number of concurrent |
||||
|
clients, each handled by a greenlet. |
||||
|
|
||||
|
Eventlet provides a ``monkey_patch()`` function that replaces all the blocking |
||||
|
functions in the standard library with equivalent asynchronous versions. While |
||||
|
python-socketio does not require monkey patching, other libraries such as |
||||
|
database drivers are likely to require it. |
||||
|
|
||||
|
Gevent |
||||
|
------ |
||||
|
|
||||
|
`Gevent <http://gevent.org/>`_ is another asynchronous framework based on |
||||
|
coroutines, very similar to eventlet. An Socket.IO server deployed with |
||||
|
gevent has access to the long-polling transport. If project |
||||
|
`gevent-websocket <https://bitbucket.org/Jeffrey/gevent-websocket/>`_ is |
||||
|
installed, the WebSocket transport is also available. |
||||
|
|
||||
|
Instances of class ``socketio.Server`` will automatically use gevent for |
||||
|
asynchronous operations if the library is installed and eventlet is not |
||||
|
installed. To request gevent to be selected explicitly, the ``async_mode`` |
||||
|
option can be given in the constructor:: |
||||
|
|
||||
|
sio = socketio.Server(async_mode='gevent') |
||||
|
|
||||
|
A server configured for gevent is deployed as a regular WSGI application, |
||||
|
using the provided ``socketio.Middleware``:: |
||||
|
|
||||
|
app = socketio.Middleware(sio) |
||||
|
from gevent import pywsgi |
||||
|
pywsgi.WSGIServer(('', 8000), app).serve_forever() |
||||
|
|
||||
|
If the WebSocket transport is installed, then the server must be started as |
||||
|
follows:: |
||||
|
|
||||
|
from gevent import pywsgi |
||||
|
from geventwebsocket.handler import WebSocketHandler |
||||
|
app = socketio.Middleware(sio) |
||||
|
pywsgi.WSGIServer(('', 8000), app, |
||||
|
handler_class=WebSocketHandler).serve_forever() |
||||
|
|
||||
|
Using Gunicorn with Gevent |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
An alternative to running the gevent WSGI server as above is to use |
||||
|
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
||||
|
command to launch the application under gunicorn is shown below:: |
||||
|
|
||||
|
$ gunicorn -k gevent -w 1 module:app |
||||
|
|
||||
|
Or to include WebSocket:: |
||||
|
|
||||
|
$ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app |
||||
|
|
||||
|
Same as with eventlet, due to limitations in its load balancing algorithm, |
||||
|
gunicorn can only be used with one worker process, so the ``-w`` option cannot |
||||
|
be higher than 1. A single gevent worker can handle a large number of |
||||
|
concurrent clients through the use of greenlets. |
||||
|
|
||||
|
Gevent provides a ``monkey_patch()`` function that replaces all the blocking |
||||
|
functions in the standard library with equivalent asynchronous versions. While |
||||
|
python-socketio does not require monkey patching, other libraries such as |
||||
|
database drivers are likely to require it. |
||||
|
|
||||
|
uWSGI |
||||
|
----- |
||||
|
|
||||
|
When using the uWSGI server in combination with gevent, the Socket.IO server |
||||
|
can take advantage of uWSGI's native WebSocket support. |
||||
|
|
||||
|
Instances of class ``socketio.Server`` will automatically use this option for |
||||
|
asynchronous operations if both gevent and uWSGI are installed and eventlet is |
||||
|
not installed. To request this asynchronous mode explicitly, the |
||||
|
``async_mode`` option can be given in the constructor:: |
||||
|
|
||||
|
# gevent with uWSGI |
||||
|
sio = socketio.Server(async_mode='gevent_uwsgi') |
||||
|
|
||||
|
A complete explanation of the configuration and usage of the uWSGI server is |
||||
|
beyond the scope of this documentation. The uWSGI server is a fairly complex |
||||
|
package that provides a large and comprehensive set of options. It must be |
||||
|
compiled with WebSocket and SSL support for the WebSocket transport to be |
||||
|
available. As way of an introduction, the following command starts a uWSGI |
||||
|
server for the ``latency.py`` example on port 5000:: |
||||
|
|
||||
|
$ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app |
||||
|
|
||||
|
Standard Threads |
||||
|
---------------- |
||||
|
|
||||
|
While not comparable to eventlet and gevent in terms of performance, |
||||
|
the Socket.IO server can also be configured to work with multi-threaded web |
||||
|
servers that use standard Python threads. This is an ideal setup to use with |
||||
|
development servers such as `Werkzeug <http://werkzeug.pocoo.org>`_. Only the |
||||
|
long-polling transport is currently available when using standard threads. |
||||
|
|
||||
|
Instances of class ``socketio.Server`` will automatically use the threading |
||||
|
mode if neither eventlet nor gevent are not installed. To request the |
||||
|
threading mode explicitly, the ``async_mode`` option can be given in the |
||||
|
constructor:: |
||||
|
|
||||
|
sio = socketio.Server(async_mode='threading') |
||||
|
|
||||
|
A server configured for threading is deployed as a regular web application, |
||||
|
using any WSGI complaint multi-threaded server. The example below deploys an |
||||
|
Socket.IO application combined with a Flask web application, using Flask's |
||||
|
development web server based on Werkzeug:: |
||||
|
|
||||
|
sio = socketio.Server(async_mode='threading') |
||||
|
app = Flask(__name__) |
||||
|
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app) |
||||
|
|
||||
|
# ... Socket.IO and Flask handler functions ... |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
app.run(threaded=True) |
||||
|
|
||||
|
When using the threading mode, it is important to ensure that the WSGI server |
||||
|
can handle multiple concurrent requests using threads, since a client can have |
||||
|
up to two outstanding requests at any given time. The Werkzeug server is |
||||
|
single-threaded by default, so the ``threaded=True`` option is required. |
||||
|
|
||||
|
Note that servers that use worker processes instead of threads, such as |
||||
|
gunicorn, do not support a Socket.IO server configured in threading mode. |
||||
|
|
||||
|
Scalability Notes |
||||
|
----------------- |
||||
|
|
||||
|
Socket.IO is a stateful protocol, which makes horizontal scaling more |
||||
|
difficult. To deploy a cluster of Socket.IO processes hosted on one or |
||||
|
multiple servers, the following conditions must be met: |
||||
|
|
||||
|
- Each Socket.IO process must be able to handle multiple requests |
||||
|
concurrently. This is required because long-polling clients send two |
||||
|
requests in parallel. Worker processes that can only handle one request at a |
||||
|
time are not supported. |
||||
|
- The load balancer must be configured to always forward requests from a |
||||
|
client to the same worker process. Load balancers call this *sticky |
||||
|
sessions*, or *session affinity*. |
||||
|
- The worker processes need to communicate with each other to coordinate |
||||
|
complex operations such as broadcasts. This is done through a configured |
||||
|
message queue. See the section on using message queues for details. |
@ -0,0 +1,346 @@ |
|||||
|
User's Guide |
||||
|
============ |
||||
|
|
||||
|
The ``Server`` and ``AsyncServer`` classes |
||||
|
------------------------------------------ |
||||
|
|
||||
|
A Socket.IO server is an instance of class :class:`socketio.Server`. This |
||||
|
instance can be transformed into a standard WSGI application by wrapping it |
||||
|
with the :class:`socketio.WSGIApp` class:: |
||||
|
|
||||
|
import socketio |
||||
|
|
||||
|
# create a Socket.IO server |
||||
|
sio = socketio.Server() |
||||
|
|
||||
|
# wrap with a WSGI application |
||||
|
app = socketio.WSGIApp(sio) |
||||
|
|
||||
|
For asyncio based servers, the :class:`socketio.AsyncServer` class provides |
||||
|
the same functionality, but in a coroutine friendly format. If desired, The |
||||
|
:class:`socketio.ASGIApp` class can transform the server into a standard |
||||
|
ASGI application:: |
||||
|
|
||||
|
# create a Socket.IO server |
||||
|
sio = socketio.AsyncServer() |
||||
|
|
||||
|
# wrap with ASGI application |
||||
|
app = socketio.ASGIApp(sio) |
||||
|
|
||||
|
The WSGI and ASGI application wrappers support serving static files, which is |
||||
|
a convenient way to deliver JavaScript based Socket.IO clients to the web |
||||
|
browser:: |
||||
|
|
||||
|
app = socketio.ASGIApp(sio, static_files={ |
||||
|
'/': {'content_type': 'text/html', 'filename': 'latency.html'}, |
||||
|
'/static/style.css': {'content_type': 'text/css', |
||||
|
'filename': 'static/style.css'}, |
||||
|
}) |
||||
|
|
||||
|
The dictionary provided with the ``static_files`` argument has static file |
||||
|
endpoints as keys. For each of these endpoints, a dictionary with the file's |
||||
|
content type and local filename is given. |
||||
|
|
||||
|
These wrappers can also act as middlewares, forwarding any traffic that is not |
||||
|
intended to Socket.IO server to another application. This allows Socket.IO |
||||
|
servers to integrate easily into existing WSGI or ASGI applications:: |
||||
|
|
||||
|
from wsgi import app # a Flask, Django, etc. application |
||||
|
|
||||
|
app = socketio.WSGIApp(sio, app) |
||||
|
|
||||
|
Receiving Events |
||||
|
---------------- |
||||
|
|
||||
|
The Socket.IO protocol is event based. When a client wants to communicate with |
||||
|
the server it *emits* an event. Each event has a name, and a list of |
||||
|
arguments. The server registers event handler functions with the |
||||
|
:func:`socketio.Server.on` decorator:: |
||||
|
|
||||
|
@sio.on('my custom event') |
||||
|
def my_custom_event(sid, data): |
||||
|
pass |
||||
|
|
||||
|
For asyncio servers, event handlers can optionally be given as coroutines:: |
||||
|
|
||||
|
@sio.on('my custom event') |
||||
|
async def my_custom_event(sid, data): |
||||
|
pass |
||||
|
|
||||
|
The ``sid`` argument is the Socket.IO session id, a unique identifier of each |
||||
|
client connection. All the events sent by a given client will have the same |
||||
|
``sid`` value. |
||||
|
|
||||
|
The ``connect`` and ``disconnect`` are special; they are invoked automatically |
||||
|
when a client connects or disconnects from the server:: |
||||
|
|
||||
|
@sio.on('connect') |
||||
|
def connect(sid, environ): |
||||
|
print('connect ', sid) |
||||
|
|
||||
|
@sio.on('disconnect') |
||||
|
def disconnect(sid): |
||||
|
print('disconnect ', sid) |
||||
|
|
||||
|
The ``connect`` event is an ideal place to perform user authentication, and |
||||
|
any necessary mapping between user entities in the application and the ``sid`` |
||||
|
that was assigned to the client. The ``environ`` argument is a dictionary in |
||||
|
standard WSGI format containing the request information, including HTTP |
||||
|
headers. After inspecting the request, the connect event handler can return |
||||
|
``False`` to reject the connection with the client. |
||||
|
|
||||
|
Sending Events |
||||
|
-------------- |
||||
|
|
||||
|
Socket.IO is a bidirectional protocol, so at any time the server can send an |
||||
|
event to its connected clients. The :func:`socketio.Server.emit` method is |
||||
|
used for this task:: |
||||
|
|
||||
|
sio.emit('my event', {'data': 'foobar'}) |
||||
|
|
||||
|
Sometimes the server may want to send an event just to a particular client. |
||||
|
This can be achieved by adding a ``room`` argument to the emit call:: |
||||
|
|
||||
|
sio.emit('my event', {'data': 'foobar'}, room=user_sid) |
||||
|
|
||||
|
The :func:`socketio.Server.emit` method takes an event name, a message payload |
||||
|
of type ``str``, ``bytes``, ``list``, ``dict`` or ``tuple``, and the recipient |
||||
|
room. When sending a ``tuple``, the elements in it need to be of any of the |
||||
|
other four allowed types. The elements of the tuple will be passed as multiple |
||||
|
arguments to the client-side event handler function. The ``room`` argument is |
||||
|
used to identify the client that should receive the event, and is set to the |
||||
|
``sid`` value assigned to that client's connection with the server. When |
||||
|
omitted, the event is broadcasted to all connected clients. |
||||
|
|
||||
|
Rooms |
||||
|
----- |
||||
|
|
||||
|
To make it easy for the server to emit events to groups of related clients, |
||||
|
the application can put its clients into "rooms", and then address messages to |
||||
|
these rooms. |
||||
|
|
||||
|
In the previous section the ``room`` argument of the |
||||
|
:func:`socketio.SocketIO.emit` method was used to designate a specific |
||||
|
client as the recipient of the event. This is because upon connection, a |
||||
|
personal room for each client is created and named with the ``sid`` assigned |
||||
|
to the connection. The application is then free to create additional rooms and |
||||
|
manage which clients are in them using the :func:`socketio.Server.enter_room` |
||||
|
and :func:`socketio.Server.leave_room` methods. Clients can be in as many |
||||
|
rooms as needed and can be moved between rooms as often as necessary. |
||||
|
|
||||
|
:: |
||||
|
|
||||
|
@sio.on('chat') |
||||
|
def begin_chat(sid): |
||||
|
sio.enter_room(sid, 'chat_users') |
||||
|
|
||||
|
@sio.on('exit_chat') |
||||
|
def exit_chat(sid): |
||||
|
sio.leave_room(sid, 'chat_users') |
||||
|
|
||||
|
In chat applications it is often desired that an event is broadcasted to all |
||||
|
the members of the room except one, which is the originator of the event such |
||||
|
as a chat message. The :func:`socketio.Server.emit` method provides an |
||||
|
optional ``skip_sid`` argument to indicate a client that should be skipped |
||||
|
during the broadcast. |
||||
|
|
||||
|
:: |
||||
|
|
||||
|
@sio.on('my message') |
||||
|
def message(sid, data): |
||||
|
sio.emit('my reply', data, room='chat_users', skip_sid=sid) |
||||
|
|
||||
|
Event Callbacks |
||||
|
--------------- |
||||
|
|
||||
|
When a client sends an event to the server, it can optionally provide a |
||||
|
callback function, to be invoked as a way of acknowledgment that the server |
||||
|
has processed the event. While this is entirely managed by the client, the |
||||
|
server can provide a list of values that are to be passed on to the callback |
||||
|
function, simply by returning them from the handler function:: |
||||
|
|
||||
|
@sio.on('my event', namespace='/chat') |
||||
|
def my_event_handler(sid, data): |
||||
|
# handle the message |
||||
|
return "OK", 123 |
||||
|
|
||||
|
Likewise, the server can request a callback function to be invoked after a |
||||
|
client has processed an event. The :func:`socketio.Server.emit` method has an |
||||
|
optional ``callback`` argument that can be set to a callable. If this |
||||
|
argument is given, the callable will be invoked after the client has processed |
||||
|
the event, and any values returned by the client will be passed as arguments |
||||
|
to this function. Using callback functions when broadcasting to multiple |
||||
|
clients is not recommended, as the callback function will be invoked once for |
||||
|
each client that received the message. |
||||
|
|
||||
|
Namespaces |
||||
|
---------- |
||||
|
|
||||
|
The Socket.IO protocol supports multiple logical connections, all multiplexed |
||||
|
on the same physical connection. Clients can open multiple connections by |
||||
|
specifying a different *namespace* on each. A namespace is given by the client |
||||
|
as a pathname following the hostname and port. For example, connecting to |
||||
|
*http://example.com:8000/chat* would open a connection to the namespace |
||||
|
*/chat*. |
||||
|
|
||||
|
Each namespace is handled independently from the others, with separate session |
||||
|
IDs (``sid``\ s), event handlers and rooms. It is important that applications |
||||
|
that use multiple namespaces specify the correct namespace when setting up |
||||
|
their event handlers and rooms, using the optional ``namespace`` argument |
||||
|
available in all the methods in the :class:`socketio.Server` class:: |
||||
|
|
||||
|
@sio.on('my custom event', namespace='/chat') |
||||
|
def my_custom_event(sid, data): |
||||
|
pass |
||||
|
|
||||
|
When emitting an event, the ``namespace`` optional argument is used to specify |
||||
|
which namespace to send it on. When the ``namespace`` argument is omitted, the |
||||
|
default Socket.IO namespace, which is named ``/``, is used. |
||||
|
|
||||
|
Class-Based Namespaces |
||||
|
---------------------- |
||||
|
|
||||
|
As an alternative to the decorator-based event handlers, the event handlers |
||||
|
that belong to a namespace can be created as methods of a subclass of |
||||
|
:class:`socketio.Namespace`:: |
||||
|
|
||||
|
class MyCustomNamespace(socketio.Namespace): |
||||
|
def on_connect(self, sid, environ): |
||||
|
pass |
||||
|
|
||||
|
def on_disconnect(self, sid): |
||||
|
pass |
||||
|
|
||||
|
def on_my_event(self, sid, data): |
||||
|
self.emit('my_response', data) |
||||
|
|
||||
|
sio.register_namespace(MyCustomNamespace('/test')) |
||||
|
|
||||
|
For asyncio based severs, namespaces must inherit from |
||||
|
:class:`socketio.AsyncNamespace`, and can define event handlers as coroutines |
||||
|
if desired:: |
||||
|
|
||||
|
class MyCustomNamespace(socketio.AsyncNamespace): |
||||
|
def on_connect(self, sid, environ): |
||||
|
pass |
||||
|
|
||||
|
def on_disconnect(self, sid): |
||||
|
pass |
||||
|
|
||||
|
async def on_my_event(self, sid, data): |
||||
|
await self.emit('my_response', data) |
||||
|
|
||||
|
sio.register_namespace(MyCustomNamespace('/test')) |
||||
|
|
||||
|
When class-based namespaces are used, any events received by the server are |
||||
|
dispatched to a method named as the event name with the ``on_`` prefix. For |
||||
|
example, event ``my_event`` will be handled by a method named ``on_my_event``. |
||||
|
If an event is received for which there is no corresponding method defined in |
||||
|
the namespace class, then the event is ignored. All event names used in |
||||
|
class-based namespaces must use characters that are legal in method names. |
||||
|
|
||||
|
As a convenience to methods defined in a class-based namespace, the namespace |
||||
|
instance includes versions of several of the methods in the |
||||
|
:class:`socketio.Server` and :class:`socketio.AsyncServer` classes that default |
||||
|
to the proper namespace when the ``namespace`` argument is not given. |
||||
|
|
||||
|
In the case that an event has a handler in a class-based namespace, and also a |
||||
|
decorator-based function handler, only the standalone function handler is |
||||
|
invoked. |
||||
|
|
||||
|
It is important to note that class-based namespaces are singletons. This means |
||||
|
that a single instance of a namespace class is used for all clients, and |
||||
|
consequently, a namespace instance cannot be used to store client specific |
||||
|
information. |
||||
|
|
||||
|
Using a Message Queue |
||||
|
--------------------- |
||||
|
|
||||
|
When working with distributed applications, it is often necessary to access |
||||
|
the functionality of the Socket.IO from multiple processes. There are two |
||||
|
specific use cases: |
||||
|
|
||||
|
- Applications that use a work queues such as |
||||
|
`Celery <http://www.celeryproject.org/>`_ may need to emit an event to a |
||||
|
client once a background job completes. The most convenient place to carry |
||||
|
out this task is the worker process that handled this job. |
||||
|
|
||||
|
- Highly available applications may want to use horizontal scaling of the |
||||
|
Socket.IO server to be able to handle very large number of concurrent |
||||
|
clients. |
||||
|
|
||||
|
As a solution to the above problems, the Socket.IO server can be configured |
||||
|
to connect to a message queue such as `Redis <http://redis.io/>`_ or |
||||
|
`RabbitMQ <https://www.rabbitmq.com/>`_, to communicate with other related |
||||
|
Socket.IO servers or auxiliary workers. |
||||
|
|
||||
|
Redis |
||||
|
~~~~~ |
||||
|
|
||||
|
To use a Redis message queue, a Python Redis client must be installed:: |
||||
|
|
||||
|
# socketio.Server class |
||||
|
pip install redis |
||||
|
|
||||
|
# socketio.AsyncServer class |
||||
|
pip install aioredis |
||||
|
|
||||
|
The Redis queue is configured through the :class:`socketio.RedisManager` and |
||||
|
:class:`socketio.AsyncRedisManager` classes. These classes connect directly to |
||||
|
the Redis store and use the queue's pub/sub functionality:: |
||||
|
|
||||
|
# socketio.Server class |
||||
|
mgr = socketio.RedisManager('redis://') |
||||
|
sio = socketio.Server(client_manager=mgr) |
||||
|
|
||||
|
# socketio.AsyncServer class |
||||
|
mgr = socketio.AsyncRedisManager('redis://') |
||||
|
sio = socketio.AsyncServer(client_manager=mgr) |
||||
|
|
||||
|
The ``client_manager`` argument instructs the server to connect to the given |
||||
|
message queue, and to coordinate with other processes connected to the queue. |
||||
|
|
||||
|
Kombu |
||||
|
~~~~~ |
||||
|
|
||||
|
`Kombu <http://kombu.readthedocs.org/en/latest/>`_ is a Python package that |
||||
|
provides access to RabbitMQ and many other message queues. It can be installed |
||||
|
with pip:: |
||||
|
|
||||
|
pip install kombu |
||||
|
|
||||
|
To use RabbitMQ or other AMQP protocol compatible queues, that is the only |
||||
|
required dependency. But for other message queues, Kombu may require |
||||
|
additional packages. For example, to use a Redis queue via Kombu, the Python |
||||
|
package for Redis needs to be installed as well:: |
||||
|
|
||||
|
pip install redis |
||||
|
|
||||
|
The queue is configured through the :class:`socketio.KombuManager`:: |
||||
|
|
||||
|
mgr = socketio.KombuManager('amqp://') |
||||
|
sio = socketio.Server(client_manager=mgr) |
||||
|
|
||||
|
The connection URL passed to the :class:`KombuManager` constructor is passed |
||||
|
directly to Kombu's `Connection object |
||||
|
<http://kombu.readthedocs.org/en/latest/userguide/connections.html>`_, so |
||||
|
the Kombu documentation should be consulted for information on how to build |
||||
|
the correct URL for a given message queue. |
||||
|
|
||||
|
Note that Kombu currently does not support asyncio, so it cannot be used with |
||||
|
the :class:`socketio.AsyncServer` class. |
||||
|
|
||||
|
Emitting from external processes |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
To have a process other than a server connect to the queue to emit a message, |
||||
|
the same client manager classes can be used as standalone objects. In this |
||||
|
case, the ``write_only`` argument should be set to ``True`` to disable the |
||||
|
creation of a listening thread, which only makes sense in a server. For |
||||
|
example:: |
||||
|
|
||||
|
# connect to the redis queue as an external process |
||||
|
external_sio = socketio.RedisManager('redis://', write_only=True) |
||||
|
|
||||
|
# emit an event |
||||
|
external_sio.emit('my event', data={'foo': 'bar'}, room='my room') |
@ -1,756 +1,25 @@ |
|||||
.. socketio documentation master file, created by |
.. python-socketio documentation master file, created by |
||||
sphinx-quickstart on Sat Jun 13 23:41:23 2015. |
sphinx-quickstart on Sun Nov 25 11:52:38 2018. |
||||
You can adapt this file completely to your liking, but it should at least |
You can adapt this file completely to your liking, but it should at least |
||||
contain the root `toctree` directive. |
contain the root `toctree` directive. |
||||
|
|
||||
socketio documentation |
python-socketio |
||||
====================== |
=============== |
||||
|
|
||||
This project implements a Python Socket.IO server that can run standalone or |
This projects implements a Socket.IO server that can run standalone or |
||||
integrated with a web application. The following are some of its |
integrated with a variety of Python web frameworks. |
||||
features: |
|
||||
|
|
||||
- Fully compatible with the |
.. toctree:: |
||||
`Javascript <https://github.com/Automattic/socket.io-client>`_, |
:maxdepth: 2 |
||||
`Swift <https://github.com/socketio/socket.io-client-swift>`_, |
|
||||
`C++ <https://github.com/socketio/socket.io-client-cpp>`_ and |
|
||||
`Java <https://github.com/socketio/socket.io-client-java>`_ official |
|
||||
Socket.IO clients, plus any third party clients that comply with the |
|
||||
Socket.IO specification. |
|
||||
- Compatible with Python 2.7 and Python 3.3+. |
|
||||
- Supports large number of clients even on modest hardware when used with an |
|
||||
asynchronous server based on `asyncio <https://docs.python.org/3/library/asyncio.html>`_ |
|
||||
(`sanic <http://sanic.readthedocs.io/>`_, `aiohttp <http://aiohttp.readthedocs.io/>`_ or |
|
||||
`tornado <http://www.tornadoweb.org/>`_), |
|
||||
`eventlet <http://eventlet.net/>`_ or `gevent <http://gevent.org>`_. For |
|
||||
development and testing, any WSGI compliant multi-threaded server can also be |
|
||||
used. |
|
||||
- Includes a WSGI middleware that integrates Socket.IO traffic with standard |
|
||||
WSGI applications. |
|
||||
- Broadcasting of messages to all connected clients, or to subsets of them |
|
||||
assigned to "rooms". |
|
||||
- Optional support for multiple servers, connected through a messaging queue |
|
||||
such as Redis or RabbitMQ. |
|
||||
- Send messages to clients from external processes, such as Celery workers or |
|
||||
auxiliary scripts. |
|
||||
- Event-based architecture implemented with decorators that hides the details |
|
||||
of the protocol. |
|
||||
- Support for HTTP long-polling and WebSocket transports. |
|
||||
- Support for XHR2 and XHR browsers. |
|
||||
- Support for text and binary messages. |
|
||||
- Support for gzip and deflate HTTP compression. |
|
||||
- Configurable CORS responses, to avoid cross-origin problems with browsers. |
|
||||
|
|
||||
What is Socket.IO? |
intro |
||||
------------------ |
guide |
||||
|
deployment |
||||
Socket.IO is a transport protocol that enables real-time bidirectional |
api |
||||
event-based communication between clients (typically web browsers) and a |
|
||||
server. The original implementations of the client and server components are |
|
||||
written in JavaScript. |
|
||||
|
|
||||
Getting Started |
|
||||
--------------- |
|
||||
|
|
||||
The Socket.IO server can be installed with pip:: |
|
||||
|
|
||||
pip install python-socketio |
|
||||
|
|
||||
The following is a basic example of a Socket.IO server that uses the |
|
||||
`aiohttp <http://aiohttp.readthedocs.io/>`_ framework for asyncio (Python 3.5+ |
|
||||
only): |
|
||||
|
|
||||
.. code:: python |
|
||||
|
|
||||
from aiohttp import web |
|
||||
import socketio |
|
||||
|
|
||||
sio = socketio.AsyncServer() |
|
||||
app = web.Application() |
|
||||
sio.attach(app) |
|
||||
|
|
||||
async def index(request): |
|
||||
"""Serve the client-side application.""" |
|
||||
with open('index.html') as f: |
|
||||
return web.Response(text=f.read(), content_type='text/html') |
|
||||
|
|
||||
@sio.on('connect', namespace='/chat') |
|
||||
def connect(sid, environ): |
|
||||
print("connect ", sid) |
|
||||
|
|
||||
@sio.on('chat message', namespace='/chat') |
|
||||
async def message(sid, data): |
|
||||
print("message ", data) |
|
||||
await sio.emit('reply', room=sid) |
|
||||
|
|
||||
@sio.on('disconnect', namespace='/chat') |
|
||||
def disconnect(sid): |
|
||||
print('disconnect ', sid) |
|
||||
|
|
||||
app.router.add_static('/static', 'static') |
|
||||
app.router.add_get('/', index) |
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
web.run_app(app) |
|
||||
|
|
||||
And below is a similar example, but using Flask and Eventlet. This example is |
|
||||
compatible with Python 2.7 and 3.3+:: |
|
||||
|
|
||||
import socketio |
|
||||
import eventlet |
|
||||
from flask import Flask, render_template |
|
||||
|
|
||||
sio = socketio.Server() |
|
||||
app = Flask(__name__) |
|
||||
|
|
||||
@app.route('/') |
|
||||
def index(): |
|
||||
"""Serve the client-side application.""" |
|
||||
return render_template('index.html') |
|
||||
|
|
||||
@sio.on('connect') |
|
||||
def connect(sid, environ): |
|
||||
print('connect ', sid) |
|
||||
|
|
||||
@sio.on('my message') |
|
||||
def message(sid, data): |
|
||||
print('message ', data) |
|
||||
|
|
||||
@sio.on('disconnect') |
|
||||
def disconnect(sid): |
|
||||
print('disconnect ', sid) |
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
# wrap Flask application with socketio's middleware |
|
||||
app = socketio.Middleware(sio, app) |
|
||||
|
|
||||
# deploy as an eventlet WSGI server |
|
||||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
|
||||
|
|
||||
The client-side application must include the |
|
||||
`socket.io-client <https://github.com/Automattic/socket.io-client>`_ library |
|
||||
(versions 1.3.5 or newer recommended). |
|
||||
|
|
||||
Each time a client connects to the server the ``connect`` event handler is |
|
||||
invoked with the ``sid`` (session ID) assigned to the connection and the WSGI |
|
||||
environment dictionary. The server can inspect authentication or other headers |
|
||||
to decide if the client is allowed to connect. To reject a client the handler |
|
||||
must return ``False``. |
|
||||
|
|
||||
When the client sends an event to the server, the appropriate event handler is |
|
||||
invoked with the ``sid`` and the message, which can be a single or multiple |
|
||||
arguments. The application can define as many events as needed and associate |
|
||||
them with event handlers. An event is defined simply by a name. |
|
||||
|
|
||||
When a connection with a client is broken, the ``disconnect`` event is called, |
|
||||
allowing the application to perform cleanup. |
|
||||
|
|
||||
Server |
|
||||
------ |
|
||||
|
|
||||
Socket.IO servers are instances of class :class:`socketio.Server`, which can be |
|
||||
combined with a WSGI compliant application using :class:`socketio.Middleware`:: |
|
||||
|
|
||||
# create a Socket.IO server |
|
||||
sio = socketio.Server() |
|
||||
|
|
||||
# wrap WSGI application with socketio's middleware |
|
||||
app = socketio.Middleware(sio, app) |
|
||||
|
|
||||
|
|
||||
For asyncio based servers, the :class:`socketio.AsyncServer` class provides a |
|
||||
coroutine friendly server:: |
|
||||
|
|
||||
# create a Socket.IO server |
|
||||
sio = socketio.AsyncServer() |
|
||||
|
|
||||
# attach server to application |
|
||||
sio.attach(app) |
|
||||
|
|
||||
Event handlers for servers are registered using the :func:`socketio.Server.on` |
|
||||
method:: |
|
||||
|
|
||||
@sio.on('my custom event') |
|
||||
def my_custom_event(): |
|
||||
pass |
|
||||
|
|
||||
For asyncio servers, event handlers can be regular functions or coroutines:: |
|
||||
|
|
||||
@sio.on('my custom event') |
|
||||
async def my_custom_event(): |
|
||||
await sio.emit('my reply') |
|
||||
|
|
||||
Rooms |
|
||||
----- |
|
||||
|
|
||||
Because Socket.IO is a bidirectional protocol, the server can send messages to |
|
||||
any connected client at any time. To make it easy to address groups of clients, |
|
||||
the application can put clients into rooms, and then address messages to the |
|
||||
entire room. |
|
||||
|
|
||||
When clients first connect, they are assigned to their own rooms, named with |
|
||||
the session ID (the ``sid`` argument passed to all event handlers). The |
|
||||
application is free to create additional rooms and manage which clients are in |
|
||||
them using the :func:`socketio.Server.enter_room` and |
|
||||
:func:`socketio.Server.leave_room` methods. Clients can be in as many rooms as |
|
||||
needed and can be moved between rooms as often as necessary. The individual |
|
||||
rooms assigned to clients when they connect are not special in any way, the |
|
||||
application is free to add or remove clients from them, though once it does |
|
||||
that it will lose the ability to address individual clients. |
|
||||
|
|
||||
:: |
|
||||
|
|
||||
@sio.on('enter room') |
|
||||
def enter_room(sid, data): |
|
||||
sio.enter_room(sid, data['room']) |
|
||||
|
|
||||
@sio.on('leave room') |
|
||||
def leave_room(sid, data): |
|
||||
sio.leave_room(sid, data['room']) |
|
||||
|
|
||||
The :func:`socketio.Server.emit` method takes an event name, a message payload |
|
||||
of type ``str``, ``bytes``, ``list``, ``dict`` or ``tuple``, and the recipient |
|
||||
room. When sending a ``tuple``, the elements in it need to be of any of the |
|
||||
other four allowed types. The elements of the tuple will be passed as multiple |
|
||||
arguments to the client-side callback function. To address an individual |
|
||||
client, the ``sid`` of that client should be given as room (assuming the |
|
||||
application did not alter these initial rooms). To address all connected |
|
||||
clients, the ``room`` argument should be omitted. |
|
||||
|
|
||||
:: |
|
||||
|
|
||||
@sio.on('my message') |
|
||||
def message(sid, data): |
|
||||
print('message ', data) |
|
||||
sio.emit('my reply', data, room='my room') |
|
||||
|
|
||||
Often when broadcasting a message to group of users in a room, it is desirable |
|
||||
that the sender does not receive its own message. The |
|
||||
:func:`socketio.Server.emit` method provides an optional ``skip_sid`` argument |
|
||||
to specify a client that should be skipped during the broadcast. |
|
||||
|
|
||||
:: |
|
||||
|
|
||||
@sio.on('my message') |
|
||||
def message(sid, data): |
|
||||
print('message ', data) |
|
||||
sio.emit('my reply', data, room='my room', skip_sid=sid) |
|
||||
|
|
||||
Responses |
|
||||
--------- |
|
||||
|
|
||||
When a client sends an event to the server, it can optionally provide a |
|
||||
callback function, to be invoked with a response provided by the server. The |
|
||||
server can provide a response simply by returning it from the corresponding |
|
||||
event handler. |
|
||||
|
|
||||
:: |
|
||||
|
|
||||
@sio.on('my event', namespace='/chat') |
|
||||
def my_event_handler(sid, data): |
|
||||
# handle the message |
|
||||
return "OK", 123 |
|
||||
|
|
||||
The event handler can return a single value, or a tuple with several values. |
|
||||
The callback function on the client side will be invoked with these returned |
|
||||
values as arguments. |
|
||||
|
|
||||
Callbacks |
|
||||
--------- |
|
||||
|
|
||||
The server can also request a response to an event sent to a client. The |
|
||||
:func:`socketio.Server.emit` method has an optional ``callback`` argument that |
|
||||
can be set to a callable. When this argument is given, the callable will be |
|
||||
invoked with the arguments returned by the client as a response. |
|
||||
|
|
||||
Using callback functions when broadcasting to multiple clients is not |
|
||||
recommended, as the callback function will be invoked once for each client |
|
||||
that received the message. |
|
||||
|
|
||||
Namespaces |
|
||||
---------- |
|
||||
|
|
||||
The Socket.IO protocol supports multiple logical connections, all multiplexed |
|
||||
on the same physical connection. Clients can open multiple connections by |
|
||||
specifying a different *namespace* on each. A namespace is given by the client |
|
||||
as a pathname following the hostname and port. For example, connecting to |
|
||||
*http://example.com:8000/chat* would open a connection to the namespace |
|
||||
*/chat*. |
|
||||
|
|
||||
Each namespace is handled independently from the others, with separate session |
|
||||
IDs (``sid``\ s), event handlers and rooms. It is important that applications |
|
||||
that use multiple namespaces specify the correct namespace when setting up |
|
||||
their event handlers and rooms, using the optional ``namespace`` argument |
|
||||
available in all the methods in the :class:`socketio.Server` class. |
|
||||
|
|
||||
When the ``namespace`` argument is omitted, set to ``None`` or to ``'/'``, a |
|
||||
default namespace is used. |
|
||||
|
|
||||
Class-Based Namespaces |
|
||||
---------------------- |
|
||||
|
|
||||
As an alternative to the decorator-based event handlers, the event handlers |
|
||||
that belong to a namespace can be created as methods of a subclass of |
|
||||
:class:`socketio.Namespace`:: |
|
||||
|
|
||||
class MyCustomNamespace(socketio.Namespace): |
|
||||
def on_connect(self, sid, environ): |
|
||||
pass |
|
||||
|
|
||||
def on_disconnect(self, sid): |
|
||||
pass |
|
||||
|
|
||||
def on_my_event(self, sid, data): |
|
||||
self.emit('my_response', data) |
|
||||
|
|
||||
sio.register_namespace(MyCustomNamespace('/test')) |
|
||||
|
|
||||
For asyncio based severs, namespaces must inherit from |
|
||||
:class:`socketio.AsyncNamespace`, and can define event handlers as regular |
|
||||
methods or coroutines:: |
|
||||
|
|
||||
class MyCustomNamespace(socketio.AsyncNamespace): |
|
||||
def on_connect(self, sid, environ): |
|
||||
pass |
|
||||
|
|
||||
def on_disconnect(self, sid): |
|
||||
pass |
|
||||
|
|
||||
async def on_my_event(self, sid, data): |
|
||||
await self.emit('my_response', data) |
|
||||
|
|
||||
sio.register_namespace(MyCustomNamespace('/test')) |
|
||||
|
|
||||
When class-based namespaces are used, any events received by the server are |
|
||||
dispatched to a method named as the event name with the ``on_`` prefix. For |
|
||||
example, event ``my_event`` will be handled by a method named ``on_my_event``. |
|
||||
If an event is received for which there is no corresponding method defined in |
|
||||
the namespace class, then the event is ignored. All event names used in |
|
||||
class-based namespaces must used characters that are legal in method names. |
|
||||
|
|
||||
As a convenience to methods defined in a class-based namespace, the namespace |
|
||||
instance includes versions of several of the methods in the |
|
||||
:class:`socketio.Server` and :class:`socketio.AsyncServer` classes that default |
|
||||
to the proper namespace when the ``namespace`` argument is not given. |
|
||||
|
|
||||
In the case that an event has a handler in a class-based namespace, and also a |
|
||||
decorator-based function handler, only the standalone function handler is |
|
||||
invoked. |
|
||||
|
|
||||
It is important to note that class-based namespaces are singletons. This means |
|
||||
that a single instance of a namespace class is used for all clients, and |
|
||||
consequently, a namespace instance cannot be used to store client specific |
|
||||
information. |
|
||||
|
|
||||
Using a Message Queue |
|
||||
--------------------- |
|
||||
|
|
||||
The Socket.IO server owns the socket connections to all the clients, so it is |
|
||||
the only process that can emit events to them. Unfortunately this becomes a |
|
||||
limitation for many applications that use more than one process. A common need |
|
||||
is to emit events to clients from a process other than the server, for example |
|
||||
a `Celery <http://www.celeryproject.org/>`_ worker. |
|
||||
|
|
||||
To enable these auxiliary processes to emit events, the server can be |
|
||||
configured to listen for externally issued events on a message queue such as |
|
||||
`Redis <http://redis.io/>`_ or `RabbitMQ <https://www.rabbitmq.com/>`_. |
|
||||
Processes that need to emit events to client then post these events to the |
|
||||
queue. |
|
||||
|
|
||||
Another situation in which the use of a message queue is necessary is with |
|
||||
high traffic applications that work with large number of clients. To support |
|
||||
these clients, it may be necessary to horizontally scale the Socket.IO |
|
||||
server by splitting the client list among multiple server processes. In this |
|
||||
type of installation, each server processes owns the connections to a subset |
|
||||
of the clients. To make broadcasting work in this environment, the servers |
|
||||
communicate with each other through the message queue. |
|
||||
|
|
||||
Kombu |
|
||||
~~~~~ |
|
||||
|
|
||||
One of the messaging options offered by this package to access the message |
|
||||
queue is `Kombu <http://kombu.readthedocs.org/en/latest/>`_ , which means that |
|
||||
any message queue supported by this package can be used. Kombu can be installed |
|
||||
with pip:: |
|
||||
|
|
||||
pip install kombu |
|
||||
|
|
||||
To use RabbitMQ or other AMQP protocol compatible queues, that is the only |
|
||||
required dependency. But for other message queues, Kombu may require |
|
||||
additional packages. For example, to use a Redis queue, Kombu needs the Python |
|
||||
package for Redis installed as well:: |
|
||||
|
|
||||
pip install redis |
|
||||
|
|
||||
The appropriate message queue service, such as RabbitMQ or Redis, must also be |
|
||||
installed. To configure a Socket.IO server to connect to a Kombu queue, the |
|
||||
``client_manager`` argument must be passed in the server creation. The |
|
||||
following example instructs the server to connect to a Redis service running |
|
||||
on the same host and on the default port:: |
|
||||
|
|
||||
mgr = socketio.KombuManager('redis://') |
|
||||
sio = socketio.Server(client_manager=mgr) |
|
||||
|
|
||||
For a RabbitMQ queue also running on the local server with default |
|
||||
credentials, the configuration is as follows:: |
|
||||
|
|
||||
mgr = socketio.KombuManager('amqp://') |
|
||||
sio = socketio.Server(client_manager=mgr) |
|
||||
|
|
||||
The URL passed to the :class:`KombuManager` constructor is passed directly to |
|
||||
Kombu's `Connection object |
|
||||
<http://kombu.readthedocs.org/en/latest/userguide/connections.html>`_, so |
|
||||
the Kombu documentation should be consulted for information on how to |
|
||||
connect to the message queue appropriately. |
|
||||
|
|
||||
Note that Kombu currently does not support asyncio, so it cannot be used with |
|
||||
the :class:`socketio.AsyncServer` class. |
|
||||
|
|
||||
Redis |
|
||||
~~~~~ |
|
||||
|
|
||||
To use a Redis message queue, the Python package for Redis must also be |
|
||||
installed:: |
|
||||
|
|
||||
# WSGI server |
Indices and tables |
||||
pip install redis |
------------------ |
||||
|
|
||||
# asyncio server |
|
||||
pip install aioredis |
|
||||
|
|
||||
Native Redis support is accessed through the :class:`socketio.RedisManager` and |
|
||||
:class:`socketio.AsyncRedisManager` classes. These classes connect directly to |
|
||||
the Redis store and use the queue's pub/sub functionality:: |
|
||||
|
|
||||
# WSGI server |
|
||||
mgr = socketio.RedisManager('redis://') |
|
||||
sio = socketio.Server(client_manager=mgr) |
|
||||
|
|
||||
# asyncio server |
|
||||
mgr = socketio.AsyncRedisManager('redis://') |
|
||||
sio = socketio.AsyncServer(client_manager=mgr) |
|
||||
|
|
||||
Horizontal scaling |
|
||||
~~~~~~~~~~~~~~~~~~ |
|
||||
|
|
||||
If multiple Socket.IO servers are connected to the same message queue, they |
|
||||
automatically communicate with each other and manage a combined client list, |
|
||||
without any need for additional configuration. When a load balancer such as |
|
||||
nginx is used, this provides virtually unlimited scaling capabilities for the |
|
||||
server. |
|
||||
|
|
||||
Emitting from external processes |
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
||||
|
|
||||
To have a process other than a server connect to the queue to emit a message, |
|
||||
the same client manager classes can be used as standalone objects. In this |
|
||||
case, the ``write_only`` argument should be set to ``True`` to disable the |
|
||||
creation of a listening thread, which only makes sense in a server. For |
|
||||
example:: |
|
||||
|
|
||||
# connect to the redis queue through Kombu |
|
||||
external_sio = socketio.KombuManager('redis://', write_only=True) |
|
||||
|
|
||||
# emit an event |
|
||||
external_sio.emit('my event', data={'foo': 'bar'}, room='my room') |
|
||||
|
|
||||
Deployment |
|
||||
---------- |
|
||||
|
|
||||
The following sections describe a variety of deployment strategies for |
|
||||
Socket.IO servers. |
|
||||
|
|
||||
Sanic |
|
||||
~~~~~ |
|
||||
|
|
||||
`Sanic <http://sanic.readthedocs.io/>`_ is a very efficient asynchronous web |
|
||||
server for Python 3.5 and newer. |
|
||||
|
|
||||
Instances of class ``socketio.AsyncServer`` will automatically use Sanic for |
|
||||
asynchronous operations if the framework is installed. To request its use |
|
||||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|
||||
|
|
||||
sio = socketio.AsyncServer(async_mode='sanic') |
|
||||
|
|
||||
A server configured for aiohttp must be attached to an existing application:: |
|
||||
|
|
||||
app = web.Application() |
|
||||
sio.attach(app) |
|
||||
|
|
||||
The Sanic application can define regular routes that will coexist with the |
|
||||
Socket.IO server. A typical pattern is to add routes that serve a client |
|
||||
application and any associated static files. |
|
||||
|
|
||||
The Sanic application is then executed in the usual manner:: |
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
app.run() |
|
||||
|
|
||||
aiohttp |
|
||||
~~~~~~~ |
|
||||
|
|
||||
`Aiohttp <http://aiohttp.readthedocs.io/>`_ is a framework with support for HTTP |
|
||||
and WebSocket, based on asyncio. Support for this framework is limited to Python |
|
||||
3.5 and newer. |
|
||||
|
|
||||
Instances of class ``socketio.AsyncServer`` will automatically use aiohttp |
|
||||
for asynchronous operations if the library is installed. To request its use |
|
||||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|
||||
|
|
||||
sio = socketio.AsyncServer(async_mode='aiohttp') |
|
||||
|
|
||||
A server configured for aiohttp must be attached to an existing application:: |
|
||||
|
|
||||
app = web.Application() |
|
||||
sio.attach(app) |
|
||||
|
|
||||
The aiohttp application can define regular routes that will coexist with the |
|
||||
Socket.IO server. A typical pattern is to add routes that serve a client |
|
||||
application and any associated static files. |
|
||||
|
|
||||
The aiohttp application is then executed in the usual manner:: |
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
web.run_app(app) |
|
||||
|
|
||||
Tornado |
|
||||
~~~~~~~ |
|
||||
|
|
||||
`Tornado <http://www.tornadoweb.org//>`_ is a web framework with support |
|
||||
for HTTP and WebSocket. Support for this framework requires Python 3.5 and |
|
||||
newer. Only Tornado version 5 and newer are supported, thanks to its tight |
|
||||
integration with asyncio. |
|
||||
|
|
||||
Instances of class ``socketio.AsyncServer`` will automatically use tornado |
|
||||
for asynchronous operations if the library is installed. To request its use |
|
||||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|
||||
|
|
||||
sio = socketio.AsyncServer(async_mode='tornado') |
|
||||
|
|
||||
A server configured for tornado must include a request handler for |
|
||||
Engine.IO:: |
|
||||
|
|
||||
app = tornado.web.Application( |
|
||||
[ |
|
||||
(r"/socketio.io/", socketio.get_tornado_handler(sio)), |
|
||||
], |
|
||||
# ... other application options |
|
||||
) |
|
||||
|
|
||||
The tornado application can define other routes that will coexist with the |
|
||||
Socket.IO server. A typical pattern is to add routes that serve a client |
|
||||
application and any associated static files. |
|
||||
|
|
||||
The tornado application is then executed in the usual manner:: |
|
||||
|
|
||||
app.listen(port) |
|
||||
tornado.ioloop.IOLoop.current().start() |
|
||||
|
|
||||
Eventlet |
|
||||
~~~~~~~~ |
|
||||
|
|
||||
`Eventlet <http://eventlet.net/>`_ is a high performance concurrent networking |
|
||||
library for Python 2 and 3 that uses coroutines, enabling code to be written in |
|
||||
the same style used with the blocking standard library functions. An Socket.IO |
|
||||
server deployed with eventlet has access to the long-polling and WebSocket |
|
||||
transports. |
|
||||
|
|
||||
Instances of class ``socketio.Server`` will automatically use eventlet for |
|
||||
asynchronous operations if the library is installed. To request its use |
|
||||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|
||||
|
|
||||
sio = socketio.Server(async_mode='eventlet') |
|
||||
|
|
||||
A server configured for eventlet is deployed as a regular WSGI application, |
|
||||
using the provided ``socketio.Middleware``:: |
|
||||
|
|
||||
app = socketio.Middleware(sio) |
|
||||
import eventlet |
|
||||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
|
||||
|
|
||||
An alternative to running the eventlet WSGI server as above is to use |
|
||||
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
|
||||
command to launch the application under gunicorn is shown below:: |
|
||||
|
|
||||
$ gunicorn -k eventlet -w 1 module:app |
|
||||
|
|
||||
Due to limitations in its load balancing algorithm, gunicorn can only be used |
|
||||
with one worker process, so the ``-w`` option cannot be set to a value higher |
|
||||
than 1. A single eventlet worker can handle a large number of concurrent |
|
||||
clients, each handled by a greenlet. |
|
||||
|
|
||||
Eventlet provides a ``monkey_patch()`` function that replaces all the blocking |
|
||||
functions in the standard library with equivalent asynchronous versions. While |
|
||||
python-socketio does not require monkey patching, other libraries such as |
|
||||
database drivers are likely to require it. |
|
||||
|
|
||||
Gevent |
|
||||
~~~~~~ |
|
||||
|
|
||||
`Gevent <http://gevent.org/>`_ is another asynchronous framework based on |
|
||||
coroutines, very similar to eventlet. An Socket.IO server deployed with |
|
||||
gevent has access to the long-polling transport. If project |
|
||||
`gevent-websocket <https://bitbucket.org/Jeffrey/gevent-websocket/>`_ is |
|
||||
installed, the WebSocket transport is also available. |
|
||||
|
|
||||
Instances of class ``socketio.Server`` will automatically use gevent for |
|
||||
asynchronous operations if the library is installed and eventlet is not |
|
||||
installed. To request gevent to be selected explicitly, the ``async_mode`` |
|
||||
option can be given in the constructor:: |
|
||||
|
|
||||
sio = socketio.Server(async_mode='gevent') |
|
||||
|
|
||||
A server configured for gevent is deployed as a regular WSGI application, |
|
||||
using the provided ``socketio.Middleware``:: |
|
||||
|
|
||||
app = socketio.Middleware(sio) |
|
||||
from gevent import pywsgi |
|
||||
pywsgi.WSGIServer(('', 8000), app).serve_forever() |
|
||||
|
|
||||
If the WebSocket transport is installed, then the server must be started as |
|
||||
follows:: |
|
||||
|
|
||||
from gevent import pywsgi |
|
||||
from geventwebsocket.handler import WebSocketHandler |
|
||||
app = socketio.Middleware(sio) |
|
||||
pywsgi.WSGIServer(('', 8000), app, |
|
||||
handler_class=WebSocketHandler).serve_forever() |
|
||||
|
|
||||
An alternative to running the gevent WSGI server as above is to use |
|
||||
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
|
||||
command to launch the application under gunicorn is shown below:: |
|
||||
|
|
||||
$ gunicorn -k gevent -w 1 module:app |
|
||||
|
|
||||
Or to include WebSocket:: |
|
||||
|
|
||||
$ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app |
|
||||
|
|
||||
Same as with eventlet, due to limitations in its load balancing algorithm, |
|
||||
gunicorn can only be used with one worker process, so the ``-w`` option cannot |
|
||||
be higher than 1. A single gevent worker can handle a large number of |
|
||||
concurrent clients through the use of greenlets. |
|
||||
|
|
||||
Gevent provides a ``monkey_patch()`` function that replaces all the blocking |
|
||||
functions in the standard library with equivalent asynchronous versions. While |
|
||||
python-socketio does not require monkey patching, other libraries such as |
|
||||
database drivers are likely to require it. |
|
||||
|
|
||||
Gevent with uWSGI |
|
||||
~~~~~~~~~~~~~~~~~ |
|
||||
|
|
||||
When using the uWSGI server in combination with gevent, the Socket.IO server |
|
||||
can take advantage of uWSGI's native WebSocket support. |
|
||||
|
|
||||
Instances of class ``socketio.Server`` will automatically use this option for |
|
||||
asynchronous operations if both gevent and uWSGI are installed and eventlet is |
|
||||
not installed. To request this asynchronous mode explicitly, the |
|
||||
``async_mode`` option can be given in the constructor:: |
|
||||
|
|
||||
# gevent with uWSGI |
|
||||
sio = socketio.Server(async_mode='gevent_uwsgi') |
|
||||
|
|
||||
A complete explanation of the configuration and usage of the uWSGI server is |
|
||||
beyond the scope of this documentation. The uWSGI server is a fairly complex |
|
||||
package that provides a large and comprehensive set of options. It must be |
|
||||
compiled with WebSocket and SSL support for the WebSocket transport to be |
|
||||
available. As way of an introduction, the following command starts a uWSGI |
|
||||
server for the ``latency.py`` example on port 5000:: |
|
||||
|
|
||||
$ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app |
|
||||
|
|
||||
Standard Threading Library |
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
||||
|
|
||||
While not comparable to eventlet and gevent in terms of performance, |
|
||||
the Socket.IO server can also be configured to work with multi-threaded web |
|
||||
servers that use standard Python threads. This is an ideal setup to use with |
|
||||
development servers such as `Werkzeug <http://werkzeug.pocoo.org>`_. Only the |
|
||||
long-polling transport is currently available when using standard threads. |
|
||||
|
|
||||
Instances of class ``socketio.Server`` will automatically use the threading |
|
||||
mode if neither eventlet nor gevent are not installed. To request the |
|
||||
threading mode explicitly, the ``async_mode`` option can be given in the |
|
||||
constructor:: |
|
||||
|
|
||||
sio = socketio.Server(async_mode='threading') |
|
||||
|
|
||||
A server configured for threading is deployed as a regular web application, |
|
||||
using any WSGI complaint multi-threaded server. The example below deploys an |
|
||||
Socket.IO application combined with a Flask web application, using Flask's |
|
||||
development web server based on Werkzeug:: |
|
||||
|
|
||||
sio = socketio.Server(async_mode='threading') |
|
||||
app = Flask(__name__) |
|
||||
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app) |
|
||||
|
|
||||
# ... Socket.IO and Flask handler functions ... |
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
app.run(threaded=True) |
|
||||
|
|
||||
When using the threading mode, it is important to ensure that the WSGI server |
|
||||
can handle multiple concurrent requests using threads, since a client can have |
|
||||
up to two outstanding requests at any given time. The Werkzeug server is |
|
||||
single-threaded by default, so the ``threaded=True`` option is required. |
|
||||
|
|
||||
Note that servers that use worker processes instead of threads, such as |
|
||||
gunicorn, do not support a Socket.IO server configured in threading mode. |
|
||||
|
|
||||
Multi-process deployments |
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
||||
|
|
||||
Socket.IO is a stateful protocol, which makes horizontal scaling more |
|
||||
difficult. To deploy a cluster of Socket.IO processes (hosted on one or |
|
||||
multiple servers), the following conditions must be met: |
|
||||
|
|
||||
- Each Socket.IO process must be able to handle multiple requests, either by |
|
||||
using asyncio, eventlet, gevent, or standard threads. Worker processes that |
|
||||
only handle one request at a time are not supported. |
|
||||
- The load balancer must be configured to always forward requests from a |
|
||||
client to the same worker process. Load balancers call this *sticky |
|
||||
sessions*, or *session affinity*. |
|
||||
- The worker processes communicate with each other through a message queue, |
|
||||
which must be installed and configured. See the section on using message |
|
||||
queues above for instructions. |
|
||||
|
|
||||
API Reference |
|
||||
------------- |
|
||||
|
|
||||
.. module:: socketio |
|
||||
|
|
||||
.. autoclass:: Middleware |
|
||||
:members: |
|
||||
|
|
||||
.. autoclass:: Server |
|
||||
:members: |
|
||||
|
|
||||
.. autoclass:: AsyncServer |
|
||||
:members: |
|
||||
:inherited-members: |
|
||||
|
|
||||
.. autoclass:: Namespace |
|
||||
:members: |
|
||||
|
|
||||
.. autoclass:: AsyncNamespace |
|
||||
:members: |
|
||||
:inherited-members: |
|
||||
|
|
||||
.. autoclass:: BaseManager |
|
||||
:members: |
|
||||
|
|
||||
.. autoclass:: PubSubManager |
|
||||
:members: |
|
||||
|
|
||||
.. autoclass:: KombuManager |
|
||||
:members: |
|
||||
|
|
||||
.. autoclass:: RedisManager |
|
||||
:members: |
|
||||
|
|
||||
.. autoclass:: AsyncManager |
|
||||
:members: |
|
||||
:inherited-members: |
|
||||
|
|
||||
.. autoclass:: AsyncRedisManager |
* :ref:`genindex` |
||||
:members: |
* :ref:`modindex` |
||||
|
* :ref:`search` |
||||
|
@ -0,0 +1,148 @@ |
|||||
|
.. socketio documentation master file, created by |
||||
|
sphinx-quickstart on Sat Jun 13 23:41:23 2015. |
||||
|
You can adapt this file completely to your liking, but it should at least |
||||
|
contain the root `toctree` directive. |
||||
|
|
||||
|
Getting Started |
||||
|
=============== |
||||
|
|
||||
|
What is Socket.IO? |
||||
|
------------------ |
||||
|
|
||||
|
Socket.IO is a transport protocol that enables real-time bidirectional |
||||
|
event-based communication between clients (typically web browsers or |
||||
|
smartphones) and a server. There are Socket.IO clients and servers implemented |
||||
|
in a variety of languages, including JavaScript, Python, C++, Swift, C# and |
||||
|
PHP. |
||||
|
|
||||
|
Features |
||||
|
-------- |
||||
|
|
||||
|
- Fully compatible with the |
||||
|
`Javascript <https://github.com/Automattic/socket.io-client>`_, |
||||
|
`Swift <https://github.com/socketio/socket.io-client-swift>`_, |
||||
|
`C++ <https://github.com/socketio/socket.io-client-cpp>`_ and |
||||
|
`Java <https://github.com/socketio/socket.io-client-java>`_ official |
||||
|
Socket.IO clients, plus any third party clients that comply with the |
||||
|
Socket.IO specification. |
||||
|
- Compatible with Python 2.7 and Python 3.3+. |
||||
|
- Supports large number of clients even on modest hardware due to being |
||||
|
asynchronous, even when asyncio is not used. |
||||
|
- Compatible with `aiohttp <http://aiohttp.readthedocs.io/>`_, |
||||
|
`sanic <http://sanic.readthedocs.io/>`_, |
||||
|
`tornado <http://www.tornadoweb.org/>`_, |
||||
|
`eventlet <http://eventlet.net/>`_, |
||||
|
`gevent <http://gevent.org>`_, |
||||
|
or any `WSGI <https://wsgi.readthedocs.io/en/latest/index.html>`_ or |
||||
|
`ASGI <https://asgi.readthedocs.io/en/latest/>`_ compatible server. |
||||
|
- Includes WSGI and ASGI middlewares that integrate Socket.IO traffic with |
||||
|
other web applications. |
||||
|
- Broadcasting of messages to all connected clients, or to subsets of them |
||||
|
assigned to "rooms". |
||||
|
- Optional support for multiple servers, connected through a messaging queue |
||||
|
such as Redis or RabbitMQ. |
||||
|
- Send messages to clients from external processes, such as Celery workers or |
||||
|
auxiliary scripts. |
||||
|
- Event-based architecture implemented with decorators that hides the details |
||||
|
of the protocol. |
||||
|
- Support for HTTP long-polling and WebSocket transports. |
||||
|
- Support for XHR2 and XHR browsers. |
||||
|
- Support for text and binary messages. |
||||
|
- Support for gzip and deflate HTTP compression. |
||||
|
- Configurable CORS responses, to avoid cross-origin problems with browsers. |
||||
|
|
||||
|
Examples |
||||
|
-------- |
||||
|
|
||||
|
The Socket.IO server can be installed with pip:: |
||||
|
|
||||
|
pip install python-socketio |
||||
|
|
||||
|
The following is a basic example of a Socket.IO server that uses the |
||||
|
`aiohttp <http://aiohttp.readthedocs.io/>`_ framework for asyncio (Python 3.5+ |
||||
|
only): |
||||
|
|
||||
|
.. code:: python |
||||
|
|
||||
|
from aiohttp import web |
||||
|
import socketio |
||||
|
|
||||
|
sio = socketio.AsyncServer() |
||||
|
app = web.Application() |
||||
|
sio.attach(app) |
||||
|
|
||||
|
async def index(request): |
||||
|
"""Serve the client-side application.""" |
||||
|
with open('index.html') as f: |
||||
|
return web.Response(text=f.read(), content_type='text/html') |
||||
|
|
||||
|
@sio.on('connect', namespace='/chat') |
||||
|
def connect(sid, environ): |
||||
|
print("connect ", sid) |
||||
|
|
||||
|
@sio.on('chat message', namespace='/chat') |
||||
|
async def message(sid, data): |
||||
|
print("message ", data) |
||||
|
await sio.emit('reply', room=sid) |
||||
|
|
||||
|
@sio.on('disconnect', namespace='/chat') |
||||
|
def disconnect(sid): |
||||
|
print('disconnect ', sid) |
||||
|
|
||||
|
app.router.add_static('/static', 'static') |
||||
|
app.router.add_get('/', index) |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
web.run_app(app) |
||||
|
|
||||
|
And below is a similar example, but using Flask and Eventlet. This example is |
||||
|
compatible with Python 2.7 and 3.3+:: |
||||
|
|
||||
|
import socketio |
||||
|
import eventlet |
||||
|
from flask import Flask, render_template |
||||
|
|
||||
|
sio = socketio.Server() |
||||
|
app = Flask(__name__) |
||||
|
|
||||
|
@app.route('/') |
||||
|
def index(): |
||||
|
"""Serve the client-side application.""" |
||||
|
return render_template('index.html') |
||||
|
|
||||
|
@sio.on('connect') |
||||
|
def connect(sid, environ): |
||||
|
print('connect ', sid) |
||||
|
|
||||
|
@sio.on('my message') |
||||
|
def message(sid, data): |
||||
|
print('message ', data) |
||||
|
|
||||
|
@sio.on('disconnect') |
||||
|
def disconnect(sid): |
||||
|
print('disconnect ', sid) |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
# wrap Flask application with socketio's middleware |
||||
|
app = socketio.WSGIApp(sio, app) |
||||
|
|
||||
|
# deploy as an eventlet WSGI server |
||||
|
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
||||
|
|
||||
|
The client-side application must include the |
||||
|
`socket.io-client <https://github.com/Automattic/socket.io-client>`_ library |
||||
|
(versions 1.3.5 or newer recommended). |
||||
|
|
||||
|
Each time a client connects to the server the ``connect`` event handler is |
||||
|
invoked with the ``sid`` (session ID) assigned to the connection and the WSGI |
||||
|
environment dictionary. The server can inspect authentication or other headers |
||||
|
to decide if the client is allowed to connect. To reject a client the handler |
||||
|
must return ``False``. |
||||
|
|
||||
|
When the client sends an event to the server, the appropriate event handler is |
||||
|
invoked with the ``sid`` and the message, which can be a single or multiple |
||||
|
arguments. The application can define as many events as needed and associate |
||||
|
them with event handlers. An event is defined simply by a name. |
||||
|
|
||||
|
When a connection with a client is broken, the ``disconnect`` event is called, |
||||
|
allowing the application to perform cleanup. |
Loading…
Reference in new issue