Add ASGI documentation

This commit is contained in:
Adam Hopkins 2019-06-18 09:57:42 +03:00
parent ab706dda7d
commit fb61834a2e
4 changed files with 63 additions and 16 deletions

View File

@ -1,7 +1,12 @@
# Deploying # Deploying
Deploying Sanic is made simple by the inbuilt webserver. After defining an Deploying Sanic is very simple using one of three options: the inbuilt webserver,
instance of `sanic.Sanic`, we can call the `run` method with the following an [ASGI webserver](https://asgi.readthedocs.io/en/latest/implementations.html), or `gunicorn`.
It is also very common to place Sanic behind a reverse proxy, like `nginx`.
## Running via Sanic webserver
After defining an instance of `sanic.Sanic`, we can call the `run` method with the following
keyword arguments: keyword arguments:
- `host` *(default `"127.0.0.1"`)*: Address to host the server on. - `host` *(default `"127.0.0.1"`)*: Address to host the server on.
@ -17,7 +22,13 @@ keyword arguments:
[asyncio.protocol](https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes). [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). - `access_log` *(default `True`)*: Enables log on handling requests (significantly slows server).
## Workers ```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 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. up the juice, just specify the number of workers in the `run` arguments.
@ -29,9 +40,9 @@ app.run(host='0.0.0.0', port=1337, workers=4)
Sanic will automatically spin up multiple processes and route traffic between Sanic will automatically spin up multiple processes and route traffic between
them. We recommend as many workers as you have available cores. them. We recommend as many workers as you have available cores.
## Running via command ### Running via command
If you like using command line arguments, you can launch a Sanic server by 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 executing the module. For example, if you initialized Sanic as `app` in a file
named `server.py`, you could run the server like so: named `server.py`, you could run the server like so:
@ -46,6 +57,33 @@ if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337, workers=4) 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.
1. 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`.
1. ASGI mode is still in "beta" as of Sanic v19.6.
## Running via Gunicorn ## Running via Gunicorn
[Gunicorn](http://gunicorn.org/) Green Unicorn is a WSGI HTTP Server for UNIX. [Gunicorn](http://gunicorn.org/) Green Unicorn is a WSGI HTTP Server for UNIX.
@ -64,7 +102,9 @@ of the memory leak.
See the [Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) for more information. See the [Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) for more information.
## Running behind a reverse proxy ## Other deployment considerations
### Running behind a reverse proxy
Sanic can be used with a reverse proxy (e.g. nginx). There's a simple example of nginx configuration: Sanic can be used with a reverse proxy (e.g. nginx). There's a simple example of nginx configuration:
@ -84,7 +124,7 @@ server {
If you want to get real client ip, you should configure `X-Real-IP` and `X-Forwarded-For` HTTP headers and set `app.config.PROXIES_COUNT` to `1`; see the configuration page for more information. If you want to get real client ip, you should configure `X-Real-IP` and `X-Forwarded-For` HTTP headers and set `app.config.PROXIES_COUNT` to `1`; see the configuration page for more information.
## Disable debug logging ### Disable debug logging for performance
To improve the performance add `debug=False` and `access_log=False` in the `run` arguments. To improve the performance add `debug=False` and `access_log=False` in the `run` arguments.
@ -104,9 +144,10 @@ Or you can rewrite app config directly
app.config.ACCESS_LOG = False app.config.ACCESS_LOG = False
``` ```
## Asynchronous support ### 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 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. to run the app in general.
Here is an incomplete example (please see `run_async.py` in examples for something more practical): Here is an incomplete example (please see `run_async.py` in examples for something more practical):
@ -116,4 +157,4 @@ server = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
task = asyncio.ensure_future(server) task = asyncio.ensure_future(server)
loop.run_forever() loop.run_forever()
``` ```

View File

@ -1393,6 +1393,9 @@ class Sanic:
# -------------------------------------------------------------------- # # -------------------------------------------------------------------- #
async def __call__(self, scope, receive, send): async def __call__(self, scope, receive, send):
"""To be ASGI compliant, our instance must be a callable that accepts
three arguments: scope, receive, send. See the ASGI reference for more
details: https://asgi.readthedocs.io/en/latest/"""
self.asgi = True self.asgi = True
asgi_app = await ASGIApp.create(self, scope, receive, send) asgi_app = await ASGIApp.create(self, scope, receive, send)
await asgi_app() await asgi_app()

View File

@ -260,7 +260,6 @@ class ASGIApp:
message = await self.transport.receive() message = await self.transport.receive()
chunk = message.get("body", b"") chunk = message.get("body", b"")
await self.request.stream.put(chunk) await self.request.stream.put(chunk)
# self.sanic_app.loop.create_task(self.request.stream.put(chunk))
more_body = message.get("more_body", False) more_body = message.get("more_body", False)
@ -288,7 +287,6 @@ class ASGIApp:
headers = [ headers = [
(str(name).encode("latin-1"), str(value).encode("latin-1")) (str(name).encode("latin-1"), str(value).encode("latin-1"))
for name, value in response.headers.items() for name, value in response.headers.items()
# if name not in ("Set-Cookie",)
] ]
except AttributeError: except AttributeError:
logger.error( logger.error(

View File

@ -183,6 +183,14 @@ class SanicASGIAdapter(requests.asgi.ASGIAdapter):
*args: typing.Any, *args: typing.Any,
**kwargs: typing.Any, **kwargs: typing.Any,
) -> requests.Response: ) -> requests.Response:
"""This method is taken MOSTLY verbatim from requests-asyn. The
difference is the capturing of a response on the ASGI call and then
returning it on the response object. This is implemented to achieve:
request, response = await app.asgi_client.get("/")
You can see the original code here:
https://github.com/encode/requests-async/blob/614f40f77f19e6c6da8a212ae799107b0384dbf9/requests_async/asgi.py#L51""" # noqa
scheme, netloc, path, query, fragment = urlsplit( scheme, netloc, path, query, fragment = urlsplit(
request.url request.url
) # type: ignore ) # type: ignore
@ -345,9 +353,6 @@ class SanicASGITestClient(requests.ASGISession):
self.app = app self.app = app
self.base_url = base_url self.base_url = base_url
# async def send(self, prepared_request, *args, **kwargs):
# return await super().send(*args, **kwargs)
async def request(self, method, url, gather_request=True, *args, **kwargs): async def request(self, method, url, gather_request=True, *args, **kwargs):
self.gather_request = gather_request self.gather_request = gather_request
print(url) print(url)