Begin documentation and annotation backfill
This commit is contained in:
parent
7028eae083
commit
2f48e81e0b
4
Makefile
4
Makefile
|
@ -83,6 +83,10 @@ docs: docs-clean
|
|||
docs-test: docs-clean
|
||||
cd docs && make dummy
|
||||
|
||||
docs-serve:
|
||||
# python -m http.server --directory=./docs/_build/html 9999
|
||||
sphinx-autobuild docs docs/_build/html --port 9999 --watch ./sanic
|
||||
|
||||
changelog:
|
||||
python scripts/changelog.py
|
||||
|
||||
|
|
4
docs/_static/custom.css
vendored
Normal file
4
docs/_static/custom.css
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
.wy-side-nav-search,
|
||||
.wy-nav-top {
|
||||
background: #444444;
|
||||
}
|
BIN
docs/_static/sanic-framework-logo-white-400x97.png
vendored
Normal file
BIN
docs/_static/sanic-framework-logo-white-400x97.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
76
docs/conf.py
76
docs/conf.py
|
@ -12,8 +12,10 @@ import sys
|
|||
|
||||
# Add support for auto-doc
|
||||
import recommonmark
|
||||
|
||||
from recommonmark.transform import AutoStructify
|
||||
|
||||
|
||||
# Ensure that sanic is present in the path, to allow sphinx-apidoc to
|
||||
# autogenerate documentation from docstrings
|
||||
root_directory = os.path.dirname(os.getcwd())
|
||||
|
@ -21,23 +23,25 @@ sys.path.insert(0, root_directory)
|
|||
|
||||
import sanic
|
||||
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
extensions = ['sphinx.ext.autodoc', "recommonmark"]
|
||||
extensions = ["sphinx.ext.autodoc", "recommonmark"]
|
||||
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# Enable support for both Restructured Text and Markdown
|
||||
source_suffix = ['.rst', '.md']
|
||||
source_suffix = [".rst", ".md"]
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = 'Sanic'
|
||||
copyright = '2018, Sanic contributors'
|
||||
author = 'Sanic contributors'
|
||||
project = "Sanic"
|
||||
copyright = "2021, Sanic Community Organization"
|
||||
author = "Sanic Community Organization"
|
||||
|
||||
html_logo = "./_static/sanic-framework-logo-white-400x97.png"
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
|
@ -52,7 +56,7 @@ release = sanic.__version__
|
|||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = 'en'
|
||||
language = "en"
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
|
@ -60,10 +64,10 @@ language = 'en'
|
|||
#
|
||||
# modules.rst is generated by sphinx-apidoc but is unused. This suppresses
|
||||
# a warning about it.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'modules.rst']
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "modules.rst"]
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
@ -72,17 +76,17 @@ todo_include_todos = False
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = ["custom.css"]
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Sanicdoc'
|
||||
htmlhelp_basename = "Sanicdoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
|
@ -90,15 +94,12 @@ latex_elements = {
|
|||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
|
@ -107,14 +108,21 @@ latex_elements = {
|
|||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [(master_doc, 'Sanic.tex', 'Sanic Documentation',
|
||||
'Sanic contributors', 'manual'), ]
|
||||
latex_documents = [
|
||||
(
|
||||
master_doc,
|
||||
"Sanic.tex",
|
||||
"Sanic Documentation",
|
||||
"Sanic contributors",
|
||||
"manual",
|
||||
),
|
||||
]
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [(master_doc, 'sanic', 'Sanic Documentation', [author], 1)]
|
||||
man_pages = [(master_doc, "sanic", "Sanic Documentation", [author], 1)]
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
|
@ -122,8 +130,15 @@ man_pages = [(master_doc, 'sanic', 'Sanic Documentation', [author], 1)]
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Sanic', 'Sanic Documentation', author, 'Sanic',
|
||||
'One line description of project.', 'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
"Sanic",
|
||||
"Sanic Documentation",
|
||||
author,
|
||||
"Sanic",
|
||||
"One line description of project.",
|
||||
"Miscellaneous",
|
||||
),
|
||||
]
|
||||
|
||||
# -- Options for Epub output ----------------------------------------------
|
||||
|
@ -135,17 +150,22 @@ epub_publisher = author
|
|||
epub_copyright = copyright
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
epub_exclude_files = ["search.html"]
|
||||
|
||||
# -- Custom Settings -------------------------------------------------------
|
||||
|
||||
suppress_warnings = ['image.nonlocal_uri']
|
||||
suppress_warnings = ["image.nonlocal_uri"]
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
# app setup hook
|
||||
def setup(app):
|
||||
app.add_config_value('recommonmark_config', {
|
||||
'enable_eval_rst': True,
|
||||
'enable_auto_doc_ref': False,
|
||||
}, True)
|
||||
app.add_config_value(
|
||||
"recommonmark_config",
|
||||
{
|
||||
"enable_eval_rst": True,
|
||||
"enable_auto_doc_ref": False,
|
||||
},
|
||||
True,
|
||||
)
|
||||
app.add_transform(AutoStructify)
|
||||
|
|
|
@ -4,37 +4,9 @@ Guides
|
|||
======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 3
|
||||
|
||||
sanic/getting_started
|
||||
sanic/config
|
||||
sanic/logging
|
||||
sanic/request_data
|
||||
sanic/response
|
||||
sanic/cookies
|
||||
sanic/routing
|
||||
sanic/blueprints
|
||||
sanic/static_files
|
||||
sanic/versioning
|
||||
sanic/exceptions
|
||||
sanic/middleware
|
||||
sanic/websocket
|
||||
sanic/decorators
|
||||
sanic/streaming
|
||||
sanic/class_based_views
|
||||
sanic/custom_protocol
|
||||
sanic/sockets
|
||||
sanic/ssl
|
||||
sanic/debug_mode
|
||||
sanic/testing
|
||||
sanic/deploying
|
||||
sanic/nginx
|
||||
sanic/extensions
|
||||
sanic/examples
|
||||
sanic/changelog
|
||||
sanic/contributing
|
||||
sanic/api_reference
|
||||
sanic/asyncio_python37
|
||||
|
||||
|
||||
Module Documentation
|
||||
|
|
|
@ -1,153 +1,131 @@
|
|||
API Reference
|
||||
=============
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
sanic.app module
|
||||
----------------
|
||||
sanic.app
|
||||
---------
|
||||
|
||||
.. automodule:: sanic.app
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.blueprints module
|
||||
-----------------------
|
||||
sanic.blueprints
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.blueprints
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.blueprint_group module
|
||||
----------------------------
|
||||
sanic.blueprint_group
|
||||
---------------------
|
||||
|
||||
.. automodule:: sanic.blueprint_group
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.config module
|
||||
-------------------
|
||||
sanic.config
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.config
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.constants module
|
||||
----------------------
|
||||
sanic.constants
|
||||
---------------
|
||||
|
||||
.. automodule:: sanic.constants
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.cookies module
|
||||
--------------------
|
||||
sanic.cookies
|
||||
-------------
|
||||
|
||||
.. automodule:: sanic.cookies
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.exceptions module
|
||||
-----------------------
|
||||
sanic.exceptions
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.exceptions
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.handlers module
|
||||
---------------------
|
||||
sanic.handlers
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.handlers
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.log module
|
||||
----------------
|
||||
sanic.log
|
||||
---------
|
||||
|
||||
.. automodule:: sanic.log
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.request module
|
||||
--------------------
|
||||
sanic.request
|
||||
-------------
|
||||
|
||||
.. automodule:: sanic.request
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.response module
|
||||
---------------------
|
||||
sanic.response
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.response
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.router module
|
||||
-------------------
|
||||
sanic.router
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.router
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.server module
|
||||
-------------------
|
||||
sanic.server
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.server
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.static module
|
||||
-------------------
|
||||
sanic.static
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.static
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.testing module
|
||||
--------------------
|
||||
sanic.testing
|
||||
-------------
|
||||
|
||||
.. automodule:: sanic.testing
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.views module
|
||||
------------------
|
||||
sanic.views
|
||||
-----------
|
||||
|
||||
.. automodule:: sanic.views
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.websocket module
|
||||
----------------------
|
||||
sanic.websocket
|
||||
---------------
|
||||
|
||||
.. automodule:: sanic.websocket
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.worker module
|
||||
-------------------
|
||||
sanic.worker
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.worker
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Module contents
|
||||
|
@ -156,4 +134,3 @@ Module contents
|
|||
.. automodule:: sanic
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
Python 3.7 AsyncIO examples
|
||||
###########################
|
||||
|
||||
With Python 3.7 AsyncIO got major update for the following types:
|
||||
|
||||
- asyncio.AbstractEventLoop
|
||||
- asyncio.AbstractServer
|
||||
|
||||
|
||||
This example shows how to use sanic with Python 3.7, to be precise: how to retrieve an asyncio server instance:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import asyncio
|
||||
import socket
|
||||
import os
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return json({"hello": "world"})
|
||||
|
||||
|
||||
server_socket = '/tmp/sanic.sock'
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
|
||||
try:
|
||||
os.remove(server_socket)
|
||||
finally:
|
||||
sock.bind(server_socket)
|
||||
|
||||
if __name__ == "__main__":
|
||||
loop = asyncio.get_event_loop()
|
||||
srv_coro = app.create_server(
|
||||
sock=sock,
|
||||
return_asyncio_server=True,
|
||||
asyncio_server_kwargs=dict(
|
||||
start_serving=False
|
||||
)
|
||||
)
|
||||
srv = loop.run_until_complete(srv_coro)
|
||||
try:
|
||||
assert srv.is_serving() is False
|
||||
loop.run_until_complete(srv.start_serving())
|
||||
assert srv.is_serving() is True
|
||||
loop.run_until_complete(srv.serve_forever())
|
||||
except KeyboardInterrupt:
|
||||
srv.close()
|
||||
loop.close()
|
||||
|
||||
|
||||
Please note that uvloop does not support these features yet.
|
|
@ -1,301 +0,0 @@
|
|||
Blueprints
|
||||
==========
|
||||
|
||||
Blueprints are objects that can be used for sub-routing within an application.
|
||||
Instead of adding routes to the application instance, blueprints define similar
|
||||
methods for adding routes, which are then registered with the application in a
|
||||
flexible and pluggable manner.
|
||||
|
||||
Blueprints are especially useful for larger applications, where your
|
||||
application logic can be broken down into several groups or areas of
|
||||
responsibility.
|
||||
|
||||
My First Blueprint
|
||||
------------------
|
||||
|
||||
The following shows a very simple blueprint that registers a handler-function at
|
||||
the root `/` of your application.
|
||||
|
||||
Suppose you save this file as `my_blueprint.py`, which can be imported into your
|
||||
main application later.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
from sanic import Blueprint
|
||||
|
||||
bp = Blueprint('my_blueprint')
|
||||
|
||||
@bp.route('/')
|
||||
async def bp_root(request):
|
||||
return json({'my': 'blueprint'})
|
||||
|
||||
Registering blueprints
|
||||
----------------------
|
||||
|
||||
Blueprints must be registered with the application.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from my_blueprint import bp
|
||||
|
||||
app = Sanic(__name__)
|
||||
app.blueprint(bp)
|
||||
|
||||
app.run(host='0.0.0.0', port=8000, debug=True)
|
||||
|
||||
This will add the blueprint to the application and register any routes defined
|
||||
by that blueprint. In this example, the registered routes in the `app.router`
|
||||
will look like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[Route(handler=<function bp_root at 0x7f908382f9d8>, methods=frozenset({'GET'}), pattern=re.compile('^/$'), parameters=[], name='my_blueprint.bp_root', uri='/')]
|
||||
|
||||
Blueprint groups and nesting
|
||||
----------------------------
|
||||
|
||||
Blueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The `Blueprint.group` method is provided to simplify this process, allowing a 'mock' backend directory structure mimicking what's seen from the front end. Consider this (quite contrived) example:
|
||||
|
||||
| api/
|
||||
| ├──content/
|
||||
| │ ├──authors.py
|
||||
| │ ├──static.py
|
||||
| │ └──__init__.py
|
||||
| ├──info.py
|
||||
| └──__init__.py
|
||||
| app.py
|
||||
|
||||
Initialization of this app's blueprint hierarchy could go as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# api/content/authors.py
|
||||
from sanic import Blueprint
|
||||
|
||||
authors = Blueprint('content_authors', url_prefix='/authors')
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# api/content/static.py
|
||||
from sanic import Blueprint
|
||||
|
||||
static = Blueprint('content_static', url_prefix='/static')
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# api/content/__init__.py
|
||||
from sanic import Blueprint
|
||||
|
||||
from .static import static
|
||||
from .authors import authors
|
||||
|
||||
content = Blueprint.group(static, authors, url_prefix='/content')
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# api/info.py
|
||||
from sanic import Blueprint
|
||||
|
||||
info = Blueprint('info', url_prefix='/info')
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# api/__init__.py
|
||||
from sanic import Blueprint
|
||||
|
||||
from .content import content
|
||||
from .info import info
|
||||
|
||||
api = Blueprint.group(content, info, url_prefix='/api')
|
||||
|
||||
And registering these blueprints in `app.py` can now be done like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# app.py
|
||||
from sanic import Sanic
|
||||
|
||||
from .api import api
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
app.blueprint(api)
|
||||
|
||||
Using Blueprints
|
||||
----------------
|
||||
|
||||
Blueprints have almost the same functionality as an application instance.
|
||||
|
||||
WebSocket routes
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
WebSocket handlers can be registered on a blueprint using the `@bp.websocket`
|
||||
decorator or `bp.add_websocket_route` method.
|
||||
|
||||
Blueprint Middleware
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using blueprints allows you to also register middleware globally.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@bp.middleware
|
||||
async def print_on_request(request):
|
||||
print("I am a spy")
|
||||
|
||||
@bp.middleware('request')
|
||||
async def halt_request(request):
|
||||
return text('I halted the request')
|
||||
|
||||
@bp.middleware('response')
|
||||
async def halt_response(request, response):
|
||||
return text('I halted the response')
|
||||
|
||||
|
||||
Blueprint Group Middleware
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using this middleware will ensure that you can apply a common middleware to all the blueprints that form the
|
||||
current blueprint group under consideration.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp1 = Blueprint('bp1', url_prefix='/bp1')
|
||||
bp2 = Blueprint('bp2', url_prefix='/bp2')
|
||||
|
||||
@bp1.middleware('request')
|
||||
async def bp1_only_middleware(request):
|
||||
print('applied on Blueprint : bp1 Only')
|
||||
|
||||
@bp1.route('/')
|
||||
async def bp1_route(request):
|
||||
return text('bp1')
|
||||
|
||||
@bp2.route('/<param>')
|
||||
async def bp2_route(request, param):
|
||||
return text(param)
|
||||
|
||||
group = Blueprint.group(bp1, bp2)
|
||||
|
||||
@group.middleware('request')
|
||||
async def group_middleware(request):
|
||||
print('common middleware applied for both bp1 and bp2')
|
||||
|
||||
# Register Blueprint group under the app
|
||||
app.blueprint(group)
|
||||
|
||||
Exceptions
|
||||
~~~~~~~~~~
|
||||
|
||||
Exceptions can be applied exclusively to blueprints globally.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@bp.exception(NotFound)
|
||||
def ignore_404s(request, exception):
|
||||
return text("Yep, I totally found the page: {}".format(request.url))
|
||||
|
||||
Static files
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Static files can be served globally, under the blueprint prefix.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# suppose bp.name == 'bp'
|
||||
|
||||
bp.static('/web/path', '/folder/to/serve')
|
||||
# also you can pass name parameter to it for url_for
|
||||
bp.static('/web/path', '/folder/to/server', name='uploads')
|
||||
app.url_for('static', name='bp.uploads', filename='file.txt') == '/bp/web/path/file.txt'
|
||||
|
||||
Start and stop
|
||||
--------------
|
||||
|
||||
Blueprints can run functions during the start and stop process of the server.
|
||||
If running in multiprocessor mode (more than 1 worker), these are triggered
|
||||
after the workers fork.
|
||||
|
||||
Available events are:
|
||||
|
||||
- `before_server_start`: Executed before the server begins to accept connections
|
||||
- `after_server_start`: Executed after the server begins to accept connections
|
||||
- `before_server_stop`: Executed before the server stops accepting connections
|
||||
- `after_server_stop`: Executed after the server is stopped and all requests are complete
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp = Blueprint('my_blueprint')
|
||||
|
||||
@bp.listener('before_server_start')
|
||||
async def setup_connection(app, loop):
|
||||
global database
|
||||
database = mysql.connect(host='127.0.0.1'...)
|
||||
|
||||
@bp.listener('after_server_stop')
|
||||
async def close_connection(app, loop):
|
||||
await database.close()
|
||||
|
||||
Use-case: API versioning
|
||||
------------------------
|
||||
|
||||
Blueprints can be very useful for API versioning, where one blueprint may point
|
||||
at `/v1/<routes>`, and another pointing at `/v2/<routes>`.
|
||||
|
||||
When a blueprint is initialised, it can take an optional `version` argument,
|
||||
which will be prepended to all routes defined on the blueprint. This feature
|
||||
can be used to implement our API versioning scheme.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# blueprints.py
|
||||
from sanic.response import text
|
||||
from sanic import Blueprint
|
||||
|
||||
blueprint_v1 = Blueprint('v1', url_prefix='/api', version="v1")
|
||||
blueprint_v2 = Blueprint('v2', url_prefix='/api', version="v2")
|
||||
|
||||
@blueprint_v1.route('/')
|
||||
async def api_v1_root(request):
|
||||
return text('Welcome to version 1 of our documentation')
|
||||
|
||||
@blueprint_v2.route('/')
|
||||
async def api_v2_root(request):
|
||||
return text('Welcome to version 2 of our documentation')
|
||||
|
||||
When we register our blueprints on the app, the routes `/v1/api` and `/v2/api` will now
|
||||
point to the individual blueprints, which allows the creation of *sub-sites*
|
||||
for each API version.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# main.py
|
||||
from sanic import Sanic
|
||||
from blueprints import blueprint_v1, blueprint_v2
|
||||
|
||||
app = Sanic(__name__)
|
||||
app.blueprint(blueprint_v1)
|
||||
app.blueprint(blueprint_v2)
|
||||
|
||||
app.run(host='0.0.0.0', port=8000, debug=True)
|
||||
|
||||
URL Building with `url_for`
|
||||
---------------------------
|
||||
|
||||
If you wish to generate a URL for a route inside of a blueprint, remember that the endpoint name
|
||||
takes the format `<blueprint_name>.<handler_name>`. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@blueprint_v1.route('/')
|
||||
async def root(request):
|
||||
url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/api/post/5'
|
||||
return redirect(url)
|
||||
|
||||
|
||||
@blueprint_v1.route('/post/<post_id>')
|
||||
async def post_handler(request, post_id):
|
||||
return text('Post {} in Blueprint V1'.format(post_id))
|
|
@ -1,4 +0,0 @@
|
|||
Changelog
|
||||
---------
|
||||
|
||||
.. include:: ../../CHANGELOG.rst
|
|
@ -1,154 +0,0 @@
|
|||
Class-Based Views
|
||||
=================
|
||||
|
||||
Class-based views are simply classes which implement response behaviour to
|
||||
requests. They provide a way to compartmentalise handling of different HTTP
|
||||
request types at the same endpoint. Rather than defining and decorating three
|
||||
different handler functions, one for each of an endpoint's supported request
|
||||
type, the endpoint can be assigned a class-based view.
|
||||
|
||||
Defining views
|
||||
--------------
|
||||
|
||||
A class-based view should subclass `HTTPMethodView`. You can then implement
|
||||
class methods for every HTTP request type you want to support. If a request is
|
||||
received that has no defined method, a `405: Method not allowed` response will
|
||||
be generated.
|
||||
|
||||
To register a class-based view on an endpoint, the `app.add_route` method is
|
||||
used. The first argument should be the defined class with the method `as_view`
|
||||
invoked, and the second should be the URL endpoint.
|
||||
|
||||
The available methods are `get`, `post`, `put`, `patch`, and `delete`. A class
|
||||
using all these methods would look like the following.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.views import HTTPMethodView
|
||||
from sanic.response import text
|
||||
|
||||
app = Sanic("class_views_example")
|
||||
|
||||
class SimpleView(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
return text('I am get method')
|
||||
|
||||
# You can also use async syntax
|
||||
async def post(self, request):
|
||||
return text('I am post method')
|
||||
|
||||
def put(self, request):
|
||||
return text('I am put method')
|
||||
|
||||
def patch(self, request):
|
||||
return text('I am patch method')
|
||||
|
||||
def delete(self, request):
|
||||
return text('I am delete method')
|
||||
|
||||
app.add_route(SimpleView.as_view(), '/')
|
||||
|
||||
|
||||
URL parameters
|
||||
--------------
|
||||
|
||||
If you need any URL parameters, as discussed in the routing guide, include them
|
||||
in the method definition.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class NameView(HTTPMethodView):
|
||||
|
||||
def get(self, request, name):
|
||||
return text('Hello {}'.format(name))
|
||||
|
||||
app.add_route(NameView.as_view(), '/<name>')
|
||||
|
||||
Decorators
|
||||
----------
|
||||
|
||||
If you want to add any decorators to the class, you can set the `decorators`
|
||||
class variable. These will be applied to the class when `as_view` is called.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ViewWithDecorator(HTTPMethodView):
|
||||
decorators = [some_decorator_here]
|
||||
|
||||
def get(self, request, name):
|
||||
return text('Hello I have a decorator')
|
||||
|
||||
def post(self, request, name):
|
||||
return text("Hello I also have a decorator")
|
||||
|
||||
app.add_route(ViewWithDecorator.as_view(), '/url')
|
||||
|
||||
But if you just want to decorate some functions and not all functions, you can do as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ViewWithSomeDecorator(HTTPMethodView):
|
||||
|
||||
@staticmethod
|
||||
@some_decorator_here
|
||||
def get(request, name):
|
||||
return text("Hello I have a decorator")
|
||||
|
||||
def post(self, request, name):
|
||||
return text("Hello I don't have any decorators")
|
||||
|
||||
URL Building
|
||||
------------
|
||||
|
||||
If you wish to build a URL for an HTTPMethodView, remember that the class name will be the endpoint
|
||||
that you will pass into `url_for`. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route('/')
|
||||
def index(request):
|
||||
url = app.url_for('SpecialClassView')
|
||||
return redirect(url)
|
||||
|
||||
|
||||
class SpecialClassView(HTTPMethodView):
|
||||
def get(self, request):
|
||||
return text('Hello from the Special Class View!')
|
||||
|
||||
|
||||
app.add_route(SpecialClassView.as_view(), '/special_class_view')
|
||||
|
||||
Using CompositionView
|
||||
---------------------
|
||||
|
||||
As an alternative to the `HTTPMethodView`, you can use `CompositionView` to
|
||||
move handler functions outside of the view class.
|
||||
|
||||
Handler functions for each supported HTTP method are defined elsewhere in the
|
||||
source, and then added to the view using the `CompositionView.add` method. The
|
||||
first parameter is a list of HTTP methods to handle (e.g. `['GET', 'POST']`),
|
||||
and the second is the handler function. The following example shows
|
||||
`CompositionView` usage with both an external handler function and an inline
|
||||
lambda:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.views import CompositionView
|
||||
from sanic.response import text
|
||||
|
||||
app = Sanic("composition_example")
|
||||
|
||||
def get_handler(request):
|
||||
return text('I am a get method')
|
||||
|
||||
view = CompositionView()
|
||||
view.add(['GET'], get_handler)
|
||||
view.add(['POST', 'PUT'], lambda request: text('I am a post/put method'))
|
||||
|
||||
# Use the new view to handle requests to the base URL
|
||||
app.add_route(view, '/')
|
||||
|
||||
Note: currently you cannot build a URL for a CompositionView using `url_for`.
|
|
@ -1,339 +0,0 @@
|
|||
Configuration
|
||||
=============
|
||||
|
||||
Any reasonably complex application will need configuration that is not baked into the actual code. Settings might be different for different environments or installations.
|
||||
|
||||
Basics
|
||||
------
|
||||
|
||||
Sanic holds the configuration in the `config` attribute of the application object. The configuration object is merely an object that can be modified either using dot-notation or like a dictionary:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic('myapp')
|
||||
app.config.DB_NAME = 'appdb'
|
||||
app.config['DB_USER'] = 'appuser'
|
||||
|
||||
Since the config object has a type that inherits from dictionary, you can use its ``update`` method in order to set several values at once:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
db_settings = {
|
||||
'DB_HOST': 'localhost',
|
||||
'DB_NAME': 'appdb',
|
||||
'DB_USER': 'appuser'
|
||||
}
|
||||
app.config.update(db_settings)
|
||||
|
||||
In general the convention is to only have UPPERCASE configuration parameters. The methods described below for loading configuration only look for such uppercase parameters.
|
||||
|
||||
Loading Configuration
|
||||
---------------------
|
||||
|
||||
There are several ways how to load configuration.
|
||||
|
||||
From Environment Variables
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Any variables defined with the `SANIC_` prefix will be applied to the sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically and fed into the `REQUEST_TIMEOUT` config variable. You can pass a different prefix to Sanic:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic(__name__, load_env='MYAPP_')
|
||||
|
||||
Then the above variable would be `MYAPP_REQUEST_TIMEOUT`. If you want to disable loading from environment variables you can set it to `False` instead:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic(__name__, load_env=False)
|
||||
|
||||
From file, dict, or any object (having __dict__ attribute).
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can store app configurations in: (1) a Python file, (2) a dictionary, or (3) in some other type of custom object.
|
||||
|
||||
In order to load configuration from ove of those, you can use ``app.upload_config()``.
|
||||
|
||||
**1) From file**
|
||||
|
||||
|
||||
Let's say you have ``my_config.py`` file that looks like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# my_config.py
|
||||
A = 1
|
||||
B = 2
|
||||
|
||||
Loading config from this file is as easy as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.update_config("/path/to/my_config.py")
|
||||
|
||||
You can also use environment variables in the path name here.
|
||||
|
||||
Let's say you have an environment variable like this:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ export my_path="/path/to"
|
||||
|
||||
Then you can use it like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.update_config("${my_path}/my_config.py")
|
||||
|
||||
.. note::
|
||||
|
||||
Just remember that you have to provide environment variables in the format ${environment_variable} and that $environment_variable is not expanded (is treated as "plain" text).
|
||||
|
||||
**2) From dict**
|
||||
|
||||
You can also set your app config by providing a ``dict``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
d = {"A": 1, "B": 2}
|
||||
|
||||
app.update_config(d)
|
||||
|
||||
**3) From _any_ object**
|
||||
|
||||
App config can be taken from an object. Internally, it uses ``__dict__`` to retrieve keys and values.
|
||||
|
||||
For example, pass the class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class C:
|
||||
A = 1
|
||||
B = 2
|
||||
|
||||
app.update_config(C)
|
||||
|
||||
or, it can be instantiated:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
c = C()
|
||||
|
||||
app.update_config(c)
|
||||
|
||||
- From an object (having __dict__ attribute)
|
||||
|
||||
|
||||
From an Object
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated, will be removed in version 21.3.
|
||||
|
||||
If there are a lot of configuration values and they have sensible defaults it might be helpful to put them into a module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import myapp.default_settings
|
||||
|
||||
app = Sanic('myapp')
|
||||
app.config.from_object(myapp.default_settings)
|
||||
|
||||
or also by path to config:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic('myapp')
|
||||
app.config.from_object('config.path.config.Class')
|
||||
|
||||
You could use a class or any other object as well.
|
||||
|
||||
From a File
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated, will be removed in version 21.3.
|
||||
|
||||
Usually you will want to load configuration from a file that is not part of the distributed application. You can load configuration from a file using `from_pyfile(/path/to/config_file)`. However, that requires the program to know the path to the config file. So instead you can specify the location of the config file in an environment variable and tell Sanic to use that to find the config file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic('myapp')
|
||||
app.config.from_envvar('MYAPP_SETTINGS')
|
||||
|
||||
Then you can run your application with the `MYAPP_SETTINGS` environment variable set:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#$ MYAPP_SETTINGS=/path/to/config_file python3 myapp.py
|
||||
#INFO: Goin' Fast @ http://0.0.0.0:8000
|
||||
|
||||
|
||||
The config files are regular Python files which are executed in order to load them. This allows you to use arbitrary logic for constructing the right configuration. Only uppercase variables are added to the configuration. Most commonly the configuration consists of simple key value pairs:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# config_file
|
||||
DB_HOST = 'localhost'
|
||||
DB_NAME = 'appdb'
|
||||
DB_USER = 'appuser'
|
||||
|
||||
Builtin Configuration Values
|
||||
----------------------------
|
||||
|
||||
Out of the box there are just a few predefined values which can be overwritten when creating the application. Note that websocket configuration values will have no impact if running in ASGI mode.
|
||||
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| Variable | Default | Description |
|
||||
+===========================+===================+=============================================================================+
|
||||
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| KEEP_ALIVE | True | Disables keep-alive when False |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_MAX_SIZE | 2^20 | Maximum size for incoming messages (bytes) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_MAX_QUEUE | 32 | Maximum length of the queue that holds incoming messages |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_READ_LIMIT | 2^16 | High-water limit of the buffer for incoming bytes |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_WRITE_LIMIT | 2^16 | High-water limit of the buffer for outgoing bytes |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_PING_INTERVAL | 20 | A Ping frame is sent every ping_interval seconds. |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_PING_TIMEOUT | 20 | Connection is closed when Pong is not received after ping_timeout seconds |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| ACCESS_LOG | True | Disable or enable access log |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| FORWARDED_SECRET | None | Used to securely identify a specific proxy server (see below) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| PROXIES_COUNT | None | The number of proxy servers in front of the app (e.g. nginx; see below) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| FORWARDED_FOR_HEADER | "X-Forwarded-For" | The name of "X-Forwarded-For" HTTP header that contains client and proxy ip |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| REAL_IP_HEADER | None | The name of "X-Real-IP" HTTP header that contains real client ip |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
|
||||
The different Timeout variables:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
`REQUEST_TIMEOUT`
|
||||
#################
|
||||
|
||||
A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the
|
||||
Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the
|
||||
`REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates an `HTTP 408` response
|
||||
and sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads
|
||||
or upload requests very slowly.
|
||||
|
||||
`RESPONSE_TIMEOUT`
|
||||
##################
|
||||
|
||||
A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the
|
||||
Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT`
|
||||
value (in seconds), this is considered a Server Error so Sanic generates an `HTTP 503` response and sends that to the
|
||||
client. Set this parameter's value higher if your application is likely to have long-running process that delay the
|
||||
generation of a response.
|
||||
|
||||
`KEEP_ALIVE_TIMEOUT`
|
||||
####################
|
||||
|
||||
What is Keep Alive? And what does the Keep Alive Timeout value do?
|
||||
******************************************************************
|
||||
|
||||
`Keep-Alive` is a HTTP feature introduced in `HTTP 1.1`. When sending a HTTP request, the client (usually a web browser application)
|
||||
can set a `Keep-Alive` header to indicate the http server (Sanic) to not close the TCP connection after it has send the response.
|
||||
This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient
|
||||
network traffic for both the client and the server.
|
||||
|
||||
The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application,
|
||||
set it to `False` to cause all client connections to close immediately after a response is sent, regardless of
|
||||
the `Keep-Alive` header on the request.
|
||||
|
||||
The amount of time the server holds the TCP connection open is decided by the server itself.
|
||||
In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds.
|
||||
This is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for
|
||||
the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless
|
||||
you know your clients are using a browser which supports TCP connections held open for that long.
|
||||
|
||||
For reference:
|
||||
|
||||
* Apache httpd server default keepalive timeout = 5 seconds
|
||||
* Nginx server default keepalive timeout = 75 seconds
|
||||
* Nginx performance tuning guidelines uses keepalive = 15 seconds
|
||||
* IE (5-9) client hard keepalive limit = 60 seconds
|
||||
* Firefox client hard keepalive limit = 115 seconds
|
||||
* Opera 11 client hard keepalive limit = 120 seconds
|
||||
* Chrome 13+ client keepalive limit > 300+ seconds
|
||||
|
||||
|
||||
Proxy configuration
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When you use a reverse proxy server (e.g. nginx), the value of `request.ip` will contain ip of a proxy,
|
||||
typically `127.0.0.1`. Sanic may be configured to use proxy headers for determining the true client IP,
|
||||
available as `request.remote_addr`. The full external URL is also constructed from header fields if available.
|
||||
|
||||
Without proper precautions, a malicious client may use proxy headers to spoof its own IP. To avoid such issues, Sanic does not use any proxy headers unless explicitly enabled.
|
||||
|
||||
Services behind reverse proxies must configure `FORWARDED_SECRET`, `REAL_IP_HEADER` and/or `PROXIES_COUNT`.
|
||||
|
||||
Forwarded header
|
||||
################
|
||||
|
||||
.. Forwarded: for="1.2.3.4"; proto="https"; host="yoursite.com"; secret="Pr0xy", for="10.0.0.1"; proto="http"; host="proxy.internal"; by="_1234proxy"
|
||||
|
||||
* Set `FORWARDED_SECRET` to an identifier used by the proxy of interest.
|
||||
|
||||
The secret is used to securely identify a specific proxy server. Given the above header, secret `Pr0xy` would use the
|
||||
information on the first line and secret `_1234proxy` would use the second line. The secret must exactly match the value
|
||||
of `secret` or `by`. A secret in `by` must begin with an underscore and use only characters specified in
|
||||
`RFC 7239 section 6.3 <https://tools.ietf.org/html/rfc7239#section-6.3>`_, while `secret` has no such restrictions.
|
||||
|
||||
Sanic ignores any elements without the secret key, and will not even parse the header if no secret is set.
|
||||
|
||||
All other proxy headers are ignored once a trusted forwarded element is found, as it already carries complete information about the client.
|
||||
|
||||
Traditional proxy headers
|
||||
#########################
|
||||
|
||||
.. X-Real-IP: 1.2.3.4
|
||||
X-Forwarded-For: 1.2.3.4, 10.0.0.1
|
||||
X-Forwarded-Proto: https
|
||||
X-Forwarded-Host: yoursite.com
|
||||
|
||||
|
||||
* Set `REAL_IP_HEADER` to `x-real-ip`, `true-client-ip`, `cf-connecting-ip` or other name of such header.
|
||||
* Set `PROXIES_COUNT` to the number of entries expected in `x-forwarded-for` (name configurable via `FORWARDED_FOR_HEADER`).
|
||||
|
||||
If client IP is found by one of these methods, Sanic uses the following headers for URL parts:
|
||||
|
||||
* `x-forwarded-proto`, `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-path` and if necessary, `x-scheme`.
|
||||
|
||||
Proxy config if using ...
|
||||
#########################
|
||||
|
||||
* a proxy that supports `forwarded`: set `FORWARDED_SECRET` to the value that the proxy inserts in the header
|
||||
* Apache Traffic Server: `CONFIG proxy.config.http.insert_forwarded STRING for|proto|host|by=_secret`
|
||||
* NGHTTPX: `nghttpx --add-forwarded=for,proto,host,by --forwarded-for=ip --forwarded-by=_secret`
|
||||
* NGINX: :ref:`nginx`.
|
||||
|
||||
* a custom header with client IP: set `REAL_IP_HEADER` to the name of that header
|
||||
* `x-forwarded-for`: set `PROXIES_COUNT` to `1` for a single proxy, or a greater number to allow Sanic to select the correct IP
|
||||
* no proxies: no configuration required!
|
||||
|
||||
Changes in Sanic 19.9
|
||||
#####################
|
||||
|
||||
Earlier Sanic versions had unsafe default settings. From 19.9 onwards proxy settings must be set manually, and support for negative PROXIES_COUNT has been removed.
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -1,87 +0,0 @@
|
|||
Cookies
|
||||
=======
|
||||
|
||||
Cookies are pieces of data which persist inside a user's browser. Sanic can
|
||||
both read and write cookies, which are stored as key-value pairs.
|
||||
|
||||
.. warning::
|
||||
|
||||
Cookies can be freely altered by the client. Therefore you cannot just store
|
||||
data such as login information in cookies as-is, as they can be freely altered
|
||||
by the client. To ensure data you store in cookies is not forged or tampered
|
||||
with by the client, use something like `itsdangerous`_ to cryptographically
|
||||
sign the data.
|
||||
|
||||
|
||||
Reading cookies
|
||||
---------------
|
||||
|
||||
A user's cookies can be accessed via the ``Request`` object's ``cookies`` dictionary.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.route("/cookie")
|
||||
async def test(request):
|
||||
test_cookie = request.cookies.get('test')
|
||||
return text("Test cookie set to: {}".format(test_cookie))
|
||||
|
||||
Writing cookies
|
||||
---------------
|
||||
|
||||
When returning a response, cookies can be set on the ``Response`` object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.route("/cookie")
|
||||
async def test(request):
|
||||
response = text("There's a cookie up in this response")
|
||||
response.cookies['test'] = 'It worked!'
|
||||
response.cookies['test']['domain'] = '.gotta-go-fast.com'
|
||||
response.cookies['test']['httponly'] = True
|
||||
return response
|
||||
|
||||
Deleting cookies
|
||||
----------------
|
||||
|
||||
Cookies can be removed semantically or explicitly.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.route("/cookie")
|
||||
async def test(request):
|
||||
response = text("Time to eat some cookies muahaha")
|
||||
|
||||
# This cookie will be set to expire in 0 seconds
|
||||
del response.cookies['kill_me']
|
||||
|
||||
# This cookie will self destruct in 5 seconds
|
||||
response.cookies['short_life'] = 'Glad to be here'
|
||||
response.cookies['short_life']['max-age'] = 5
|
||||
del response.cookies['favorite_color']
|
||||
|
||||
# This cookie will remain unchanged
|
||||
response.cookies['favorite_color'] = 'blue'
|
||||
response.cookies['favorite_color'] = 'pink'
|
||||
del response.cookies['favorite_color']
|
||||
|
||||
return response
|
||||
|
||||
Response cookies can be set like dictionary values and have the following
|
||||
parameters available:
|
||||
|
||||
- ``expires`` (datetime): The time for the cookie to expire on the client's browser.
|
||||
- ``path`` (string): The subset of URLs to which this cookie applies. Defaults to /.
|
||||
- ``comment`` (string): A comment (metadata).
|
||||
- ``domain`` (string): Specifies the domain for which the cookie is valid. An
|
||||
explicitly specified domain must always start with a dot.
|
||||
- ``max-age`` (number): Number of seconds the cookie should live for.
|
||||
- ``secure`` (boolean): Specifies whether the cookie will only be sent via HTTPS.
|
||||
- ``httponly`` (boolean): Specifies whether the cookie cannot be read by Javascript.
|
||||
|
||||
.. _itsdangerous: https://pythonhosted.org/itsdangerous/
|
|
@ -1,76 +0,0 @@
|
|||
Custom Protocols
|
||||
================
|
||||
|
||||
.. note::
|
||||
|
||||
This is advanced usage, and most readers will not need such functionality.
|
||||
|
||||
You can change the behavior of Sanic's protocol by specifying a custom
|
||||
protocol, which should be a subclass
|
||||
of `asyncio.protocol <https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes>`_.
|
||||
This protocol can then be passed as the keyword argument ``protocol`` to the ``sanic.run`` method.
|
||||
|
||||
The constructor of the custom protocol class receives the following keyword
|
||||
arguments from Sanic.
|
||||
|
||||
- ``loop``: an ``asyncio``-compatible event loop.
|
||||
- ``connections``: a ``set`` to store protocol objects. When Sanic receives
|
||||
``SIGINT`` or ``SIGTERM``, it executes ``protocol.close_if_idle`` for all protocol
|
||||
objects stored in this set.
|
||||
- ``signal``: a ``sanic.server.Signal`` object with the ``stopped`` attribute. When
|
||||
Sanic receives ``SIGINT`` or ``SIGTERM``, ``signal.stopped`` is assigned ``True``.
|
||||
- ``request_handler``: a coroutine that takes a ``sanic.request.Request`` object
|
||||
and a ``response`` callback as arguments.
|
||||
- ``error_handler``: a ``sanic.exceptions.Handler`` which is called when exceptions
|
||||
are raised.
|
||||
- ``request_timeout``: the number of seconds before a request times out.
|
||||
- ``request_max_size``: an integer specifying the maximum size of a request, in bytes.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
An error occurs in the default protocol if a handler function does not return
|
||||
an ``HTTPResponse`` object.
|
||||
|
||||
By overriding the ``write_response`` protocol method, if a handler returns a
|
||||
string it will be converted to an ``HTTPResponse object``.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.server import HttpProtocol
|
||||
from sanic.response import text
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
class CustomHttpProtocol(HttpProtocol):
|
||||
|
||||
def __init__(self, *, loop, request_handler, error_handler,
|
||||
signal, connections, request_timeout, request_max_size):
|
||||
super().__init__(
|
||||
loop=loop, request_handler=request_handler,
|
||||
error_handler=error_handler, signal=signal,
|
||||
connections=connections, request_timeout=request_timeout,
|
||||
request_max_size=request_max_size)
|
||||
|
||||
def write_response(self, response):
|
||||
if isinstance(response, str):
|
||||
response = text(response)
|
||||
self.transport.write(
|
||||
response.output(self.request.version)
|
||||
)
|
||||
self.transport.close()
|
||||
|
||||
|
||||
@app.route('/')
|
||||
async def string(request):
|
||||
return 'string'
|
||||
|
||||
|
||||
@app.route('/1')
|
||||
async def response(request):
|
||||
return text('response')
|
||||
|
||||
app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
Debug Mode
|
||||
=============
|
||||
|
||||
When enabling Sanic's debug mode, Sanic will provide a more verbose logging output
|
||||
and by default will enable the Auto Reload feature.
|
||||
|
||||
.. warning::
|
||||
|
||||
Sanic's debug more will slow down the server's performance
|
||||
and is therefore advised to enable it only in development environments.
|
||||
|
||||
|
||||
Setting the debug mode
|
||||
----------------------
|
||||
|
||||
By setting the ``debug`` mode a more verbose output from Sanic will be output
|
||||
and the Automatic Reloader will be activated.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
@app.route('/')
|
||||
async def hello_world(request):
|
||||
return json({"hello": "world"})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
|
||||
|
||||
Manually setting auto reload
|
||||
----------------------------
|
||||
|
||||
Sanic offers a way to enable or disable the Automatic Reloader manually,
|
||||
the ``auto_reload`` argument will activate or deactivate the Automatic Reloader.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
@app.route('/')
|
||||
async def hello_world(request):
|
||||
return json({"hello": "world"})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host="0.0.0.0", port=8000, auto_reload=True)
|
|
@ -1,40 +0,0 @@
|
|||
Handler Decorators
|
||||
==================
|
||||
|
||||
Since Sanic handlers are simple Python functions, you can apply decorators to them in a similar manner to Flask. A typical use case is when you want some code to run before a handler's code is executed.
|
||||
|
||||
Authorization Decorator
|
||||
-----------------------
|
||||
|
||||
Let's say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from functools import wraps
|
||||
from sanic.response import json
|
||||
|
||||
def authorized():
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
async def decorated_function(request, *args, **kwargs):
|
||||
# run some method that checks the request
|
||||
# for the client's authorization status
|
||||
is_authorized = check_request_for_authorization_status(request)
|
||||
|
||||
if is_authorized:
|
||||
# the user is authorized.
|
||||
# run the handler method and return the response
|
||||
response = await f(request, *args, **kwargs)
|
||||
return response
|
||||
else:
|
||||
# the user is not authorized.
|
||||
return json({'status': 'not_authorized'}, 403)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@authorized()
|
||||
async def test(request):
|
||||
return json({'status': 'authorized'})
|
|
@ -1,195 +0,0 @@
|
|||
Deploying
|
||||
=========
|
||||
|
||||
Sanic has three serving options: the inbuilt webserver,
|
||||
an `ASGI webserver <https://asgi.readthedocs.io/en/latest/implementations.html>`_, or `gunicorn`.
|
||||
|
||||
Sanic's own webserver is the fastest option, and it can be securely run on
|
||||
the Internet. Still, it is also very common to place Sanic behind a reverse
|
||||
proxy, as shown in :ref:`nginx`.
|
||||
|
||||
Running via Sanic webserver
|
||||
---------------------------
|
||||
|
||||
After defining an instance of `sanic.Sanic`, we can call the `run` method with the following
|
||||
keyword arguments:
|
||||
|
||||
- `host` *(default `"127.0.0.1"`)*: Address to host the server on.
|
||||
- `port` *(default `8000`)*: Port to host the server on.
|
||||
- `unix` *(default `None`)*: Unix socket name to host the server on (instead of TCP).
|
||||
- `debug` *(default `False`)*: Enables debug output (slows server).
|
||||
- `ssl` *(default `None`)*: `SSLContext` for SSL encryption of worker(s).
|
||||
- `sock` *(default `None`)*: Socket for the server to accept connections from.
|
||||
- `workers` *(default `1`)*: Number of worker processes to spawn.
|
||||
- `loop` *(default `None`)*: An `asyncio`-compatible event loop. If none is specified, Sanic creates its own event loop.
|
||||
- `protocol` *(default `HttpProtocol`)*: Subclass of `asyncio.protocol <https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes>`_.
|
||||
- `access_log` *(default `True`)*: Enables log on handling requests (significantly slows server).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(host='0.0.0.0', port=1337, access_log=False)
|
||||
|
||||
In the above example, we decided to turn off the access log in order to increase performance.
|
||||
|
||||
Workers
|
||||
~~~~~~~
|
||||
|
||||
By default, Sanic listens in the main process using only one CPU core. To crank
|
||||
up the juice, just specify the number of workers in the `run` arguments.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(host='0.0.0.0', port=1337, workers=4)
|
||||
|
||||
Sanic will automatically spin up multiple processes and route traffic between
|
||||
them. We recommend as many workers as you have available cores.
|
||||
|
||||
Running via command
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you like using command line arguments, you can launch a Sanic webserver by
|
||||
executing the module. For example, if you initialized Sanic as `app` in a file
|
||||
named `server.py`, you could run the server like so:
|
||||
|
||||
::
|
||||
|
||||
sanic server.app --host=0.0.0.0 --port=1337 --workers=4
|
||||
|
||||
It can also be called directly as a module.
|
||||
|
||||
::
|
||||
|
||||
python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4
|
||||
|
||||
With this way of running sanic, it is not necessary to invoke `app.run` in your
|
||||
Python file. If you do, make sure you wrap it so that it only executes when
|
||||
directly run by the interpreter.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=1337, workers=4)
|
||||
|
||||
Running via ASGI
|
||||
----------------
|
||||
|
||||
Sanic is also ASGI-compliant. This means you can use your preferred ASGI webserver
|
||||
to run Sanic. The three main implementations of ASGI are
|
||||
`Daphne <http://github.com/django/daphne>`_, `Uvicorn <https://www.uvicorn.org/>`_,
|
||||
and `Hypercorn <https://pgjones.gitlab.io/hypercorn/index.html>`_.
|
||||
|
||||
Follow their documentation for the proper way to run them, but it should look
|
||||
something like:
|
||||
|
||||
::
|
||||
|
||||
daphne myapp:app
|
||||
uvicorn myapp:app
|
||||
hypercorn myapp:app
|
||||
|
||||
A couple things to note when using ASGI:
|
||||
|
||||
1. When using the Sanic webserver, websockets will run using the `websockets <https://websockets.readthedocs.io/>`_ package.
|
||||
In ASGI mode, there is no need for this package since websockets are managed in the ASGI server.
|
||||
2. The ASGI `lifespan protocol <https://asgi.readthedocs.io/en/latest/specs/lifespan.html>`, supports
|
||||
only two server events: startup and shutdown. Sanic has four: before startup, after startup,
|
||||
before shutdown, and after shutdown. Therefore, in ASGI mode, the startup and shutdown events will
|
||||
run consecutively and not actually around the server process beginning and ending (since that
|
||||
is now controlled by the ASGI server). Therefore, it is best to use `after_server_start` and
|
||||
`before_server_stop`.
|
||||
|
||||
Sanic has experimental support for running on `Trio <https://trio.readthedocs.io/en/stable/>`_ with::
|
||||
|
||||
hypercorn -k trio myapp:app
|
||||
|
||||
|
||||
Running via Gunicorn
|
||||
--------------------
|
||||
|
||||
`Gunicorn <http://gunicorn.org/>`_ ‘Green Unicorn’ is a WSGI HTTP Server for UNIX.
|
||||
It’s a pre-fork worker model ported from Ruby’s Unicorn project.
|
||||
|
||||
In order to run Sanic application with Gunicorn, you need to use the special `sanic.worker.GunicornWorker`
|
||||
for Gunicorn `worker-class` argument:
|
||||
|
||||
::
|
||||
|
||||
gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker
|
||||
|
||||
|
||||
If your application suffers from memory leaks, you can configure Gunicorn to gracefully restart a worker
|
||||
after it has processed a given number of requests. This can be a convenient way to help limit the effects
|
||||
of the memory leak.
|
||||
|
||||
See the `Gunicorn Docs <http://docs.gunicorn.org/en/latest/settings.html#max-requests>`_ for more information.
|
||||
|
||||
Other deployment considerations
|
||||
-------------------------------
|
||||
|
||||
Disable debug logging for performance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To improve the performance add `debug=False` and `access_log=False` in the `run` arguments.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(host='0.0.0.0', port=1337, workers=4, debug=False, access_log=False)
|
||||
|
||||
Running via Gunicorn you can set Environment variable `SANIC_ACCESS_LOG="False"`
|
||||
|
||||
::
|
||||
|
||||
env SANIC_ACCESS_LOG="False" gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker --log-level warning
|
||||
|
||||
Or you can rewrite app config directly
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.config.ACCESS_LOG = False
|
||||
|
||||
Asynchronous support and sharing the loop
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is suitable if you *need* to share the Sanic process with other applications, in particular the `loop`.
|
||||
However, be advised that this method does not support using multiple processes, and is not the preferred way
|
||||
to run the app in general.
|
||||
|
||||
Here is an incomplete example (please see `run_async.py` in examples for something more practical):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
server = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||
loop = asyncio.get_event_loop()
|
||||
task = asyncio.ensure_future(server)
|
||||
loop.run_forever()
|
||||
|
||||
Caveat: using this method, calling `app.create_server()` will trigger "before_server_start" server events, but not
|
||||
"after_server_start", "before_server_stop", or "after_server_stop" server events.
|
||||
|
||||
For more advanced use-cases, you can trigger these events using the AsyncioServer object, returned by awaiting
|
||||
the server task.
|
||||
|
||||
Here is an incomplete example (please see `run_async_advanced.py` in examples for something more complete):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||
loop = asyncio.get_event_loop()
|
||||
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||
server = loop.run_until_complete(serv_task)
|
||||
server.after_start()
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
loop.stop()
|
||||
finally:
|
||||
server.before_stop()
|
||||
|
||||
# Wait for server to close
|
||||
close_task = server.close()
|
||||
loop.run_until_complete(close_task)
|
||||
|
||||
# Complete all tasks on the loop
|
||||
for connection in server.connections:
|
||||
connection.close_if_idle()
|
||||
server.after_stop()
|
|
@ -1,167 +0,0 @@
|
|||
Examples
|
||||
========
|
||||
|
||||
This section of the documentation is a simple collection of example code that can help you get a quick start
|
||||
on your application development. Most of these examples are categorized and provide you with a link to the
|
||||
working code example in the `Sanic Repository <https://github.com/huge-success/sanic/tree/master/examples>`_
|
||||
|
||||
|
||||
Basic Examples
|
||||
--------------
|
||||
|
||||
This section of the examples are a collection of code that provide a simple use case example of the sanic application.
|
||||
|
||||
Simple Apps
|
||||
~~~~~~~~~~~~
|
||||
|
||||
A simple sanic application with a single ``async`` method with ``text`` and ``json`` type response.
|
||||
|
||||
|
||||
.. literalinclude:: ../../examples/teapot.py
|
||||
|
||||
.. literalinclude:: ../../examples/simple_server.py
|
||||
|
||||
|
||||
Simple App with ``Sanic Views``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Showcasing the simple mechanism of using :class:`sanic.views.HTTPMethodView` as well as a way to extend the same
|
||||
into providing a custom ``async`` behavior for ``view``.
|
||||
|
||||
.. literalinclude:: ../../examples/simple_async_view.py
|
||||
|
||||
|
||||
URL Redirect
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. literalinclude:: ../../examples/redirect_example.py
|
||||
|
||||
|
||||
Named URL redirection
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``Sanic`` provides an easy to use way of redirecting the requests via a helper method called ``url_for`` that takes a
|
||||
unique url name as argument and returns you the actual route assigned for it. This will help in simplifying the
|
||||
efforts required in redirecting the user between different section of the application.
|
||||
|
||||
.. literalinclude:: ../../examples/url_for_example.py
|
||||
|
||||
Blueprints
|
||||
~~~~~~~~~~
|
||||
``Sanic`` provides an amazing feature to group your APIs and routes under a logical collection that can easily be
|
||||
imported and plugged into any of your sanic application and it's called ``blueprints``
|
||||
|
||||
.. literalinclude:: ../../examples/blueprints.py
|
||||
|
||||
Logging Enhancements
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Even though ``Sanic`` comes with a battery of Logging support it allows the end users to customize the way logging
|
||||
is handled in the application runtime.
|
||||
|
||||
.. literalinclude:: ../../examples/override_logging.py
|
||||
|
||||
The following sample provides an example code that demonstrates the usage of :func:`sanic.app.Sanic.middleware` in order
|
||||
to provide a mechanism to assign a unique request ID for each of the incoming requests and log them via
|
||||
`aiotask-context <https://github.com/Skyscanner/aiotask-context>`_.
|
||||
|
||||
|
||||
.. literalinclude:: ../../examples/log_request_id.py
|
||||
|
||||
Sanic Streaming Support
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``Sanic`` framework comes with in-built support for streaming large files and the following code explains the process
|
||||
to setup a ``Sanic`` application with streaming support.
|
||||
|
||||
.. literalinclude:: ../../examples/request_stream/server.py
|
||||
|
||||
Sample Client app to show the usage of streaming application by a client code.
|
||||
|
||||
.. literalinclude:: ../../examples/request_stream/client.py
|
||||
|
||||
Sanic Concurrency Support
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``Sanic`` supports the ability to start an app with multiple worker support. However, it's important to be able to limit
|
||||
the concurrency per process/loop in order to ensure an efficient execution. The following section of the code provides a
|
||||
brief example of how to limit the concurrency with the help of :class:`asyncio.Semaphore`
|
||||
|
||||
.. literalinclude:: ../../examples/limit_concurrency.py
|
||||
|
||||
|
||||
Sanic Deployment via Docker
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Deploying a ``sanic`` app via ``docker`` and ``docker-compose`` is an easy task to achieve and the following example
|
||||
provides a deployment of the sample ``simple_server.py``
|
||||
|
||||
.. literalinclude:: ../../examples/Dockerfile
|
||||
|
||||
.. literalinclude:: ../../examples/docker-compose.yml
|
||||
|
||||
|
||||
Monitoring and Error Handling
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``Sanic`` provides an extendable bare minimum implementation of a global exception handler via
|
||||
:class:`sanic.handlers.ErrorHandler`. This example shows how to extend it to enable some custom behaviors.
|
||||
|
||||
.. literalinclude:: ../../examples/exception_monitoring.py
|
||||
|
||||
Monitoring using external Service Providers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* `LogDNA <https://logdna.com/>`_
|
||||
|
||||
.. literalinclude:: ../../examples/logdna_example.py
|
||||
|
||||
* `RayGun <https://raygun.com/>`_
|
||||
|
||||
.. literalinclude:: ../../examples/raygun_example.py
|
||||
|
||||
* `Rollbar <https://rollbar.com>`_
|
||||
|
||||
.. literalinclude:: ../../examples/rollbar_example.py
|
||||
|
||||
* `Sentry <http://sentry.io>`_
|
||||
|
||||
.. literalinclude:: ../../examples/sentry_example.py
|
||||
|
||||
|
||||
Security
|
||||
~~~~~~~~
|
||||
|
||||
The following sample code shows a simple decorator based authentication and authorization mechanism that can be setup
|
||||
to secure your ``sanic`` api endpoints.
|
||||
|
||||
.. literalinclude:: ../../examples/authorized_sanic.py
|
||||
|
||||
Sanic Websocket
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
``Sanic`` provides an ability to easily add a route and map it to a ``websocket`` handlers.
|
||||
|
||||
.. literalinclude:: ../../examples/websocket.html
|
||||
.. literalinclude:: ../../examples/websocket.py
|
||||
|
||||
vhost Suppport
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. literalinclude:: ../../examples/vhosts.py
|
||||
|
||||
Unit Testing With Parallel Test Run Support
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following example shows you how to get up and running with unit testing ``sanic`` application with parallel test
|
||||
execution support provided by the ``pytest-xdist`` plugin.
|
||||
|
||||
.. literalinclude:: ../../examples/pytest_xdist.py
|
||||
|
||||
|
||||
Amending Request Object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``request`` object in ``Sanic`` is a kind of ``dict`` object, this means that ``request`` object can be manipulated as a regular ``dict`` object.
|
||||
|
||||
.. literalinclude:: ../../examples/amending_request_object.py
|
||||
|
||||
For more examples and useful samples please visit the `Huge-Sanic's GitHub Page <https://github.com/huge-success/sanic/tree/master/examples>`_
|
|
@ -1,92 +0,0 @@
|
|||
Exceptions
|
||||
==========
|
||||
|
||||
Exceptions can be thrown from within request handlers and will automatically be
|
||||
handled by Sanic. Exceptions take a message as their first argument, and can
|
||||
also take a status code to be passed back in the HTTP response.
|
||||
|
||||
Throwing an exception
|
||||
---------------------
|
||||
|
||||
To throw an exception, simply `raise` the relevant exception from the
|
||||
`sanic.exceptions` module.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.exceptions import ServerError
|
||||
|
||||
@app.route('/killme')
|
||||
async def i_am_ready_to_die(request):
|
||||
raise ServerError("Something bad happened", status_code=500)
|
||||
|
||||
You can also use the `abort` function with the appropriate status code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.exceptions import abort
|
||||
from sanic.response import text
|
||||
|
||||
@app.route('/youshallnotpass')
|
||||
async def no_no(request):
|
||||
abort(401)
|
||||
# this won't happen
|
||||
text("OK")
|
||||
|
||||
Handling exceptions
|
||||
-------------------
|
||||
|
||||
To override Sanic's default handling of an exception, the `@app.exception`
|
||||
decorator is used. The decorator expects a list of exceptions to handle as
|
||||
arguments. You can pass `SanicException` to catch them all! The decorated
|
||||
exception handler function must take a `Request` and `Exception` object as
|
||||
arguments.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
from sanic.exceptions import NotFound
|
||||
|
||||
@app.exception(NotFound)
|
||||
async def ignore_404s(request, exception):
|
||||
return text("Yep, I totally found the page: {}".format(request.url))
|
||||
|
||||
You can also add an exception handler as such:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
async def server_error_handler(request, exception):
|
||||
return text("Oops, server error", status=500)
|
||||
|
||||
app = Sanic("error_handler_example")
|
||||
app.error_handler.add(Exception, server_error_handler)
|
||||
|
||||
In some cases, you might want to add some more error handling
|
||||
functionality to what is provided by default. In that case, you
|
||||
can subclass Sanic's default error handler as such:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.handlers import ErrorHandler
|
||||
|
||||
class CustomErrorHandler(ErrorHandler):
|
||||
def default(self, request, exception):
|
||||
''' handles errors that have no error handlers assigned '''
|
||||
# You custom error handling logic...
|
||||
return super().default(request, exception)
|
||||
|
||||
app = Sanic("custom_error_handler_example")
|
||||
app.error_handler = CustomErrorHandler()
|
||||
|
||||
Useful exceptions
|
||||
-----------------
|
||||
|
||||
Some of the most useful exceptions are presented below:
|
||||
|
||||
- `NotFound`: called when a suitable route for the request isn't found.
|
||||
- `ServerError`: called when something goes wrong inside the server. This
|
||||
usually occurs if there is an exception raised in user code.
|
||||
|
||||
See the `sanic.exceptions` module for the full list of exceptions to throw.
|
|
@ -1,4 +0,0 @@
|
|||
Extensions
|
||||
==========
|
||||
|
||||
Moved to the `awesome-sanic <https://github.com/mekicha/awesome-sanic>`_ list.
|
|
@ -1,85 +0,0 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
Make sure you have both `pip <https://pip.pypa.io/en/stable/installing/>`_ and at
|
||||
least version 3.6 of Python before starting. Sanic uses the new `async`/`await`
|
||||
syntax, so earlier versions of python won't work.
|
||||
|
||||
1. Install Sanic
|
||||
----------------
|
||||
|
||||
If you are running on a clean install of Fedora 28 or above, please make sure you have the ``redhat-rpm-config`` package installed in case if you want to use ``sanic`` with ``ujson`` dependency.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip3 install sanic
|
||||
|
||||
To install sanic without `uvloop` or `ujson` using bash, you can provide either or both of these environmental variables
|
||||
using any truthy string like `'y', 'yes', 't', 'true', 'on', '1'` and setting the `SANIC_NO_X` ( with`X` = `UVLOOP`/`UJSON`)
|
||||
to true will stop that features installation.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true pip3 install --no-binary :all: sanic
|
||||
|
||||
You can also install Sanic from `conda-forge <https://anaconda.org/conda-forge/sanic>`_
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
conda config --add channels conda-forge
|
||||
conda install sanic
|
||||
|
||||
2. Create a file called `main.py`
|
||||
---------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic("hello_example")
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return json({"hello": "world"})
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
3. Run the server
|
||||
-----------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 main.py
|
||||
|
||||
4. Check your browser
|
||||
---------------------
|
||||
|
||||
Open the address `http://0.0.0.0:8000 <http://0.0.0.0:8000>`_ in your web browser. You should see
|
||||
the message *Hello world!*.
|
||||
|
||||
You now have a working Sanic server!
|
||||
|
||||
5. Application registry
|
||||
-----------------------
|
||||
|
||||
When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# ./path/to/server.py
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic("my_awesome_server")
|
||||
|
||||
# ./path/to/somewhere_else.py
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic.get_app("my_awesome_server")
|
||||
|
||||
If you call ``Sanic.get_app("non-existing")`` on an app that does not exist, it will raise ``SanicException`` by default. You can, instead, force the method to return a new instance of ``Sanic`` with that name:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic.get_app("my_awesome_server", force_create=True)
|
|
@ -1,103 +0,0 @@
|
|||
Logging
|
||||
=======
|
||||
|
||||
Sanic allows you to do different types of logging (access log, error
|
||||
log) on the requests based on the `python3 logging API`_. You should
|
||||
have some basic knowledge on python3 logging if you want to create a new
|
||||
configuration.
|
||||
|
||||
Quick Start
|
||||
~~~~~~~~~~~
|
||||
|
||||
A simple example using default settings would be like this:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.log import logger
|
||||
from sanic.response import text
|
||||
|
||||
app = Sanic('logging_example')
|
||||
|
||||
@app.route('/')
|
||||
async def test(request):
|
||||
logger.info('Here is your log')
|
||||
return text('Hello World!')
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True, access_log=True)
|
||||
|
||||
After the server is running, you can see some messages looks like:
|
||||
|
||||
::
|
||||
|
||||
[2018-11-06 21:16:53 +0800] [24622] [INFO] Goin' Fast @ http://127.0.0.1:8000
|
||||
[2018-11-06 21:16:53 +0800] [24667] [INFO] Starting worker [24667]
|
||||
|
||||
You can send a request to server and it will print the log messages:
|
||||
|
||||
::
|
||||
|
||||
[2018-11-06 21:18:53 +0800] [25685] [INFO] Here is your log
|
||||
[2018-11-06 21:18:53 +0800] - (sanic.access)[INFO][127.0.0.1:57038]: GET http://localhost:8000/ 200 12
|
||||
|
||||
To use your own logging config, simply use
|
||||
``logging.config.dictConfig``, or pass ``log_config`` when you
|
||||
initialize ``Sanic`` app:
|
||||
|
||||
.. code:: python
|
||||
|
||||
app = Sanic('logging_example', log_config=LOGGING_CONFIG)
|
||||
|
||||
And to close logging, simply assign access_log=False:
|
||||
|
||||
.. code:: python
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(access_log=False)
|
||||
|
||||
This would skip calling logging functions when handling requests. And
|
||||
you could even do further in production to gain extra speed:
|
||||
|
||||
.. code:: python
|
||||
|
||||
if __name__ == "__main__":
|
||||
# disable debug messages
|
||||
app.run(debug=False, access_log=False)
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
By default, ``log_config`` parameter is set to use
|
||||
``sanic.log.LOGGING_CONFIG_DEFAULTS`` dictionary for configuration.
|
||||
|
||||
There are three ``loggers`` used in sanic, and **must be defined if you
|
||||
want to create your own logging configuration**:
|
||||
|
||||
================ ==============================
|
||||
Logger Name Usecase
|
||||
================ ==============================
|
||||
``sanic.root`` Used to log internal messages.
|
||||
``sanic.error`` Used to log error logs.
|
||||
``sanic.access`` Used to log access logs.
|
||||
================ ==============================
|
||||
|
||||
Log format:
|
||||
^^^^^^^^^^^
|
||||
|
||||
In addition to default parameters provided by python (``asctime``,
|
||||
``levelname``, ``message``), Sanic provides additional parameters for
|
||||
access logger with:
|
||||
|
||||
===================== ========================================== ========
|
||||
Log Context Parameter Parameter Value Datatype
|
||||
===================== ========================================== ========
|
||||
``host`` ``request.ip`` str
|
||||
``request`` ``request.method`` + " " + ``request.url`` str
|
||||
``status`` ``response.status`` int
|
||||
``byte`` ``len(response.body)`` int
|
||||
===================== ========================================== ========
|
||||
|
||||
The default access log format is ``%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d``
|
||||
|
||||
.. _python3 logging API: https://docs.python.org/3/howto/logging.html
|
|
@ -1,188 +0,0 @@
|
|||
Middleware And Listeners
|
||||
========================
|
||||
|
||||
Middleware are functions which are executed before or after requests to the
|
||||
server. They can be used to modify the *request to* or *response from*
|
||||
user-defined handler functions.
|
||||
|
||||
Additionally, Sanic provides listeners which allow you to run code at various points of your application's lifecycle.
|
||||
|
||||
Middleware
|
||||
----------
|
||||
|
||||
There are two types of middleware: request and response. Both are declared
|
||||
using the `@app.middleware` decorator, with the decorator's parameter being a
|
||||
string representing its type: `'request'` or `'response'`.
|
||||
|
||||
* Request middleware receives only the `request` as an argument and are executed in the order they were added.
|
||||
* Response middleware receives both the `request` and `response` and are executed in *reverse* order.
|
||||
|
||||
The simplest middleware doesn't modify the request or response at all:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.middleware('request')
|
||||
async def print_on_request(request):
|
||||
print("I print when a request is received by the server")
|
||||
|
||||
@app.middleware('response')
|
||||
async def print_on_response(request, response):
|
||||
print("I print when a response is returned by the server")
|
||||
|
||||
Modifying the request or response
|
||||
---------------------------------
|
||||
|
||||
Middleware can modify the request or response parameter it is given, *as long
|
||||
as it does not return it*. The following example shows a practical use-case for
|
||||
this.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
@app.middleware('request')
|
||||
async def add_key(request):
|
||||
# Arbitrary data may be stored in request context:
|
||||
request.ctx.foo = 'bar'
|
||||
|
||||
|
||||
@app.middleware('response')
|
||||
async def custom_banner(request, response):
|
||||
response.headers["Server"] = "Fake-Server"
|
||||
|
||||
|
||||
@app.middleware('response')
|
||||
async def prevent_xss(request, response):
|
||||
response.headers["x-xss-protection"] = "1; mode=block"
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def index(request):
|
||||
return sanic.response.text(request.ctx.foo)
|
||||
|
||||
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
The three middlewares are executed in the following order:
|
||||
|
||||
1. The first request middleware **add_key** adds a new key `foo` into request context.
|
||||
2. Request is routed to handler **index**, which gets the key from context and returns a text response.
|
||||
3. The second response middleware **prevent_xss** adds the HTTP header for preventing Cross-Site-Scripting (XSS) attacks.
|
||||
4. The first response middleware **custom_banner** changes the HTTP response header *Server* to say *Fake-Server*
|
||||
|
||||
Responding early
|
||||
----------------
|
||||
|
||||
If middleware returns a `HTTPResponse` object, the request will stop processing
|
||||
and the response will be returned. If this occurs to a request before the
|
||||
relevant user route handler is reached, the handler will never be called.
|
||||
Returning a response will also prevent any further middleware from running.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.middleware('request')
|
||||
async def halt_request(request):
|
||||
return text('I halted the request')
|
||||
|
||||
@app.middleware('response')
|
||||
async def halt_response(request, response):
|
||||
return text('I halted the response')
|
||||
|
||||
Custom context
|
||||
--------------
|
||||
|
||||
Arbitrary data may be stored in `request.ctx`. A typical use case
|
||||
would be to store the user object acquired from database in an authentication
|
||||
middleware. Keys added are accessible to all later middleware as well as
|
||||
the handler over the duration of the request.
|
||||
|
||||
Custom context is reserved for applications and extensions. Sanic itself makes
|
||||
no use of it.
|
||||
|
||||
Listeners
|
||||
---------
|
||||
|
||||
If you want to execute startup/teardown code as your server starts or closes, you can use the following listeners:
|
||||
|
||||
- `before_server_start`
|
||||
- `after_server_start`
|
||||
- `before_server_stop`
|
||||
- `after_server_stop`
|
||||
|
||||
These listeners are implemented as decorators on functions which accept the app object as well as the asyncio loop.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.listener('before_server_start')
|
||||
async def setup_db(app, loop):
|
||||
app.db = await db_setup()
|
||||
|
||||
@app.listener('after_server_start')
|
||||
async def notify_server_started(app, loop):
|
||||
print('Server successfully started!')
|
||||
|
||||
@app.listener('before_server_stop')
|
||||
async def notify_server_stopping(app, loop):
|
||||
print('Server shutting down!')
|
||||
|
||||
@app.listener('after_server_stop')
|
||||
async def close_db(app, loop):
|
||||
await app.db.close()
|
||||
|
||||
Note:
|
||||
|
||||
The listeners are deconstructed in the reverse order of being constructed.
|
||||
|
||||
For example:
|
||||
|
||||
If the first listener in before_server_start handler setups a database connection,
|
||||
ones registered after it can rely on that connection being alive both when they are started
|
||||
and stopped, because stopping is done in reverse order, and the database connection is
|
||||
torn down last.
|
||||
|
||||
It's also possible to register a listener using the `register_listener` method.
|
||||
This may be useful if you define your listeners in another module besides
|
||||
the one you instantiate your app in.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
async def setup_db(app, loop):
|
||||
app.db = await db_setup()
|
||||
|
||||
app.register_listener(setup_db, 'before_server_start')
|
||||
|
||||
If you want to schedule a background task to run after the loop has started,
|
||||
Sanic provides the `add_task` method to easily do so.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def notify_server_started_after_five_seconds():
|
||||
await asyncio.sleep(5)
|
||||
print('Server successfully started!')
|
||||
|
||||
app.add_task(notify_server_started_after_five_seconds())
|
||||
|
||||
Sanic will attempt to automatically inject the app, passing it as an argument to the task:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def notify_server_started_after_five_seconds(app):
|
||||
await asyncio.sleep(5)
|
||||
print(app.name)
|
||||
|
||||
app.add_task(notify_server_started_after_five_seconds)
|
||||
|
||||
Or you can pass the app explicitly for the same effect:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def notify_server_started_after_five_seconds(app):
|
||||
await asyncio.sleep(5)
|
||||
print(app.name)
|
||||
|
||||
app.add_task(notify_server_started_after_five_seconds(app))
|
|
@ -1,222 +0,0 @@
|
|||
|
||||
.. _nginx:
|
||||
|
||||
Nginx Deployment
|
||||
================
|
||||
|
||||
Introduction
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Although Sanic can be run directly on Internet, it may be useful to use a proxy
|
||||
server such as Nginx in front of it. This is particularly useful for running
|
||||
multiple virtual hosts on the same IP, serving NodeJS or other services beside
|
||||
a single Sanic app, and it also allows for efficient serving of static files.
|
||||
SSL and HTTP/2 are also easily implemented on such proxy.
|
||||
|
||||
We are setting the Sanic app to serve only locally at `127.0.0.1:8000`, while the
|
||||
Nginx installation is responsible for providing the service to public Internet
|
||||
on domain `example.com`. Static files will be served from `/var/www/`.
|
||||
|
||||
|
||||
Proxied Sanic app
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The app needs to be setup with a secret key used to identify a trusted proxy,
|
||||
so that real client IP and other information can be identified. This protects
|
||||
against anyone on the Internet sending fake headers to spoof their IP addresses
|
||||
and other details. Choose any random string and configure it both on the app
|
||||
and in Nginx config.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import text
|
||||
|
||||
app = Sanic("proxied_example")
|
||||
app.config.FORWARDED_SECRET = "YOUR SECRET"
|
||||
|
||||
@app.get("/")
|
||||
def index(request):
|
||||
# This should display external (public) addresses:
|
||||
return text(
|
||||
f"{request.remote_addr} connected to {request.url_for('index')}\n"
|
||||
f"Forwarded: {request.forwarded}\n"
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='127.0.0.1', port=8000, workers=8, access_log=False)
|
||||
|
||||
Since this is going to be a system service, save your code to
|
||||
`/srv/sanicexample/sanicexample.py`.
|
||||
|
||||
For testing, run your app in a terminal.
|
||||
|
||||
|
||||
Nginx configuration
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Quite much configuration is required to allow fast transparent proxying, but
|
||||
for the most part these don't need to be modified, so bear with me.
|
||||
|
||||
Upstream servers need to be configured in a separate `upstream` block to enable
|
||||
HTTP keep-alive, which can drastically improve performance, so we use this
|
||||
instead of directly providing an upstream address in `proxy_pass` directive. In
|
||||
this example, the upstream section is named by `server_name`, i.e. the public
|
||||
domain name, which then also gets passed to Sanic in the `Host` header. You may
|
||||
change the naming as you see fit. Multiple servers may also be provided for
|
||||
load balancing and failover.
|
||||
|
||||
Change the two occurrences of `example.com` to your true domain name, and
|
||||
instead of `YOUR SECRET` use the secret you chose for your app.
|
||||
|
||||
::
|
||||
|
||||
upstream example.com {
|
||||
keepalive 100;
|
||||
server 127.0.0.1:8000;
|
||||
#server unix:/tmp/sanic.sock;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name example.com;
|
||||
listen 443 ssl http2 default_server;
|
||||
listen [::]:443 ssl http2 default_server;
|
||||
# Serve static files if found, otherwise proxy to Sanic
|
||||
location / {
|
||||
root /var/www;
|
||||
try_files $uri @sanic;
|
||||
}
|
||||
location @sanic {
|
||||
proxy_pass http://$server_name;
|
||||
# Allow fast streaming HTTP/1.1 pipes (keep-alive, unbuffered)
|
||||
proxy_http_version 1.1;
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
# Proxy forwarding (password configured in app.config.FORWARDED_SECRET)
|
||||
proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\"";
|
||||
# Allow websockets
|
||||
proxy_set_header connection "upgrade";
|
||||
proxy_set_header upgrade $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
To avoid cookie visibility issues and inconsistent addresses on search engines,
|
||||
it is a good idea to redirect all visitors to one true domain, always using
|
||||
HTTPS:
|
||||
|
||||
::
|
||||
|
||||
# Redirect all HTTP to HTTPS with no-WWW
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name ~^(?:www\.)?(.*)$;
|
||||
return 301 https://$1$request_uri;
|
||||
}
|
||||
|
||||
# Redirect WWW to no-WWW
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name ~^www\.(.*)$;
|
||||
return 301 $scheme://$1$request_uri;
|
||||
}
|
||||
|
||||
The above config sections may be placed in `/etc/nginx/sites-available/default`
|
||||
or in other site configs (be sure to symlink them to `sites-enabled` if you
|
||||
create new ones).
|
||||
|
||||
Make sure that your SSL certificates are configured in the main config, or
|
||||
add the `ssl_certificate` and `ssl_certificate_key` directives to each
|
||||
`server` section that listens on SSL.
|
||||
|
||||
Additionally, copy&paste all of this into `nginx/conf.d/forwarded.conf`:
|
||||
|
||||
::
|
||||
|
||||
# RFC 7239 Forwarded header for Nginx proxy_pass
|
||||
|
||||
# Add within your server or location block:
|
||||
# proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\"";
|
||||
|
||||
# Configure your upstream web server to identify this proxy by that password
|
||||
# because otherwise anyone on the Internet could spoof these headers and fake
|
||||
# their real IP address and other information to your service.
|
||||
|
||||
|
||||
# Provide the full proxy chain in $proxy_forwarded
|
||||
map $proxy_add_forwarded $proxy_forwarded {
|
||||
default "$proxy_add_forwarded;by=\"_$hostname\";proto=$scheme;host=\"$http_host\";path=\"$request_uri\"";
|
||||
}
|
||||
|
||||
# The following mappings are based on
|
||||
# https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
|
||||
|
||||
map $remote_addr $proxy_forwarded_elem {
|
||||
# IPv4 addresses can be sent as-is
|
||||
~^[0-9.]+$ "for=$remote_addr";
|
||||
|
||||
# IPv6 addresses need to be bracketed and quoted
|
||||
~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";
|
||||
|
||||
# Unix domain socket names cannot be represented in RFC 7239 syntax
|
||||
default "for=unknown";
|
||||
}
|
||||
|
||||
map $http_forwarded $proxy_add_forwarded {
|
||||
# If the incoming Forwarded header is syntactically valid, append to it
|
||||
"~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";
|
||||
|
||||
# Otherwise, replace it
|
||||
default "$proxy_forwarded_elem";
|
||||
}
|
||||
|
||||
For installs that don't use `conf.d` and `sites-available`, all of the above
|
||||
configs may also be placed inside the `http` section of the main `nginx.conf`.
|
||||
|
||||
Reload Nginx config after changes:
|
||||
|
||||
::
|
||||
|
||||
sudo nginx -s reload
|
||||
|
||||
Now you should be able to connect your app on `https://example.com/`. Any 404
|
||||
errors and such will be handled by Sanic's error pages, and whenever a static
|
||||
file is present at a given path, it will be served by Nginx.
|
||||
|
||||
|
||||
SSL certificates
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
If you haven't already configured valid certificates on your server, now is a
|
||||
good time to do so. Install `certbot` and `python3-certbot-nginx`, then run
|
||||
|
||||
::
|
||||
|
||||
certbot --nginx -d example.com -d www.example.com
|
||||
|
||||
`<https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/>`_
|
||||
|
||||
Running as a service
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This part is for Linux distributions based on `systemd`. Create a unit file
|
||||
`/etc/systemd/system/sanicexample.service`::
|
||||
|
||||
[Unit]
|
||||
Description=Sanic Example
|
||||
|
||||
[Service]
|
||||
User=nobody
|
||||
WorkingDirectory=/srv/sanicexample
|
||||
ExecStart=/usr/bin/env python3 sanicexample.py
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
Then reload service files, start your service and enable it on boot::
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl start sanicexample
|
||||
sudo systemctl enable sanicexample
|
|
@ -1,270 +0,0 @@
|
|||
Request Data
|
||||
============
|
||||
|
||||
When an endpoint receives a HTTP request, the route function is passed a
|
||||
`Request` object.
|
||||
|
||||
The following variables are accessible as properties on `Request` objects:
|
||||
|
||||
- `json` (any) - JSON body
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
|
||||
@app.route("/json")
|
||||
def post_json(request):
|
||||
return json({ "received": True, "message": request.json })
|
||||
|
||||
|
||||
- `args` (dict) - Query string variables. A query string is the section of a
|
||||
URL that resembles ``?key1=value1&key2=value2``.
|
||||
If that URL were to be parsed, the `args` dictionary would look like `{'key1': ['value1'], 'key2': ['value2']}`.
|
||||
The request's `query_string` variable holds the unparsed string value. Property is providing the default parsing
|
||||
strategy. If you would like to change it look to the section below (`Changing the default parsing rules of the queryset`).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
|
||||
@app.route("/query_string")
|
||||
def query_string(request):
|
||||
return json({ "parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string })
|
||||
|
||||
- `query_args` (list) - On many cases you would need to access the url arguments in
|
||||
a less packed form. `query_args` is the list of `(key, value)` tuples.
|
||||
Property is providing the default parsing strategy. If you would like to change it look to the section below
|
||||
(`Changing the default parsing rules of the queryset`). For the same previous URL queryset `?key1=value1&key2=value2`,
|
||||
the `query_args` list would look like `[('key1', 'value1'), ('key2', 'value2')]`. And in case of the multiple params
|
||||
with the same key like `?key1=value1&key2=value2&key1=value3` the `query_args` list would look like
|
||||
`[('key1', 'value1'), ('key2', 'value2'), ('key1', 'value3')]`.
|
||||
|
||||
The difference between Request.args and Request.query_args for the queryset `?key1=value1&key2=value2&key1=value3`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
@app.route("/test_request_args")
|
||||
async def test_request_args(request):
|
||||
return json({
|
||||
"parsed": True,
|
||||
"url": request.url,
|
||||
"query_string": request.query_string,
|
||||
"args": request.args,
|
||||
"query_args": request.query_args,
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
Output
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"parsed":true,
|
||||
"url":"http:\/\/0.0.0.0:8000\/test_request_args?key1=value1&key2=value2&key1=value3",
|
||||
"query_string":"key1=value1&key2=value2&key1=value3",
|
||||
"args":{"key1":["value1","value3"],"key2":["value2"]},
|
||||
"query_args":[["key1","value1"],["key2","value2"],["key1","value3"]]
|
||||
}
|
||||
|
||||
- `files` (dictionary of `File` objects) - List of files that have a name, body, and type
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
|
||||
@app.route("/files")
|
||||
def post_json(request):
|
||||
test_file = request.files.get('test')
|
||||
|
||||
file_parameters = {
|
||||
'body': test_file.body,
|
||||
'name': test_file.name,
|
||||
'type': test_file.type,
|
||||
}
|
||||
|
||||
return json({ "received": True, "file_names": request.files.keys(), "test_file_parameters": file_parameters })
|
||||
|
||||
- `form` (dict) - Posted form variables.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
|
||||
@app.route("/form")
|
||||
def post_json(request):
|
||||
return json({ "received": True, "form_data": request.form, "test": request.form.get('test') })
|
||||
|
||||
- `body` (bytes) - Posted raw body. This property allows retrieval of the
|
||||
request's raw data, regardless of content type.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.route("/users", methods=["POST",])
|
||||
def create_user(request):
|
||||
return text("You are trying to create a user with the following POST: %s" % request.body)
|
||||
|
||||
|
||||
- `headers` (dict) - A case-insensitive dictionary that contains the request headers.
|
||||
|
||||
- `method` (str) - HTTP method of the request (ie `GET`, `POST`).
|
||||
|
||||
- `ip` (str) - IP address of the requester.
|
||||
|
||||
- `port` (str) - Port address of the requester.
|
||||
|
||||
- `socket` (tuple) - (IP, port) of the requester.
|
||||
|
||||
- `app` - a reference to the Sanic application object that is handling this request. This is useful when inside blueprints or other handlers in modules that do not have access to the global `app` object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
from sanic import Blueprint
|
||||
|
||||
bp = Blueprint('my_blueprint')
|
||||
|
||||
@bp.route('/')
|
||||
async def bp_root(request):
|
||||
if request.app.config['DEBUG']:
|
||||
return json({'status': 'debug'})
|
||||
else:
|
||||
return json({'status': 'production'})
|
||||
|
||||
- `url`: The full URL of the request, ie: `http://localhost:8000/posts/1/?foo=bar`
|
||||
- `scheme`: The URL scheme associated with the request: 'http|https|ws|wss' or arbitrary value given by the headers.
|
||||
- `host`: The host associated with the request(which in the `Host` header): `localhost:8080`
|
||||
- `server_name`: The hostname of the server, without port number. the value is seeked in this order: `config.SERVER_NAME`, `x-forwarded-host` header, :func:`Request.host`
|
||||
- `server_port`: Like `server_name`. Seeked in this order: `x-forwarded-port` header, :func:`Request.host`, actual port used by the transport layer socket.
|
||||
- `path`: The path of the request: `/posts/1/`
|
||||
- `query_string`: The query string of the request: `foo=bar` or a blank string `''`
|
||||
- `uri_template`: Template for matching route handler: `/posts/<id>/`
|
||||
- `token`: The value of Authorization header: `Basic YWRtaW46YWRtaW4=`
|
||||
|
||||
- `url_for`: Just like `sanic.Sanic.url_for`, but automatically determine `scheme` and `netloc` base on the request. Since this method is aiming to generate correct schema & netloc, `_external` is implied.
|
||||
|
||||
|
||||
Changing the default parsing rules of the queryset
|
||||
--------------------------------------------------
|
||||
|
||||
The default parameters that are using internally in `args` and `query_args` properties to parse queryset:
|
||||
|
||||
- `keep_blank_values` (bool): `False` - flag indicating whether blank values in
|
||||
percent-encoded queries should be treated as blank strings.
|
||||
A true value indicates that blanks should be retained as blank
|
||||
strings. The default false value indicates that blank values
|
||||
are to be ignored and treated as if they were not included.
|
||||
- `strict_parsing` (bool): `False` - flag indicating what to do with parsing errors. If
|
||||
false (the default), errors are silently ignored. If true,
|
||||
errors raise a ValueError exception.
|
||||
- `encoding` and `errors` (str): 'utf-8' and 'replace' - specify how to decode percent-encoded sequences
|
||||
into Unicode characters, as accepted by the bytes.decode() method.
|
||||
|
||||
If you would like to change that default parameters you could call `get_args` and `get_query_args` methods
|
||||
with the new values.
|
||||
|
||||
For the queryset `/?test1=value1&test2=&test3=value3`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
|
||||
@app.route("/query_string")
|
||||
def query_string(request):
|
||||
args_with_blank_values = request.get_args(keep_blank_values=True)
|
||||
return json({
|
||||
"parsed": True,
|
||||
"url": request.url,
|
||||
"args_with_blank_values": args_with_blank_values,
|
||||
"query_string": request.query_string
|
||||
})
|
||||
|
||||
The output will be:
|
||||
|
||||
.. code-block:: JSON
|
||||
|
||||
{
|
||||
"parsed": true,
|
||||
"url": "http:\/\/0.0.0.0:8000\/query_string?test1=value1&test2=&test3=value3",
|
||||
"args_with_blank_values": {"test1": ["value1"], "test2": "", "test3": ["value3"]},
|
||||
"query_string": "test1=value1&test2=&test3=value3"
|
||||
}
|
||||
|
||||
Accessing values using `get` and `getlist`
|
||||
------------------------------------------
|
||||
|
||||
The `request.args` returns a subclass of `dict` called `RequestParameters`.
|
||||
The key difference when using this object is the distinction between the `get` and `getlist` methods.
|
||||
|
||||
- `get(key, default=None)` operates as normal, except that when the value of
|
||||
the given key is a list, *only the first item is returned*.
|
||||
- `getlist(key, default=None)` operates as normal, *returning the entire list*.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.request import RequestParameters
|
||||
|
||||
args = RequestParameters()
|
||||
args['titles'] = ['Post 1', 'Post 2']
|
||||
|
||||
args.get('titles') # => 'Post 1'
|
||||
|
||||
args.getlist('titles') # => ['Post 1', 'Post 2']
|
||||
|
||||
.. code-block:: python
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def get_handler(request):
|
||||
return json({
|
||||
"p1": request.args.getlist("p1")
|
||||
})
|
||||
|
||||
Accessing the handler name with the request.endpoint attribute
|
||||
--------------------------------------------------------------
|
||||
|
||||
The `request.endpoint` attribute holds the handler's name. For instance, the below
|
||||
route will return "hello".
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
@app.get("/")
|
||||
def hello(request):
|
||||
return text(request.endpoint)
|
||||
|
||||
Or, with a blueprint it will be include both, separated by a period. For example, the below route would return foo.bar:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic import Blueprint
|
||||
from sanic.response import text
|
||||
|
||||
|
||||
app = Sanic(__name__)
|
||||
blueprint = Blueprint('foo')
|
||||
|
||||
@blueprint.get('/')
|
||||
async def bar(request):
|
||||
return text(request.endpoint)
|
||||
|
||||
app.blueprint(blueprint)
|
||||
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
|
@ -1,139 +0,0 @@
|
|||
Response
|
||||
========
|
||||
|
||||
Use functions in `sanic.response` module to create responses.
|
||||
|
||||
Plain Text
|
||||
----------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/text')
|
||||
def handle_request(request):
|
||||
return response.text('Hello world!')
|
||||
|
||||
HTML
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/html')
|
||||
def handle_request(request):
|
||||
return response.html('<p>Hello world!</p>')
|
||||
|
||||
JSON
|
||||
----
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/json')
|
||||
def handle_request(request):
|
||||
return response.json({'message': 'Hello world!'})
|
||||
|
||||
File
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/file')
|
||||
async def handle_request(request):
|
||||
return await response.file('/srv/www/whatever.png')
|
||||
|
||||
Streaming
|
||||
---------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
@app.route("/streaming")
|
||||
async def index(request):
|
||||
async def streaming_fn(response):
|
||||
await response.write('foo')
|
||||
await response.write('bar')
|
||||
return response.stream(streaming_fn, content_type='text/plain')
|
||||
|
||||
See `Streaming <streaming.html>`_ for more information.
|
||||
|
||||
File Streaming
|
||||
--------------
|
||||
|
||||
For large files, a combination of File and Streaming above
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
@app.route('/big_file.png')
|
||||
async def handle_request(request):
|
||||
return await response.file_stream('/srv/www/whatever.png')
|
||||
|
||||
Redirect
|
||||
--------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/redirect')
|
||||
def handle_request(request):
|
||||
return response.redirect('/json')
|
||||
|
||||
Raw
|
||||
---
|
||||
|
||||
Response without encoding the body
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/raw')
|
||||
def handle_request(request):
|
||||
return response.raw(b'raw data')
|
||||
|
||||
Empty
|
||||
--------------
|
||||
|
||||
For responding with an empty message as defined by `RFC 2616 <https://tools.ietf.org/search/rfc2616#section-7.2.1>`_
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
@app.route('/empty')
|
||||
async def handle_request(request):
|
||||
return response.empty()
|
||||
|
||||
Modify headers or status
|
||||
------------------------
|
||||
|
||||
To modify headers or status code, pass the `headers` or `status` argument to those functions:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/json')
|
||||
def handle_request(request):
|
||||
return response.json(
|
||||
{'message': 'Hello world!'},
|
||||
headers={'X-Served-By': 'sanic'},
|
||||
status=200
|
||||
)
|
|
@ -1,433 +0,0 @@
|
|||
Routing
|
||||
-------
|
||||
|
||||
Routing allows the user to specify handler functions for different URL endpoints.
|
||||
|
||||
A basic route looks like the following, where `app` is an instance of the
|
||||
`Sanic` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import json
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return json({ "hello": "world" })
|
||||
|
||||
When the url `http://server.url/` is accessed (the base url of the server), the
|
||||
final `/` is matched by the router to the handler function, `test`, which then
|
||||
returns a JSON object.
|
||||
|
||||
Sanic handler functions must be defined using the `async def` syntax, as they
|
||||
are asynchronous functions.
|
||||
|
||||
Request parameters
|
||||
==================
|
||||
|
||||
Sanic comes with a basic router that supports request parameters.
|
||||
|
||||
To specify a parameter, surround it with angle quotes like so: `<PARAM>`.
|
||||
Request parameters will be passed to the route handler functions as keyword
|
||||
arguments.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.route('/tag/<tag>')
|
||||
async def tag_handler(request, tag):
|
||||
return text('Tag - {}'.format(tag))
|
||||
|
||||
To specify a type for the parameter, add a `:type` after the parameter name,
|
||||
inside the quotes. If the parameter does not match the specified type, Sanic
|
||||
will throw a `NotFound` exception, resulting in a `404: Page not found` error
|
||||
on the URL.
|
||||
|
||||
Supported types
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
* `string`
|
||||
* "Bob"
|
||||
* "Python 3"
|
||||
* `int`
|
||||
* 10
|
||||
* 20
|
||||
* 30
|
||||
* -10
|
||||
* (No floats work here)
|
||||
* `number`
|
||||
* 1
|
||||
* 1.5
|
||||
* 10
|
||||
* -10
|
||||
* `alpha`
|
||||
* "Bob"
|
||||
* "Python"
|
||||
* (If it contains a symbol or a non alphanumeric character it will fail)
|
||||
* `path`
|
||||
* "hello"
|
||||
* "hello.text"
|
||||
* "hello world"
|
||||
* `uuid`
|
||||
* 123a123a-a12a-1a1a-a1a1-1a12a1a12345 (UUIDv4 Support)
|
||||
* `regex expression`
|
||||
|
||||
If no type is set then a string is expected. The argument given to the function will always be a string, independent of the type.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.route('/string/<string_arg:string>')
|
||||
async def string_handler(request, string_arg):
|
||||
return text('String - {}'.format(string_arg))
|
||||
|
||||
@app.route('/int/<integer_arg:int>')
|
||||
async def integer_handler(request, integer_arg):
|
||||
return text('Integer - {}'.format(integer_arg))
|
||||
|
||||
@app.route('/number/<number_arg:number>')
|
||||
async def number_handler(request, number_arg):
|
||||
return text('Number - {}'.format(number_arg))
|
||||
|
||||
@app.route('/alpha/<alpha_arg:alpha>')
|
||||
async def number_handler(request, alpha_arg):
|
||||
return text('Alpha - {}'.format(alpha_arg))
|
||||
|
||||
@app.route('/path/<path_arg:path>')
|
||||
async def number_handler(request, path_arg):
|
||||
return text('Path - {}'.format(path_arg))
|
||||
|
||||
@app.route('/uuid/<uuid_arg:uuid>')
|
||||
async def number_handler(request, uuid_arg):
|
||||
return text('Uuid - {}'.format(uuid_arg))
|
||||
|
||||
@app.route('/person/<name:[A-z]+>')
|
||||
async def person_handler(request, name):
|
||||
return text('Person - {}'.format(name))
|
||||
|
||||
@app.route('/folder/<folder_id:[A-z0-9]{0,4}>')
|
||||
async def folder_handler(request, folder_id):
|
||||
return text('Folder - {}'.format(folder_id))
|
||||
|
||||
.. warning::
|
||||
|
||||
`str` is not a valid type tag. If you want `str` recognition then you must use `string`
|
||||
|
||||
HTTP request types
|
||||
==================
|
||||
|
||||
By default, a route defined on a URL will be available for only GET requests to that URL.
|
||||
However, the `@app.route` decorator accepts an optional parameter, `methods`,
|
||||
which allows the handler function to work with any of the HTTP methods in the list.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.route('/post', methods=['POST'])
|
||||
async def post_handler(request):
|
||||
return text('POST request - {}'.format(request.json))
|
||||
|
||||
@app.route('/get', methods=['GET'])
|
||||
async def get_handler(request):
|
||||
return text('GET request - {}'.format(request.args))
|
||||
|
||||
There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is also a route with no host, it will be the default.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route('/get', methods=['GET'], host='example.com')
|
||||
async def get_handler(request):
|
||||
return text('GET request - {}'.format(request.args))
|
||||
|
||||
# if the host header doesn't match example.com, this route will be used
|
||||
@app.route('/get', methods=['GET'])
|
||||
async def get_handler(request):
|
||||
return text('GET request in default - {}'.format(request.args))
|
||||
|
||||
There are also shorthand method decorators:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
@app.post('/post')
|
||||
async def post_handler(request):
|
||||
return text('POST request - {}'.format(request.json))
|
||||
|
||||
@app.get('/get')
|
||||
async def get_handler(request):
|
||||
return text('GET request - {}'.format(request.args))
|
||||
|
||||
The `add_route` method
|
||||
======================
|
||||
|
||||
As we have seen, routes are often specified using the `@app.route` decorator.
|
||||
However, this decorator is really just a wrapper for the `app.add_route`
|
||||
method, which is used as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import text
|
||||
|
||||
# Define the handler functions
|
||||
async def handler1(request):
|
||||
return text('OK')
|
||||
|
||||
async def handler2(request, name):
|
||||
return text('Folder - {}'.format(name))
|
||||
|
||||
async def person_handler2(request, name):
|
||||
return text('Person - {}'.format(name))
|
||||
|
||||
# Add each handler function as a route
|
||||
app.add_route(handler1, '/test')
|
||||
app.add_route(handler2, '/folder/<name>')
|
||||
app.add_route(person_handler2, '/person/<name:[A-z]>', methods=['GET'])
|
||||
|
||||
URL building with `url_for`
|
||||
===========================
|
||||
|
||||
Sanic provides a `url_for` method, to generate URLs based on the handler method name. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic.response import redirect
|
||||
|
||||
@app.route('/')
|
||||
async def index(request):
|
||||
# generate a URL for the endpoint `post_handler`
|
||||
url = app.url_for('post_handler', post_id=5)
|
||||
# the URL is `/posts/5`, redirect to it
|
||||
return redirect(url)
|
||||
|
||||
@app.route('/posts/<post_id>')
|
||||
async def post_handler(request, post_id):
|
||||
return text('Post - {}'.format(post_id))
|
||||
|
||||
Other things to keep in mind when using `url_for`:
|
||||
|
||||
- Keyword arguments passed to `url_for` that are not request parameters will be included in the URL's query string. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two')
|
||||
# /posts/5?arg_one=one&arg_two=two
|
||||
|
||||
- Multivalue argument can be passed to `url_for`. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
|
||||
# /posts/5?arg_one=one&arg_one=two
|
||||
|
||||
- Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not supported now and will be ignored). For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor')
|
||||
# /posts/5?arg_one=one#anchor
|
||||
|
||||
url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True)
|
||||
# //server/posts/5?arg_one=one
|
||||
# _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external
|
||||
|
||||
url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True)
|
||||
# http://server/posts/5?arg_one=one
|
||||
# when specifying _scheme, _external must be True
|
||||
|
||||
# you can pass all special arguments at once
|
||||
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888')
|
||||
# http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor
|
||||
|
||||
- All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be raised.
|
||||
|
||||
WebSocket routes
|
||||
================
|
||||
|
||||
Routes for the WebSocket protocol can be defined with the `@app.websocket`
|
||||
decorator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.websocket('/feed')
|
||||
async def feed(request, ws):
|
||||
while True:
|
||||
data = 'hello!'
|
||||
print('Sending: ' + data)
|
||||
await ws.send(data)
|
||||
data = await ws.recv()
|
||||
print('Received: ' + data)
|
||||
|
||||
Alternatively, the `app.add_websocket_route` method can be used instead of the
|
||||
decorator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def feed(request, ws):
|
||||
pass
|
||||
|
||||
app.add_websocket_route(my_websocket_handler, '/feed')
|
||||
|
||||
Handlers to a WebSocket route are invoked with the request as first argument, and a
|
||||
WebSocket protocol object as second argument. The protocol object has `send`
|
||||
and `recv` methods to send and receive data respectively.
|
||||
|
||||
WebSocket support requires the `websockets <https://github.com/aaugustin/websockets>`_
|
||||
package by Aymeric Augustin.
|
||||
|
||||
|
||||
About `strict_slashes`
|
||||
======================
|
||||
|
||||
You can make `routes` strict to trailing slash or not, it's configurable.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# provide default strict_slashes value for all routes
|
||||
app = Sanic('test_route_strict_slash', strict_slashes=True)
|
||||
|
||||
# you can also overwrite strict_slashes value for specific route
|
||||
@app.get('/get', strict_slashes=False)
|
||||
def handler(request):
|
||||
return text('OK')
|
||||
|
||||
# It also works for blueprints
|
||||
bp = Blueprint('test_bp_strict_slash', strict_slashes=True)
|
||||
|
||||
@bp.get('/bp/get', strict_slashes=False)
|
||||
def handler(request):
|
||||
return text('OK')
|
||||
|
||||
app.blueprint(bp)
|
||||
|
||||
The behavior of how the `strict_slashes` flag follows a defined hierarchy which decides if a specific route
|
||||
falls under the `strict_slashes` behavior.
|
||||
|
||||
| Route/
|
||||
| ├──Blueprint/
|
||||
| ├──Application/
|
||||
|
||||
Above hierarchy defines how the `strict_slashes` flag will behave. The first non `None` value of the `strict_slashes`
|
||||
found in the above order will be applied to the route in question.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic, Blueprint
|
||||
from sanic.response import text
|
||||
|
||||
app = Sanic("sample_strict_slashes", strict_slashes=True)
|
||||
|
||||
@app.get("/r1")
|
||||
def r1(request):
|
||||
return text("strict_slashes is applicable from App level")
|
||||
|
||||
@app.get("/r2", strict_slashes=False)
|
||||
def r2(request):
|
||||
return text("strict_slashes is not applicable due to False value set in route level")
|
||||
|
||||
bp = Blueprint("bp", strict_slashes=False)
|
||||
|
||||
@bp.get("/r3", strict_slashes=True)
|
||||
def r3(request):
|
||||
return text("strict_slashes applicable from blueprint route level")
|
||||
|
||||
bp1 = Blueprint("bp1", strict_slashes=True)
|
||||
|
||||
@bp.get("/r4")
|
||||
def r3(request):
|
||||
return text("strict_slashes applicable from blueprint level")
|
||||
|
||||
User defined route name
|
||||
=======================
|
||||
|
||||
A custom route name can be used by passing a `name` argument while registering the route which will
|
||||
override the default route name generated using the `handler.__name__` attribute.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic('test_named_route')
|
||||
|
||||
@app.get('/get', name='get_handler')
|
||||
def handler(request):
|
||||
return text('OK')
|
||||
|
||||
# then you need use `app.url_for('get_handler')`
|
||||
# instead of # `app.url_for('handler')`
|
||||
|
||||
# It also works for blueprints
|
||||
bp = Blueprint('test_named_bp')
|
||||
|
||||
@bp.get('/bp/get', name='get_handler')
|
||||
def handler(request):
|
||||
return text('OK')
|
||||
|
||||
app.blueprint(bp)
|
||||
|
||||
# then you need use `app.url_for('test_named_bp.get_handler')`
|
||||
# instead of `app.url_for('test_named_bp.handler')`
|
||||
|
||||
# different names can be used for same url with different methods
|
||||
|
||||
@app.get('/test', name='route_test')
|
||||
def handler(request):
|
||||
return text('OK')
|
||||
|
||||
@app.post('/test', name='route_post')
|
||||
def handler2(request):
|
||||
return text('OK POST')
|
||||
|
||||
@app.put('/test', name='route_put')
|
||||
def handler3(request):
|
||||
return text('OK PUT')
|
||||
|
||||
# below url are the same, you can use any of them
|
||||
# '/test'
|
||||
app.url_for('route_test')
|
||||
# app.url_for('route_post')
|
||||
# app.url_for('route_put')
|
||||
|
||||
# for same handler name with different methods
|
||||
# you need specify the name (it's url_for issue)
|
||||
@app.get('/get')
|
||||
def handler(request):
|
||||
return text('OK')
|
||||
|
||||
@app.post('/post', name='post_handler')
|
||||
def handler(request):
|
||||
return text('OK')
|
||||
|
||||
# then
|
||||
# app.url_for('handler') == '/get'
|
||||
# app.url_for('post_handler') == '/post'
|
||||
|
||||
Build URL for static files
|
||||
==========================
|
||||
|
||||
Sanic supports using `url_for` method to build static file urls. In case if the static url
|
||||
is pointing to a directory, `filename` parameter to the `url_for` can be ignored.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic('test_static')
|
||||
app.static('/static', './static')
|
||||
app.static('/uploads', './uploads', name='uploads')
|
||||
app.static('/the_best.png', '/home/ubuntu/test.png', name='best_png')
|
||||
|
||||
bp = Blueprint('bp', url_prefix='bp')
|
||||
bp.static('/static', './static')
|
||||
bp.static('/uploads', './uploads', name='uploads')
|
||||
bp.static('/the_best.png', '/home/ubuntu/test.png', name='best_png')
|
||||
app.blueprint(bp)
|
||||
|
||||
# then build the url
|
||||
app.url_for('static', filename='file.txt') == '/static/file.txt'
|
||||
app.url_for('static', name='static', filename='file.txt') == '/static/file.txt'
|
||||
app.url_for('static', name='uploads', filename='file.txt') == '/uploads/file.txt'
|
||||
app.url_for('static', name='best_png') == '/the_best.png'
|
||||
|
||||
# blueprint url building
|
||||
app.url_for('static', name='bp.static', filename='file.txt') == '/bp/static/file.txt'
|
||||
app.url_for('static', name='bp.uploads', filename='file.txt') == '/bp/uploads/file.txt'
|
||||
app.url_for('static', name='bp.best_png') == '/bp/static/the_best.png'
|
|
@ -1,66 +0,0 @@
|
|||
Sockets
|
||||
=======
|
||||
|
||||
Sanic can use the python
|
||||
`socket module <https://docs.python.org/3/library/socket.html>`_ to accommodate
|
||||
non IPv4 sockets.
|
||||
|
||||
IPv6 example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
import socket
|
||||
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
sock.bind(('::', 7777))
|
||||
|
||||
app = Sanic("ipv6_example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return json({"hello": "world"})
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(sock=sock)
|
||||
|
||||
to test IPv6 ``curl -g -6 "http://[::1]:7777/"``
|
||||
|
||||
|
||||
UNIX socket example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import signal
|
||||
import sys
|
||||
import socket
|
||||
import os
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
|
||||
server_socket = '/tmp/sanic.sock'
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.bind(server_socket)
|
||||
|
||||
app = Sanic("unix_socket_example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return json({"hello": "world"})
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
print('Exiting')
|
||||
os.unlink(server_socket)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(sock=sock)
|
||||
|
||||
to test UNIX: ``curl -v --unix-socket /tmp/sanic.sock http://localhost/hello``
|
|
@ -1,20 +0,0 @@
|
|||
SSL Example
|
||||
-----------
|
||||
|
||||
Optionally pass in an SSLContext:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import ssl
|
||||
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
|
||||
context.load_cert_chain("/path/to/cert", keyfile="/path/to/keyfile")
|
||||
|
||||
app.run(host="0.0.0.0", port=8443, ssl=context)
|
||||
|
||||
You can also pass in the locations of a certificate and key as a dictionary:
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
ssl = {'cert': "/path/to/cert", 'key': "/path/to/keyfile"}
|
||||
app.run(host="0.0.0.0", port=8443, ssl=ssl)
|
|
@ -1,92 +0,0 @@
|
|||
Static Files
|
||||
============
|
||||
|
||||
Static files and directories, such as an image file, are served by Sanic when
|
||||
registered with the `app.static()` method. The method takes an endpoint URL and a
|
||||
filename. The file specified will then be accessible via the given endpoint.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.blueprints import Blueprint
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
# Serves files from the static folder to the URL /static
|
||||
app.static('/static', './static')
|
||||
# use url_for to build the url, name defaults to 'static' and can be ignored
|
||||
app.url_for('static', filename='file.txt') == '/static/file.txt'
|
||||
app.url_for('static', name='static', filename='file.txt') == '/static/file.txt'
|
||||
|
||||
# Serves the file /home/ubuntu/test.png when the URL /the_best.png
|
||||
# is requested
|
||||
app.static('/the_best.png', '/home/ubuntu/test.png', name='best_png')
|
||||
|
||||
# you can use url_for to build the static file url
|
||||
# you can ignore name and filename parameters if you don't define it
|
||||
app.url_for('static', name='best_png') == '/the_best.png'
|
||||
app.url_for('static', name='best_png', filename='any') == '/the_best.png'
|
||||
|
||||
# you need define the name for other static files
|
||||
app.static('/another.png', '/home/ubuntu/another.png', name='another')
|
||||
app.url_for('static', name='another') == '/another.png'
|
||||
app.url_for('static', name='another', filename='any') == '/another.png'
|
||||
|
||||
# also, you can use static for blueprint
|
||||
bp = Blueprint('bp', url_prefix='/bp')
|
||||
bp.static('/static', './static')
|
||||
|
||||
# specify a different content_type for your files
|
||||
# such as adding 'charset'
|
||||
app.static('/', '/public/index.html', content_type="text/html; charset=utf-8")
|
||||
|
||||
# servers the file directly
|
||||
bp.static('/the_best.png', '/home/ubuntu/test.png', name='best_png')
|
||||
app.blueprint(bp)
|
||||
|
||||
app.url_for('static', name='bp.static', filename='file.txt') == '/bp/static/file.txt'
|
||||
app.url_for('static', name='bp.best_png') == '/bp/test_best.png'
|
||||
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
> **Note:** Sanic does not provide directory index when you serve a static directory.
|
||||
|
||||
Virtual Host
|
||||
------------
|
||||
|
||||
The `app.static()` method also support **virtual host**. You can serve your static files with specific **virtual host** with `host` argument. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
app.static('/static', './static')
|
||||
app.static('/example_static', './example_static', host='www.example.com')
|
||||
|
||||
Streaming Large File
|
||||
--------------------
|
||||
|
||||
In some cases, you might server large file(ex: videos, images, etc.) with Sanic. You can choose to use **streaming file** rather than download directly.
|
||||
|
||||
Here is an example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
app.static('/large_video.mp4', '/home/ubuntu/large_video.mp4', stream_large_files=True)
|
||||
|
||||
When `stream_large_files` is `True`, Sanic will use `file_stream()` instead of `file()` to serve static files. This will use **1KB** as the default chunk size. And, if needed, you can also use a custom chunk size. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
chunk_size = 1024 * 1024 * 8 # Set chunk size to 8MiB
|
||||
app.static('/large_video.mp4', '/home/ubuntu/large_video.mp4', stream_large_files=chunk_size)
|
|
@ -1,147 +0,0 @@
|
|||
Streaming
|
||||
=========
|
||||
|
||||
Request Streaming
|
||||
-----------------
|
||||
|
||||
Sanic allows you to get request data by stream, as below. When the request ends, `await request.stream.read()` returns `None`. Only post, put and patch decorator have stream argument.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.views import CompositionView
|
||||
from sanic.views import HTTPMethodView
|
||||
from sanic.views import stream as stream_decorator
|
||||
from sanic.blueprints import Blueprint
|
||||
from sanic.response import stream, text
|
||||
|
||||
bp = Blueprint('blueprint_request_stream')
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
class SimpleView(HTTPMethodView):
|
||||
|
||||
@stream_decorator
|
||||
async def post(self, request):
|
||||
result = ''
|
||||
while True:
|
||||
body = await request.stream.read()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode('utf-8')
|
||||
return text(result)
|
||||
|
||||
|
||||
@app.post('/stream', stream=True)
|
||||
async def handler(request):
|
||||
async def streaming(response):
|
||||
while True:
|
||||
body = await request.stream.read()
|
||||
if body is None:
|
||||
break
|
||||
body = body.decode('utf-8').replace('1', 'A')
|
||||
await response.write(body)
|
||||
return stream(streaming)
|
||||
|
||||
|
||||
@bp.put('/bp_stream', stream=True)
|
||||
async def bp_put_handler(request):
|
||||
result = ''
|
||||
while True:
|
||||
body = await request.stream.read()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode('utf-8').replace('1', 'A')
|
||||
return text(result)
|
||||
|
||||
|
||||
# You can also use `bp.add_route()` with stream argument
|
||||
async def bp_post_handler(request):
|
||||
result = ''
|
||||
while True:
|
||||
body = await request.stream.read()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode('utf-8').replace('1', 'A')
|
||||
return text(result)
|
||||
|
||||
bp.add_route(bp_post_handler, '/bp_stream', methods=['POST'], stream=True)
|
||||
|
||||
|
||||
async def post_handler(request):
|
||||
result = ''
|
||||
while True:
|
||||
body = await request.stream.read()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode('utf-8')
|
||||
return text(result)
|
||||
|
||||
app.blueprint(bp)
|
||||
app.add_route(SimpleView.as_view(), '/method_view')
|
||||
view = CompositionView()
|
||||
view.add(['POST'], post_handler, stream=True)
|
||||
app.add_route(view, '/composition_view')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='127.0.0.1', port=8000)
|
||||
|
||||
Response Streaming
|
||||
------------------
|
||||
|
||||
Sanic allows you to stream content to the client with the `stream` method. This method accepts a coroutine callback which is passed a `StreamingHTTPResponse` object that is written to. A simple example is like follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import stream
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
async def sample_streaming_fn(response):
|
||||
await response.write('foo,')
|
||||
await response.write('bar')
|
||||
|
||||
return stream(sample_streaming_fn, content_type='text/csv')
|
||||
|
||||
This is useful in situations where you want to stream content to the client that originates in an external service, like a database. For example, you can stream database records to the client with the asynchronous cursor that `asyncpg` provides:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route("/")
|
||||
async def index(request):
|
||||
async def stream_from_db(response):
|
||||
conn = await asyncpg.connect(database='test')
|
||||
async with conn.transaction():
|
||||
async for record in conn.cursor('SELECT generate_series(0, 10)'):
|
||||
await response.write(record[0])
|
||||
|
||||
return stream(stream_from_db)
|
||||
|
||||
If a client supports HTTP/1.1, Sanic will use `chunked transfer encoding <https://en.wikipedia.org/wiki/Chunked_transfer_encoding>`_; you can explicitly enable or disable it using `chunked` option of the `stream` function.
|
||||
|
||||
File Streaming
|
||||
--------------
|
||||
|
||||
Sanic provides `sanic.response.file_stream` function that is useful when you want to send a large file. It returns a `StreamingHTTPResponse` object and will use chunked transfer encoding by default; for this reason Sanic doesn't add `Content-Length` HTTP header in the response. If you want to use this header, you can disable chunked transfer encoding and add it manually:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from aiofiles import os as async_os
|
||||
from sanic.response import file_stream
|
||||
|
||||
@app.route("/")
|
||||
async def index(request):
|
||||
file_path = "/srv/www/whatever.png"
|
||||
|
||||
file_stat = await async_os.stat(file_path)
|
||||
headers = {"Content-Length": str(file_stat.st_size)}
|
||||
|
||||
return await file_stream(
|
||||
file_path,
|
||||
headers=headers,
|
||||
chunked=False,
|
||||
)
|
|
@ -1,171 +0,0 @@
|
|||
Testing
|
||||
=======
|
||||
|
||||
Sanic endpoints can be tested locally using the `test_client` object, which
|
||||
depends on an additional package: `httpx <https://www.encode.io/httpx/>`_
|
||||
library, which implements an API that mirrors the `requests` library.
|
||||
|
||||
The `test_client` exposes `get`, `post`, `put`, `delete`, `patch`, `head` and `options` methods
|
||||
for you to run against your application. A simple example (using pytest) is like follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Import the Sanic app, usually created with Sanic(__name__)
|
||||
from external_server import app
|
||||
|
||||
def test_index_returns_200():
|
||||
request, response = app.test_client.get('/')
|
||||
assert response.status == 200
|
||||
|
||||
def test_index_put_not_allowed():
|
||||
request, response = app.test_client.put('/')
|
||||
assert response.status == 405
|
||||
|
||||
Internally, each time you call one of the `test_client` methods, the Sanic app is run at `127.0.0.1:42101` and
|
||||
your test request is executed against your application, using `httpx`.
|
||||
|
||||
The `test_client` methods accept the following arguments and keyword arguments:
|
||||
|
||||
- `uri` *(default `'/'`)* A string representing the URI to test.
|
||||
- `gather_request` *(default `True`)* A boolean which determines whether the
|
||||
original request will be returned by the function. If set to `True`, the
|
||||
return value is a tuple of `(request, response)`, if `False` only the
|
||||
response is returned.
|
||||
- `server_kwargs` *(default `{}`)* a dict of additional arguments to pass into `app.run` before the test request is run.
|
||||
- `debug` *(default `False`)* A boolean which determines whether to run the server in debug mode.
|
||||
|
||||
The function further takes the `*request_args` and `**request_kwargs`, which are passed directly to the request.
|
||||
|
||||
For example, to supply data to a GET request, you would do the following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_get_request_includes_data():
|
||||
params = {'key1': 'value1', 'key2': 'value2'}
|
||||
request, response = app.test_client.get('/', params=params)
|
||||
assert request.args.get('key1') == 'value1'
|
||||
|
||||
And to supply data to a JSON POST request:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_post_json_request_includes_data():
|
||||
data = {'key1': 'value1', 'key2': 'value2'}
|
||||
request, response = app.test_client.post('/', data=json.dumps(data))
|
||||
assert request.json.get('key1') == 'value1'
|
||||
|
||||
More information about
|
||||
the available arguments to `httpx` can be found
|
||||
[in the documentation for `httpx <https://www.encode.io/httpx/>`_.
|
||||
|
||||
.. code-block:: python
|
||||
@pytest.mark.asyncio
|
||||
async def test_index_returns_200():
|
||||
request, response = await app.asgi_client.put('/')
|
||||
assert response.status == 200
|
||||
.. note::
|
||||
|
||||
Whenever one of the test clients run, you can test your app instance to determine if it is in testing mode:
|
||||
`app.test_mode`.
|
||||
|
||||
Additionally, Sanic has an asynchronous testing client. The difference is that the async client will not stand up an
|
||||
instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still
|
||||
executed.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_index_returns_200():
|
||||
request, response = await app.asgi_client.put('/')
|
||||
assert response.status == 200
|
||||
|
||||
.. note::
|
||||
|
||||
Whenever one of the test clients run, you can test your app instance to determine if it is in testing mode:
|
||||
`app.test_mode`.
|
||||
|
||||
|
||||
Using a random port
|
||||
-------------------
|
||||
|
||||
If you need to test using a free unpriveleged port chosen by the kernel
|
||||
instead of the default with `SanicTestClient`, you can do so by specifying
|
||||
`port=None`. On most systems the port will be in the range 1024 to 65535.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Import the Sanic app, usually created with Sanic(__name__)
|
||||
from external_server import app
|
||||
from sanic.testing import SanicTestClient
|
||||
|
||||
def test_index_returns_200():
|
||||
request, response = SanicTestClient(app, port=None).get('/')
|
||||
assert response.status == 200
|
||||
|
||||
pytest-sanic
|
||||
------------
|
||||
|
||||
`pytest-sanic <https://github.com/yunstanford/pytest-sanic>`_ is a pytest plugin, it helps you to test your code asynchronously.
|
||||
Just write tests like,
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def test_sanic_db_find_by_id(app):
|
||||
"""
|
||||
Let's assume that, in db we have,
|
||||
{
|
||||
"id": "123",
|
||||
"name": "Kobe Bryant",
|
||||
"team": "Lakers",
|
||||
}
|
||||
"""
|
||||
doc = await app.db["players"].find_by_id("123")
|
||||
assert doc.name == "Kobe Bryant"
|
||||
assert doc.team == "Lakers"
|
||||
|
||||
`pytest-sanic <https://github.com/yunstanford/pytest-sanic>`_ also provides some useful fixtures, like loop, unused_port,
|
||||
test_server, test_client.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.yield_fixture
|
||||
def app():
|
||||
app = Sanic("test_sanic_app")
|
||||
|
||||
@app.route("/test_get", methods=['GET'])
|
||||
async def test_get(request):
|
||||
return response.json({"GET": True})
|
||||
|
||||
@app.route("/test_post", methods=['POST'])
|
||||
async def test_post(request):
|
||||
return response.json({"POST": True})
|
||||
|
||||
yield app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_cli(loop, app, test_client):
|
||||
return loop.run_until_complete(test_client(app, protocol=WebSocketProtocol))
|
||||
|
||||
|
||||
#########
|
||||
# Tests #
|
||||
#########
|
||||
|
||||
async def test_fixture_test_client_get(test_cli):
|
||||
"""
|
||||
GET request
|
||||
"""
|
||||
resp = await test_cli.get('/test_get')
|
||||
assert resp.status == 200
|
||||
resp_json = await resp.json()
|
||||
assert resp_json == {"GET": True}
|
||||
|
||||
async def test_fixture_test_client_post(test_cli):
|
||||
"""
|
||||
POST request
|
||||
"""
|
||||
resp = await test_cli.post('/test_post')
|
||||
assert resp.status == 200
|
||||
resp_json = await resp.json()
|
||||
assert resp_json == {"POST": True}
|
|
@ -1,54 +0,0 @@
|
|||
Versioning
|
||||
==========
|
||||
|
||||
You can pass the `version` keyword to the route decorators, or to a blueprint initializer. It will result in the `v{version}` url prefix where `{version}` is the version number.
|
||||
|
||||
Per route
|
||||
---------
|
||||
|
||||
You can pass a version number to the routes directly.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
|
||||
|
||||
@app.route('/text', version=1)
|
||||
def handle_request(request):
|
||||
return response.text('Hello world! Version 1')
|
||||
|
||||
@app.route('/text', version=2)
|
||||
def handle_request(request):
|
||||
return response.text('Hello world! Version 2')
|
||||
|
||||
app.run(port=80)
|
||||
|
||||
Then with curl:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl localhost/v1/text
|
||||
curl localhost/v2/text
|
||||
|
||||
|
||||
Global blueprint version
|
||||
------------------------
|
||||
|
||||
You can also pass a version number to the blueprint, which will apply to all routes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from sanic import response
|
||||
from sanic.blueprints import Blueprint
|
||||
|
||||
bp = Blueprint('test', version=1)
|
||||
|
||||
@bp.route('/html')
|
||||
def handle_request(request):
|
||||
return response.html('<p>Hello world!</p>')
|
||||
|
||||
Then with curl:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl localhost/v1/html
|
|
@ -1,59 +0,0 @@
|
|||
WebSocket
|
||||
=========
|
||||
|
||||
Sanic provides an easy to use abstraction on top of `websockets`.
|
||||
Sanic Supports websocket versions 7 and 8.
|
||||
|
||||
To setup a WebSocket:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
from sanic.websocket import WebSocketProtocol
|
||||
|
||||
app = Sanic("websocket_example")
|
||||
|
||||
@app.websocket('/feed')
|
||||
async def feed(request, ws):
|
||||
while True:
|
||||
data = 'hello!'
|
||||
print('Sending: ' + data)
|
||||
await ws.send(data)
|
||||
data = await ws.recv()
|
||||
print('Received: ' + data)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, protocol=WebSocketProtocol)
|
||||
|
||||
|
||||
Alternatively, the ``app.add_websocket_route`` method can be used instead of the
|
||||
decorator:
|
||||
|
||||
.. code:: python
|
||||
|
||||
async def feed(request, ws):
|
||||
pass
|
||||
|
||||
app.add_websocket_route(feed, '/feed')
|
||||
|
||||
|
||||
Handlers for a WebSocket route is invoked with the request as first argument, and a
|
||||
WebSocket protocol object as second argument. The protocol object has ``send``
|
||||
and ``recv`` methods to send and receive data respectively.
|
||||
|
||||
|
||||
You could setup your own WebSocket configuration through ``app.config``, like
|
||||
|
||||
.. code:: python
|
||||
|
||||
app.config.WEBSOCKET_MAX_SIZE = 2 ** 20
|
||||
app.config.WEBSOCKET_MAX_QUEUE = 32
|
||||
app.config.WEBSOCKET_READ_LIMIT = 2 ** 16
|
||||
app.config.WEBSOCKET_WRITE_LIMIT = 2 ** 16
|
||||
app.config.WEBSOCKET_PING_INTERVAL = 20
|
||||
app.config.WEBSOCKET_PING_TIMEOUT = 20
|
||||
|
||||
These settings will have no impact if running in ASGI mode.
|
||||
|
||||
Find more in ``Configuration`` section.
|
248
sanic/app.py
248
sanic/app.py
|
@ -11,7 +11,18 @@ from inspect import isawaitable, signature
|
|||
from socket import socket
|
||||
from ssl import Purpose, SSLContext, create_default_context
|
||||
from traceback import format_exc
|
||||
from typing import Any, Dict, Iterable, List, Optional, Set, Type, Union
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from urllib.parse import urlencode, urlunparse
|
||||
|
||||
from sanic import reloader_helpers
|
||||
|
@ -25,7 +36,7 @@ from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType
|
|||
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||
from sanic.request import Request
|
||||
from sanic.response import BaseHTTPResponse, HTTPResponse
|
||||
from sanic.router import Router
|
||||
from sanic.router import Route, Router
|
||||
from sanic.server import (
|
||||
AsyncioServer,
|
||||
HttpProtocol,
|
||||
|
@ -40,6 +51,10 @@ from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
|||
|
||||
|
||||
class Sanic:
|
||||
"""
|
||||
The main application instance
|
||||
"""
|
||||
|
||||
_app_registry: Dict[str, "Sanic"] = {}
|
||||
test_mode = False
|
||||
|
||||
|
@ -98,9 +113,12 @@ class Sanic:
|
|||
|
||||
@property
|
||||
def loop(self):
|
||||
"""Synonymous with asyncio.get_event_loop().
|
||||
"""
|
||||
Synonymous with asyncio.get_event_loop().
|
||||
|
||||
Only supported when using the `app.run` method.
|
||||
.. note::
|
||||
|
||||
Only supported when using the `app.run` method.
|
||||
"""
|
||||
if not self.is_running and self.asgi is False:
|
||||
raise SanicException(
|
||||
|
@ -113,8 +131,9 @@ class Sanic:
|
|||
# Registration
|
||||
# -------------------------------------------------------------------- #
|
||||
|
||||
def add_task(self, task):
|
||||
"""Schedule a task to run later, after the loop has started.
|
||||
def add_task(self, task) -> None:
|
||||
"""
|
||||
Schedule a task to run later, after the loop has started.
|
||||
Different from asyncio.ensure_future in that it does not
|
||||
also return a future, and the actual ensure_future call
|
||||
is delayed until before server start.
|
||||
|
@ -130,19 +149,28 @@ class Sanic:
|
|||
)
|
||||
|
||||
# Decorator
|
||||
def listener(self, event):
|
||||
"""Create a listener from a decorated function.
|
||||
def listener(self, event: str):
|
||||
"""
|
||||
Create a listener from a decorated function.
|
||||
|
||||
To be used as a deocrator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@bp.listener("before_server_start")
|
||||
async def before_server_start(app, loop):
|
||||
...
|
||||
|
||||
:param event: event to listen to
|
||||
"""
|
||||
|
||||
def decorator(listener):
|
||||
def decorator(listener: Callable):
|
||||
self.listeners[event].append(listener)
|
||||
return listener
|
||||
|
||||
return decorator
|
||||
|
||||
def register_listener(self, listener, event):
|
||||
def register_listener(self, listener: Callable, event: str) -> Any:
|
||||
"""
|
||||
Register the listener for a given event.
|
||||
|
||||
|
@ -156,24 +184,27 @@ class Sanic:
|
|||
# Decorator
|
||||
def route(
|
||||
self,
|
||||
uri,
|
||||
methods=frozenset({"GET"}),
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
stream=False,
|
||||
version=None,
|
||||
name=None,
|
||||
ignore_body=False,
|
||||
uri: str,
|
||||
methods: Iterable[str] = frozenset({"GET"}),
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
stream: bool = False,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
ignore_body: bool = False,
|
||||
):
|
||||
"""Decorate a function to be registered as a route
|
||||
"""
|
||||
Decorate a function to be registered as a route
|
||||
|
||||
:param uri: path of the URL
|
||||
:param methods: list or tuple of methods allowed
|
||||
:param host:
|
||||
:param strict_slashes:
|
||||
:param stream:
|
||||
:param version:
|
||||
:param host: the host, if required
|
||||
:param strict_slashes: whether to apply strict slashes to the route
|
||||
:param stream: whether to allow the request to stream its body
|
||||
:param version: route specific versioning
|
||||
:param name: user defined route name for url_for
|
||||
:param ignore_body: whether the handler should ignore request
|
||||
body (eg. GET requests)
|
||||
:return: tuple of routes, decorated function
|
||||
"""
|
||||
|
||||
|
@ -224,12 +255,12 @@ class Sanic:
|
|||
# Shorthand method decorators
|
||||
def get(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
version=None,
|
||||
name=None,
|
||||
ignore_body=True,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
ignore_body: bool = True,
|
||||
):
|
||||
"""
|
||||
Add an API URL under the **GET** *HTTP* method
|
||||
|
@ -254,12 +285,12 @@ class Sanic:
|
|||
|
||||
def post(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
stream=False,
|
||||
version=None,
|
||||
name=None,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
stream: bool = False,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Add an API URL under the **POST** *HTTP* method
|
||||
|
@ -284,13 +315,13 @@ class Sanic:
|
|||
|
||||
def put(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
stream=False,
|
||||
version=None,
|
||||
name=None,
|
||||
):
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
stream: bool = False,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> HTTPResponse:
|
||||
"""
|
||||
Add an API URL under the **PUT** *HTTP* method
|
||||
|
||||
|
@ -314,13 +345,32 @@ class Sanic:
|
|||
|
||||
def head(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
version=None,
|
||||
name=None,
|
||||
ignore_body=True,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
ignore_body: bool = True,
|
||||
):
|
||||
"""
|
||||
Add an API URL under the **HEAD** *HTTP* method
|
||||
|
||||
:param uri: URL to be tagged to **HEAD** method of *HTTP*
|
||||
:type uri: str
|
||||
:param host: Host IP or FQDN for the service to use
|
||||
:type host: Optional[str], optional
|
||||
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||
URLs need to terminate with a */*
|
||||
:type strict_slashes: Optional[bool], optional
|
||||
:param version: API Version
|
||||
:type version: Optional[str], optional
|
||||
:param name: Unique name that can be used to identify the Route
|
||||
:type name: Optional[str], optional
|
||||
:param ignore_body: whether the handler should ignore request
|
||||
body (eg. GET requests), defaults to True
|
||||
:type ignore_body: bool, optional
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
uri,
|
||||
methods=frozenset({"HEAD"}),
|
||||
|
@ -333,22 +383,30 @@ class Sanic:
|
|||
|
||||
def options(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
version=None,
|
||||
name=None,
|
||||
ignore_body=True,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
ignore_body: bool = True,
|
||||
):
|
||||
"""
|
||||
Add an API URL under the **OPTIONS** *HTTP* method
|
||||
|
||||
:param uri: URL to be tagged to **OPTIONS** method of *HTTP*
|
||||
:type uri: str
|
||||
:param host: Host IP or FQDN for the service to use
|
||||
:type host: Optional[str], optional
|
||||
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||
URLs need to terminate with a */*
|
||||
:type strict_slashes: Optional[bool], optional
|
||||
:param version: API Version
|
||||
:type version: Optional[str], optional
|
||||
:param name: Unique name that can be used to identify the Route
|
||||
:type name: Optional[str], optional
|
||||
:param ignore_body: whether the handler should ignore request
|
||||
body (eg. GET requests), defaults to True
|
||||
:type ignore_body: bool, optional
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
|
@ -363,22 +421,32 @@ class Sanic:
|
|||
|
||||
def patch(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
stream=False,
|
||||
version=None,
|
||||
name=None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Add an API URL under the **PATCH** *HTTP* method
|
||||
|
||||
:param uri: URL to be tagged to **PATCH** method of *HTTP*
|
||||
:type uri: str
|
||||
:param host: Host IP or FQDN for the service to use
|
||||
:type host: Optional[str], optional
|
||||
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||
URLs need to terminate with a */*
|
||||
:type strict_slashes: Optional[bool], optional
|
||||
:param stream: whether to allow the request to stream its body
|
||||
:type stream: Optional[bool], optional
|
||||
:param version: API Version
|
||||
:type version: Optional[str], optional
|
||||
:param name: Unique name that can be used to identify the Route
|
||||
:type name: Optional[str], optional
|
||||
:param ignore_body: whether the handler should ignore request
|
||||
body (eg. GET requests), defaults to True
|
||||
:type ignore_body: bool, optional
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
|
@ -393,12 +461,12 @@ class Sanic:
|
|||
|
||||
def delete(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
version=None,
|
||||
name=None,
|
||||
ignore_body=True,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
ignore_body: bool = True,
|
||||
):
|
||||
"""
|
||||
Add an API URL under the **DELETE** *HTTP* method
|
||||
|
@ -424,12 +492,12 @@ class Sanic:
|
|||
def add_route(
|
||||
self,
|
||||
handler,
|
||||
uri,
|
||||
uri: str,
|
||||
methods=frozenset({"GET"}),
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
version=None,
|
||||
name=None,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
stream=False,
|
||||
):
|
||||
"""A helper method to register class instance or
|
||||
|
@ -483,12 +551,12 @@ class Sanic:
|
|||
# Decorator
|
||||
def websocket(
|
||||
self,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
subprotocols=None,
|
||||
version=None,
|
||||
name=None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Decorate a function to be registered as a websocket route
|
||||
|
@ -544,12 +612,12 @@ class Sanic:
|
|||
def add_websocket_route(
|
||||
self,
|
||||
handler,
|
||||
uri,
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
uri: str,
|
||||
host: Optional[str] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
subprotocols=None,
|
||||
version=None,
|
||||
name=None,
|
||||
version: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
A helper method to register a function as a websocket route.
|
||||
|
@ -682,7 +750,7 @@ class Sanic:
|
|||
stream_large_files=False,
|
||||
name="static",
|
||||
host=None,
|
||||
strict_slashes=None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
content_type=None,
|
||||
):
|
||||
"""
|
||||
|
@ -747,7 +815,7 @@ class Sanic:
|
|||
blueprint.register(self, options)
|
||||
|
||||
def url_for(self, view_name: str, **kwargs):
|
||||
r"""Build a URL based on a view name and the values provided.
|
||||
"""Build a URL based on a view name and the values provided.
|
||||
|
||||
In order to build a URL, all request parameters must be supplied as
|
||||
keyword arguments, and each parameter must pass the test for the
|
||||
|
@ -891,6 +959,15 @@ class Sanic:
|
|||
pass
|
||||
|
||||
async def handle_exception(self, request, exception):
|
||||
"""
|
||||
[summary]
|
||||
|
||||
:param request: [description]
|
||||
:type request: [type]
|
||||
:param exception: [description]
|
||||
:type exception: [type]
|
||||
:raises ServerError: [description]
|
||||
"""
|
||||
# -------------------------------------------- #
|
||||
# Request Middleware
|
||||
# -------------------------------------------- #
|
||||
|
@ -1036,6 +1113,13 @@ class Sanic:
|
|||
|
||||
@property
|
||||
def asgi_client(self):
|
||||
"""
|
||||
A testing client that uses ASGI to reach into the application to
|
||||
execute hanlers.
|
||||
|
||||
:return: testing client
|
||||
:rtype: :class:`SanicASGITestClient`
|
||||
"""
|
||||
return SanicASGITestClient(self)
|
||||
|
||||
# -------------------------------------------------------------------- #
|
||||
|
|
|
@ -101,11 +101,13 @@ class Config(dict):
|
|||
def update_config(self, config: Union[bytes, str, dict, Any]):
|
||||
"""Update app.config.
|
||||
|
||||
Note:: only upper case settings are considered.
|
||||
..note:: only upper case settings are considered.
|
||||
|
||||
You can upload app config by providing path to py file
|
||||
holding settings.
|
||||
|
||||
..code-block::
|
||||
|
||||
# /some/py/file
|
||||
A = 1
|
||||
B = 2
|
||||
|
@ -118,12 +120,16 @@ class Config(dict):
|
|||
|
||||
You can upload app config by providing dict holding settings.
|
||||
|
||||
..code-block::
|
||||
|
||||
d = {"A": 1, "B": 2}
|
||||
config.update_config(d)
|
||||
|
||||
You can upload app config by providing any object holding settings,
|
||||
but in such case config.__dict__ will be used as dict holding settings.
|
||||
|
||||
..code-block::
|
||||
|
||||
class C:
|
||||
A = 1
|
||||
B = 2
|
||||
|
|
|
@ -178,9 +178,10 @@ def abort(status_code, message=None):
|
|||
Raise an exception based on SanicException. Returns the HTTP response
|
||||
message appropriate for the given status code, unless provided.
|
||||
|
||||
STATUS_CODES from sanic.helpers for the given status code.
|
||||
|
||||
:param status_code: The HTTP status code to return.
|
||||
:param message: The HTTP response body. Defaults to the messages in
|
||||
STATUS_CODES from sanic.helpers for the given status code.
|
||||
"""
|
||||
if message is None:
|
||||
message = STATUS_CODES.get(status_code)
|
||||
|
|
Loading…
Reference in New Issue
Block a user