From f839a4cbe24644500efad287cce24c38a058a36c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 23 May 2021 23:24:34 +0100 Subject: [PATCH] Added index to documentation (Fixes #724) --- docs/Makefile | 176 +--- docs/_static/README.md | 1 + docs/_static/custom.css | 5 + docs/_static/index.html | 12 - docs/_static/logo.png | Bin 15963 -> 0 bytes docs/_themes/LICENSE | 37 - docs/_themes/README | 31 - docs/_themes/flask/layout.html | 24 - docs/_themes/flask/relations.html | 19 - docs/_themes/flask/static/flasky.css_t | 577 ------------ docs/_themes/flask/theme.conf | 9 - docs/_themes/flask_small/layout.html | 22 - docs/_themes/flask_small/static/flasky.css_t | 287 ------ docs/_themes/flask_small/theme.conf | 10 - docs/_themes/flask_theme_support.py | 86 -- docs/api.rst | 17 + docs/conf.py | 279 +++--- docs/deployment.rst | 272 ++++++ docs/getting_started.rst | 381 ++++++++ docs/implementation_notes.rst | 112 +++ docs/index.rst | 893 +------------------ docs/intro.rst | 73 ++ docs/make.bat | 225 +---- docs/upgrading.rst | 25 + 24 files changed, 1019 insertions(+), 2554 deletions(-) create mode 100644 docs/_static/README.md create mode 100644 docs/_static/custom.css delete mode 100644 docs/_static/index.html delete mode 100644 docs/_static/logo.png delete mode 100644 docs/_themes/LICENSE delete mode 100644 docs/_themes/README delete mode 100644 docs/_themes/flask/layout.html delete mode 100644 docs/_themes/flask/relations.html delete mode 100644 docs/_themes/flask/static/flasky.css_t delete mode 100644 docs/_themes/flask/theme.conf delete mode 100644 docs/_themes/flask_small/layout.html delete mode 100644 docs/_themes/flask_small/static/flasky.css_t delete mode 100644 docs/_themes/flask_small/theme.conf delete mode 100644 docs/_themes/flask_theme_support.py create mode 100644 docs/api.rst create mode 100644 docs/deployment.rst create mode 100644 docs/getting_started.rst create mode 100644 docs/implementation_notes.rst create mode 100644 docs/intro.rst create mode 100644 docs/upgrading.rst diff --git a/docs/Makefile b/docs/Makefile index c25cc342..298ea9e2 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,177 +1,19 @@ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build -PAPER = +SOURCEDIR = . BUILDDIR = _build -# User-friendly check for sphinx-build -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 gettext - +# Put it first so that "make" without argument is like "make help". help: - @echo "Please use \`make ' where is one of" - @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 " 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)" - -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/Flask-SocketIO.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-SocketIO.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-SocketIO" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-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." + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." +.PHONY: help Makefile -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/_static/README.md b/docs/_static/README.md new file mode 100644 index 00000000..0d94aa7d --- /dev/null +++ b/docs/_static/README.md @@ -0,0 +1 @@ +Place static files used by the documentation here. diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 00000000..9a862943 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,5 @@ +div.sphinxsidebar { + max-height: calc(100% - 30px); + overflow-y: auto; + overflow-x: hidden; +} diff --git a/docs/_static/index.html b/docs/_static/index.html deleted file mode 100644 index 6f8bab2b..00000000 --- a/docs/_static/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - Flask-SocketIO documentation - - - - - The Flask-SocketIO documentation is available at Read the Docs. - If your browser does not automatically redirect you, please click here. - - diff --git a/docs/_static/logo.png b/docs/_static/logo.png deleted file mode 100644 index 6f911038a830ec6cc0654b9c2e4a1fea3789026f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15963 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 diff --git a/docs/_themes/LICENSE b/docs/_themes/LICENSE deleted file mode 100644 index 8daab7ee..00000000 --- a/docs/_themes/LICENSE +++ /dev/null @@ -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. diff --git a/docs/_themes/README b/docs/_themes/README deleted file mode 100644 index b3292bdf..00000000 --- a/docs/_themes/README +++ /dev/null @@ -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 diff --git a/docs/_themes/flask/layout.html b/docs/_themes/flask/layout.html deleted file mode 100644 index 19c43fbb..00000000 --- a/docs/_themes/flask/layout.html +++ /dev/null @@ -1,24 +0,0 @@ -{%- extends "basic/layout.html" %} -{%- block extrahead %} - {{ super() }} - {% if theme_touch_icon %} - - {% endif %} - -{% endblock %} -{%- block relbar2 %}{% endblock %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{%- block footer %} - - {% if pagename == 'index' %} -
- {% endif %} -{%- endblock %} diff --git a/docs/_themes/flask/relations.html b/docs/_themes/flask/relations.html deleted file mode 100644 index 3bbcde85..00000000 --- a/docs/_themes/flask/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -

Related Topics

- diff --git a/docs/_themes/flask/static/flasky.css_t b/docs/_themes/flask/static/flasky.css_t deleted file mode 100644 index 5906e751..00000000 --- a/docs/_themes/flask/static/flasky.css_t +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/docs/_themes/flask/theme.conf b/docs/_themes/flask/theme.conf deleted file mode 100644 index 18c720f8..00000000 --- a/docs/_themes/flask/theme.conf +++ /dev/null @@ -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 = diff --git a/docs/_themes/flask_small/layout.html b/docs/_themes/flask_small/layout.html deleted file mode 100644 index aa1716aa..00000000 --- a/docs/_themes/flask_small/layout.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "basic/layout.html" %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{% block footer %} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{# do not display relbars #} -{% block relbar1 %}{% endblock %} -{% block relbar2 %} - {% if theme_github_fork %} - Fork me on GitHub - {% endif %} -{% endblock %} -{% block sidebar1 %}{% endblock %} -{% block sidebar2 %}{% endblock %} diff --git a/docs/_themes/flask_small/static/flasky.css_t b/docs/_themes/flask_small/static/flasky.css_t deleted file mode 100644 index fe2141c5..00000000 --- a/docs/_themes/flask_small/static/flasky.css_t +++ /dev/null @@ -1,287 +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; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; -} - -a:hover tt { - background: #EEE; -} diff --git a/docs/_themes/flask_small/theme.conf b/docs/_themes/flask_small/theme.conf deleted file mode 100644 index 542b4625..00000000 --- a/docs/_themes/flask_small/theme.conf +++ /dev/null @@ -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 = '' diff --git a/docs/_themes/flask_theme_support.py b/docs/_themes/flask_theme_support.py deleted file mode 100644 index 33f47449..00000000 --- a/docs/_themes/flask_theme_support.py +++ /dev/null @@ -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' - } diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 00000000..2e940129 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,17 @@ +API Reference +============= + +.. module:: flask_socketio +.. autoclass:: SocketIO + :members: +.. autofunction:: emit +.. autofunction:: send +.. autofunction:: join_room +.. autofunction:: leave_room +.. autofunction:: close_room +.. autofunction:: rooms +.. autofunction:: disconnect +.. autoclass:: Namespace + :members: +.. autoclass:: SocketIOTestClient + :members: diff --git a/docs/conf.py b/docs/conf.py index 10f85185..fdc35de2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,264 +1,185 @@ # -*- coding: utf-8 -*- # -# Flask-SocketIO documentation build configuration file, created by -# sphinx-quickstart on Sun Feb 9 12:36:23 2014. +# Configuration file for the Sphinx documentation builder. # -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config -import sys -import os +# -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) -sys.path.append(os.path.abspath('_themes')) +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Flask-SocketIO' +copyright = '2018, Miguel Grinberg' +author = 'Miguel Grinberg' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '' -# -- General configuration ------------------------------------------------ + +# -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc'] +extensions = [ + 'sphinx.ext.autodoc', +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# The suffix of source filenames. +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] source_suffix = '.rst' -# The encoding of source files. -#source_encoding = 'utf-8-sig' - # The master toctree document. master_doc = 'index' -# General information about the project. -project = u'Flask-SocketIO' -copyright = u'2014, Miguel Grinberg' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -#version = '0.1.0' -# The full version, including alpha/beta/rc tags. -#release = '0.1.0' - # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = None -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- +# -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'flask_small' +# +html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. +# html_theme_options = { - 'index_logo': 'logo.png', - 'github_fork': 'miguelgrinberg/Flask-SocketIO' -} - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_themes'] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None + 'github_user': 'miguelgrinberg', + 'github_repo': 'flask-socketio', + 'github_banner': True, + 'github_button': True, + 'github_type': 'star', + 'fixed_sidebar': True, -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None +} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'Flask-SocketIOdoc' +htmlhelp_basename = 'flask-socketiodoc' -# -- Options for LaTeX output --------------------------------------------- +# -- Options for LaTeX output ------------------------------------------------ latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'Flask-SocketIO.tex', u'Flask-SocketIO Documentation', - u'Miguel Grinberg', 'manual'), + (master_doc, 'flask-socketio.tex', 'flask-socketio Documentation', + 'Miguel Grinberg', 'manual'), ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - -# -- Options for manual page output --------------------------------------- +# -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'flask-socketio', u'Flask-SocketIO Documentation', - [u'Miguel Grinberg'], 1) + (master_doc, 'flask-socketio', 'flask-socketio Documentation', + [author], 1) ] -# If true, show URL addresses after external links. -#man_show_urls = False - -# -- Options for Texinfo output ------------------------------------------- +# -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Flask-SocketIO', u'Flask-SocketIO Documentation', - u'Miguel Grinberg', 'Flask-SocketIO', 'One line description of project.', - 'Miscellaneous'), + (master_doc, 'flask-socketio', 'flask-socketio Documentation', + author, 'flask-socketio', 'One line description of project.', + 'Miscellaneous'), ] -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] -# If false, no module index is generated. -#texinfo_domain_indices = True +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False -autodoc_member_order = 'bysource' +# -- Extension configuration ------------------------------------------------- diff --git a/docs/deployment.rst b/docs/deployment.rst new file mode 100644 index 00000000..cb0841c6 --- /dev/null +++ b/docs/deployment.rst @@ -0,0 +1,272 @@ +Deployment +---------- + +There are many options to deploy a Flask-SocketIO server, ranging from simple +to the insanely complex. In this section, the most commonly used options are +described. + +Embedded Server +~~~~~~~~~~~~~~~ + +The simplest deployment strategy is to start the web server by calling +``socketio.run(app)`` as shown in examples above. This will look through the +packages that are installed for the best available web server start the +application on it. The current web server choices that are evaluated are +``eventlet``, ``gevent`` and the Flask development server. + +If eventlet or gevent are available, ``socketio.run(app)`` starts a +production-ready server using one of these frameworks. If neither of these are +installed, then the Flask development web server is used, and in this case the +server is not intended to be used in a production deployment. + +Unfortunately this option is not available when using gevent with uWSGI. See +the uWSGI section below for information on this option. + +Gunicorn Web Server +~~~~~~~~~~~~~~~~~~~ + +An alternative to ``socketio.run(app)`` is to use +`gunicorn `_ as web server, using the eventlet or gevent +workers. For this option, eventlet or gevent need to be installed, in addition +to gunicorn. The command line that starts the eventlet server via gunicorn is:: + + gunicorn --worker-class eventlet -w 1 module:app + +If you prefer to use gevent, the command to start the server is:: + + gunicorn -k gevent -w 1 module:app + +When using gunicorn with the gevent worker and the WebSocket support provided +by gevent-websocket, the command that starts the server must be changed to +select a custom gevent web server that supports the WebSocket protocol. The +modified command is:: + + gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module:app + +A third option with Gunicorn is to use the threaded worker, along with the +`simple-websocket `_. + +uWSGI Web Server +~~~~~~~~~~~~~~~~ + +When using the uWSGI server in combination with gevent, the Socket.IO server +can take advantage of uWSGI’s native WebSocket support. + +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 example application app.py on port 5000:: + + $ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file app.py --callable app + +Using nginx as a WebSocket Reverse Proxy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is possible to use nginx as a front-end reverse proxy that passes requests +to the application. However, only releases of nginx 1.4 and newer support +proxying of the WebSocket protocol. Below is a basic nginx configuration that +proxies HTTP and WebSocket requests:: + + server { + listen 80; + server_name _; + + location / { + include proxy_params; + proxy_pass http://127.0.0.1:5000; + } + + location /static { + alias /static; + expires 30d; + } + + location /socket.io { + include proxy_params; + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass http://127.0.0.1:5000/socket.io; + } + } + +The next example adds the support for load balancing multiple Socket.IO +servers:: + + upstream socketio_nodes { + ip_hash; + + server 127.0.0.1:5000; + server 127.0.0.1:5001; + server 127.0.0.1:5002; + # to scale the app, just add more nodes here! + } + + server { + listen 80; + server_name _; + + location / { + include proxy_params; + proxy_pass http://127.0.0.1:5000; + } + + location /static { + alias /static; + expires 30d; + } + + location /socket.io { + include proxy_params; + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass http://socketio_nodes/socket.io; + } + } + +While the above examples can work as an initial configuration, be aware that a +production install of nginx will need a more complete configuration covering +other deployment aspects such as SSL support. + +Using Multiple Workers +~~~~~~~~~~~~~~~~~~~~~~ + +Flask-SocketIO supports multiple workers behind a load balancer starting with +release 2.0. Deploying multiple workers gives applications that use +Flask-SocketIO the ability to spread the client connections among multiple +processes and hosts, and in this way scale to support very large numbers of +concurrent clients. + +There are two requirements to use multiple Flask-SocketIO workers: + +- The load balancer must be configured to forward all HTTP requests from a + given client always to the same worker. This is sometimes referenced as + "sticky sessions". For nginx, use the ``ip_hash`` directive to achieve this. + Gunicorn cannot be used with multiple workers because its load balancer + algorithm does not support sticky sessions. + +- Since each of the servers owns only a subset of the client connections, a + message queue such as Redis or RabbitMQ is used by the servers to coordinate + complex operations such as broadcasting and rooms. + +When working with a message queue, there are additional dependencies that need to +be installed: + +- For Redis, the package ``redis`` must be installed (``pip install redis``). +- For RabbitMQ, the package ``kombu`` must be installed (``pip install kombu``). +- For Kafka, the package ``kafka-python`` must be installed (``pip install kafka-python``). +- For other message queues supported by Kombu, see the `Kombu documentation + `_ + to find out what dependencies are needed. +- If eventlet or gevent are used, then monkey patching the Python standard + library is normally required to force the message queue package to use + coroutine friendly functions and classes. + +For eventlet, monkey patching is done with:: + + import eventlet + eventlet.monkey_patch() + +For gevent, you can monkey patch the standard library with:: + + from gevent import monkey + monkey.patch_all() + +In both cases it is recommended that you apply the monkey patching at the top +of your main script, even above your imports. + +To start multiple Flask-SocketIO servers, you must first ensure you have the +message queue service running. To start a Socket.IO server and have it connect to +the message queue, add the ``message_queue`` argument to the ``SocketIO`` +constructor:: + + socketio = SocketIO(app, message_queue='redis://') + +The value of the ``message_queue`` argument is the connection URL of the +queue service that is used. For a redis queue running on the same host as the +server, the ``'redis://'`` URL can be used. Likewise, for a default RabbitMQ +queue the ``'amqp://'`` URL can be used. For Kafka, use a ``kafka://`` URL. +The Kombu package has a `documentation +section `_ +that describes the format of the URLs for all the supported queues. + +Emitting from an External Process +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For many types of applications, it is necessary to emit events from a process +that is not the SocketIO server, for an example a Celery worker. If the +SocketIO server or servers are configured to listen on a message queue as +shown in the previous section, then any other process can create its own +``SocketIO`` instance and use it to emit events in the same way the server +does. + +For example, for an application that runs on an eventlet web server and uses +a Redis message queue, the following Python script broadcasts an event to +all clients:: + + socketio = SocketIO(message_queue='redis://') + socketio.emit('my event', {'data': 'foo'}, namespace='/test') + +When using the ``SocketIO`` instance in this way, the Flask application +instance is not passed to the constructor. + +The ``channel`` argument to ``SocketIO`` can be used to select a specific +channel of communication through the message queue. Using a custom channel +name is necessary when there are multiple independent SocketIO services +sharing the same queue. + +Flask-SocketIO does not apply monkey patching when eventlet or gevent are +used. But when working with a message queue, it is very likely that the Python +package that talks to the message queue service will hang if the Python +standard library is not monkey patched. + +It is important to note that an external process that wants to connect to +a SocketIO server does not need to use eventlet or gevent like the main +server. Having a server use a coroutine framework, while an external process +is not a problem. For example, Celery workers do not need to be +configured to use eventlet or gevent just because the main server does. But if +your external process does use a coroutine framework for whatever reason, then +monkey patching is likely required, so that the message queue accesses +coroutine friendly functions and classes. + +Cross-Origin Controls +~~~~~~~~~~~~~~~~~~~~~ + +For security reasons, this server enforces a same-origin policy by default. In +practical terms, this means the following: + +- If an incoming HTTP or WebSocket request includes the ``Origin`` header, + this header must match the scheme and host of the connection URL. In case + of a mismatch, a 400 status code response is returned and the connection is + rejected. +- No restrictions are imposed on incoming requests that do not include the + ``Origin`` header. + +If necessary, the ``cors_allowed_origins`` option can be used to allow other +origins. This argument can be set to a string to set a single allowed origin, or +to a list to allow multiple origins. A special value of ``'*'`` can be used to +instruct the server to allow all origins, but this should be done with care, as +this could make the server vulnerable to Cross-Site Request Forgery (CSRF) +attacks. diff --git a/docs/getting_started.rst b/docs/getting_started.rst new file mode 100644 index 00000000..667d4ac6 --- /dev/null +++ b/docs/getting_started.rst @@ -0,0 +1,381 @@ +Getting Started +=============== + +Initialization +-------------- + +The following code example shows how to add Flask-SocketIO to a Flask +application:: + + from flask import Flask, render_template + from flask_socketio import SocketIO + + app = Flask(__name__) + app.config['SECRET_KEY'] = 'secret!' + socketio = SocketIO(app) + + if __name__ == '__main__': + socketio.run(app) + +The ``init_app()`` style of initialization is also supported. To start the +web server simply execute your script. Note the way the web server is started. +The ``socketio.run()`` function encapsulates the start up of the web server and +replaces the ``app.run()`` standard Flask development server start up. When the +application is in debug mode the Werkzeug development server is still used and +configured properly inside ``socketio.run()``. In production mode the eventlet +web server is used if available, else the gevent web server is used. If +eventlet and gevent are not installed, the Werkzeug development web server is +used. + +The ``flask run`` command introduced in Flask 0.11 can be used to start a +Flask-SocketIO development server based on Werkzeug, but this method of starting +the Flask-SocketIO server is not recommended due to lack of WebSocket support. +Previous versions of this package included a customized version of the +``flask run`` command that allowed the use of WebSocket on eventlet and gevent +production servers, but this functionality has been discontinued in favor of the +``socketio.run(app)`` startup method shown above which is more robust. + +The application must serve a page to the client that loads the Socket.IO +library and establishes a connection:: + + + + +Receiving Messages +------------------ + +When using SocketIO, messages are received by both parties as events. On the +client side Javascript callbacks are used. With Flask-SocketIO the server +needs to register handlers for these events, similarly to how routes are +handled by view functions. + +The following example creates a server-side event handler for an unnamed +event:: + + @socketio.on('message') + def handle_message(data): + print('received message: ' + data) + +The above example uses string messages. Another type of unnamed events use +JSON data:: + + @socketio.on('json') + def handle_json(json): + print('received json: ' + str(json)) + +The most flexible type of event uses custom event names. The message data for +these events can be string, bytes, int, or JSON:: + + @socketio.on('my event') + def handle_my_custom_event(json): + print('received json: ' + str(json)) + +Custom named events can also support multiple arguments:: + + @socketio.on('my_event') + def handle_my_custom_event(arg1, arg2, arg3): + print('received args: ' + arg1 + arg2 + arg3) + +When the name of the event is a valid Python identifier that does not collide +with other defined symbols, the ``@socketio.event`` decorator provides a more +compact syntax that takes the event name from the decorated function:: + + @socketio.event + def my_custom_event(arg1, arg2, arg3): + print('received args: ' + arg1 + arg2 + arg3) + +Named events are the most flexible, as they eliminate the need to include +additional metadata to describe the message type. The names ``message``, +``json``, ``connect`` and ``disconnect`` are reserved and cannot be used for +named events. + +Flask-SocketIO also supports SocketIO namespaces, which allow the client to +multiplex several independent connections on the same physical socket:: + + @socketio.on('my event', namespace='/test') + def handle_my_custom_namespace_event(json): + print('received json: ' + str(json)) + +When a namespace is not specified a default global namespace with the name +``'/'`` is used. + +For cases when a decorator syntax isn't convenient, the ``on_event`` method +can be used:: + + def my_function_handler(data): + pass + + socketio.on_event('my event', my_function_handler, namespace='/test') + +Clients may request an acknowledgement callback that confirms receipt of a +message they sent. Any values returned from the handler function will be +passed to the client as arguments in the callback function:: + + @socketio.on('my event') + def handle_my_custom_event(json): + print('received json: ' + str(json)) + return 'one', 2 + +In the above example, the client callback function will be invoked with +two arguments, ``'one'`` and ``2``. If a handler function does not return any +values, the client callback function will be invoked without arguments. + +Sending Messages +---------------- + +SocketIO event handlers defined as shown in the previous section can send +reply messages to the connected client using the ``send()`` and ``emit()`` +functions. + +The following examples bounce received events back to the client that sent +them:: + + from flask_socketio import send, emit + + @socketio.on('message') + def handle_message(message): + send(message) + + @socketio.on('json') + def handle_json(json): + send(json, json=True) + + @socketio.on('my event') + def handle_my_custom_event(json): + emit('my response', json) + +Note how ``send()`` and ``emit()`` are used for unnamed and named events +respectively. + +When working with namespaces, ``send()`` and ``emit()`` use the namespace of +the incoming message by default. A different namespace can be specified with +the optional ``namespace`` argument:: + + @socketio.on('message') + def handle_message(message): + send(message, namespace='/chat') + + @socketio.on('my event') + def handle_my_custom_event(json): + emit('my response', json, namespace='/chat') + +To send an event with multiple arguments, send a tuple:: + + @socketio.on('my event') + def handle_my_custom_event(json): + emit('my response', ('foo', 'bar', json), namespace='/chat') + +SocketIO supports acknowledgment callbacks that confirm that a message was +received by the client:: + + def ack(): + print 'message was received!' + + @socketio.on('my event') + def handle_my_custom_event(json): + emit('my response', json, callback=ack) + +When using callbacks, the Javascript client receives a callback function to +invoke upon receipt of the message. After the client application invokes the +callback function the server invokes the corresponding server-side callback. +If the client-side callback is invoked with arguments, these are provided as +arguments to the server-side callback as well. + +Broadcasting +------------ + +Another very useful feature of SocketIO is the broadcasting of messages. +Flask-SocketIO supports this feature with the ``broadcast=True`` optional +argument to ``send()`` and ``emit()``:: + + @socketio.on('my event') + def handle_my_custom_event(data): + emit('my response', data, broadcast=True) + +When a message is sent with the broadcast option enabled, all clients +connected to the namespace receive it, including the sender. When namespaces +are not used, the clients connected to the global namespace receive the +message. Note that callbacks are not invoked for broadcast messages. + +In all the examples shown until this point the server responds to an event +sent by the client. But for some applications, the server needs to be the +originator of a message. This can be useful to send notifications to clients +of events that originated in the server, for example in a background thread. +The ``socketio.send()`` and ``socketio.emit()`` methods can be used to +broadcast to all connected clients:: + + def some_function(): + socketio.emit('some event', {'data': 42}) + +Note that ``socketio.send()`` and ``socketio.emit()`` are not the same +functions as the context-aware ``send()`` and ``emit()``. Also note that in the +above usage there is no client context, so ``broadcast=True`` is assumed and +does not need to be specified. + +Rooms +----- + +For many applications it is necessary to group users into subsets that can be +addressed together. The best example is a chat application with multiple rooms, +where users receive messages from the room or rooms they are in, but not from +other rooms where other users are. Flask-SocketIO supports this concept of +rooms through the ``join_room()`` and ``leave_room()`` functions:: + + from flask_socketio import join_room, leave_room + + @socketio.on('join') + def on_join(data): + username = data['username'] + room = data['room'] + join_room(room) + send(username + ' has entered the room.', to=room) + + @socketio.on('leave') + def on_leave(data): + username = data['username'] + room = data['room'] + leave_room(room) + send(username + ' has left the room.', to=room) + +The ``send()`` and ``emit()`` functions accept an optional ``to`` argument +that cause the message to be sent to all the clients that are in the given +room. + +All clients are assigned a room when they connect, named with the session ID +of the connection, which can be obtained from ``request.sid``. A given client +can join any rooms, which can be given any names. When a client disconnects it +is removed from all the rooms it was in. The context-free ``socketio.send()`` +and ``socketio.emit()`` functions also accept a ``to`` argument to broadcast +to all clients in a room. + +Since all clients are assigned a personal room, to address a message to a +single client, the session ID of the client can be used as the ``to`` argument. + +Connection Events +----------------- + +Flask-SocketIO also dispatches connection and disconnection events. The +following example shows how to register handlers for them:: + + @socketio.on('connect') + def test_connect(auth): + emit('my response', {'data': 'Connected'}) + + @socketio.on('disconnect') + def test_disconnect(): + print('Client disconnected') + +The ``auth`` argument in the connection handler is optional. The client can +use it to pass authentication data such as tokens in dictionary format. If the +client does not provide authentication details, then this argument is set to +``None``. If the server defines a connection event handler without this +argument, then any authentication data passed by the cient is discarded. + +The connection event handler can return ``False`` to reject the connection, or +it can also raise `ConectionRefusedError`. This is so that the client can be +authenticated at this point. When using the exception, any arguments passed to +the exception are returned to the client in the error packet. Examples:: + + from flask_socketio import ConnectionRefusedError + + @socketio.on('connect') + def connect(): + if not self.authenticate(request.args): + raise ConnectionRefusedError('unauthorized!') + +Note that connection and disconnection events are sent individually on each +namespace used. + +Class-Based Namespaces +---------------------- + +As an alternative to the decorator-based event handlers described above, the +event handlers that belong to a namespace can be created as methods of a +class. The :class:`flask_socketio.Namespace` is provided as a base class to +create class-based namespaces:: + + from flask_socketio import Namespace, emit + + class MyCustomNamespace(Namespace): + def on_connect(self): + pass + + def on_disconnect(self): + pass + + def on_my_event(self, data): + emit('my_response', data) + + socketio.on_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:`flask_socketio.SocketIO` class that default to the proper namespace +when the ``namespace`` argument is not given. + +If an event has a handler in a class-based namespace, and also a +decorator-based function handler, only the decorated function handler is +invoked. + +Error Handling +-------------- + +Flask-SocketIO can also deal with exceptions:: + + @socketio.on_error() # Handles the default namespace + def error_handler(e): + pass + + @socketio.on_error('/chat') # handles the '/chat' namespace + def error_handler_chat(e): + pass + + @socketio.on_error_default # handles all namespaces without an explicit error handler + def default_error_handler(e): + pass + +Error handler functions take the exception object as an argument. + +The message and data arguments of the current request can also be inspected +with the ``request.event`` variable, which is useful for error logging and +debugging outside the event handler:: + + from flask import request + + @socketio.on("my error event") + def on_my_event(data): + raise RuntimeError() + + @socketio.on_error_default + def default_error_handler(e): + print(request.event["message"]) # "my error event" + print(request.event["args"]) # (data,) + +Debugging and Troubleshooting +----------------------------- + +To help you debug issues, the server can be configured to output logs to the +terminal:: + + socketio = SocketIO(logger=True, engineio_logger=True) + +The ``logger`` argument controls logging related to the Socket.IO protocol, +while ``engineio_logger`` controls logs that originate in the low-level +Engine.IO transport. These arguments can be set to ``True`` to output logs to +``stderr``, or to an object compatible with Python's ``logging`` package +where the logs should be emitted to. A value of ``False`` disables logging. + +Logging can help identify the cause of connection problems, 400 responses, +bad performance and other issues. diff --git a/docs/implementation_notes.rst b/docs/implementation_notes.rst new file mode 100644 index 00000000..5ff8655f --- /dev/null +++ b/docs/implementation_notes.rst @@ -0,0 +1,112 @@ +Implementation Notes +==================== + +Access to Flask's Context Globals +--------------------------------- + +Handlers for SocketIO events are different than handlers for routes and that +introduces a lot of confusion around what can and cannot be done in a SocketIO +handler. The main difference is that all the SocketIO events generated for a +client occur in the context of a single long running request. + +In spite of the differences, Flask-SocketIO attempts to make working with +SocketIO event handlers easier by making the environment similar to that of a +regular HTTP request. The following list describes what works and what doesn't: + +- An application context is pushed before invoking an event handler making + ``current_app`` and ``g`` available to the handler. +- A request context is also pushed before invoking a handler, also making + ``request`` and ``session`` available. But note that WebSocket events do not + have individual requests associated with them, so the request context that + started the connection is pushed for all the events that are dispatched + during the life of the connection. +- The ``request`` context global is enhanced with a ``sid`` member that is set + to a unique session ID for the connection. This value is used as an initial + room where the client is added. +- The ``request`` context global is enhanced with ``namespace`` and ``event`` + members that contain the currently handled namespace and event arguments. + The ``event`` member is a dictionary with ``message`` and ``args`` keys. +- The ``session`` context global behaves in a different way than in regular + requests. A copy of the user session at the time the SocketIO connection is + established is made available to handlers invoked in the context of that + connection. If a SocketIO handler modifies the session, the modified session + will be preserved for future SocketIO handlers, but regular HTTP route + handlers will not see these changes. Effectively, when a SocketIO handler + modifies the session, a "fork" of the session is created exclusively for + these handlers. The technical reason for this limitation is that to save the + user session a cookie needs to be sent to the client, and that requires HTTP + request and response, which do not exist in a SocketIO connection. When + using server-side sessions such as those provided by the Flask-Session or + Flask-KVSession extensions, changes made to the session in HTTP route + handlers can be seen by SocketIO handlers, as long as the session is not + modified in the SocketIO handlers. +- The ``before_request`` and ``after_request`` hooks are not invoked for + SocketIO event handlers. +- SocketIO handlers can take custom decorators, but most Flask decorators will + not be appropriate to use for a SocketIO handler, given that there is no + concept of a ``Response`` object during a SocketIO connection. + +Authentication +-------------- + +A common need of applications is to validate the identity of their users. The +traditional mechanisms based on web forms and HTTP requests cannot be used in +a SocketIO connection, since there is no place to send HTTP requests and +responses. If necessary, an application can implement a customized login form +that sends credentials to the server as a SocketIO message when the submit +button is pressed by the user. + +However, in most cases it is more convenient to perform the traditional +authentication process before the SocketIO connection is established. The +user's identity can then be recorded in the user session or in a cookie, and +later when the SocketIO connection is established that information will be +accessible to SocketIO event handlers. + +Recent revisions of the Socket.IO protocol include the ability to pass a +dictionary with authentication information during the connection. This is an +ideal place for the client to include a token or other authentication details. +If the client uses this capability, the server will provide this dictionary as +an argument to the ``connect`` event handler, as shown above. + + +Using Flask-Login with Flask-SocketIO +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Flask-SocketIO can access login information maintained by +`Flask-Login `_. After a +regular Flask-Login authentication is performed and the ``login_user()`` +function is called to record the user in the user session, any SocketIO +connections will have access to the ``current_user`` context variable:: + + @socketio.on('connect') + def connect_handler(): + if current_user.is_authenticated: + emit('my response', + {'message': '{0} has joined'.format(current_user.name)}, + broadcast=True) + else: + return False # not allowed here + +Note that the ``login_required`` decorator cannot be used with SocketIO event +handlers, but a custom decorator that disconnects non-authenticated users can +be created as follows:: + + import functools + from flask import request + from flask_login import current_user + from flask_socketio import disconnect, emit + + def authenticated_only(f): + @functools.wraps(f) + def wrapped(*args, **kwargs): + if not current_user.is_authenticated: + disconnect() + else: + return f(*args, **kwargs) + return wrapped + + @socketio.on('my event') + @authenticated_only + def handle_my_custom_event(data): + emit('my response', {'message': '{0} has joined'.format(current_user.name)}, + broadcast=True) diff --git a/docs/index.rst b/docs/index.rst index 27716b07..6c2747fb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,10 +1,10 @@ -.. Flask-SocketIO documentation master file, created by - sphinx-quickstart on Sun Feb 9 12:36:23 2014. +.. flask-socketio documentation master file, created by + sphinx-quickstart on Sun Nov 25 11:52:38 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to Flask-SocketIO's documentation! -========================================== +Flask-SocketIO +=============== **Flask-SocketIO** gives Flask applications access to low latency bi-directional communications between the clients and the server. The @@ -12,879 +12,16 @@ client-side application can use any of the `SocketIO `_ official clients libraries in Javascript, C++, Java and Swift, or any compatible client to establish a permanent connection to the server. -Installation ------------- +.. toctree:: + :maxdepth: 3 -You can install this package in the usual way using ``pip``:: + intro + getting_started + implementation_notes + deployment + upgrading + api - pip install flask-socketio - -Requirements ------------- - -Flask-SocketIO is compatible with Python 3.6+. The asynchronous services that -this package relies on can be selected among three choices: - -- `eventlet `_ is the best performant option, with - support for long-polling and WebSocket transports. -- `gevent `_ is supported in a number of different - configurations. The long-polling transport is fully supported with the - gevent package, but unlike eventlet, gevent does not have native WebSocket - support. To add support for WebSocket there are currently two options. - Installing the `gevent-websocket `_ - package adds WebSocket support to gevent or one can use the `uWSGI - `_ web server, which - comes with WebSocket functionality. The use of gevent is also a performant - option, but slightly lower than eventlet. -- The Flask development server based on Werkzeug can be used as well, with the - caveat that this web server is intended only for development use, so it - should only be used to simplify the development workflow and not for - production. - -The extension automatically detects which asynchronous framework to use based -on what is installed. Preference is given to eventlet, followed by gevent. -For WebSocket support in gevent, uWSGI is preferred, followed by -gevent-websocket. If neither eventlet nor gevent are installed, then the Flask -development server is used. - -If using multiple processes, a message queue service is used by the processes -to coordinate operations such as broadcasting. The supported queues are -`Redis `_, `RabbitMQ `_, -`Kafka `_, and any -other message queues supported by the -`Kombu `_ package. - -On the client-side, the official Socket.IO Javascript client library can be -used to establish a connection to the server. There are also official clients -written in Swift, Java and C++. Unofficial clients may also work, as long as -they implement the -`Socket.IO protocol `_. -The `python-socketio `_ -package includes a Python client. - -Version compatibility ---------------------- - -The Socket.IO protocol has been through a number of revisions, and some of these -introduced backward incompatible changes, which means that the client and the -server must use compatible versions for everything to work. - -The version compatibility chart below maps versions of this package to versions -of the JavaScript reference implementation and the versions of the Socket.IO and -Engine.IO protocols. - -+------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ -| JavaScript Socket.IO version | Socket.IO protocol revision | Engine.IO protocol revision | Flask-SocketIO version | python-socketio version | python-engineio version | -+==============================+=============================+=============================+========================+=========================+=========================+ -| 0.9.x | 1, 2 | 1, 2 | Not supported | Not supported | Not supported | -+------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ -| 1.x and 2.x | 3, 4 | 3 | 4.x | 4.x | 3.x | -+------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ -| 3.x and 4.x | 5 | 4 | 5.x | 5.x | 4.x | -+------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ - -Initialization --------------- - -The following code example shows how to add Flask-SocketIO to a Flask -application:: - - from flask import Flask, render_template - from flask_socketio import SocketIO - - app = Flask(__name__) - app.config['SECRET_KEY'] = 'secret!' - socketio = SocketIO(app) - - if __name__ == '__main__': - socketio.run(app) - -The ``init_app()`` style of initialization is also supported. To start the -web server simply execute your script. Note the way the web server is started. -The ``socketio.run()`` function encapsulates the start up of the web server and -replaces the ``app.run()`` standard Flask development server start up. When the -application is in debug mode the Werkzeug development server is still used and -configured properly inside ``socketio.run()``. In production mode the eventlet -web server is used if available, else the gevent web server is used. If -eventlet and gevent are not installed, the Werkzeug development web server is -used. - -The ``flask run`` command introduced in Flask 0.11 can be used to start a -Flask-SocketIO development server based on Werkzeug, but this method of starting -the Flask-SocketIO server is not recommended due to lack of WebSocket support. -Previous versions of this package included a customized version of the -``flask run`` command that allowed the use of WebSocket on eventlet and gevent -production servers, but this functionality has been discontinued in favor of the -``socketio.run(app)`` startup method shown above which is more robust. - -The application must serve a page to the client that loads the Socket.IO -library and establishes a connection:: - - - - -Receiving Messages ------------------- - -When using SocketIO, messages are received by both parties as events. On the -client side Javascript callbacks are used. With Flask-SocketIO the server -needs to register handlers for these events, similarly to how routes are -handled by view functions. - -The following example creates a server-side event handler for an unnamed -event:: - - @socketio.on('message') - def handle_message(data): - print('received message: ' + data) - -The above example uses string messages. Another type of unnamed events use -JSON data:: - - @socketio.on('json') - def handle_json(json): - print('received json: ' + str(json)) - -The most flexible type of event uses custom event names. The message data for -these events can be string, bytes, int, or JSON:: - - @socketio.on('my event') - def handle_my_custom_event(json): - print('received json: ' + str(json)) - -Custom named events can also support multiple arguments:: - - @socketio.on('my_event') - def handle_my_custom_event(arg1, arg2, arg3): - print('received args: ' + arg1 + arg2 + arg3) - -When the name of the event is a valid Python identifier that does not collide -with other defined symbols, the ``@socketio.event`` decorator provides a more -compact syntax that takes the event name from the decorated function:: - - @socketio.event - def my_custom_event(arg1, arg2, arg3): - print('received args: ' + arg1 + arg2 + arg3) - -Named events are the most flexible, as they eliminate the need to include -additional metadata to describe the message type. The names ``message``, -``json``, ``connect`` and ``disconnect`` are reserved and cannot be used for -named events. - -Flask-SocketIO also supports SocketIO namespaces, which allow the client to -multiplex several independent connections on the same physical socket:: - - @socketio.on('my event', namespace='/test') - def handle_my_custom_namespace_event(json): - print('received json: ' + str(json)) - -When a namespace is not specified a default global namespace with the name -``'/'`` is used. - -For cases when a decorator syntax isn't convenient, the ``on_event`` method -can be used:: - - def my_function_handler(data): - pass - - socketio.on_event('my event', my_function_handler, namespace='/test') - -Clients may request an acknowledgement callback that confirms receipt of a -message they sent. Any values returned from the handler function will be -passed to the client as arguments in the callback function:: - - @socketio.on('my event') - def handle_my_custom_event(json): - print('received json: ' + str(json)) - return 'one', 2 - -In the above example, the client callback function will be invoked with -two arguments, ``'one'`` and ``2``. If a handler function does not return any -values, the client callback function will be invoked without arguments. - -Sending Messages ----------------- - -SocketIO event handlers defined as shown in the previous section can send -reply messages to the connected client using the ``send()`` and ``emit()`` -functions. - -The following examples bounce received events back to the client that sent -them:: - - from flask_socketio import send, emit - - @socketio.on('message') - def handle_message(message): - send(message) - - @socketio.on('json') - def handle_json(json): - send(json, json=True) - - @socketio.on('my event') - def handle_my_custom_event(json): - emit('my response', json) - -Note how ``send()`` and ``emit()`` are used for unnamed and named events -respectively. - -When working with namespaces, ``send()`` and ``emit()`` use the namespace of -the incoming message by default. A different namespace can be specified with -the optional ``namespace`` argument:: - - @socketio.on('message') - def handle_message(message): - send(message, namespace='/chat') - - @socketio.on('my event') - def handle_my_custom_event(json): - emit('my response', json, namespace='/chat') - -To send an event with multiple arguments, send a tuple:: - - @socketio.on('my event') - def handle_my_custom_event(json): - emit('my response', ('foo', 'bar', json), namespace='/chat') - -SocketIO supports acknowledgment callbacks that confirm that a message was -received by the client:: - - def ack(): - print 'message was received!' - - @socketio.on('my event') - def handle_my_custom_event(json): - emit('my response', json, callback=ack) - -When using callbacks, the Javascript client receives a callback function to -invoke upon receipt of the message. After the client application invokes the -callback function the server invokes the corresponding server-side callback. -If the client-side callback is invoked with arguments, these are provided as -arguments to the server-side callback as well. - -Broadcasting ------------- - -Another very useful feature of SocketIO is the broadcasting of messages. -Flask-SocketIO supports this feature with the ``broadcast=True`` optional -argument to ``send()`` and ``emit()``:: - - @socketio.on('my event') - def handle_my_custom_event(data): - emit('my response', data, broadcast=True) - -When a message is sent with the broadcast option enabled, all clients -connected to the namespace receive it, including the sender. When namespaces -are not used, the clients connected to the global namespace receive the -message. Note that callbacks are not invoked for broadcast messages. - -In all the examples shown until this point the server responds to an event -sent by the client. But for some applications, the server needs to be the -originator of a message. This can be useful to send notifications to clients -of events that originated in the server, for example in a background thread. -The ``socketio.send()`` and ``socketio.emit()`` methods can be used to -broadcast to all connected clients:: - - def some_function(): - socketio.emit('some event', {'data': 42}) - -Note that ``socketio.send()`` and ``socketio.emit()`` are not the same -functions as the context-aware ``send()`` and ``emit()``. Also note that in the -above usage there is no client context, so ``broadcast=True`` is assumed and -does not need to be specified. - -Rooms ------ - -For many applications it is necessary to group users into subsets that can be -addressed together. The best example is a chat application with multiple rooms, -where users receive messages from the room or rooms they are in, but not from -other rooms where other users are. Flask-SocketIO supports this concept of -rooms through the ``join_room()`` and ``leave_room()`` functions:: - - from flask_socketio import join_room, leave_room - - @socketio.on('join') - def on_join(data): - username = data['username'] - room = data['room'] - join_room(room) - send(username + ' has entered the room.', to=room) - - @socketio.on('leave') - def on_leave(data): - username = data['username'] - room = data['room'] - leave_room(room) - send(username + ' has left the room.', to=room) - -The ``send()`` and ``emit()`` functions accept an optional ``to`` argument -that cause the message to be sent to all the clients that are in the given -room. - -All clients are assigned a room when they connect, named with the session ID -of the connection, which can be obtained from ``request.sid``. A given client -can join any rooms, which can be given any names. When a client disconnects it -is removed from all the rooms it was in. The context-free ``socketio.send()`` -and ``socketio.emit()`` functions also accept a ``to`` argument to broadcast -to all clients in a room. - -Since all clients are assigned a personal room, to address a message to a -single client, the session ID of the client can be used as the ``to`` argument. - -Connection Events ------------------ - -Flask-SocketIO also dispatches connection and disconnection events. The -following example shows how to register handlers for them:: - - @socketio.on('connect') - def test_connect(auth): - emit('my response', {'data': 'Connected'}) - - @socketio.on('disconnect') - def test_disconnect(): - print('Client disconnected') - -The ``auth`` argument in the connection handler is optional. The client can -use it to pass authentication data such as tokens in dictionary format. If the -client does not provide authentication details, then this argument is set to -``None``. If the server defines a connection event handler without this -argument, then any authentication data passed by the cient is discarded. - -The connection event handler can return ``False`` to reject the connection, or -it can also raise `ConectionRefusedError`. This is so that the client can be -authenticated at this point. When using the exception, any arguments passed to -the exception are returned to the client in the error packet. Examples:: - - from flask_socketio import ConnectionRefusedError - - @socketio.on('connect') - def connect(): - if not self.authenticate(request.args): - raise ConnectionRefusedError('unauthorized!') - -Note that connection and disconnection events are sent individually on each -namespace used. - -Class-Based Namespaces ----------------------- - -As an alternative to the decorator-based event handlers described above, the -event handlers that belong to a namespace can be created as methods of a -class. The :class:`flask_socketio.Namespace` is provided as a base class to -create class-based namespaces:: - - from flask_socketio import Namespace, emit - - class MyCustomNamespace(Namespace): - def on_connect(self): - pass - - def on_disconnect(self): - pass - - def on_my_event(self, data): - emit('my_response', data) - - socketio.on_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:`flask_socketio.SocketIO` class that default to the proper namespace -when the ``namespace`` argument is not given. - -If an event has a handler in a class-based namespace, and also a -decorator-based function handler, only the decorated function handler is -invoked. - -Error Handling --------------- - -Flask-SocketIO can also deal with exceptions:: - - @socketio.on_error() # Handles the default namespace - def error_handler(e): - pass - - @socketio.on_error('/chat') # handles the '/chat' namespace - def error_handler_chat(e): - pass - - @socketio.on_error_default # handles all namespaces without an explicit error handler - def default_error_handler(e): - pass - -Error handler functions take the exception object as an argument. - -The message and data arguments of the current request can also be inspected -with the ``request.event`` variable, which is useful for error logging and -debugging outside the event handler:: - - from flask import request - - @socketio.on("my error event") - def on_my_event(data): - raise RuntimeError() - - @socketio.on_error_default - def default_error_handler(e): - print(request.event["message"]) # "my error event" - print(request.event["args"]) # (data,) - -Debugging and Troubleshooting ------------------------------ - -To help you debug issues, the server can be configured to output logs to the -terminal:: - - socketio = SocketIO(logger=True, engineio_logger=True) - -The ``logger`` argument controls logging related to the Socket.IO protocol, -while ``engineio_logger`` controls logs that originate in the low-level -Engine.IO transport. These arguments can be set to ``True`` to output logs to -``stderr``, or to an object compatible with Python's ``logging`` package -where the logs should be emitted to. A value of ``False`` disables logging. - -Logging can help identify the cause of connection problems, 400 responses, -bad performance and other issues. - -Access to Flask's Context Globals ---------------------------------- - -Handlers for SocketIO events are different than handlers for routes and that -introduces a lot of confusion around what can and cannot be done in a SocketIO -handler. The main difference is that all the SocketIO events generated for a -client occur in the context of a single long running request. - -In spite of the differences, Flask-SocketIO attempts to make working with -SocketIO event handlers easier by making the environment similar to that of a -regular HTTP request. The following list describes what works and what doesn't: - -- An application context is pushed before invoking an event handler making - ``current_app`` and ``g`` available to the handler. -- A request context is also pushed before invoking a handler, also making - ``request`` and ``session`` available. But note that WebSocket events do not - have individual requests associated with them, so the request context that - started the connection is pushed for all the events that are dispatched - during the life of the connection. -- The ``request`` context global is enhanced with a ``sid`` member that is set - to a unique session ID for the connection. This value is used as an initial - room where the client is added. -- The ``request`` context global is enhanced with ``namespace`` and ``event`` - members that contain the currently handled namespace and event arguments. - The ``event`` member is a dictionary with ``message`` and ``args`` keys. -- The ``session`` context global behaves in a different way than in regular - requests. A copy of the user session at the time the SocketIO connection is - established is made available to handlers invoked in the context of that - connection. If a SocketIO handler modifies the session, the modified session - will be preserved for future SocketIO handlers, but regular HTTP route - handlers will not see these changes. Effectively, when a SocketIO handler - modifies the session, a "fork" of the session is created exclusively for - these handlers. The technical reason for this limitation is that to save the - user session a cookie needs to be sent to the client, and that requires HTTP - request and response, which do not exist in a SocketIO connection. When - using server-side sessions such as those provided by the Flask-Session or - Flask-KVSession extensions, changes made to the session in HTTP route - handlers can be seen by SocketIO handlers, as long as the session is not - modified in the SocketIO handlers. -- The ``before_request`` and ``after_request`` hooks are not invoked for - SocketIO event handlers. -- SocketIO handlers can take custom decorators, but most Flask decorators will - not be appropriate to use for a SocketIO handler, given that there is no - concept of a ``Response`` object during a SocketIO connection. - -Authentication --------------- - -A common need of applications is to validate the identity of their users. The -traditional mechanisms based on web forms and HTTP requests cannot be used in -a SocketIO connection, since there is no place to send HTTP requests and -responses. If necessary, an application can implement a customized login form -that sends credentials to the server as a SocketIO message when the submit -button is pressed by the user. - -However, in most cases it is more convenient to perform the traditional -authentication process before the SocketIO connection is established. The -user's identity can then be recorded in the user session or in a cookie, and -later when the SocketIO connection is established that information will be -accessible to SocketIO event handlers. - -Recent revisions of the Socket.IO protocol include the ability to pass a -dictionary with authentication information during the connection. This is an -ideal place for the client to include a token or other authentication details. -If the client uses this capability, the server will provide this dictionary as -an argument to the ``connect`` event handler, as shown above. - - -Using Flask-Login with Flask-SocketIO -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Flask-SocketIO can access login information maintained by -`Flask-Login `_. After a -regular Flask-Login authentication is performed and the ``login_user()`` -function is called to record the user in the user session, any SocketIO -connections will have access to the ``current_user`` context variable:: - - @socketio.on('connect') - def connect_handler(): - if current_user.is_authenticated: - emit('my response', - {'message': '{0} has joined'.format(current_user.name)}, - broadcast=True) - else: - return False # not allowed here - -Note that the ``login_required`` decorator cannot be used with SocketIO event -handlers, but a custom decorator that disconnects non-authenticated users can -be created as follows:: - - import functools - from flask import request - from flask_login import current_user - from flask_socketio import disconnect, emit - - def authenticated_only(f): - @functools.wraps(f) - def wrapped(*args, **kwargs): - if not current_user.is_authenticated: - disconnect() - else: - return f(*args, **kwargs) - return wrapped - - @socketio.on('my event') - @authenticated_only - def handle_my_custom_event(data): - emit('my response', {'message': '{0} has joined'.format(current_user.name)}, - broadcast=True) - -Deployment ----------- - -There are many options to deploy a Flask-SocketIO server, ranging from simple -to the insanely complex. In this section, the most commonly used options are -described. - -Embedded Server -~~~~~~~~~~~~~~~ - -The simplest deployment strategy is to start the web server by calling -``socketio.run(app)`` as shown in examples above. This will look through the -packages that are installed for the best available web server start the -application on it. The current web server choices that are evaluated are -``eventlet``, ``gevent`` and the Flask development server. - -If eventlet or gevent are available, ``socketio.run(app)`` starts a -production-ready server using one of these frameworks. If neither of these are -installed, then the Flask development web server is used, and in this case the -server is not intended to be used in a production deployment. - -Unfortunately this option is not available when using gevent with uWSGI. See -the uWSGI section below for information on this option. - -Gunicorn Web Server -~~~~~~~~~~~~~~~~~~~ - -An alternative to ``socketio.run(app)`` is to use -`gunicorn `_ as web server, using the eventlet or gevent -workers. For this option, eventlet or gevent need to be installed, in addition -to gunicorn. The command line that starts the eventlet server via gunicorn is:: - - gunicorn --worker-class eventlet -w 1 module:app - -If you prefer to use gevent, the command to start the server is:: - - gunicorn -k gevent -w 1 module:app - -When using gunicorn with the gevent worker and the WebSocket support provided -by gevent-websocket, the command that starts the server must be changed to -select a custom gevent web server that supports the WebSocket protocol. The -modified command is:: - - gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module:app - -A third option with Gunicorn is to use the threaded worker, along with the -`simple-websocket `_. - -uWSGI Web Server -~~~~~~~~~~~~~~~~ - -When using the uWSGI server in combination with gevent, the Socket.IO server -can take advantage of uWSGI’s native WebSocket support. - -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 example application app.py on port 5000:: - - $ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file app.py --callable app - -Using nginx as a WebSocket Reverse Proxy -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It is possible to use nginx as a front-end reverse proxy that passes requests -to the application. However, only releases of nginx 1.4 and newer support -proxying of the WebSocket protocol. Below is a basic nginx configuration that -proxies HTTP and WebSocket requests:: - - server { - listen 80; - server_name _; - - location / { - include proxy_params; - proxy_pass http://127.0.0.1:5000; - } - - location /static { - alias /static; - expires 30d; - } - - location /socket.io { - include proxy_params; - proxy_http_version 1.1; - proxy_buffering off; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - proxy_pass http://127.0.0.1:5000/socket.io; - } - } - -The next example adds the support for load balancing multiple Socket.IO -servers:: - - upstream socketio_nodes { - ip_hash; - - server 127.0.0.1:5000; - server 127.0.0.1:5001; - server 127.0.0.1:5002; - # to scale the app, just add more nodes here! - } - - server { - listen 80; - server_name _; - - location / { - include proxy_params; - proxy_pass http://127.0.0.1:5000; - } - - location /static { - alias /static; - expires 30d; - } - - location /socket.io { - include proxy_params; - proxy_http_version 1.1; - proxy_buffering off; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - proxy_pass http://socketio_nodes/socket.io; - } - } - -While the above examples can work as an initial configuration, be aware that a -production install of nginx will need a more complete configuration covering -other deployment aspects such as SSL support. - -Using Multiple Workers -~~~~~~~~~~~~~~~~~~~~~~ - -Flask-SocketIO supports multiple workers behind a load balancer starting with -release 2.0. Deploying multiple workers gives applications that use -Flask-SocketIO the ability to spread the client connections among multiple -processes and hosts, and in this way scale to support very large numbers of -concurrent clients. - -There are two requirements to use multiple Flask-SocketIO workers: - -- The load balancer must be configured to forward all HTTP requests from a - given client always to the same worker. This is sometimes referenced as - "sticky sessions". For nginx, use the ``ip_hash`` directive to achieve this. - Gunicorn cannot be used with multiple workers because its load balancer - algorithm does not support sticky sessions. - -- Since each of the servers owns only a subset of the client connections, a - message queue such as Redis or RabbitMQ is used by the servers to coordinate - complex operations such as broadcasting and rooms. - -When working with a message queue, there are additional dependencies that need to -be installed: - -- For Redis, the package ``redis`` must be installed (``pip install redis``). -- For RabbitMQ, the package ``kombu`` must be installed (``pip install kombu``). -- For Kafka, the package ``kafka-python`` must be installed (``pip install kafka-python``). -- For other message queues supported by Kombu, see the `Kombu documentation - `_ - to find out what dependencies are needed. -- If eventlet or gevent are used, then monkey patching the Python standard - library is normally required to force the message queue package to use - coroutine friendly functions and classes. - -For eventlet, monkey patching is done with:: - - import eventlet - eventlet.monkey_patch() - -For gevent, you can monkey patch the standard library with:: - - from gevent import monkey - monkey.patch_all() - -In both cases it is recommended that you apply the monkey patching at the top -of your main script, even above your imports. - -To start multiple Flask-SocketIO servers, you must first ensure you have the -message queue service running. To start a Socket.IO server and have it connect to -the message queue, add the ``message_queue`` argument to the ``SocketIO`` -constructor:: - - socketio = SocketIO(app, message_queue='redis://') - -The value of the ``message_queue`` argument is the connection URL of the -queue service that is used. For a redis queue running on the same host as the -server, the ``'redis://'`` URL can be used. Likewise, for a default RabbitMQ -queue the ``'amqp://'`` URL can be used. For Kafka, use a ``kafka://`` URL. -The Kombu package has a `documentation -section `_ -that describes the format of the URLs for all the supported queues. - -Emitting from an External Process -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For many types of applications, it is necessary to emit events from a process -that is not the SocketIO server, for an example a Celery worker. If the -SocketIO server or servers are configured to listen on a message queue as -shown in the previous section, then any other process can create its own -``SocketIO`` instance and use it to emit events in the same way the server -does. - -For example, for an application that runs on an eventlet web server and uses -a Redis message queue, the following Python script broadcasts an event to -all clients:: - - socketio = SocketIO(message_queue='redis://') - socketio.emit('my event', {'data': 'foo'}, namespace='/test') - -When using the ``SocketIO`` instance in this way, the Flask application -instance is not passed to the constructor. - -The ``channel`` argument to ``SocketIO`` can be used to select a specific -channel of communication through the message queue. Using a custom channel -name is necessary when there are multiple independent SocketIO services -sharing the same queue. - -Flask-SocketIO does not apply monkey patching when eventlet or gevent are -used. But when working with a message queue, it is very likely that the Python -package that talks to the message queue service will hang if the Python -standard library is not monkey patched. - -It is important to note that an external process that wants to connect to -a SocketIO server does not need to use eventlet or gevent like the main -server. Having a server use a coroutine framework, while an external process -is not a problem. For example, Celery workers do not need to be -configured to use eventlet or gevent just because the main server does. But if -your external process does use a coroutine framework for whatever reason, then -monkey patching is likely required, so that the message queue accesses -coroutine friendly functions and classes. - -Cross-Origin Controls ---------------------- - -For security reasons, this server enforces a same-origin policy by default. In -practical terms, this means the following: - -- If an incoming HTTP or WebSocket request includes the ``Origin`` header, - this header must match the scheme and host of the connection URL. In case - of a mismatch, a 400 status code response is returned and the connection is - rejected. -- No restrictions are imposed on incoming requests that do not include the - ``Origin`` header. - -If necessary, the ``cors_allowed_origins`` option can be used to allow other -origins. This argument can be set to a string to set a single allowed origin, or -to a list to allow multiple origins. A special value of ``'*'`` can be used to -instruct the server to allow all origins, but this should be done with care, as -this could make the server vulnerable to Cross-Site Request Forgery (CSRF) -attacks. - -Upgrading to Flask-SocketIO 5.x from the 4.x releases -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The Socket.IO protocol recently introduced a series of backwards incompatible -changes. The 5.x releases of Flask-SocketIO adopted these changes, and for -that reason it can only be used with clients that have also been updated to -the current version of the protocol. In particular, this means that the -JavaScript client must be upgraded to a 3.x release, and if your client hasn't -been upgraded to the latest version of the Socket.IO protocol, then you must -use a Flask-SocketIO 4.x release. - -The following protocol changes are of importance, as they may affect existing -applications: - -- The default namespace ``'/'`` is not automatically connected anymore, and is - now treated in the same way as other namespaces. -- Each namespace connection has its own ``sid`` value, different from the others - and different from the Engine.IO ``sid``. -- Flask-SocketIO now uses the same ping interval and timeout values as the - JavaScript reference implementation, which are 25 and 5 seconds respectively. -- The ping/pong mechanism has been reversed. In the current version of the - protocol, the server issues a ping and the client responds with a pong. -- The default allowed payload size for long--polling packets has been lowered - from 100MB to 1MB. -- The `io` cookie is not sent to the client anymore by default. - -API Reference -------------- - -.. module:: flask_socketio -.. autoclass:: SocketIO - :members: -.. autofunction:: emit -.. autofunction:: send -.. autofunction:: join_room -.. autofunction:: leave_room -.. autofunction:: close_room -.. autofunction:: rooms -.. autofunction:: disconnect -.. autoclass:: Namespace - :members: -.. autoclass:: SocketIOTestClient - :members: +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/intro.rst b/docs/intro.rst new file mode 100644 index 00000000..5064a659 --- /dev/null +++ b/docs/intro.rst @@ -0,0 +1,73 @@ +Introduction +============ + +Installation +------------ + +You can install this package in the usual way using ``pip``:: + + pip install flask-socketio + +Requirements +------------ + +Flask-SocketIO is compatible with Python 3.6+. The asynchronous services that +this package relies on can be selected among three choices: + +- `eventlet `_ is the best performant option, with + support for long-polling and WebSocket transports. +- `gevent `_ is supported in a number of different + configurations. The long-polling transport is fully supported with the + gevent package, but unlike eventlet, gevent does not have native WebSocket + support. To add support for WebSocket there are currently two options. + Installing the `gevent-websocket `_ + package adds WebSocket support to gevent or one can use the `uWSGI + `_ web server, which + comes with WebSocket functionality. The use of gevent is also a performant + option, but slightly lower than eventlet. +- The Flask development server based on Werkzeug can be used as well, with the + caveat that this web server is intended only for development use, so it + should only be used to simplify the development workflow and not for + production. + +The extension automatically detects which asynchronous framework to use based +on what is installed. Preference is given to eventlet, followed by gevent. +For WebSocket support in gevent, uWSGI is preferred, followed by +gevent-websocket. If neither eventlet nor gevent are installed, then the Flask +development server is used. + +If using multiple processes, a message queue service is used by the processes +to coordinate operations such as broadcasting. The supported queues are +`Redis `_, `RabbitMQ `_, +`Kafka `_, and any +other message queues supported by the +`Kombu `_ package. + +On the client-side, the official Socket.IO Javascript client library can be +used to establish a connection to the server. There are also official clients +written in Swift, Java and C++. Unofficial clients may also work, as long as +they implement the +`Socket.IO protocol `_. +The `python-socketio `_ +package includes a Python client. + +Version compatibility +--------------------- + +The Socket.IO protocol has been through a number of revisions, and some of these +introduced backward incompatible changes, which means that the client and the +server must use compatible versions for everything to work. + +The version compatibility chart below maps versions of this package to versions +of the JavaScript reference implementation and the versions of the Socket.IO and +Engine.IO protocols. + ++------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ +| JavaScript Socket.IO version | Socket.IO protocol revision | Engine.IO protocol revision | Flask-SocketIO version | python-socketio version | python-engineio version | ++==============================+=============================+=============================+========================+=========================+=========================+ +| 0.9.x | 1, 2 | 1, 2 | Not supported | Not supported | Not supported | ++------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ +| 1.x and 2.x | 3, 4 | 3 | 4.x | 4.x | 3.x | ++------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ +| 3.x and 4.x | 5 | 4 | 5.x | 5.x | 4.x | ++------------------------------+-----------------------------+-----------------------------+------------------------+-------------------------+-------------------------+ diff --git a/docs/make.bat b/docs/make.bat index 3476b700..27f573b8 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,53 +1,18 @@ @ECHO OFF +pushd %~dp0 + REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) +set SOURCEDIR=. set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) if "%1" == "" goto help -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - 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. 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. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over 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 - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -%SPHINXBUILD% 2> nul +%SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx @@ -60,183 +25,11 @@ if errorlevel 9009 ( exit /b 1 ) -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-SocketIO.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-SocketIO.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end +popd diff --git a/docs/upgrading.rst b/docs/upgrading.rst new file mode 100644 index 00000000..435290be --- /dev/null +++ b/docs/upgrading.rst @@ -0,0 +1,25 @@ +Upgrading to Flask-SocketIO 5.x from the 4.x releases +----------------------------------------------------- + +The Socket.IO protocol recently introduced a series of backwards incompatible +changes. The 5.x releases of Flask-SocketIO adopted these changes, and for +that reason it can only be used with clients that have also been updated to +the current version of the protocol. In particular, this means that the +JavaScript client must be upgraded to a 3.x release, and if your client hasn't +been upgraded to the latest version of the Socket.IO protocol, then you must +use a Flask-SocketIO 4.x release. + +The following protocol changes are of importance, as they may affect existing +applications: + +- The default namespace ``'/'`` is not automatically connected anymore, and is + now treated in the same way as other namespaces. +- Each namespace connection has its own ``sid`` value, different from the others + and different from the Engine.IO ``sid``. +- Flask-SocketIO now uses the same ping interval and timeout values as the + JavaScript reference implementation, which are 25 and 5 seconds respectively. +- The ping/pong mechanism has been reversed. In the current version of the + protocol, the server issues a ping and the client responds with a pong. +- The default allowed payload size for long--polling packets has been lowered + from 100MB to 1MB. +- The `io` cookie is not sent to the client anymore by default.