Merge branch 'master' into ssl
This commit is contained in:
commit
9102a9cd6e
|
@ -74,6 +74,7 @@ app.run(host="0.0.0.0", port=8443, ssl=context)
|
||||||
* [Custom Protocol](docs/custom_protocol.md)
|
* [Custom Protocol](docs/custom_protocol.md)
|
||||||
* [Testing](docs/testing.md)
|
* [Testing](docs/testing.md)
|
||||||
* [Deploying](docs/deploying.md)
|
* [Deploying](docs/deploying.md)
|
||||||
|
* [Extensions](docs/extensions.md)
|
||||||
* [Contributing](docs/contributing.md)
|
* [Contributing](docs/contributing.md)
|
||||||
* [License](LICENSE)
|
* [License](LICENSE)
|
||||||
|
|
||||||
|
|
6
docs/extensions.md
Normal file
6
docs/extensions.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Sanic Extensions
|
||||||
|
|
||||||
|
A list of Sanic extensions created by the community.
|
||||||
|
|
||||||
|
* [Sessions](https://github.com/subyraman/sanic_session) — Support for sessions. Allows using redis, memcache or an in memory store.
|
||||||
|
* [CORS](https://github.com/ashleysommer/sanic-cors) — A port of flask-cors.
|
|
@ -9,6 +9,7 @@ The following request variables are accessible as properties:
|
||||||
`request.args` (dict) - Query String variables. Use getlist to get multiple of the same name
|
`request.args` (dict) - Query String variables. Use getlist to get multiple of the same name
|
||||||
`request.form` (dict) - Posted form variables. Use getlist to get multiple of the same name
|
`request.form` (dict) - Posted form variables. Use getlist to get multiple of the same name
|
||||||
`request.body` (bytes) - Posted raw body. To get the raw data, regardless of content type
|
`request.body` (bytes) - Posted raw body. To get the raw data, regardless of content type
|
||||||
|
`request.ip` (str) - IP address of the requester
|
||||||
|
|
||||||
See request.py for more information
|
See request.py for more information
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ logging.basicConfig(
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
|
||||||
# Set logger to override default basicConfig
|
# Set logger to override default basicConfig
|
||||||
sanic = Sanic(logger=True)
|
sanic = Sanic()
|
||||||
@sanic.route("/")
|
@sanic.route("/")
|
||||||
def test(request):
|
def test(request):
|
||||||
log.info("received request; responding with 'hey'")
|
log.info("received request; responding with 'hey'")
|
||||||
|
|
|
@ -12,3 +12,4 @@ kyoukai
|
||||||
falcon
|
falcon
|
||||||
tornado
|
tornado
|
||||||
aiofiles
|
aiofiles
|
||||||
|
beautifulsoup4
|
||||||
|
|
|
@ -2,4 +2,3 @@ httptools
|
||||||
ujson
|
ujson
|
||||||
uvloop
|
uvloop
|
||||||
aiofiles
|
aiofiles
|
||||||
multidict
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from .sanic import Sanic
|
from .sanic import Sanic
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
|
|
||||||
__version__ = '0.1.9'
|
__version__ = '0.2.0'
|
||||||
|
|
||||||
__all__ = ['Sanic', 'Blueprint']
|
__all__ = ['Sanic', 'Blueprint']
|
||||||
|
|
|
@ -1,6 +1,104 @@
|
||||||
from .response import text
|
from .response import text, html
|
||||||
from .log import log
|
from .log import log
|
||||||
from traceback import format_exc
|
from traceback import format_exc, extract_tb
|
||||||
|
import sys
|
||||||
|
|
||||||
|
TRACEBACK_STYLE = '''
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 20px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 code {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-line > * {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-line {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-code {
|
||||||
|
font-size: 16px;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tb-wrapper {
|
||||||
|
border: 1px solid #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tb-header {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-descriptor {
|
||||||
|
background-color: #e2eafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-descriptor {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
'''
|
||||||
|
|
||||||
|
TRACEBACK_WRAPPER_HTML = '''
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{style}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{exc_name}</h1>
|
||||||
|
<h3><code>{exc_value}</code></h3>
|
||||||
|
<div class="tb-wrapper">
|
||||||
|
<p class="tb-header">Traceback (most recent call last):</p>
|
||||||
|
{frame_html}
|
||||||
|
<p class="summary">
|
||||||
|
<b>{exc_name}: {exc_value}</b>
|
||||||
|
while handling uri <code>{uri}</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
|
||||||
|
TRACEBACK_LINE_HTML = '''
|
||||||
|
<div class="frame-line">
|
||||||
|
<p class="frame-descriptor">
|
||||||
|
File {0.filename}, line <i>{0.lineno}</i>,
|
||||||
|
in <code><b>{0.name}</b></code>
|
||||||
|
</p>
|
||||||
|
<p class="frame-code"><code>{0.line}</code></p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
INTERNAL_SERVER_ERROR_HTML = '''
|
||||||
|
<h1>Internal Server Error</h1>
|
||||||
|
<p>
|
||||||
|
The server encountered an internal error and cannot complete
|
||||||
|
your request.
|
||||||
|
</p>
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
class SanicException(Exception):
|
class SanicException(Exception):
|
||||||
|
@ -46,6 +144,21 @@ class Handler:
|
||||||
self.handlers = {}
|
self.handlers = {}
|
||||||
self.sanic = sanic
|
self.sanic = sanic
|
||||||
|
|
||||||
|
def _render_traceback_html(self, exception, request):
|
||||||
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
|
frames = extract_tb(tb)
|
||||||
|
|
||||||
|
frame_html = []
|
||||||
|
for frame in frames:
|
||||||
|
frame_html.append(TRACEBACK_LINE_HTML.format(frame))
|
||||||
|
|
||||||
|
return TRACEBACK_WRAPPER_HTML.format(
|
||||||
|
style=TRACEBACK_STYLE,
|
||||||
|
exc_name=exc_type.__name__,
|
||||||
|
exc_value=exc_value,
|
||||||
|
frame_html=''.join(frame_html),
|
||||||
|
uri=request.url)
|
||||||
|
|
||||||
def add(self, exception, handler):
|
def add(self, exception, handler):
|
||||||
self.handlers[exception] = handler
|
self.handlers[exception] = handler
|
||||||
|
|
||||||
|
@ -77,11 +190,12 @@ class Handler:
|
||||||
'Error: {}'.format(exception),
|
'Error: {}'.format(exception),
|
||||||
status=getattr(exception, 'status_code', 500))
|
status=getattr(exception, 'status_code', 500))
|
||||||
elif self.sanic.debug:
|
elif self.sanic.debug:
|
||||||
|
html_output = self._render_traceback_html(exception, request)
|
||||||
|
|
||||||
response_message = (
|
response_message = (
|
||||||
'Exception occurred while handling uri: "{}"\n{}'.format(
|
'Exception occurred while handling uri: "{}"\n{}'.format(
|
||||||
request.url, format_exc()))
|
request.url, format_exc()))
|
||||||
log.error(response_message)
|
log.error(response_message)
|
||||||
return text(response_message, status=500)
|
return html(html_output, status=500)
|
||||||
else:
|
else:
|
||||||
return text(
|
return html(INTERNAL_SERVER_ERROR_HTML, status=500)
|
||||||
'An error occurred while generating the response', status=500)
|
|
||||||
|
|
|
@ -41,18 +41,20 @@ class Request(dict):
|
||||||
Properties of an HTTP request such as URL, headers, etc.
|
Properties of an HTTP request such as URL, headers, etc.
|
||||||
"""
|
"""
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'url', 'headers', 'version', 'method', '_cookies',
|
'url', 'headers', 'version', 'method', '_cookies', 'transport',
|
||||||
'query_string', 'body',
|
'query_string', 'body',
|
||||||
'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
|
'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
|
||||||
|
'_ip',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, url_bytes, headers, version, method):
|
def __init__(self, url_bytes, headers, version, method, transport):
|
||||||
# TODO: Content-Encoding detection
|
# TODO: Content-Encoding detection
|
||||||
url_parsed = parse_url(url_bytes)
|
url_parsed = parse_url(url_bytes)
|
||||||
self.url = url_parsed.path.decode('utf-8')
|
self.url = url_parsed.path.decode('utf-8')
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
self.version = version
|
self.version = version
|
||||||
self.method = method
|
self.method = method
|
||||||
|
self.transport = transport
|
||||||
self.query_string = None
|
self.query_string = None
|
||||||
if url_parsed.query:
|
if url_parsed.query:
|
||||||
self.query_string = url_parsed.query.decode('utf-8')
|
self.query_string = url_parsed.query.decode('utf-8')
|
||||||
|
@ -139,6 +141,12 @@ class Request(dict):
|
||||||
self._cookies = {}
|
self._cookies = {}
|
||||||
return self._cookies
|
return self._cookies
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip(self):
|
||||||
|
if not hasattr(self, '_ip'):
|
||||||
|
self._ip = self.transport.get_extra_info('peername')
|
||||||
|
return self._ip
|
||||||
|
|
||||||
|
|
||||||
File = namedtuple('File', ['type', 'body', 'name'])
|
File = namedtuple('File', ['type', 'body', 'name'])
|
||||||
|
|
||||||
|
|
|
@ -83,10 +83,10 @@ class HTTPResponse:
|
||||||
if body is not None:
|
if body is not None:
|
||||||
try:
|
try:
|
||||||
# Try to encode it regularly
|
# Try to encode it regularly
|
||||||
self.body = body.encode('utf-8')
|
self.body = body.encode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Convert it to a str if you can't
|
# Convert it to a str if you can't
|
||||||
self.body = str(body).encode('utf-8')
|
self.body = str(body).encode()
|
||||||
else:
|
else:
|
||||||
self.body = body_bytes
|
self.body = body_bytes
|
||||||
|
|
||||||
|
@ -169,3 +169,26 @@ async def file(location, mime_type=None, headers=None):
|
||||||
headers=headers,
|
headers=headers,
|
||||||
content_type=mime_type,
|
content_type=mime_type,
|
||||||
body_bytes=out_stream)
|
body_bytes=out_stream)
|
||||||
|
|
||||||
|
|
||||||
|
def redirect(to, headers=None, status=302,
|
||||||
|
content_type="text/html; charset=utf-8"):
|
||||||
|
"""
|
||||||
|
Aborts execution and causes a 302 redirect (by default).
|
||||||
|
|
||||||
|
:param to: path or fully qualified URL to redirect to
|
||||||
|
:param headers: optional dict of headers to include in the new request
|
||||||
|
:param status: status code (int) of the new request, defaults to 302
|
||||||
|
:param content_type:
|
||||||
|
the content type (string) of the response
|
||||||
|
:returns: the redirecting Response
|
||||||
|
"""
|
||||||
|
headers = headers or {}
|
||||||
|
|
||||||
|
# According to RFC 7231, a relative URI is now permitted.
|
||||||
|
headers['Location'] = to
|
||||||
|
|
||||||
|
return HTTPResponse(
|
||||||
|
status=status,
|
||||||
|
headers=headers,
|
||||||
|
content_type=content_type)
|
||||||
|
|
|
@ -21,12 +21,7 @@ from os import set_inheritable
|
||||||
|
|
||||||
class Sanic:
|
class Sanic:
|
||||||
def __init__(self, name=None, router=None,
|
def __init__(self, name=None, router=None,
|
||||||
error_handler=None, logger=None):
|
error_handler=None):
|
||||||
if logger is None:
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format="%(asctime)s: %(levelname)s: %(message)s"
|
|
||||||
)
|
|
||||||
if name is None:
|
if name is None:
|
||||||
frame_records = stack()[1]
|
frame_records = stack()[1]
|
||||||
name = getmodulename(frame_records[1])
|
name = getmodulename(frame_records[1])
|
||||||
|
@ -154,7 +149,8 @@ class Sanic:
|
||||||
def register_blueprint(self, *args, **kwargs):
|
def register_blueprint(self, *args, **kwargs):
|
||||||
# TODO: deprecate 1.0
|
# TODO: deprecate 1.0
|
||||||
log.warning("Use of register_blueprint will be deprecated in "
|
log.warning("Use of register_blueprint will be deprecated in "
|
||||||
"version 1.0. Please use the blueprint method instead")
|
"version 1.0. Please use the blueprint method instead",
|
||||||
|
DeprecationWarning)
|
||||||
return self.blueprint(*args, **kwargs)
|
return self.blueprint(*args, **kwargs)
|
||||||
|
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
@ -245,6 +241,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, ssl=None,
|
after_start=None, before_stop=None, after_stop=None, ssl=None,
|
||||||
sock=None, workers=1, loop=None, protocol=HttpProtocol,
|
sock=None, workers=1, loop=None, protocol=HttpProtocol,
|
||||||
backlog=100, stop_event=None):
|
backlog=100, stop_event=None):
|
||||||
|
@ -270,6 +267,10 @@ class Sanic:
|
||||||
:param protocol: Subclass of asyncio protocol class
|
:param protocol: Subclass of asyncio protocol class
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s: %(levelname)s: %(message)s"
|
||||||
|
)
|
||||||
self.error_handler.debug = True
|
self.error_handler.debug = True
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
|
@ -356,6 +357,12 @@ class Sanic:
|
||||||
:param stop_event: if provided, is used as a stop signal
|
:param stop_event: if provided, is used as a stop signal
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
# In case this is called directly, we configure logging here too.
|
||||||
|
# This won't interfere with the same call from run()
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s: %(levelname)s: %(message)s"
|
||||||
|
)
|
||||||
server_settings['reuse_port'] = True
|
server_settings['reuse_port'] = True
|
||||||
|
|
||||||
# Create a stop event to be triggered by a signal
|
# Create a stop event to be triggered by a signal
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from multidict import CIMultiDict
|
|
||||||
from signal import SIGINT, SIGTERM
|
from signal import SIGINT, SIGTERM
|
||||||
from time import time
|
from time import time
|
||||||
from httptools import HttpRequestParser
|
from httptools import HttpRequestParser
|
||||||
|
@ -18,11 +17,30 @@ from .request import Request
|
||||||
from .exceptions import RequestTimeout, PayloadTooLarge, InvalidUsage
|
from .exceptions import RequestTimeout, PayloadTooLarge, InvalidUsage
|
||||||
|
|
||||||
|
|
||||||
|
current_time = None
|
||||||
|
|
||||||
|
|
||||||
class Signal:
|
class Signal:
|
||||||
stopped = False
|
stopped = False
|
||||||
|
|
||||||
|
|
||||||
current_time = None
|
class CIDict(dict):
|
||||||
|
"""
|
||||||
|
Case Insensitive dict where all keys are converted to lowercase
|
||||||
|
This does not maintain the inputted case when calling items() or keys()
|
||||||
|
in favor of speed, since headers are case insensitive
|
||||||
|
"""
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return super().get(key.casefold(), default)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return super().__getitem__(key.casefold())
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
return super().__setitem__(key.casefold(), value)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return super().__contains__(key.casefold())
|
||||||
|
|
||||||
|
|
||||||
class HttpProtocol(asyncio.Protocol):
|
class HttpProtocol(asyncio.Protocol):
|
||||||
|
@ -118,18 +136,15 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
exception = PayloadTooLarge('Payload Too Large')
|
exception = PayloadTooLarge('Payload Too Large')
|
||||||
self.write_error(exception)
|
self.write_error(exception)
|
||||||
|
|
||||||
self.headers.append((name.decode(), value.decode('utf-8')))
|
self.headers.append((name.decode().casefold(), value.decode()))
|
||||||
|
|
||||||
def on_headers_complete(self):
|
def on_headers_complete(self):
|
||||||
remote_addr = self.transport.get_extra_info('peername')
|
|
||||||
if remote_addr:
|
|
||||||
self.headers.append(('Remote-Addr', '%s:%s' % remote_addr))
|
|
||||||
|
|
||||||
self.request = Request(
|
self.request = Request(
|
||||||
url_bytes=self.url,
|
url_bytes=self.url,
|
||||||
headers=CIMultiDict(self.headers),
|
headers=CIDict(self.headers),
|
||||||
version=self.parser.get_http_version(),
|
version=self.parser.get_http_version(),
|
||||||
method=self.parser.get_method().decode()
|
method=self.parser.get_method().decode(),
|
||||||
|
transport=self.transport
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_body(self, body):
|
def on_body(self, body):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
@ -75,7 +76,13 @@ def test_handled_unhandled_exception(exception_app):
|
||||||
request, response = sanic_endpoint_test(
|
request, response = sanic_endpoint_test(
|
||||||
exception_app, uri='/divide_by_zero')
|
exception_app, uri='/divide_by_zero')
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
assert response.body == b'An error occurred while generating the response'
|
soup = BeautifulSoup(response.body, 'html.parser')
|
||||||
|
assert soup.h1.text == 'Internal Server Error'
|
||||||
|
|
||||||
|
message = " ".join(soup.p.text.split())
|
||||||
|
assert message == (
|
||||||
|
"The server encountered an internal error and "
|
||||||
|
"cannot complete your request.")
|
||||||
|
|
||||||
|
|
||||||
def test_exception_in_exception_handler(exception_app):
|
def test_exception_in_exception_handler(exception_app):
|
||||||
|
|
|
@ -2,6 +2,7 @@ from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.exceptions import InvalidUsage, ServerError, NotFound
|
from sanic.exceptions import InvalidUsage, ServerError, NotFound
|
||||||
from sanic.utils import sanic_endpoint_test
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
exception_handler_app = Sanic('test_exception_handler')
|
exception_handler_app = Sanic('test_exception_handler')
|
||||||
|
|
||||||
|
@ -21,6 +22,12 @@ def handler_3(request):
|
||||||
raise NotFound("OK")
|
raise NotFound("OK")
|
||||||
|
|
||||||
|
|
||||||
|
@exception_handler_app.route('/4')
|
||||||
|
def handler_4(request):
|
||||||
|
foo = bar
|
||||||
|
return text(foo)
|
||||||
|
|
||||||
|
|
||||||
@exception_handler_app.exception(NotFound, ServerError)
|
@exception_handler_app.exception(NotFound, ServerError)
|
||||||
def handler_exception(request, exception):
|
def handler_exception(request, exception):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
@ -47,3 +54,20 @@ def test_text_exception__handler():
|
||||||
exception_handler_app, uri='/random')
|
exception_handler_app, uri='/random')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_traceback_output_in_debug_mode():
|
||||||
|
request, response = sanic_endpoint_test(
|
||||||
|
exception_handler_app, uri='/4', debug=True)
|
||||||
|
assert response.status == 500
|
||||||
|
soup = BeautifulSoup(response.body, 'html.parser')
|
||||||
|
html = str(soup)
|
||||||
|
|
||||||
|
assert 'response = handler(request, *args, **kwargs)' in html
|
||||||
|
assert 'handler_4' in html
|
||||||
|
assert 'foo = bar' in html
|
||||||
|
|
||||||
|
summary_text = " ".join(soup.select('.summary')[0].text.split())
|
||||||
|
assert (
|
||||||
|
"NameError: name 'bar' "
|
||||||
|
"is not defined while handling uri /4") == summary_text
|
||||||
|
|
|
@ -19,7 +19,7 @@ def test_log():
|
||||||
stream=log_stream
|
stream=log_stream
|
||||||
)
|
)
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
app = Sanic('test_logging', logger=True)
|
app = Sanic('test_logging')
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
log.info('hello world')
|
log.info('hello world')
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
from json import loads as json_loads, dumps as json_dumps
|
from json import loads as json_loads, dumps as json_dumps
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text, redirect
|
||||||
from sanic.utils import sanic_endpoint_test
|
from sanic.utils import sanic_endpoint_test
|
||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
# GET
|
# GET
|
||||||
|
@ -188,3 +189,73 @@ def test_post_form_multipart_form_data():
|
||||||
request, response = sanic_endpoint_test(app, data=payload, headers=headers)
|
request, response = sanic_endpoint_test(app, data=payload, headers=headers)
|
||||||
|
|
||||||
assert request.form.get('test') == 'OK'
|
assert request.form.get('test') == 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def redirect_app():
|
||||||
|
app = Sanic('test_redirection')
|
||||||
|
|
||||||
|
@app.route('/redirect_init')
|
||||||
|
async def redirect_init(request):
|
||||||
|
return redirect("/redirect_target")
|
||||||
|
|
||||||
|
@app.route('/redirect_init_with_301')
|
||||||
|
async def redirect_init_with_301(request):
|
||||||
|
return redirect("/redirect_target", status=301)
|
||||||
|
|
||||||
|
@app.route('/redirect_target')
|
||||||
|
async def redirect_target(request):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_redirect_default_302(redirect_app):
|
||||||
|
"""
|
||||||
|
We expect a 302 default status code and the headers to be set.
|
||||||
|
"""
|
||||||
|
request, response = sanic_endpoint_test(
|
||||||
|
redirect_app, method="get",
|
||||||
|
uri="/redirect_init",
|
||||||
|
allow_redirects=False)
|
||||||
|
|
||||||
|
assert response.status == 302
|
||||||
|
assert response.headers["Location"] == "/redirect_target"
|
||||||
|
assert response.headers["Content-Type"] == 'text/html; charset=utf-8'
|
||||||
|
|
||||||
|
|
||||||
|
def test_redirect_headers_none(redirect_app):
|
||||||
|
request, response = sanic_endpoint_test(
|
||||||
|
redirect_app, method="get",
|
||||||
|
uri="/redirect_init",
|
||||||
|
headers=None,
|
||||||
|
allow_redirects=False)
|
||||||
|
|
||||||
|
assert response.status == 302
|
||||||
|
assert response.headers["Location"] == "/redirect_target"
|
||||||
|
|
||||||
|
|
||||||
|
def test_redirect_with_301(redirect_app):
|
||||||
|
"""
|
||||||
|
Test redirection with a different status code.
|
||||||
|
"""
|
||||||
|
request, response = sanic_endpoint_test(
|
||||||
|
redirect_app, method="get",
|
||||||
|
uri="/redirect_init_with_301",
|
||||||
|
allow_redirects=False)
|
||||||
|
|
||||||
|
assert response.status == 301
|
||||||
|
assert response.headers["Location"] == "/redirect_target"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_then_redirect_follow_redirect(redirect_app):
|
||||||
|
"""
|
||||||
|
With `allow_redirects` we expect a 200.
|
||||||
|
"""
|
||||||
|
response = sanic_endpoint_test(
|
||||||
|
redirect_app, method="get",
|
||||||
|
uri="/redirect_init", gather_request=False,
|
||||||
|
allow_redirects=True)
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user