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