Merge branch 'master' into master

This commit is contained in:
Raphael Deem 2017-04-24 00:47:01 -07:00 committed by GitHub
commit 74cc7be922
27 changed files with 404 additions and 69 deletions

62
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,62 @@
# Contributing
Thank you for your interest! Sanic is always looking for contributors. If you
don't feel comfortable contributing code, adding docstrings to the source files
is very appreciated.
## Installation
To develop on sanic (and mainly to just run the tests) it is highly recommend to
install from sources.
So assume you have already cloned the repo and are in the working directory with
a virtual environment already set up, then run:
```bash
python setup.py develop && pip install -r requirements-dev.txt
```
## Running tests
To run the tests for sanic it is recommended to use tox like so:
```bash
tox
```
See it's that simple!
## Pull requests!
So the pull request approval rules are pretty simple:
1. All pull requests must pass unit tests
2. All pull requests must be reviewed and approved by at least
one current collaborator on the project
3. All pull requests must pass flake8 checks
4. If you decide to remove/change anything from any common interface
a deprecation message should accompany it.
5. If you implement a new feature you should have at least one unit
test to accompany it.
## Documentation
Sanic's documentation is built
using [sphinx](http://www.sphinx-doc.org/en/1.5.1/). Guides are written in
Markdown and can be found in the `docs` folder, while the module reference is
automatically generated using `sphinx-apidoc`.
To generate the documentation from scratch:
```bash
sphinx-apidoc -fo docs/_api/ sanic
sphinx-build -b html docs docs/_build
```
The HTML documentation will be created in the `docs/_build` folder.
## Warning
One of the main goals of Sanic is speed. Code that lowers the performance of
Sanic without significant gains in usability, security, or features may not be
merged. Please don't let this intimidate you! If you have any concerns about an
idea, open an issue for discussion and help.

View File

@ -83,3 +83,4 @@ Out of the box there are just a few predefined values which can be overwritten w
| ----------------- | --------- | --------------------------------- | | ----------------- | --------- | --------------------------------- |
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) | | REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
| REQUEST_TIMEOUT | 60 | How long a request can take (sec) | | REQUEST_TIMEOUT | 60 | How long a request can take (sec) |
| KEEP_ALIVE | True | Disables keep-alive when False |

View File

@ -4,10 +4,39 @@ Thank you for your interest! Sanic is always looking for contributors. If you
don't feel comfortable contributing code, adding docstrings to the source files don't feel comfortable contributing code, adding docstrings to the source files
is very appreciated. is very appreciated.
## Installation
To develop on sanic (and mainly to just run the tests) it is highly recommend to
install from sources.
So assume you have already cloned the repo and are in the working directory with
a virtual environment already set up, then run:
```bash
python setup.py develop && pip install -r requirements-dev.txt
```
## Running tests ## Running tests
* `python -m pip install pytest` To run the tests for sanic it is recommended to use tox like so:
* `python -m pytest tests`
```bash
tox
```
See it's that simple!
## Pull requests!
So the pull request approval rules are pretty simple:
1. All pull requests must pass unit tests
* All pull requests must be reviewed and approved by at least
one current collaborator on the project
* All pull requests must pass flake8 checks
* If you decide to remove/change anything from any common interface
a deprecation message should accompany it.
* If you implement a new feature you should have at least one unit
test to accompany it.
## Documentation ## Documentation

View File

@ -0,0 +1,41 @@
from sanic import Sanic
from sanic import response
from tornado.platform.asyncio import BaseAsyncIOLoop, to_asyncio_future
from distributed import LocalCluster, Client
app = Sanic(__name__)
def square(x):
return x**2
@app.listener('after_server_start')
async def setup(app, loop):
# configure tornado use asyncio's loop
ioloop = BaseAsyncIOLoop(loop)
# init distributed client
app.client = Client('tcp://localhost:8786', loop=ioloop, start=False)
await to_asyncio_future(app.client._start())
@app.listener('before_server_stop')
async def stop(app, loop):
await to_asyncio_future(app.client._shutdown())
@app.route('/<value:int>')
async def test(request, value):
future = app.client.submit(square, value)
result = await to_asyncio_future(future._result())
return response.text(f'The square of {value} is {result}')
if __name__ == '__main__':
# Distributed cluster should run somewhere else
with LocalCluster(scheduler_port=8786, nanny=False, n_workers=2,
threads_per_worker=1) as cluster:
app.run(host="0.0.0.0", port=8000)

View File

@ -1,9 +1,7 @@
""" """
Example intercepting uncaught exceptions using Sanic's error handler framework. Example intercepting uncaught exceptions using Sanic's error handler framework.
This may be useful for developers wishing to use Sentry, Airbrake, etc. This may be useful for developers wishing to use Sentry, Airbrake, etc.
or a custom system to log and monitor unexpected errors in production. or a custom system to log and monitor unexpected errors in production.
First we create our own class inheriting from Handler in sanic.exceptions, First we create our own class inheriting from Handler in sanic.exceptions,
and pass in an instance of it when we create our Sanic instance. Inside this and pass in an instance of it when we create our Sanic instance. Inside this
class' default handler, we can do anything including sending exceptions to class' default handler, we can do anything including sending exceptions to
@ -39,7 +37,7 @@ server's error_handler to an instance of our CustomHandler
""" """
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic import response
app = Sanic(__name__) app = Sanic(__name__)
@ -52,7 +50,7 @@ async def test(request):
# Here, something occurs which causes an unexpected exception # Here, something occurs which causes an unexpected exception
# This exception will flow to our custom handler. # This exception will flow to our custom handler.
1 / 0 1 / 0
return json({"test": True}) return response.json({"test": True})
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,18 +1,27 @@
## To use this example: # Render templates in a Flask like way from a "template" directory in the project
# curl -d '{"name": "John Doe"}' localhost:8000
from sanic import Sanic from sanic import Sanic
from sanic.response import html from sanic import response
from jinja2 import Template from jinja2 import Evironment, PackageLoader, select_autoescape
template = Template('Hello {{ name }}!')
app = Sanic(__name__) app = Sanic(__name__)
# Load the template environment with async support
template_env = Environment(
loader=jinja2.PackageLoader('yourapplication', 'templates'),
autoescape=jinja2.select_autoescape(['html', 'xml']),
enable_async=True
)
# Load the template from file
template = template_env.get_template("example_template.html")
@app.route('/') @app.route('/')
async def test(request): async def test(request):
data = request.json data = request.json
return html(template.render(**data)) rendered_template = await template.render_async(**data)
return response.html(rendered_template)
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8080, debug=True)

View File

@ -0,0 +1,26 @@
"""
Modify header or status in response
"""
from sanic import Sanic
from sanic import response
app = Sanic(__name__)
@app.route('/')
def handle_request(request):
return response.json(
{'message': 'Hello world!'},
headers={'X-Served-By': 'sanic'},
status=200
)
@app.route('/unauthorized')
def handle_request(request):
return response.json(
{'message': 'You are not authorized'},
headers={'X-Served-By': 'sanic'},
status=404
)
app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,6 +1,5 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import text from sanic import response
import json
import logging import logging
logging_format = "[%(asctime)s] %(process)d-%(levelname)s " logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
@ -18,6 +17,6 @@ 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'")
return text("hey") return response.text("hey")
sanic.run(host="0.0.0.0", port=8000) sanic.run(host="0.0.0.0", port=8000)

View File

@ -0,0 +1,85 @@
from sanic import Sanic
from sanic_session import InMemorySessionInterface
from sanic_jinja2 import SanicJinja2
import json
import plotly
import pandas as pd
import numpy as np
app = Sanic(__name__)
jinja = SanicJinja2(app)
session = InMemorySessionInterface(cookie_name=app.name, prefix=app.name)
@app.middleware('request')
async def print_on_request(request):
print(request.headers)
await session.open(request)
@app.middleware('response')
async def print_on_response(request, response):
await session.save(request, response)
@app.route('/')
async def index(request):
rng = pd.date_range('1/1/2011', periods=7500, freq='H')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
graphs = [
dict(
data=[
dict(
x=[1, 2, 3],
y=[10, 20, 30],
type='scatter'
),
],
layout=dict(
title='first graph'
)
),
dict(
data=[
dict(
x=[1, 3, 5],
y=[10, 50, 30],
type='bar'
),
],
layout=dict(
title='second graph'
)
),
dict(
data=[
dict(
x=ts.index, # Can use the pandas data structures directly
y=ts
)
]
)
]
# Add "ids" to each of the graphs to pass up to the client
# for templating
ids = ['graph-{}'.format(i) for i, _ in enumerate(graphs)]
# Convert the figures to JSON
# PlotlyJSONEncoder appropriately converts pandas, datetime, etc
# objects to their JSON equivalents
graphJSON = json.dumps(graphs, cls=plotly.utils.PlotlyJSONEncoder)
return jinja.render('index.html', request,
ids=ids,
graphJSON=graphJSON)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)

View File

@ -0,0 +1,5 @@
pandas==0.19.2
plotly==2.0.7
sanic==0.5.0
sanic-jinja2==0.5.1
sanic-session==0.1.3

View File

@ -1,6 +1,6 @@
from sanic import Sanic
import asyncio import asyncio
from sanic.response import text from sanic import Sanic
from sanic import response
from sanic.config import Config from sanic.config import Config
from sanic.exceptions import RequestTimeout from sanic.exceptions import RequestTimeout
@ -11,11 +11,11 @@ app = Sanic(__name__)
@app.route('/') @app.route('/')
async def test(request): async def test(request):
await asyncio.sleep(3) await asyncio.sleep(3)
return text('Hello, world!') return response.text('Hello, world!')
@app.exception(RequestTimeout) @app.exception(RequestTimeout)
def timeout(request, exception): def timeout(request, exception):
return text('RequestTimeout from error_handler.', 408) return response.text('RequestTimeout from error_handler.', 408)
app.run(host='0.0.0.0', port=8000) app.run(host='0.0.0.0', port=8000)

View File

@ -5,7 +5,7 @@ motor==1.1
sanic==0.2.0 sanic==0.2.0
""" """
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic import response
app = Sanic('motor_mongodb') app = Sanic('motor_mongodb')
@ -25,7 +25,7 @@ async def get(request):
for doc in docs: for doc in docs:
doc['id'] = str(doc['_id']) doc['id'] = str(doc['_id'])
del doc['_id'] del doc['_id']
return json(docs) return response.json(docs)
@app.route('/post', methods=['POST']) @app.route('/post', methods=['POST'])
@ -34,8 +34,8 @@ async def new(request):
print(doc) print(doc)
db = get_db() db = get_db()
object_id = await db.test_col.save(doc) object_id = await db.test_col.save(doc)
return json({'object_id': str(object_id)}) return response.json({'object_id': str(object_id)})
if __name__ == "__main__": if __name__ == "__main__":
app.run(host='127.0.0.1', port=8000) app.run(host='0.0.0.0', port=8000, debug=True)

View File

@ -1,12 +1,12 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic import response
app = Sanic(__name__) app = Sanic(__name__)
@app.route("/") @app.route("/")
async def test(request): async def test(request):
return json({"test": True}) return response.json({"test": True})
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,7 +2,7 @@ import os
from sanic import Sanic from sanic import Sanic
from sanic.log import log from sanic.log import log
from sanic.response import json, text, file from sanic import response
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
app = Sanic(__name__) app = Sanic(__name__)
@ -10,17 +10,17 @@ app = Sanic(__name__)
@app.route("/") @app.route("/")
async def test_async(request): async def test_async(request):
return json({"test": True}) return response.json({"test": True})
@app.route("/sync", methods=['GET', 'POST']) @app.route("/sync", methods=['GET', 'POST'])
def test_sync(request): def test_sync(request):
return json({"test": True}) return response.json({"test": True})
@app.route("/dynamic/<name>/<id:int>") @app.route("/dynamic/<name>/<id:int>")
def test_params(request, name, id): def test_params(request, name, id):
return text("yeehaww {} {}".format(name, id)) return response.text("yeehaww {} {}".format(name, id))
@app.route("/exception") @app.route("/exception")
@ -31,11 +31,11 @@ def exception(request):
async def test_await(request): async def test_await(request):
import asyncio import asyncio
await asyncio.sleep(5) await asyncio.sleep(5)
return text("I'm feeling sleepy") return response.text("I'm feeling sleepy")
@app.route("/file") @app.route("/file")
async def test_file(request): async def test_file(request):
return await file(os.path.abspath("setup.py")) return await response.file(os.path.abspath("setup.py"))
# ----------------------------------------------- # # ----------------------------------------------- #
@ -44,7 +44,7 @@ async def test_file(request):
@app.exception(ServerError) @app.exception(ServerError)
async def test(request, exception): async def test(request, exception):
return json({"exception": "{}".format(exception), "status": exception.status_code}, status=exception.status_code) return response.json({"exception": "{}".format(exception), "status": exception.status_code}, status=exception.status_code)
# ----------------------------------------------- # # ----------------------------------------------- #
@ -53,17 +53,17 @@ async def test(request, exception):
@app.route("/json") @app.route("/json")
def post_json(request): def post_json(request):
return json({"received": True, "message": request.json}) return response.json({"received": True, "message": request.json})
@app.route("/form") @app.route("/form")
def post_json(request): def post_json(request):
return json({"received": True, "form_data": request.form, "test": request.form.get('test')}) return response.json({"received": True, "form_data": request.form, "test": request.form.get('test')})
@app.route("/query_string") @app.route("/query_string")
def query_string(request): def query_string(request):
return json({"parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string}) return response.json({"parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string})
# ----------------------------------------------- # # ----------------------------------------------- #

View File

@ -0,0 +1,18 @@
from sanic import Sanic
from sanic import response
app = Sanic(__name__)
@app.route('/')
async def index(request):
# generate a URL for the endpoint `post_handler`
url = app.url_for('post_handler', post_id=5)
# the URL is `/posts/5`, redirect to it
return response.redirect(url)
@app.route('/posts/<post_id>')
async def post_handler(request, post_id):
return response.text('Post - {}'.format(post_id))
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,4 +1,4 @@
from sanic.response import text from sanic import response
from sanic import Sanic from sanic import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
@ -15,25 +15,25 @@ bp = Blueprint("bp", host="bp.example.com")
"somethingelse.com", "somethingelse.com",
"therestofyourdomains.com"]) "therestofyourdomains.com"])
async def hello(request): async def hello(request):
return text("Some defaults") return response.text("Some defaults")
@app.route('/', host="example.com") @app.route('/', host="example.com")
async def hello(request): async def hello(request):
return text("Answer") return response.text("Answer")
@app.route('/', host="sub.example.com") @app.route('/', host="sub.example.com")
async def hello(request): async def hello(request):
return text("42") return response.text("42")
@bp.route("/question") @bp.route("/question")
async def hello(request): async def hello(request):
return text("What is the meaning of life?") return response.text("What is the meaning of life?")
@bp.route("/answer") @bp.route("/answer")
async def hello(request): async def hello(request):
return text("42") return response.text("42")
app.register_blueprint(bp) app.register_blueprint(bp)
if __name__ == '__main__': if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -17,7 +17,7 @@ from sanic.handlers import ErrorHandler
from sanic.log import log from sanic.log import log
from sanic.response import HTTPResponse, StreamingHTTPResponse from sanic.response import HTTPResponse, StreamingHTTPResponse
from sanic.router import Router from sanic.router import Router
from sanic.server import serve, serve_multiple, HttpProtocol from sanic.server import serve, serve_multiple, HttpProtocol, Signal
from sanic.static import register as static_register from sanic.static import register as static_register
from sanic.testing import SanicTestClient from sanic.testing import SanicTestClient
from sanic.views import CompositionView from sanic.views import CompositionView
@ -293,7 +293,7 @@ class Sanic:
attach_to=middleware_or_request) attach_to=middleware_or_request)
# Static Files # Static Files
def static(self, uri, file_or_directory, pattern='.+', def static(self, uri, file_or_directory, pattern=r'/?.+',
use_modified_since=True, use_content_range=False): use_modified_since=True, use_content_range=False):
"""Register a root to serve files from. The input can either be a """Register a root to serve files from. The input can either be a
file or a directory. See file or a directory. See
@ -687,11 +687,13 @@ class Sanic:
'port': port, 'port': port,
'sock': sock, 'sock': sock,
'ssl': ssl, 'ssl': ssl,
'signal': Signal(),
'debug': debug, 'debug': debug,
'request_handler': self.handle_request, 'request_handler': self.handle_request,
'error_handler': self.error_handler, 'error_handler': self.error_handler,
'request_timeout': self.config.REQUEST_TIMEOUT, 'request_timeout': self.config.REQUEST_TIMEOUT,
'request_max_size': self.config.REQUEST_MAX_SIZE, 'request_max_size': self.config.REQUEST_MAX_SIZE,
'keep_alive': self.config.KEEP_ALIVE,
'loop': loop, 'loop': loop,
'register_sys_signals': register_sys_signals, 'register_sys_signals': register_sys_signals,
'backlog': backlog, 'backlog': backlog,

View File

@ -116,7 +116,7 @@ if type(_addr) is str and not os.path.exists(_addr):
class Config(dict): class Config(dict):
def __init__(self, defaults=None, load_env=True): def __init__(self, defaults=None, load_env=True, keep_alive=True):
super().__init__(defaults or {}) super().__init__(defaults or {})
self.LOGO = """ self.LOGO = """
@ -141,6 +141,7 @@ class Config(dict):
""" """
self.REQUEST_MAX_SIZE = 100000000 # 100 megababies self.REQUEST_MAX_SIZE = 100000000 # 100 megababies
self.REQUEST_TIMEOUT = 60 # 60 seconds self.REQUEST_TIMEOUT = 60 # 60 seconds
self.KEEP_ALIVE = keep_alive
if load_env: if load_env:
self.load_environment_vars() self.load_environment_vars()
@ -208,11 +209,11 @@ class Config(dict):
self[key] = getattr(obj, key) self[key] = getattr(obj, key)
def load_environment_vars(self): def load_environment_vars(self):
"""
Looks for any SANIC_ prefixed environment variables and applies
them to the configuration if present.
"""
for k, v in os.environ.items(): for k, v in os.environ.items():
"""
Looks for any SANIC_ prefixed environment variables and applies
them to the configuration if present.
"""
if k.startswith(SANIC_PREFIX): if k.startswith(SANIC_PREFIX):
_, config_key = k.split(SANIC_PREFIX, 1) _, config_key = k.split(SANIC_PREFIX, 1)
self[config_key] = v self[config_key] = v

View File

@ -78,9 +78,10 @@ class Request(dict):
:return: token related to request :return: token related to request
""" """
auth_header = self.headers.get('Authorization') auth_header = self.headers.get('Authorization')
if auth_header is not None: if 'Token ' in auth_header:
return auth_header.split()[1] return auth_header.partition('Token ')[-1]
return auth_header else:
return auth_header
@property @property
def form(self): def form(self):

View File

@ -210,7 +210,7 @@ class HTTPResponse(BaseHTTPResponse):
# Speeds up response rate 6% over pulling from all # Speeds up response rate 6% over pulling from all
status = COMMON_STATUS_CODES.get(self.status) status = COMMON_STATUS_CODES.get(self.status)
if not status: if not status:
status = ALL_STATUS_CODES.get(self.status) status = ALL_STATUS_CODES.get(self.status, b'UNKNOWN RESPONSE')
return (b'HTTP/%b %d %b\r\n' return (b'HTTP/%b %d %b\r\n'
b'Connection: %b\r\n' b'Connection: %b\r\n'

View File

@ -16,6 +16,7 @@ REGEX_TYPES = {
'int': (int, r'\d+'), 'int': (int, r'\d+'),
'number': (float, r'[0-9\\.]+'), 'number': (float, r'[0-9\\.]+'),
'alpha': (str, r'[A-Za-z]+'), 'alpha': (str, r'[A-Za-z]+'),
'path': (str, r'[^/].*?'),
} }
ROUTER_CACHE_SIZE = 1024 ROUTER_CACHE_SIZE = 1024
@ -71,7 +72,8 @@ class Router:
self.routes_always_check = [] self.routes_always_check = []
self.hosts = set() self.hosts = set()
def parse_parameter_string(self, parameter_string): @classmethod
def parse_parameter_string(cls, parameter_string):
"""Parse a parameter string into its constituent name, type, and """Parse a parameter string into its constituent name, type, and
pattern pattern
@ -161,10 +163,10 @@ class Router:
parameters.append(parameter) parameters.append(parameter)
# Mark the whole route as unhashable if it has the hash key in it # Mark the whole route as unhashable if it has the hash key in it
if re.search('(^|[^^]){1}/', pattern): if re.search(r'(^|[^^]){1}/', pattern):
properties['unhashable'] = True properties['unhashable'] = True
# Mark the route as unhashable if it matches the hash key # Mark the route as unhashable if it matches the hash key
elif re.search(pattern, '/'): elif re.search(r'/', pattern):
properties['unhashable'] = True properties['unhashable'] = True
return '({})'.format(pattern) return '({})'.format(pattern)

View File

@ -73,7 +73,8 @@ class HttpProtocol(asyncio.Protocol):
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):
self.loop = loop self.loop = loop
self.transport = None self.transport = None
self.request = None self.request = None
@ -92,10 +93,13 @@ class HttpProtocol(asyncio.Protocol):
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._keep_alive = keep_alive
@property @property
def keep_alive(self): def keep_alive(self):
return self.parser.should_keep_alive() and not self.signal.stopped return (self._keep_alive
and not self.signal.stopped
and self.parser.should_keep_alive())
# -------------------------------------------- # # -------------------------------------------- #
# Connection # Connection
@ -357,7 +361,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): signal=Signal(), request_class=None, has_log=True, keep_alive=True):
signal=Signal(), request_class=None, keep_alive=True):
"""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
@ -406,7 +411,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
request_timeout=request_timeout, request_timeout=request_timeout,
request_max_size=request_max_size, request_max_size=request_max_size,
request_class=request_class, request_class=request_class,
has_log=has_log has_log=has_log,
keep_alive=keep_alive,
) )
server_coroutine = loop.create_server( server_coroutine = loop.create_server(

View File

@ -48,14 +48,18 @@ def register(app, uri, file_or_directory, pattern,
# Merge served directory and requested file if provided # Merge served directory and requested file if provided
# Strip all / that in the beginning of the URL to help prevent python # Strip all / that in the beginning of the URL to help prevent python
# from herping a derp and treating the uri as an absolute path # from herping a derp and treating the uri as an absolute path
file_path = file_or_directory root_path = file_path = file_or_directory
if file_uri: if file_uri:
file_path = path.join( file_path = path.join(
file_or_directory, sub('^[/]*', '', file_uri)) file_or_directory, sub('^[/]*', '', file_uri))
# URL decode the path sent by the browser otherwise we won't be able to # URL decode the path sent by the browser otherwise we won't be able to
# match filenames which got encoded (filenames with spaces etc) # match filenames which got encoded (filenames with spaces etc)
file_path = unquote(file_path) file_path = path.abspath(unquote(file_path))
if not file_path.startswith(path.abspath(unquote(root_path))):
raise FileNotFound('File not found',
path=file_or_directory,
relative_url=file_uri)
try: try:
headers = {} headers = {}
# Check if the client has been sent this file before # Check if the client has been sent this file before

View File

@ -3,6 +3,7 @@ import sys
import signal import signal
import asyncio import asyncio
import logging import logging
try: try:
import ssl import ssl
except ImportError: except ImportError:
@ -50,8 +51,8 @@ class GunicornWorker(base.Worker):
debug=is_debug, debug=is_debug,
protocol=protocol, protocol=protocol,
ssl=self.ssl_context, ssl=self.ssl_context,
run_async=True run_async=True)
) self._server_settings['signal'] = self.signal
self._server_settings.pop('sock') self._server_settings.pop('sock')
trigger_events(self._server_settings.get('before_start', []), trigger_events(self._server_settings.get('before_start', []),
self.loop) self.loop)
@ -97,7 +98,6 @@ class GunicornWorker(base.Worker):
self.servers.append(await serve( self.servers.append(await serve(
sock=sock, sock=sock,
connections=self.connections, connections=self.connections,
signal=self.signal,
**self._server_settings **self._server_settings
)) ))

View File

@ -141,6 +141,16 @@ def test_token():
return text('OK') return text('OK')
# uuid4 generated token. # uuid4 generated token.
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
headers = {
'content-type': 'application/json',
'Authorization': '{}'.format(token)
}
request, response = app.test_client.get('/', headers=headers)
assert request.token == token
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf' token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
headers = { headers = {
'content-type': 'application/json', 'content-type': 'application/json',
@ -151,6 +161,18 @@ def test_token():
assert request.token == token assert request.token == token
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
headers = {
'content-type': 'application/json',
'Authorization': 'Bearer Token {}'.format(token)
}
request, response = app.test_client.get('/', headers=headers)
assert request.token == token
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
# POST # POST
# ------------------------------------------------------------ # # ------------------------------------------------------------ #

View File

@ -238,6 +238,30 @@ def test_dynamic_route_regex():
assert response.status == 200 assert response.status == 200
def test_dynamic_route_path():
app = Sanic('test_dynamic_route_path')
@app.route('/<path:path>/info')
async def handler(request, path):
return text('OK')
request, response = app.test_client.get('/path/1/info')
assert response.status == 200
request, response = app.test_client.get('/info')
assert response.status == 404
@app.route('/<path:path>')
async def handler1(request, path):
return text('OK')
request, response = app.test_client.get('/info')
assert response.status == 200
request, response = app.test_client.get('/whatever/you/set')
assert response.status == 200
def test_dynamic_route_unhashable(): def test_dynamic_route_unhashable():
app = Sanic('test_dynamic_route_unhashable') app = Sanic('test_dynamic_route_unhashable')