From 84faa991890f33e8fbb5e5db884674d2dd32b1f3 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 24 Nov 2018 11:21:07 +0000 Subject: [PATCH] reorganized documentation --- docs/Makefile | 191 +----- docs/_static/index.html | 12 - docs/_static/logo.png | Bin 42736 -> 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 | 291 ---------- docs/_themes/flask_small/theme.conf | 10 - docs/_themes/flask_theme_support.py | 86 --- docs/api.rst | 26 + docs/conf.py | 285 +++------ docs/deployment.rst | 268 +++++++++ docs/index.rst | 428 +------------- docs/intro.rst | 148 +++++ docs/make.bat | 298 ++-------- engineio/middleware.py | 1 + 20 files changed, 590 insertions(+), 2173 deletions(-) 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/intro.rst diff --git a/docs/Makefile b/docs/Makefile index aa78c496..298ea9e2 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,192 +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 coverage 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 " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/engineio.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/engineio.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/engineio" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/engineio" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." + @$(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/index.html b/docs/_static/index.html deleted file mode 100644 index 72bb0698..00000000 --- a/docs/_static/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - python-engineio documentation - - - - - The python-engineio 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 c0002537eaf55bf6286d2f332187a93b6d7ff317..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42736 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 - {% endif %} - -{% endblock %} -{%- block relbar2 %}{% endblock %} -{% block header %} - {{ super() }} - {% 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 802ebaff..00000000 --- a/docs/_themes/flask_small/static/flasky.css_t +++ /dev/null @@ -1,291 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- flasky theme based on nature theme. - * - * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - color: #000; - background: white; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 40px auto 0 auto; - width: 700px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - text-align: right; - color: #888; - padding: 10px; - font-size: 14px; - width: 650px; - margin: 0 auto 40px auto; -} - -div.footer a { - color: #888; - text-decoration: underline; -} - -div.related { - line-height: 32px; - color: #888; -} - -div.related ul { - padding: 0 0 0 10px; -} - -div.related a { - color: #444; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body { - padding-bottom: 40px; /* saved for footer */ -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} - -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: white; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight{ - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.85em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td { - padding: 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -pre { - padding: 0; - margin: 15px -30px; - padding: 8px; - line-height: 1.3em; - padding: 7px 30px; - background: #eee; - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; -} - -dl pre { - margin-left: -60px; - padding-left: 60px; -} - -dl.class { - margin-bottom: 50px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; -} - -a:hover tt { - background: #EEE; -} 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..240f9459 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,26 @@ +API Reference +============= + +.. toctree:: + :maxdepth: 3 + +.. module:: engineio + +``Middleware`` class +-------------------- + +.. autoclass:: Middleware + :members: + +``Server`` class +---------------- + +.. autoclass:: Server + :members: + +``AsyncServer`` class +--------------------- + +.. autoclass:: AsyncServer + :members: + :inherited-members: diff --git a/docs/conf.py b/docs/conf.py index 96e64441..e7bfde47 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,31 +1,39 @@ # -*- coding: utf-8 -*- # -# engineio documentation build configuration file, created by -# sphinx-quickstart on Sat Jun 13 23:41:23 2015. +# 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 -import shlex +# -- 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 = 'python-engineio' +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 @@ -39,29 +47,13 @@ # 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'engineio' -copyright = u'2015, Miguel Grinberg' -author = u'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' -# The full version, including alpha/beta/rc tags. -release = '0.1' - # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # @@ -69,222 +61,125 @@ # Usually you set "language" from the command line for these cases. 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' - # 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 - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = 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' #'alabaster' +# +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/python-engineio' -} - -# 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': 'python-engineio', + '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 - -# 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 - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# 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 = {} -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'engineiodoc' +htmlhelp_basename = 'python-engineiodoc' -# -- 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', + # 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 = [ - (master_doc, 'engineio.tex', u'engineio Documentation', - u'Miguel Grinberg', 'manual'), + (master_doc, 'python-engineio.tex', 'python-engineio 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 = [ - (master_doc, 'engineio', u'engineio Documentation', + (master_doc, 'python-engineio', 'python-engineio 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 = [ - (master_doc, 'engineio', u'engineio Documentation', - author, 'engineio', 'One line description of project.', - 'Miscellaneous'), + (master_doc, 'python-engineio', 'python-engineio Documentation', + author, 'python-engineio', '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 = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# -- Extension configuration ------------------------------------------------- diff --git a/docs/deployment.rst b/docs/deployment.rst new file mode 100644 index 00000000..1f85ff55 --- /dev/null +++ b/docs/deployment.rst @@ -0,0 +1,268 @@ +Deployment +========== + +The following sections describe a variety of deployment strategies for +Engine.IO servers. + +Sanic +----- + +`Sanic `_ is a very efficient asynchronous web +server for Python 3.5 and newer. + +Instances of class ``engineio.AsyncServer`` will automatically use Sanic for +asynchronous operations if the framework is installed. To request its use +explicitly, the ``async_mode`` option can be given in the constructor:: + + eio = engineio.AsyncServer(async_mode='sanic') + +A server configured for Sanic must be attached to an existing application:: + + app = Sanic() + eio.attach(app) + +The Sanic application can define regular routes that will coexist with the +Engine.IO server. A typical pattern is to add routes that serve a client +application and any associated static files to this application. + +The Sanic application is then executed in the usual manner:: + + if __name__ == '__main__': + app.run() + +aiohttp +------- + +`aiohttp `_ provides a framework with support +for HTTP and WebSocket, based on asyncio. Support for this framework is limited +to Python 3.5 and newer. + +Instances of class ``engineio.AsyncServer`` will automatically use aiohttp +for asynchronous operations if the library is installed. To request its use +explicitly, the ``async_mode`` option can be given in the constructor:: + + eio = engineio.AsyncServer(async_mode='aiohttp') + +A server configured for aiohttp must be attached to an existing application:: + + app = web.Application() + eio.attach(app) + +The aiohttp application can define regular routes that will coexist with the +Engine.IO server. A typical pattern is to add routes that serve a client +application and any associated static files. + +The aiohttp application is then executed in the usual manner:: + + if __name__ == '__main__': + web.run_app(app) + +Tornado +------- + +`Tornado `_ is a web framework with support +for HTTP and WebSocket. Support for this framework requires Python 3.5 and +newer. Only Tornado version 5 and newer are supported, thanks to its tight +integration with asyncio. + +Instances of class ``engineio.AsyncServer`` will automatically use tornado +for asynchronous operations if the library is installed. To request its use +explicitly, the ``async_mode`` option can be given in the constructor:: + + eio = engineio.AsyncServer(async_mode='tornado') + +A server configured for tornado must include a request handler for +Engine.IO:: + + app = tornado.web.Application( + [ + (r"/engine.io/", engineio.get_tornado_handler(eio)), + ], + # ... other application options + ) + +The tornado application can define other routes that will coexist with the +Engine.IO server. A typical pattern is to add routes that serve a client +application and any associated static files. + +The tornado application is then executed in the usual manner:: + + app.listen(port) + tornado.ioloop.IOLoop.current().start() + +Eventlet +-------- + +`Eventlet `_ is a high performance concurrent networking +library for Python 2 and 3 that uses coroutines, enabling code to be written in +the same style used with the blocking standard library functions. An Engine.IO +server deployed with eventlet has access to the long-polling and WebSocket +transports. + +Instances of class ``engineio.Server`` will automatically use eventlet for +asynchronous operations if the library is installed. To request its use +explicitly, the ``async_mode`` option can be given in the constructor:: + + eio = engineio.Server(async_mode='eventlet') + +A server configured for eventlet is deployed as a regular WSGI application, +using the provided ``engineio.Middleware``:: + + app = engineio.Middleware(eio) + import eventlet + eventlet.wsgi.server(eventlet.listen(('', 8000)), app) + +Using Gunicorn with Eventlet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An alternative to running the eventlet WSGI server as above is to use +`gunicorn `_, a fully featured pure Python web server. The +command to launch the application under gunicorn is shown below:: + + $ gunicorn -k eventlet -w 1 module:app + +Due to limitations in its load balancing algorithm, gunicorn can only be used +with one worker process, so the ``-w 1`` option is required. Note that a +single eventlet worker can handle a large number of concurrent clients. + +Another limitation when using gunicorn is that the WebSocket transport is not +available, because this transport it requires extensions to the WSGI standard. + +Note: Eventlet provides a ``monkey_patch()`` function that replaces all the +blocking functions in the standard library with equivalent asynchronous +versions. While python-engineio does not require monkey patching, other +libraries such as database drivers are likely to require it. + +Gevent +------ + +`Gevent `_ is another asynchronous framework based on +coroutines, very similar to eventlet. An Engine.IO server deployed with +gevent has access to the long-polling transport. If project +`gevent-websocket `_ is +installed, the WebSocket transport is also available. Note that when using the +uWSGI server, the native WebSocket implementation of uWSGI can be used instead +of gevent-websocket (see next section for details on this). + +Instances of class ``engineio.Server`` will automatically use gevent for +asynchronous operations if the library is installed and eventlet is not +installed. To request gevent to be selected explicitly, the ``async_mode`` +option can be given in the constructor:: + + # gevent alone or with gevent-websocket + eio = engineio.Server(async_mode='gevent') + +A server configured for gevent is deployed as a regular WSGI application, +using the provided ``engineio.Middleware``:: + + from gevent import pywsgi + app = engineio.Middleware(eio) + pywsgi.WSGIServer(('', 8000), app).serve_forever() + +If the WebSocket transport is installed, then the server must be started as +follows:: + + from gevent import pywsgi + from geventwebsocket.handler import WebSocketHandler + app = engineio.Middleware(eio) + pywsgi.WSGIServer(('', 8000), app, + handler_class=WebSocketHandler).serve_forever() + +Using Gunicorn with Gevent +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An alternative to running the gevent WSGI server as above is to use +`gunicorn `_, a fully featured pure Python web server. The +command to launch the application under gunicorn is shown below:: + + $ gunicorn -k gevent -w 1 module:app + +Or to include WebSocket:: + + $ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app + +Same as with eventlet, due to limitations in its load balancing algorithm, +gunicorn can only be used with one worker process, so the ``-w 1`` option is +required. Note that a single gevent worker can handle a large number of +concurrent clients. + +Note: Gevent provides a ``monkey_patch()`` function that replaces all the +blocking functions in the standard library with equivalent asynchronous +versions. While python-engineio does not require monkey patching, other +libraries such as database drivers are likely to require it. + +uWSGI +----- + +When using the uWSGI server in combination with gevent, the Engine.IO server +can take advantage of uWSGI's native WebSocket support. + +Instances of class ``engineio.Server`` will automatically use this option for +asynchronous operations if both gevent and uWSGI are installed and eventlet is +not installed. To request this asynchoronous mode explicitly, the +``async_mode`` option can be given in the constructor:: + + # gevent with uWSGI + eio = engineio.Server(async_mode='gevent_uwsgi') + +A complete explanation of the configuration and usage of the uWSGI server is +beyond the scope of this documentation. The uWSGI server is a fairly complex +package that provides a large and comprehensive set of options. It must be +compiled with WebSocket and SSL support for the WebSocket transport to be +available. As way of an introduction, the following command starts a uWSGI +server for the ``latency.py`` example on port 5000:: + + $ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app + +Standard Threads +---------------- + +While not comparable to eventlet and gevent in terms of performance, +the Engine.IO server can also be configured to work with multi-threaded web +servers that use standard Python threads. This is an ideal setup to use with +development servers such as `Werkzeug `_. Only the +long-polling transport is currently available when using standard threads. + +Instances of class ``engineio.Server`` will automatically use the threading +mode if neither eventlet nor gevent are not installed. To request the +threading mode explicitly, the ``async_mode`` option can be given in the +constructor:: + + eio = engineio.Server(async_mode='threading') + +A server configured for threading is deployed as a regular web application, +using any WSGI complaint multi-threaded server. The example below deploys an +Engine.IO application combined with a Flask web application, using Flask's +development web server based on Werkzeug:: + + eio = engineio.Server(async_mode='threading') + app = Flask(__name__) + app.wsgi_app = engineio.Middleware(eio, app.wsgi_app) + + # ... Engine.IO and Flask handler functions ... + + if __name__ == '__main__': + app.run(threaded=True) + +When using the threading mode, it is important to ensure that the WSGI server +can handle multiple concurrent requests using threads, since a client can have +up to two outstanding requests at any given time. The Werkzeug server is +single-threaded by default, so the ``threaded=True`` option is required. + +Note that servers that use worker processes instead of threads, such as +gunicorn, do not support an Engine.IO server configured in threading mode. + +Scalability Notes +----------------- + +Engine.IO is a stateful protocol, which makes horizontal scaling more +difficult. To deploy a cluster of Engine.IO processes hosted on one or +multiple servers the following conditions must be met: + +- Each Engine.IO server process must be able to handle multiple requests + concurrently. This is required because long-polling clients send two + requests in parallel. Worker processes that can only handle one request at a + time are not supported. +- The load balancer must be configured to always forward requests from a client + to the same process. Load balancers call this *sticky sessions*, or + *session affinity*. diff --git a/docs/index.rst b/docs/index.rst index 44004717..0d672a79 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,426 +1,24 @@ -.. engineio documentation master file, created by - sphinx-quickstart on Sat Jun 13 23:41:23 2015. +.. python-engineio documentation master file, created by + sphinx-quickstart on Sat Nov 24 09:42:25 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -engineio documentation -====================== +python-engineio +=============== This project implements an Engine.IO server that can run standalone or -integrated with a Python WSGI application. The following are some of its -features: - -- Fully compatible with the Javascript - `engine.io-client `_ library.. -- Compatible with Python 2.7 and Python 3.3+. -- Supports large number of clients even on modest hardware when used with an - asynchronous server based on `asyncio `_ - (`sanic `_, `aiohttp `_ or - `tornado `_), - `eventlet `_ or `gevent `_. For - development and testing, any WSGI compliant multi-threaded server can also be - used. -- Includes a WSGI middleware that integrates Engine.IO traffic with standard - WSGI applications. -- Uses an event-based architecture implemented with decorators that hides the - details of the protocol. -- Implements HTTP long-polling and WebSocket transports. -- Supports XHR2 and XHR browsers as clients. -- Supports text and binary messages. -- Supports gzip and deflate HTTP compression. -- Configurable CORS responses to avoid cross-origin problems with browsers. - -What is Engine.IO? ------------------- - -Engine.IO is a lightweight transport protocol that enables real-time -bidirectional event-based communication between clients (typically web -browsers) and a server. The official implementations of the client and -server components are written in JavaScript. - -The protocol is extremely simple. The example that follows shows the -client-side Javascript code required to setup an Engine.IO connection to -a server:: - - var socket = eio('http://chat.example.com'); - socket.on('open', function() { alert('connected'); }); - socket.on('message', function(data) { alert(data); }); - socket.on('close', function() { alert('disconnected'); }); - socket.send('Hello from the client!'); - -Getting Started ---------------- - -The following application is a basic example that uses the Eventlet -asynchronous server and includes a small Flask application that serves the -HTML/Javascript to the client:: - - import engineio - import eventlet - from flask import Flask, render_template - - eio = engineio.Server() - app = Flask(__name__) - - @app.route('/') - def index(): - """Serve the client-side application.""" - return render_template('index.html') - - @eio.on('connect') - def connect(sid, environ): - print("connect ", sid) - - @eio.on('message') - def message(sid, data): - print("message ", data) - eio.send(sid, 'reply') - - @eio.on('disconnect') - def disconnect(sid): - print('disconnect ', sid) - - if __name__ == '__main__': - # wrap Flask application with engineio's middleware - app = engineio.Middleware(eio, app) - - # deploy as an eventlet WSGI server - eventlet.wsgi.server(eventlet.listen(('', 8000)), app) - -Below is a similar application, coded for asyncio (Python 3.5+ only) with the -aiohttp framework:: - - from aiohttp import web - import engineio - - eio = engineio.AsyncServer() - app = web.Application() - - # attach the Engine.IO server to the application - eio.attach(app) - - async def index(request): - """Serve the client-side application.""" - with open('index.html') as f: - return web.Response(text=f.read(), content_type='text/html') - - @eio.on('connect') - def connect(sid, environ): - print("connect ", sid) - - @eio.on('message') - async def message(sid, data): - print("message ", data) - await eio.send(sid, 'reply') - - @eio.on('disconnect') - def disconnect(sid): - print('disconnect ', sid) - - app.router.add_static('/static', 'static') - app.router.add_get('/', index) - - if __name__ == '__main__': - # run the aiohttp application - web.run_app(app) - -The client-side application must include the -`engine.io-client `_ library -(version 1.5.0 or newer recommended). - -Each time a client connects to the server the ``connect`` event handler is -invoked with the ``sid`` (session ID) assigned to the connection and the WSGI -environment dictionary. The server can inspect authentication or other headers -to decide if the client is allowed to connect. To reject a client the handler -must return ``False``. - -When the client sends a message to the server the ``message`` event handler is -invoked with the ``sid`` and the message. - -Finally, when the connection is broken, the ``disconnect`` event is called, -allowing the application to perform cleanup. - -Because Engine.IO is a bidirectional protocol, the server can send messages to -any connected client at any time. The ``engineio.Server.send()`` method takes -the client's ``sid`` and the message payload, which can be of type ``str``, -``bytes``, ``list`` or ``dict`` (the last two are JSON encoded). - -Deployment ----------- - -The following sections describe a variety of deployment strategies for -Engine.IO servers. - -Sanic -~~~~~ - -`Sanic `_ is a very efficient asynchronous web -server for Python 3.5 and newer. - -Instances of class ``engineio.AsyncServer`` will automatically use Sanic for -asynchronous operations if the framework is installed. To request its use -explicitly, the ``async_mode`` option can be given in the constructor:: - - eio = engineio.AsyncServer(async_mode='sanic') - -A server configured for aiohttp must be attached to an existing application:: - - app = web.Application() - eio.attach(app) - -The Sanic application can define regular routes that will coexist with the -Engine.IO server. A typical pattern is to add routes that serve a client -application and any associated static files. - -The Sanic application is then executed in the usual manner:: - - if __name__ == '__main__': - app.run() - -aiohttp -~~~~~~~ - -`aiohttp `_ provides a framework with support -for HTTP and WebSocket, based on asyncio. Support for this framework is limited -to Python 3.5 and newer. - -Instances of class ``engineio.AsyncServer`` will automatically use aiohttp -for asynchronous operations if the library is installed. To request its use -explicitly, the ``async_mode`` option can be given in the constructor:: - - eio = engineio.AsyncServer(async_mode='aiohttp') - -A server configured for aiohttp must be attached to an existing application:: - - app = web.Application() - eio.attach(app) - -The aiohttp application can define regular routes that will coexist with the -Engine.IO server. A typical pattern is to add routes that serve a client -application and any associated static files. - -The aiohttp application is then executed in the usual manner:: - - if __name__ == '__main__': - web.run_app(app) - -Tornado -~~~~~~~ - -`Tornado `_ is a web framework with support -for HTTP and WebSocket. Support for this framework requires Python 3.5 and -newer. Only Tornado version 5 and newer are supported, thanks to its tight -integration with asyncio. - -Instances of class ``engineio.AsyncServer`` will automatically use tornado -for asynchronous operations if the library is installed. To request its use -explicitly, the ``async_mode`` option can be given in the constructor:: - - eio = engineio.AsyncServer(async_mode='tornado') - -A server configured for tornado must include a request handler for -Engine.IO:: - - app = tornado.web.Application( - [ - (r"/engine.io/", engineio.get_tornado_handler(eio)), - ], - # ... other application options - ) - -The tornado application can define other routes that will coexist with the -Engine.IO server. A typical pattern is to add routes that serve a client -application and any associated static files. - -The tornado application is then executed in the usual manner:: - - app.listen(port) - tornado.ioloop.IOLoop.current().start() - -Eventlet -~~~~~~~~ - -`Eventlet `_ is a high performance concurrent networking -library for Python 2 and 3 that uses coroutines, enabling code to be written in -the same style used with the blocking standard library functions. An Engine.IO -server deployed with eventlet has access to the long-polling and WebSocket -transports. - -Instances of class ``engineio.Server`` will automatically use eventlet for -asynchronous operations if the library is installed. To request its use -explicitly, the ``async_mode`` option can be given in the constructor:: - - eio = engineio.Server(async_mode='eventlet') - -A server configured for eventlet is deployed as a regular WSGI application, -using the provided ``engineio.Middleware``:: - - app = engineio.Middleware(eio) - import eventlet - eventlet.wsgi.server(eventlet.listen(('', 8000)), app) - -An alternative to running the eventlet WSGI server as above is to use -`gunicorn `_, a fully featured pure Python web server. The -command to launch the application under gunicorn is shown below:: - - $ gunicorn -k eventlet -w 1 module:app - -Due to limitations in its load balancing algorithm, gunicorn can only be used -with one worker process, so the ``-w 1`` option is required. Note that a -single eventlet worker can handle a large number of concurrent clients. - -Another limitation when using gunicorn is that the WebSocket transport is not -available, because this transport it requires extensions to the WSGI standard. - -Note: Eventlet provides a ``monkey_patch()`` function that replaces all the -blocking functions in the standard library with equivalent asynchronous -versions. While python-engineio does not require monkey patching, other -libraries such as database drivers are likely to require it. - -Gevent -~~~~~~ - -`Gevent `_ is another asynchronous framework based on -coroutines, very similar to eventlet. An Engine.IO server deployed with -gevent has access to the long-polling transport. If project -`gevent-websocket `_ is -installed, the WebSocket transport is also available. Note that when using the -uWSGI server, the native WebSocket implementation of uWSGI can be used instead -of gevent-websocket (see next section for details on this). - -Instances of class ``engineio.Server`` will automatically use gevent for -asynchronous operations if the library is installed and eventlet is not -installed. To request gevent to be selected explicitly, the ``async_mode`` -option can be given in the constructor:: - - # gevent alone or with gevent-websocket - eio = engineio.Server(async_mode='gevent') - -A server configured for gevent is deployed as a regular WSGI application, -using the provided ``engineio.Middleware``:: - - from gevent import pywsgi - app = engineio.Middleware(eio) - pywsgi.WSGIServer(('', 8000), app).serve_forever() - -If the WebSocket transport is installed, then the server must be started as -follows:: - - from gevent import pywsgi - from geventwebsocket.handler import WebSocketHandler - app = engineio.Middleware(eio) - pywsgi.WSGIServer(('', 8000), app, - handler_class=WebSocketHandler).serve_forever() - -An alternative to running the gevent WSGI server as above is to use -`gunicorn `_, a fully featured pure Python web server. The -command to launch the application under gunicorn is shown below:: - - $ gunicorn -k gevent -w 1 module:app - -Or to include WebSocket:: - - $ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app - -Same as with eventlet, due to limitations in its load balancing algorithm, -gunicorn can only be used with one worker process, so the ``-w 1`` option is -required. Note that a single gevent worker can handle a large number of -concurrent clients. - -Note: Gevent provides a ``monkey_patch()`` function that replaces all the -blocking functions in the standard library with equivalent asynchronous -versions. While python-engineio does not require monkey patching, other -libraries such as database drivers are likely to require it. - -Gevent with uWSGI -~~~~~~~~~~~~~~~~~ - -When using the uWSGI server in combination with gevent, the Engine.IO server -can take advantage of uWSGI's native WebSocket support. - -Instances of class ``engineio.Server`` will automatically use this option for -asynchronous operations if both gevent and uWSGI are installed and eventlet is -not installed. To request this asynchoronous mode explicitly, the -``async_mode`` option can be given in the constructor:: - - # gevent with uWSGI - eio = engineio.Server(async_mode='gevent_uwsgi') - -A complete explanation of the configuration and usage of the uWSGI server is -beyond the scope of this documentation. The uWSGI server is a fairly complex -package that provides a large and comprehensive set of options. It must be -compiled with WebSocket and SSL support for the WebSocket transport to be -available. As way of an introduction, the following command starts a uWSGI -server for the ``latency.py`` example on port 5000:: - - $ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app - -Standard Threading Library -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -While not comparable to eventlet and gevent in terms of performance, -the Engine.IO server can also be configured to work with multi-threaded web -servers that use standard Python threads. This is an ideal setup to use with -development servers such as `Werkzeug `_. Only the -long-polling transport is currently available when using standard threads. - -Instances of class ``engineio.Server`` will automatically use the threading -mode if neither eventlet nor gevent are not installed. To request the -threading mode explicitly, the ``async_mode`` option can be given in the -constructor:: - - eio = engineio.Server(async_mode='threading') - -A server configured for threading is deployed as a regular web application, -using any WSGI complaint multi-threaded server. The example below deploys an -Engine.IO application combined with a Flask web application, using Flask's -development web server based on Werkzeug:: - - eio = engineio.Server(async_mode='threading') - app = Flask(__name__) - app.wsgi_app = engineio.Middleware(eio, app.wsgi_app) - - # ... Engine.IO and Flask handler functions ... - - if __name__ == '__main__': - app.run(threaded=True) - -When using the threading mode, it is important to ensure that the WSGI server -can handle multiple concurrent requests using threads, since a client can have -up to two outstanding requests at any given time. The Werkzeug server is -single-threaded by default, so the ``threaded=True`` option is required. - -Note that servers that use worker processes instead of threads, such as -gunicorn, do not support an Engine.IO server configured in threading mode. - -Multi-process deployments -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Engine.IO is a stateful protocol, which makes horizontal scaling more -difficult. To deploy a cluster of Engine.IO processes (hosted on one or -multiple servers), the following conditions must be met: - -- Each Engine.IO process must be able to handle multiple requests, either by - using eventlet, gevent, or standard threads. Worker processes that only - handle one request at a time are not supported. -- The load balancer must be configured to always forward requests from a client - to the same process. Load balancers call this *sticky sessions*, or - *session affinity*. - -API Reference -------------- +integrated with a variety of Python web frameworks. .. toctree:: :maxdepth: 2 -.. module:: engineio - -.. autoclass:: Middleware - :members: + intro + deployment + api -.. autoclass:: Server - :members: +Indices and tables +================== -.. autoclass:: AsyncServer - :members: - :inherited-members: +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/intro.rst b/docs/intro.rst new file mode 100644 index 00000000..84bd9fb0 --- /dev/null +++ b/docs/intro.rst @@ -0,0 +1,148 @@ +.. engineio documentation master file, created by + sphinx-quickstart on Sat Jun 13 23:41:23 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Getting Started +=============== + +What is Engine.IO? +------------------ + +Engine.IO is a lightweight transport protocol that enables real-time +bidirectional event-based communication between clients (typically web +browsers) and a server. The official implementations of the client and +server components are written in JavaScript. + +The Engine.IO protocol is extremely simple. The example that follows shows the +client-side Javascript code required to setup an Engine.IO connection to +a server:: + + var socket = eio('http://chat.example.com'); + socket.on('open', function() { alert('connected'); }); + socket.on('message', function(data) { alert(data); }); + socket.on('close', function() { alert('disconnected'); }); + socket.send('Hello from the client!'); + +Features +-------- + +- Fully compatible with the Javascript + `engine.io-client `_ library, + and with other Engine.IO clients. +- Compatible with Python 2.7 and Python 3.3+. +- Supports large number of clients even on modest hardware due to being + asynchronous. +- Compatible with `aiohttp `_, + `sanic `_, + `tornado `_, + `eventlet `_, + `gevent `_, + or any `WSGI `_ or + `ASGI `_ compatible server. +- Includes WSGI and ASGI middlewares that integrate Engine.IO traffic with + other web applications. +- Uses an event-based architecture implemented with decorators that hides the + details of the protocol. +- Implements HTTP long-polling and WebSocket transports. +- Supports XHR2 and XHR browsers as clients. +- Supports text and binary messages. +- Supports gzip and deflate HTTP compression. +- Configurable CORS responses to avoid cross-origin problems with browsers. + +Examples +-------- + +The following application is a basic example that uses the Eventlet +asynchronous server and includes a small Flask application that serves the +HTML/Javascript to the client:: + + import engineio + import eventlet + from flask import Flask, render_template + + eio = engineio.Server() + app = Flask(__name__) + + @app.route('/') + def index(): + """Serve the client-side application.""" + return render_template('index.html') + + @eio.on('connect') + def connect(sid, environ): + print("connect ", sid) + + @eio.on('message') + def message(sid, data): + print("message ", data) + eio.send(sid, 'reply') + + @eio.on('disconnect') + def disconnect(sid): + print('disconnect ', sid) + + if __name__ == '__main__': + # wrap Flask application with engineio's middleware + app = engineio.Middleware(eio, app) + + # deploy as an eventlet WSGI server + eventlet.wsgi.server(eventlet.listen(('', 8000)), app) + +Below is a similar application, coded for asyncio (Python 3.5+ only) with the +aiohttp framework:: + + from aiohttp import web + import engineio + + eio = engineio.AsyncServer() + app = web.Application() + + # attach the Engine.IO server to the application + eio.attach(app) + + async def index(request): + """Serve the client-side application.""" + with open('index.html') as f: + return web.Response(text=f.read(), content_type='text/html') + + @eio.on('connect') + def connect(sid, environ): + print("connect ", sid) + + @eio.on('message') + async def message(sid, data): + print("message ", data) + await eio.send(sid, 'reply') + + @eio.on('disconnect') + def disconnect(sid): + print('disconnect ', sid) + + app.router.add_static('/static', 'static') + app.router.add_get('/', index) + + if __name__ == '__main__': + # run the aiohttp application + web.run_app(app) + +The client-side application must include the +`engine.io-client `_ library +(version 1.5.0 or newer recommended). + +Each time a client connects to the server the ``connect`` event handler is +invoked with the ``sid`` (session ID) assigned to the connection and the WSGI +environment dictionary. The server can inspect authentication or other headers +to decide if the client is allowed to connect. To reject a client the handler +must return ``False``. + +When the client sends a message to the server the ``message`` event handler is +invoked with the ``sid`` and the message. + +Finally, when the connection is broken, the ``disconnect`` event is called, +allowing the application to perform cleanup. + +Because Engine.IO is a bidirectional protocol, the server can send messages to +any connected client at any time. The ``engineio.Server.send()`` method takes +the client's ``sid`` and the message payload, which can be of type ``str``, +``bytes``, ``list`` or ``dict`` (the last two are JSON encoded). diff --git a/docs/make.bat b/docs/make.bat index 0e00321e..27f573b8 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,263 +1,35 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -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 - echo. coverage to run coverage check of 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 -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -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\engineio.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\engineio.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 %~dp0 - 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 %~dp0 - 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 -) - -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" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.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 -) - -:end +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/engineio/middleware.py b/engineio/middleware.py index 28a45426..93af0b0f 100644 --- a/engineio/middleware.py +++ b/engineio/middleware.py @@ -14,6 +14,7 @@ class Middleware(object): a dictionary with ``content_type`` and ``filename`` keys. This option is intended to be used for serving client files during development. + Example usage:: import engineio