commit
48de321869
|
@ -1,5 +1,79 @@
|
||||||
# Streaming
|
# Streaming
|
||||||
|
|
||||||
|
## Request Streaming
|
||||||
|
|
||||||
|
Sanic allows you to get request data by stream, as below. When the request ends, `request.stream.get()` returns `None`. Only post, put and patch decorator have stream argument.
|
||||||
|
|
||||||
|
```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('request_stream')
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleView(HTTPMethodView):
|
||||||
|
|
||||||
|
@stream_decorator
|
||||||
|
async def post(self, request):
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
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.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
body = body.decode('utf-8').replace('1', 'A')
|
||||||
|
response.write(body)
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.put('/bp_stream', stream=True)
|
||||||
|
async def bp_handler(request):
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
result += body.decode('utf-8').replace('1', 'A')
|
||||||
|
return text(result)
|
||||||
|
|
||||||
|
|
||||||
|
async def post_handler(request):
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
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:
|
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:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
10
examples/request_stream/client.py
Normal file
10
examples/request_stream/client.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Warning: This is a heavy process.
|
||||||
|
|
||||||
|
data = ""
|
||||||
|
for i in range(1, 250000):
|
||||||
|
data += str(i)
|
||||||
|
|
||||||
|
r = requests.post('http://127.0.0.1:8000/stream', data=data)
|
||||||
|
print(r.text)
|
65
examples/request_stream/server.py
Normal file
65
examples/request_stream/server.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
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('request_stream')
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleView(HTTPMethodView):
|
||||||
|
|
||||||
|
@stream_decorator
|
||||||
|
async def post(self, request):
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
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.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
body = body.decode('utf-8').replace('1', 'A')
|
||||||
|
response.write(body)
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.put('/bp_stream', stream=True)
|
||||||
|
async def bp_handler(request):
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
result += body.decode('utf-8').replace('1', 'A')
|
||||||
|
return text(result)
|
||||||
|
|
||||||
|
|
||||||
|
async def post_handler(request):
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
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)
|
36
sanic/app.py
36
sanic/app.py
|
@ -60,6 +60,7 @@ class Sanic:
|
||||||
self.sock = None
|
self.sock = None
|
||||||
self.listeners = defaultdict(list)
|
self.listeners = defaultdict(list)
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
self.is_request_stream = False
|
||||||
self.websocket_enabled = False
|
self.websocket_enabled = False
|
||||||
self.websocket_tasks = []
|
self.websocket_tasks = []
|
||||||
|
|
||||||
|
@ -110,12 +111,14 @@ class Sanic:
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
||||||
strict_slashes=False):
|
strict_slashes=False, stream=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 uri: path of the URL
|
||||||
:param methods: list or tuple of methods allowed
|
:param methods: list or tuple of methods allowed
|
||||||
:param host:
|
:param host:
|
||||||
|
:param strict_slashes:
|
||||||
|
:param stream:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -124,7 +127,12 @@ class Sanic:
|
||||||
if not uri.startswith('/'):
|
if not uri.startswith('/'):
|
||||||
uri = '/' + uri
|
uri = '/' + uri
|
||||||
|
|
||||||
|
if stream:
|
||||||
|
self.is_request_stream = True
|
||||||
|
|
||||||
def response(handler):
|
def response(handler):
|
||||||
|
if stream:
|
||||||
|
handler.is_stream = stream
|
||||||
self.router.add(uri=uri, methods=methods, handler=handler,
|
self.router.add(uri=uri, methods=methods, handler=handler,
|
||||||
host=host, strict_slashes=strict_slashes)
|
host=host, strict_slashes=strict_slashes)
|
||||||
return handler
|
return handler
|
||||||
|
@ -136,13 +144,13 @@ class Sanic:
|
||||||
return self.route(uri, methods=frozenset({"GET"}), host=host,
|
return self.route(uri, methods=frozenset({"GET"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes)
|
||||||
|
|
||||||
def post(self, uri, host=None, strict_slashes=False):
|
def post(self, uri, host=None, strict_slashes=False, stream=False):
|
||||||
return self.route(uri, methods=frozenset({"POST"}), host=host,
|
return self.route(uri, methods=frozenset({"POST"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, stream=stream)
|
||||||
|
|
||||||
def put(self, uri, host=None, strict_slashes=False):
|
def put(self, uri, host=None, strict_slashes=False, stream=False):
|
||||||
return self.route(uri, methods=frozenset({"PUT"}), host=host,
|
return self.route(uri, methods=frozenset({"PUT"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, stream=stream)
|
||||||
|
|
||||||
def head(self, uri, host=None, strict_slashes=False):
|
def head(self, uri, host=None, strict_slashes=False):
|
||||||
return self.route(uri, methods=frozenset({"HEAD"}), host=host,
|
return self.route(uri, methods=frozenset({"HEAD"}), host=host,
|
||||||
|
@ -152,9 +160,9 @@ class Sanic:
|
||||||
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
|
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes)
|
||||||
|
|
||||||
def patch(self, uri, host=None, strict_slashes=False):
|
def patch(self, uri, host=None, strict_slashes=False, stream=False):
|
||||||
return self.route(uri, methods=frozenset({"PATCH"}), host=host,
|
return self.route(uri, methods=frozenset({"PATCH"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, stream=stream)
|
||||||
|
|
||||||
def delete(self, uri, host=None, strict_slashes=False):
|
def delete(self, uri, host=None, strict_slashes=False):
|
||||||
return self.route(uri, methods=frozenset({"DELETE"}), host=host,
|
return self.route(uri, methods=frozenset({"DELETE"}), host=host,
|
||||||
|
@ -173,20 +181,28 @@ class Sanic:
|
||||||
:param host:
|
:param host:
|
||||||
:return: function or class instance
|
:return: function or class instance
|
||||||
"""
|
"""
|
||||||
|
stream = False
|
||||||
# Handle HTTPMethodView differently
|
# Handle HTTPMethodView differently
|
||||||
if hasattr(handler, 'view_class'):
|
if hasattr(handler, 'view_class'):
|
||||||
methods = set()
|
methods = set()
|
||||||
|
|
||||||
for method in HTTP_METHODS:
|
for method in HTTP_METHODS:
|
||||||
if getattr(handler.view_class, method.lower(), None):
|
_handler = getattr(handler.view_class, method.lower(), None)
|
||||||
|
if _handler:
|
||||||
methods.add(method)
|
methods.add(method)
|
||||||
|
if hasattr(_handler, 'is_stream'):
|
||||||
|
stream = True
|
||||||
|
|
||||||
# handle composition view differently
|
# handle composition view differently
|
||||||
if isinstance(handler, CompositionView):
|
if isinstance(handler, CompositionView):
|
||||||
methods = handler.handlers.keys()
|
methods = handler.handlers.keys()
|
||||||
|
for _handler in handler.handlers.values():
|
||||||
|
if hasattr(_handler, 'is_stream'):
|
||||||
|
stream = True
|
||||||
|
break
|
||||||
|
|
||||||
self.route(uri=uri, methods=methods, host=host,
|
self.route(uri=uri, methods=methods, host=host,
|
||||||
strict_slashes=strict_slashes)(handler)
|
strict_slashes=strict_slashes, stream=stream)(handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
|
@ -664,6 +680,8 @@ class Sanic:
|
||||||
server_settings = {
|
server_settings = {
|
||||||
'protocol': protocol,
|
'protocol': protocol,
|
||||||
'request_class': self.request_class,
|
'request_class': self.request_class,
|
||||||
|
'is_request_stream': self.is_request_stream,
|
||||||
|
'router': self.router,
|
||||||
'host': host,
|
'host': host,
|
||||||
'port': port,
|
'port': port,
|
||||||
'sock': sock,
|
'sock': sock,
|
||||||
|
|
|
@ -5,7 +5,7 @@ from sanic.views import CompositionView
|
||||||
|
|
||||||
FutureRoute = namedtuple('Route',
|
FutureRoute = namedtuple('Route',
|
||||||
['handler', 'uri', 'methods',
|
['handler', 'uri', 'methods',
|
||||||
'host', 'strict_slashes'])
|
'host', 'strict_slashes', 'stream'])
|
||||||
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
|
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
|
||||||
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
|
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
|
||||||
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
|
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
|
||||||
|
@ -47,7 +47,8 @@ class Blueprint:
|
||||||
uri=uri[1:] if uri.startswith('//') else uri,
|
uri=uri[1:] if uri.startswith('//') else uri,
|
||||||
methods=future.methods,
|
methods=future.methods,
|
||||||
host=future.host or self.host,
|
host=future.host or self.host,
|
||||||
strict_slashes=future.strict_slashes
|
strict_slashes=future.strict_slashes,
|
||||||
|
stream=future.stream
|
||||||
)(future.handler)
|
)(future.handler)
|
||||||
|
|
||||||
for future in self.websocket_routes:
|
for future in self.websocket_routes:
|
||||||
|
@ -88,14 +89,15 @@ class Blueprint:
|
||||||
app.listener(event)(listener)
|
app.listener(event)(listener)
|
||||||
|
|
||||||
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
||||||
strict_slashes=False):
|
strict_slashes=False, stream=False):
|
||||||
"""Create a blueprint route from a decorated function.
|
"""Create a blueprint route from a decorated function.
|
||||||
|
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
:param methods: list of acceptable HTTP methods.
|
:param methods: list of acceptable HTTP methods.
|
||||||
"""
|
"""
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
route = FutureRoute(handler, uri, methods, host, strict_slashes)
|
route = FutureRoute(
|
||||||
|
handler, uri, methods, host, strict_slashes, stream)
|
||||||
self.routes.append(route)
|
self.routes.append(route)
|
||||||
return handler
|
return handler
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -132,7 +134,7 @@ class Blueprint:
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
"""
|
"""
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
route = FutureRoute(handler, uri, [], host, strict_slashes)
|
route = FutureRoute(handler, uri, [], host, strict_slashes, False)
|
||||||
self.websocket_routes.append(route)
|
self.websocket_routes.append(route)
|
||||||
return handler
|
return handler
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -195,13 +197,13 @@ class Blueprint:
|
||||||
return self.route(uri, methods=["GET"], host=host,
|
return self.route(uri, methods=["GET"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes)
|
||||||
|
|
||||||
def post(self, uri, host=None, strict_slashes=False):
|
def post(self, uri, host=None, strict_slashes=False, stream=False):
|
||||||
return self.route(uri, methods=["POST"], host=host,
|
return self.route(uri, methods=["POST"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, stream=stream)
|
||||||
|
|
||||||
def put(self, uri, host=None, strict_slashes=False):
|
def put(self, uri, host=None, strict_slashes=False, stream=False):
|
||||||
return self.route(uri, methods=["PUT"], host=host,
|
return self.route(uri, methods=["PUT"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, stream=stream)
|
||||||
|
|
||||||
def head(self, uri, host=None, strict_slashes=False):
|
def head(self, uri, host=None, strict_slashes=False):
|
||||||
return self.route(uri, methods=["HEAD"], host=host,
|
return self.route(uri, methods=["HEAD"], host=host,
|
||||||
|
@ -211,9 +213,9 @@ class Blueprint:
|
||||||
return self.route(uri, methods=["OPTIONS"], host=host,
|
return self.route(uri, methods=["OPTIONS"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes)
|
||||||
|
|
||||||
def patch(self, uri, host=None, strict_slashes=False):
|
def patch(self, uri, host=None, strict_slashes=False, stream=False):
|
||||||
return self.route(uri, methods=["PATCH"], host=host,
|
return self.route(uri, methods=["PATCH"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, stream=stream)
|
||||||
|
|
||||||
def delete(self, uri, host=None, strict_slashes=False):
|
def delete(self, uri, host=None, strict_slashes=False):
|
||||||
return self.route(uri, methods=["DELETE"], host=host,
|
return self.route(uri, methods=["DELETE"], host=host,
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Request(dict):
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'app', 'headers', 'version', 'method', '_cookies', 'transport',
|
'app', 'headers', 'version', 'method', '_cookies', 'transport',
|
||||||
'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
|
'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
|
||||||
'_ip', '_parsed_url', 'uri_template'
|
'_ip', '_parsed_url', 'uri_template', 'stream'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, url_bytes, headers, version, method, transport):
|
def __init__(self, url_bytes, headers, version, method, transport):
|
||||||
|
@ -66,6 +66,7 @@ class Request(dict):
|
||||||
self.parsed_args = None
|
self.parsed_args = None
|
||||||
self.uri_template = None
|
self.uri_template = None
|
||||||
self._cookies = None
|
self._cookies = None
|
||||||
|
self.stream = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def json(self):
|
def json(self):
|
||||||
|
|
|
@ -345,3 +345,14 @@ class Router:
|
||||||
if hasattr(route_handler, 'handlers'):
|
if hasattr(route_handler, 'handlers'):
|
||||||
route_handler = route_handler.handlers[method]
|
route_handler = route_handler.handlers[method]
|
||||||
return route_handler, [], kwargs, route.uri
|
return route_handler, [], kwargs, route.uri
|
||||||
|
|
||||||
|
def is_stream_handler(self, request):
|
||||||
|
""" Handler for request is stream or not.
|
||||||
|
:param request: Request object
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
handler = self.get(request)[0]
|
||||||
|
if (hasattr(handler, 'view_class') and
|
||||||
|
hasattr(handler.view_class, request.method.lower())):
|
||||||
|
handler = getattr(handler.view_class, request.method.lower())
|
||||||
|
return hasattr(handler, 'is_stream')
|
||||||
|
|
|
@ -64,22 +64,24 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
'parser', 'request', 'url', 'headers',
|
'parser', 'request', 'url', 'headers',
|
||||||
# request config
|
# request config
|
||||||
'request_handler', 'request_timeout', 'request_max_size',
|
'request_handler', 'request_timeout', 'request_max_size',
|
||||||
'request_class',
|
'request_class', 'is_request_stream', 'router',
|
||||||
# enable or disable access log / error log purpose
|
# enable or disable access log / error log purpose
|
||||||
'has_log',
|
'has_log',
|
||||||
# connection management
|
# connection management
|
||||||
'_total_request_size', '_timeout_handler', '_last_communication_time')
|
'_total_request_size', '_timeout_handler', '_last_communication_time',
|
||||||
|
'_is_stream_handler')
|
||||||
|
|
||||||
def __init__(self, *, loop, request_handler, error_handler,
|
def __init__(self, *, loop, request_handler, error_handler,
|
||||||
signal=Signal(), connections=set(), request_timeout=60,
|
signal=Signal(), connections=set(), request_timeout=60,
|
||||||
request_max_size=None, request_class=None, has_log=True,
|
request_max_size=None, request_class=None, has_log=True,
|
||||||
keep_alive=True):
|
keep_alive=True, is_request_stream=False, router=None):
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.transport = None
|
self.transport = None
|
||||||
self.request = None
|
self.request = None
|
||||||
self.parser = None
|
self.parser = None
|
||||||
self.url = None
|
self.url = None
|
||||||
self.headers = None
|
self.headers = None
|
||||||
|
self.router = router
|
||||||
self.signal = signal
|
self.signal = signal
|
||||||
self.has_log = has_log
|
self.has_log = has_log
|
||||||
self.connections = connections
|
self.connections = connections
|
||||||
|
@ -88,10 +90,13 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self.request_timeout = request_timeout
|
self.request_timeout = request_timeout
|
||||||
self.request_max_size = request_max_size
|
self.request_max_size = request_max_size
|
||||||
self.request_class = request_class or Request
|
self.request_class = request_class or Request
|
||||||
|
self.is_request_stream = is_request_stream
|
||||||
|
self._is_stream_handler = False
|
||||||
self._total_request_size = 0
|
self._total_request_size = 0
|
||||||
self._timeout_handler = None
|
self._timeout_handler = None
|
||||||
self._last_request_time = None
|
self._last_request_time = None
|
||||||
self._request_handler_task = None
|
self._request_handler_task = None
|
||||||
|
self._request_stream_task = None
|
||||||
self._keep_alive = keep_alive
|
self._keep_alive = keep_alive
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -123,6 +128,8 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self._timeout_handler = (
|
self._timeout_handler = (
|
||||||
self.loop.call_later(time_left, self.connection_timeout))
|
self.loop.call_later(time_left, self.connection_timeout))
|
||||||
else:
|
else:
|
||||||
|
if self._request_stream_task:
|
||||||
|
self._request_stream_task.cancel()
|
||||||
if self._request_handler_task:
|
if self._request_handler_task:
|
||||||
self._request_handler_task.cancel()
|
self._request_handler_task.cancel()
|
||||||
exception = RequestTimeout('Request Timeout')
|
exception = RequestTimeout('Request Timeout')
|
||||||
|
@ -171,13 +178,29 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
method=self.parser.get_method().decode(),
|
method=self.parser.get_method().decode(),
|
||||||
transport=self.transport
|
transport=self.transport
|
||||||
)
|
)
|
||||||
|
if self.is_request_stream:
|
||||||
|
self._is_stream_handler = self.router.is_stream_handler(
|
||||||
|
self.request)
|
||||||
|
if self._is_stream_handler:
|
||||||
|
self.request.stream = asyncio.Queue()
|
||||||
|
self.execute_request_handler()
|
||||||
|
|
||||||
def on_body(self, body):
|
def on_body(self, body):
|
||||||
|
if self.is_request_stream and self._is_stream_handler:
|
||||||
|
self._request_stream_task = self.loop.create_task(
|
||||||
|
self.request.stream.put(body))
|
||||||
|
return
|
||||||
self.request.body.append(body)
|
self.request.body.append(body)
|
||||||
|
|
||||||
def on_message_complete(self):
|
def on_message_complete(self):
|
||||||
|
if self.is_request_stream and self._is_stream_handler:
|
||||||
|
self._request_stream_task = self.loop.create_task(
|
||||||
|
self.request.stream.put(None))
|
||||||
|
return
|
||||||
self.request.body = b''.join(self.request.body)
|
self.request.body = b''.join(self.request.body)
|
||||||
|
self.execute_request_handler()
|
||||||
|
|
||||||
|
def execute_request_handler(self):
|
||||||
self._request_handler_task = self.loop.create_task(
|
self._request_handler_task = self.loop.create_task(
|
||||||
self.request_handler(
|
self.request_handler(
|
||||||
self.request,
|
self.request,
|
||||||
|
@ -319,7 +342,9 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self.url = None
|
self.url = None
|
||||||
self.headers = None
|
self.headers = None
|
||||||
self._request_handler_task = None
|
self._request_handler_task = None
|
||||||
|
self._request_stream_task = None
|
||||||
self._total_request_size = 0
|
self._total_request_size = 0
|
||||||
|
self._is_stream_handler = False
|
||||||
|
|
||||||
def close_if_idle(self):
|
def close_if_idle(self):
|
||||||
"""Close the connection if a request is not being sent or received
|
"""Close the connection if a request is not being sent or received
|
||||||
|
@ -361,7 +386,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
request_timeout=60, ssl=None, sock=None, request_max_size=None,
|
request_timeout=60, ssl=None, sock=None, request_max_size=None,
|
||||||
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100,
|
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100,
|
||||||
register_sys_signals=True, run_async=False, connections=None,
|
register_sys_signals=True, run_async=False, connections=None,
|
||||||
signal=Signal(), request_class=None, has_log=True, keep_alive=True):
|
signal=Signal(), request_class=None, has_log=True, keep_alive=True,
|
||||||
|
is_request_stream=False, router=None):
|
||||||
"""Start asynchronous HTTP Server on an individual process.
|
"""Start asynchronous HTTP Server on an individual process.
|
||||||
|
|
||||||
:param host: Address to host on
|
:param host: Address to host on
|
||||||
|
@ -388,6 +414,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
:param protocol: subclass of asyncio protocol class
|
:param protocol: subclass of asyncio protocol class
|
||||||
:param request_class: Request class to use
|
:param request_class: Request class to use
|
||||||
:param has_log: disable/enable access log and error log
|
:param has_log: disable/enable access log and error log
|
||||||
|
:param is_request_stream: disable/enable Request.stream
|
||||||
|
:param router: Router object
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
if not run_async:
|
if not run_async:
|
||||||
|
@ -412,6 +440,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
request_class=request_class,
|
request_class=request_class,
|
||||||
has_log=has_log,
|
has_log=has_log,
|
||||||
keep_alive=keep_alive,
|
keep_alive=keep_alive,
|
||||||
|
is_request_stream=is_request_stream,
|
||||||
|
router=router,
|
||||||
)
|
)
|
||||||
|
|
||||||
server_coroutine = loop.create_server(
|
server_coroutine = loop.create_server(
|
||||||
|
|
|
@ -64,6 +64,11 @@ class HTTPMethodView:
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
|
||||||
|
def stream(func):
|
||||||
|
func.is_stream = True
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
class CompositionView:
|
class CompositionView:
|
||||||
"""Simple method-function mapped view for the sanic.
|
"""Simple method-function mapped view for the sanic.
|
||||||
You can add handler functions to methods (get, post, put, patch, delete)
|
You can add handler functions to methods (get, post, put, patch, delete)
|
||||||
|
@ -83,7 +88,9 @@ class CompositionView:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.handlers = {}
|
self.handlers = {}
|
||||||
|
|
||||||
def add(self, methods, handler):
|
def add(self, methods, handler, stream=False):
|
||||||
|
if stream:
|
||||||
|
handler.is_stream = stream
|
||||||
for method in methods:
|
for method in methods:
|
||||||
if method not in HTTP_METHODS:
|
if method not in HTTP_METHODS:
|
||||||
raise InvalidUsage(
|
raise InvalidUsage(
|
||||||
|
|
|
@ -21,6 +21,7 @@ def test_bp():
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
assert response.text == 'Hello'
|
assert response.text == 'Hello'
|
||||||
|
|
||||||
|
@ -268,38 +269,48 @@ def test_bp_shorthand():
|
||||||
|
|
||||||
@blueprint.get('/get')
|
@blueprint.get('/get')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@blueprint.put('/put')
|
@blueprint.put('/put')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@blueprint.post('/post')
|
@blueprint.post('/post')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@blueprint.head('/head')
|
@blueprint.head('/head')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@blueprint.options('/options')
|
@blueprint.options('/options')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@blueprint.patch('/patch')
|
@blueprint.patch('/patch')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@blueprint.delete('/delete')
|
@blueprint.delete('/delete')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@blueprint.websocket('/ws')
|
@blueprint.websocket('/ws')
|
||||||
async def handler(request, ws):
|
async def handler(request, ws):
|
||||||
|
assert request.stream is None
|
||||||
ev.set()
|
ev.set()
|
||||||
|
|
||||||
app.blueprint(blueprint)
|
app.blueprint(blueprint)
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
request, response = app.test_client.get('/get')
|
request, response = app.test_client.get('/get')
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
|
435
tests/test_request_stream.py
Normal file
435
tests/test_request_stream.py
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
import asyncio
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.blueprints import Blueprint
|
||||||
|
from sanic.views import CompositionView
|
||||||
|
from sanic.views import HTTPMethodView
|
||||||
|
from sanic.views import stream as stream_decorator
|
||||||
|
from sanic.response import stream, text
|
||||||
|
|
||||||
|
data = "abc" * 100000
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_stream_method_view():
|
||||||
|
'''for self.is_request_stream = True'''
|
||||||
|
|
||||||
|
app = Sanic('test_request_stream_method_view')
|
||||||
|
|
||||||
|
class SimpleView(HTTPMethodView):
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
@stream_decorator
|
||||||
|
async def post(self, request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
result += body.decode('utf-8')
|
||||||
|
return text(result)
|
||||||
|
|
||||||
|
app.add_route(SimpleView.as_view(), '/method_view')
|
||||||
|
|
||||||
|
assert app.is_request_stream is True
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/method_view')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/method_view', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_stream_app():
|
||||||
|
'''for self.is_request_stream = True and decorators'''
|
||||||
|
|
||||||
|
app = Sanic('test_request_stream_app')
|
||||||
|
|
||||||
|
@app.get('/get')
|
||||||
|
async def get(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('GET')
|
||||||
|
|
||||||
|
@app.head('/head')
|
||||||
|
async def head(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('HEAD')
|
||||||
|
|
||||||
|
@app.delete('/delete')
|
||||||
|
async def delete(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('DELETE')
|
||||||
|
|
||||||
|
@app.options('/options')
|
||||||
|
async def options(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OPTIONS')
|
||||||
|
|
||||||
|
@app.post('/_post/<id>')
|
||||||
|
async def _post(request, id):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('_POST')
|
||||||
|
|
||||||
|
@app.post('/post/<id>', stream=True)
|
||||||
|
async def post(request, id):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
|
async def streaming(response):
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
response.write(body.decode('utf-8'))
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
@app.put('/_put')
|
||||||
|
async def _put(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('_PUT')
|
||||||
|
|
||||||
|
@app.put('/put', stream=True)
|
||||||
|
async def put(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
|
async def streaming(response):
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
response.write(body.decode('utf-8'))
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
@app.patch('/_patch')
|
||||||
|
async def _patch(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('_PATCH')
|
||||||
|
|
||||||
|
@app.patch('/patch', stream=True)
|
||||||
|
async def patch(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
|
async def streaming(response):
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
response.write(body.decode('utf-8'))
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
assert app.is_request_stream is True
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/get')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'GET'
|
||||||
|
|
||||||
|
request, response = app.test_client.head('/head')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == ''
|
||||||
|
|
||||||
|
request, response = app.test_client.delete('/delete')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'DELETE'
|
||||||
|
|
||||||
|
request, response = app.test_client.options('/options')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OPTIONS'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/_post/1', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == '_POST'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/post/1', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
request, response = app.test_client.put('/_put', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == '_PUT'
|
||||||
|
|
||||||
|
request, response = app.test_client.put('/put', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
request, response = app.test_client.patch('/_patch', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == '_PATCH'
|
||||||
|
|
||||||
|
request, response = app.test_client.patch('/patch', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_stream_blueprint():
|
||||||
|
'''for self.is_request_stream = True'''
|
||||||
|
|
||||||
|
app = Sanic('test_request_stream_blueprint')
|
||||||
|
bp = Blueprint('test_blueprint_request_stream_blueprint')
|
||||||
|
|
||||||
|
@app.get('/get')
|
||||||
|
async def get(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('GET')
|
||||||
|
|
||||||
|
@bp.head('/head')
|
||||||
|
async def head(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('HEAD')
|
||||||
|
|
||||||
|
@bp.delete('/delete')
|
||||||
|
async def delete(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('DELETE')
|
||||||
|
|
||||||
|
@bp.options('/options')
|
||||||
|
async def options(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OPTIONS')
|
||||||
|
|
||||||
|
@bp.post('/_post/<id>')
|
||||||
|
async def _post(request, id):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('_POST')
|
||||||
|
|
||||||
|
@bp.post('/post/<id>', stream=True)
|
||||||
|
async def post(request, id):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
|
async def streaming(response):
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
response.write(body.decode('utf-8'))
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
@bp.put('/_put')
|
||||||
|
async def _put(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('_PUT')
|
||||||
|
|
||||||
|
@bp.put('/put', stream=True)
|
||||||
|
async def put(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
|
async def streaming(response):
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
response.write(body.decode('utf-8'))
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
@bp.patch('/_patch')
|
||||||
|
async def _patch(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('_PATCH')
|
||||||
|
|
||||||
|
@bp.patch('/patch', stream=True)
|
||||||
|
async def patch(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
|
async def streaming(response):
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
response.write(body.decode('utf-8'))
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
|
|
||||||
|
assert app.is_request_stream is True
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/get')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'GET'
|
||||||
|
|
||||||
|
request, response = app.test_client.head('/head')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == ''
|
||||||
|
|
||||||
|
request, response = app.test_client.delete('/delete')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'DELETE'
|
||||||
|
|
||||||
|
request, response = app.test_client.options('/options')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OPTIONS'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/_post/1', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == '_POST'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/post/1', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
request, response = app.test_client.put('/_put', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == '_PUT'
|
||||||
|
|
||||||
|
request, response = app.test_client.put('/put', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
request, response = app.test_client.patch('/_patch', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == '_PATCH'
|
||||||
|
|
||||||
|
request, response = app.test_client.patch('/patch', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_stream_composition_view():
|
||||||
|
'''for self.is_request_stream = True'''
|
||||||
|
|
||||||
|
app = Sanic('test_request_stream__composition_view')
|
||||||
|
|
||||||
|
def get_handler(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
async def post_handler(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
result += body.decode('utf-8')
|
||||||
|
return text(result)
|
||||||
|
|
||||||
|
view = CompositionView()
|
||||||
|
view.add(['GET'], get_handler)
|
||||||
|
view.add(['POST'], post_handler, stream=True)
|
||||||
|
app.add_route(view, '/composition_view')
|
||||||
|
|
||||||
|
assert app.is_request_stream is True
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/composition_view')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/composition_view', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_stream():
|
||||||
|
'''test for complex application'''
|
||||||
|
|
||||||
|
bp = Blueprint('test_blueprint_request_stream')
|
||||||
|
app = Sanic('test_request_stream')
|
||||||
|
|
||||||
|
class SimpleView(HTTPMethodView):
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
@stream_decorator
|
||||||
|
async def post(self, request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
result += body.decode('utf-8')
|
||||||
|
return text(result)
|
||||||
|
|
||||||
|
@app.post('/stream', stream=True)
|
||||||
|
async def handler(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
|
async def streaming(response):
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
response.write(body.decode('utf-8'))
|
||||||
|
return stream(streaming)
|
||||||
|
|
||||||
|
@app.get('/get')
|
||||||
|
async def get(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
@bp.post('/bp_stream', stream=True)
|
||||||
|
async def bp_stream(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
result += body.decode('utf-8')
|
||||||
|
return text(result)
|
||||||
|
|
||||||
|
@bp.get('/bp_get')
|
||||||
|
async def bp_get(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
def get_handler(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
async def post_handler(request):
|
||||||
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
result = ''
|
||||||
|
while True:
|
||||||
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
result += body.decode('utf-8')
|
||||||
|
return text(result)
|
||||||
|
|
||||||
|
app.add_route(SimpleView.as_view(), '/method_view')
|
||||||
|
|
||||||
|
view = CompositionView()
|
||||||
|
view.add(['GET'], get_handler)
|
||||||
|
view.add(['POST'], post_handler, stream=True)
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
|
|
||||||
|
app.add_route(view, '/composition_view')
|
||||||
|
|
||||||
|
assert app.is_request_stream is True
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/method_view')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/method_view', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/composition_view')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/composition_view', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/get')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/stream', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/bp_get')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/bp_stream', data=data)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == data
|
|
@ -28,12 +28,16 @@ def test_route_strict_slash():
|
||||||
|
|
||||||
@app.get('/get', strict_slashes=True)
|
@app.get('/get', strict_slashes=True)
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@app.post('/post/', strict_slashes=True)
|
@app.post('/post/', strict_slashes=True)
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
request, response = app.test_client.get('/get')
|
request, response = app.test_client.get('/get')
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
@ -77,21 +81,43 @@ def test_shorthand_routes_put():
|
||||||
|
|
||||||
@app.put('/put')
|
@app.put('/put')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
request, response = app.test_client.put('/put')
|
request, response = app.test_client.put('/put')
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
|
||||||
request, response = app.test_client.get('/put')
|
request, response = app.test_client.get('/put')
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
|
def test_shorthand_routes_delete():
|
||||||
|
app = Sanic('test_shorhand_routes_delete')
|
||||||
|
|
||||||
|
@app.delete('/delete')
|
||||||
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
|
request, response = app.test_client.delete('/delete')
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/delete')
|
||||||
|
assert response.status == 405
|
||||||
|
|
||||||
def test_shorthand_routes_patch():
|
def test_shorthand_routes_patch():
|
||||||
app = Sanic('test_shorhand_routes_patch')
|
app = Sanic('test_shorhand_routes_patch')
|
||||||
|
|
||||||
@app.patch('/patch')
|
@app.patch('/patch')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
request, response = app.test_client.patch('/patch')
|
request, response = app.test_client.patch('/patch')
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
@ -103,8 +129,11 @@ def test_shorthand_routes_head():
|
||||||
|
|
||||||
@app.head('/head')
|
@app.head('/head')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
request, response = app.test_client.head('/head')
|
request, response = app.test_client.head('/head')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
@ -116,8 +145,11 @@ def test_shorthand_routes_options():
|
||||||
|
|
||||||
@app.options('/options')
|
@app.options('/options')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
request, response = app.test_client.options('/options')
|
request, response = app.test_client.options('/options')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ def test_methods(method):
|
||||||
class DummyView(HTTPMethodView):
|
class DummyView(HTTPMethodView):
|
||||||
|
|
||||||
async def get(self, request):
|
async def get(self, request):
|
||||||
|
assert request.stream is None
|
||||||
return text('', headers={'method': 'GET'})
|
return text('', headers={'method': 'GET'})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
@ -37,6 +38,7 @@ def test_methods(method):
|
||||||
return text('', headers={'method': 'DELETE'})
|
return text('', headers={'method': 'DELETE'})
|
||||||
|
|
||||||
app.add_route(DummyView.as_view(), '/')
|
app.add_route(DummyView.as_view(), '/')
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
request, response = getattr(app.test_client, method.lower())('/')
|
request, response = getattr(app.test_client, method.lower())('/')
|
||||||
assert response.headers['method'] == method
|
assert response.headers['method'] == method
|
||||||
|
@ -79,6 +81,7 @@ def test_with_bp():
|
||||||
class DummyView(HTTPMethodView):
|
class DummyView(HTTPMethodView):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
assert request.stream is None
|
||||||
return text('I am get method')
|
return text('I am get method')
|
||||||
|
|
||||||
bp.add_route(DummyView.as_view(), '/')
|
bp.add_route(DummyView.as_view(), '/')
|
||||||
|
@ -86,6 +89,7 @@ def test_with_bp():
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
|
|
||||||
|
assert app.is_request_stream is False
|
||||||
assert response.text == 'I am get method'
|
assert response.text == 'I am get method'
|
||||||
|
|
||||||
|
|
||||||
|
@ -227,10 +231,15 @@ def test_composition_view_runs_methods_as_expected(method):
|
||||||
app = Sanic('test_composition_view')
|
app = Sanic('test_composition_view')
|
||||||
|
|
||||||
view = CompositionView()
|
view = CompositionView()
|
||||||
view.add(['GET', 'POST', 'PUT'], lambda x: text('first method'))
|
|
||||||
|
def first(request):
|
||||||
|
assert request.stream is None
|
||||||
|
return text('first method')
|
||||||
|
view.add(['GET', 'POST', 'PUT'], first)
|
||||||
view.add(['DELETE', 'PATCH'], lambda x: text('second method'))
|
view.add(['DELETE', 'PATCH'], lambda x: text('second method'))
|
||||||
|
|
||||||
app.add_route(view, '/')
|
app.add_route(view, '/')
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
|
||||||
if method in ['GET', 'POST', 'PUT']:
|
if method in ['GET', 'POST', 'PUT']:
|
||||||
request, response = getattr(app.test_client, method.lower())('/')
|
request, response = getattr(app.test_client, method.lower())('/')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user