commit
4ccc782e29
|
@ -59,6 +59,7 @@ if __name__ == "__main__":
|
||||||
* [Class Based Views](docs/class_based_views.md)
|
* [Class Based Views](docs/class_based_views.md)
|
||||||
* [Cookies](docs/cookies.md)
|
* [Cookies](docs/cookies.md)
|
||||||
* [Static Files](docs/static_files.md)
|
* [Static Files](docs/static_files.md)
|
||||||
|
* [Custom Protocol](docs/custom_protocol.md)
|
||||||
* [Testing](docs/testing.md)
|
* [Testing](docs/testing.md)
|
||||||
* [Deploying](docs/deploying.md)
|
* [Deploying](docs/deploying.md)
|
||||||
* [Contributing](docs/contributing.md)
|
* [Contributing](docs/contributing.md)
|
||||||
|
|
70
docs/custom_protocol.md
Normal file
70
docs/custom_protocol.md
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# Custom Protocol
|
||||||
|
|
||||||
|
You can change the behavior of protocol by using custom protocol.
|
||||||
|
If you want to use custom protocol, you should put subclass of [protocol class](https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes) in the protocol keyword argument of `sanic.run()`. The constructor of custom protocol class gets following keyword arguments from Sanic.
|
||||||
|
|
||||||
|
* loop
|
||||||
|
`loop` is an asyncio compatible event loop.
|
||||||
|
|
||||||
|
* connections
|
||||||
|
`connections` is a `set object` to store protocol objects.
|
||||||
|
When Sanic receives `SIGINT` or `SIGTERM`, Sanic executes `protocol.close_if_idle()` for a `protocol objects` stored in connections.
|
||||||
|
|
||||||
|
* signal
|
||||||
|
`signal` is a `sanic.server.Signal object` with `stopped attribute`.
|
||||||
|
When Sanic receives `SIGINT` or `SIGTERM`, `signal.stopped` becomes `True`.
|
||||||
|
|
||||||
|
* request_handler
|
||||||
|
`request_handler` is a coroutine that takes a `sanic.request.Request` object and a `response callback` as arguments.
|
||||||
|
|
||||||
|
* error_handler
|
||||||
|
`error_handler` is a `sanic.exceptions.Handler` object.
|
||||||
|
|
||||||
|
* request_timeout
|
||||||
|
`request_timeout` is seconds for timeout.
|
||||||
|
|
||||||
|
* request_max_size
|
||||||
|
`request_max_size` is bytes of max request size.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
By default protocol, an error occurs, if the handler does not return an `HTTPResponse object`.
|
||||||
|
In this example, By rewriting `write_response()`, if the handler returns `str`, it will be converted to an `HTTPResponse object`.
|
||||||
|
|
||||||
|
```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)
|
||||||
|
```
|
|
@ -13,7 +13,7 @@ from .exceptions import Handler
|
||||||
from .log import log
|
from .log import log
|
||||||
from .response import HTTPResponse
|
from .response import HTTPResponse
|
||||||
from .router import Router
|
from .router import Router
|
||||||
from .server import serve
|
from .server import serve, HttpProtocol
|
||||||
from .static import register as static_register
|
from .static import register as static_register
|
||||||
from .exceptions import ServerError
|
from .exceptions import ServerError
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ class Sanic:
|
||||||
|
|
||||||
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
|
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
|
||||||
after_start=None, before_stop=None, after_stop=None, sock=None,
|
after_start=None, before_stop=None, after_stop=None, sock=None,
|
||||||
workers=1, loop=None):
|
workers=1, loop=None, protocol=HttpProtocol):
|
||||||
"""
|
"""
|
||||||
Runs the HTTP Server and listens until keyboard interrupt or term
|
Runs the HTTP Server and listens until keyboard interrupt or term
|
||||||
signal. On termination, drains connections before closing.
|
signal. On termination, drains connections before closing.
|
||||||
|
@ -261,6 +261,7 @@ class Sanic:
|
||||||
:param workers: Number of processes
|
:param workers: Number of processes
|
||||||
received before it is respected
|
received before it is respected
|
||||||
:param loop: asyncio compatible event loop
|
:param loop: asyncio compatible event loop
|
||||||
|
:param protocol: Subclass of asyncio protocol class
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
self.error_handler.debug = True
|
self.error_handler.debug = True
|
||||||
|
@ -268,6 +269,7 @@ class Sanic:
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
|
|
||||||
server_settings = {
|
server_settings = {
|
||||||
|
'protocol': protocol,
|
||||||
'host': host,
|
'host': host,
|
||||||
'port': port,
|
'port': port,
|
||||||
'sock': sock,
|
'sock': sock,
|
||||||
|
|
|
@ -224,24 +224,30 @@ def trigger_events(events, loop):
|
||||||
|
|
||||||
|
|
||||||
def serve(host, port, request_handler, error_handler, before_start=None,
|
def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
after_start=None, before_stop=None, after_stop=None,
|
after_start=None, before_stop=None, after_stop=None, debug=False,
|
||||||
debug=False, request_timeout=60, sock=None,
|
request_timeout=60, sock=None, request_max_size=None,
|
||||||
request_max_size=None, reuse_port=False, loop=None):
|
reuse_port=False, loop=None, protocol=HttpProtocol):
|
||||||
"""
|
"""
|
||||||
Starts asynchronous HTTP Server on an individual process.
|
Starts asynchronous HTTP Server on an individual process.
|
||||||
:param host: Address to host on
|
:param host: Address to host on
|
||||||
:param port: Port to host on
|
:param port: Port to host on
|
||||||
:param request_handler: Sanic request handler with middleware
|
:param request_handler: Sanic request handler with middleware
|
||||||
|
:param error_handler: Sanic error handler with middleware
|
||||||
|
:param before_start: Function to be executed before the server starts
|
||||||
|
listening. Takes single argument `loop`
|
||||||
:param after_start: Function to be executed after the server starts
|
:param after_start: Function to be executed after the server starts
|
||||||
listening. Takes single argument `loop`
|
listening. Takes single argument `loop`
|
||||||
:param before_stop: Function to be executed when a stop signal is
|
:param before_stop: Function to be executed when a stop signal is
|
||||||
received before it is respected. Takes single argumenet `loop`
|
received before it is respected. Takes single argumenet `loop`
|
||||||
|
:param after_stop: Function to be executed when a stop signal is
|
||||||
|
received after it is respected. Takes single argumenet `loop`
|
||||||
:param debug: Enables debug output (slows server)
|
:param debug: Enables debug output (slows server)
|
||||||
:param request_timeout: time in seconds
|
:param request_timeout: time in seconds
|
||||||
:param sock: Socket for the server to accept connections from
|
:param sock: Socket for the server to accept connections from
|
||||||
:param request_max_size: size in bytes, `None` for no limit
|
:param request_max_size: size in bytes, `None` for no limit
|
||||||
:param reuse_port: `True` for multiple workers
|
:param reuse_port: `True` for multiple workers
|
||||||
:param loop: asyncio compatible event loop
|
:param loop: asyncio compatible event loop
|
||||||
|
:param protocol: Subclass of asyncio protocol class
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
loop = loop or async_loop.new_event_loop()
|
loop = loop or async_loop.new_event_loop()
|
||||||
|
@ -255,7 +261,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
connections = set()
|
connections = set()
|
||||||
signal = Signal()
|
signal = Signal()
|
||||||
server = partial(
|
server = partial(
|
||||||
HttpProtocol,
|
protocol,
|
||||||
loop=loop,
|
loop=loop,
|
||||||
connections=connections,
|
connections=connections,
|
||||||
signal=signal,
|
signal=signal,
|
||||||
|
|
|
@ -16,8 +16,8 @@ async def local_request(method, uri, cookies=None, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
||||||
loop=None, debug=False, *request_args,
|
loop=None, debug=False, server_kwargs={},
|
||||||
**request_kwargs):
|
*request_args, **request_kwargs):
|
||||||
results = []
|
results = []
|
||||||
exceptions = []
|
exceptions = []
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
||||||
app.stop()
|
app.stop()
|
||||||
|
|
||||||
app.run(host=HOST, debug=debug, port=42101,
|
app.run(host=HOST, debug=debug, port=42101,
|
||||||
after_start=_collect_response, loop=loop)
|
after_start=_collect_response, loop=loop, **server_kwargs)
|
||||||
|
|
||||||
if exceptions:
|
if exceptions:
|
||||||
raise ValueError("Exception during request: {}".format(exceptions))
|
raise ValueError("Exception during request: {}".format(exceptions))
|
||||||
|
|
32
tests/test_custom_protocol.py
Normal file
32
tests/test_custom_protocol.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.server import HttpProtocol
|
||||||
|
from sanic.response import text
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
app = Sanic('test_custom_porotocol')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomHttpProtocol(HttpProtocol):
|
||||||
|
|
||||||
|
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('/1')
|
||||||
|
async def handler_1(request):
|
||||||
|
return 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
def test_use_custom_protocol():
|
||||||
|
server_kwargs = {
|
||||||
|
'protocol': CustomHttpProtocol
|
||||||
|
}
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/1',
|
||||||
|
server_kwargs=server_kwargs)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
Loading…
Reference in New Issue
Block a user