Signed-off-by: Harsha Narayana <harsha2k4@gmail.com>
10 KiB
Routing
Routing allows the user to specify handler functions for different URL endpoints.
A basic route looks like the following, where app
is an instance of the
Sanic
class:
from sanic.response import json
@app.route("/")
async def test(request):
return json({ "hello": "world" })
When the url http://server.url/
is accessed (the base url of the server), the
final /
is matched by the router to the handler function, test
, which then
returns a JSON object.
Sanic handler functions must be defined using the async def
syntax, as they
are asynchronous functions.
Request parameters
Sanic comes with a basic router that supports request parameters.
To specify a parameter, surround it with angle quotes like so: <PARAM>
.
Request parameters will be passed to the route handler functions as keyword
arguments.
from sanic.response import text
@app.route('/tag/<tag>')
async def tag_handler(request, tag):
return text('Tag - {}'.format(tag))
To specify a type for the parameter, add a :type
after the parameter name,
inside the quotes. If the parameter does not match the specified type, Sanic
will throw a NotFound
exception, resulting in a 404: Page not found
error
on the URL.
from sanic.response import text
@app.route('/number/<integer_arg:int>')
async def integer_handler(request, integer_arg):
return text('Integer - {}'.format(integer_arg))
@app.route('/number/<number_arg:number>')
async def number_handler(request, number_arg):
return text('Number - {}'.format(number_arg))
@app.route('/person/<name:[A-z]+>')
async def person_handler(request, name):
return text('Person - {}'.format(name))
@app.route('/folder/<folder_id:[A-z0-9]{0,4}>')
async def folder_handler(request, folder_id):
return text('Folder - {}'.format(folder_id))
HTTP request types
By default, a route defined on a URL will be available for only GET requests to that URL.
However, the @app.route
decorator accepts an optional parameter, methods
,
which allows the handler function to work with any of the HTTP methods in the list.
from sanic.response import text
@app.route('/post', methods=['POST'])
async def post_handler(request):
return text('POST request - {}'.format(request.json))
@app.route('/get', methods=['GET'])
async def get_handler(request):
return text('GET request - {}'.format(request.args))
There is also an optional host
argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is a also a route with no host, it will be the default.
@app.route('/get', methods=['GET'], host='example.com')
async def get_handler(request):
return text('GET request - {}'.format(request.args))
# if the host header doesn't match example.com, this route will be used
@app.route('/get', methods=['GET'])
async def get_handler(request):
return text('GET request in default - {}'.format(request.args))
There are also shorthand method decorators:
from sanic.response import text
@app.post('/post')
async def post_handler(request):
return text('POST request - {}'.format(request.json))
@app.get('/get')
async def get_handler(request):
return text('GET request - {}'.format(request.args))
The add_route
method
As we have seen, routes are often specified using the @app.route
decorator.
However, this decorator is really just a wrapper for the app.add_route
method, which is used as follows:
from sanic.response import text
# Define the handler functions
async def handler1(request):
return text('OK')
async def handler2(request, name):
return text('Folder - {}'.format(name))
async def person_handler2(request, name):
return text('Person - {}'.format(name))
# Add each handler function as a route
app.add_route(handler1, '/test')
app.add_route(handler2, '/folder/<name>')
app.add_route(person_handler2, '/person/<name:[A-z]>', methods=['GET'])
URL building with url_for
Sanic provides a url_for
method, to generate URLs based on the handler method name. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. For example:
from sanic.response import redirect
@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 redirect(url)
@app.route('/posts/<post_id>')
async def post_handler(request, post_id):
return text('Post - {}'.format(post_id))
Other things to keep in mind when using url_for
:
- Keyword arguments passed to
url_for
that are not request parameters will be included in the URL's query string. For example:
url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two')
# /posts/5?arg_one=one&arg_two=two
- Multivalue argument can be passed to
url_for
. For example:
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
# /posts/5?arg_one=one&arg_one=two
- Also some special arguments (
_anchor
,_external
,_scheme
,_method
,_server
) passed tourl_for
will have special url building (_method
is not supported now and will be ignored). For example:
url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor')
# /posts/5?arg_one=one#anchor
url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True)
# //server/posts/5?arg_one=one
# _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external
url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True)
# http://server/posts/5?arg_one=one
# when specifying _scheme, _external must be True
# you can pass all special arguments at once
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888')
# http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor
- All valid parameters must be passed to
url_for
to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, aURLBuildError
will be raised.
WebSocket routes
Routes for the WebSocket protocol can be defined with the @app.websocket
decorator:
@app.websocket('/feed')
async def feed(request, ws):
while True:
data = 'hello!'
print('Sending: ' + data)
await ws.send(data)
data = await ws.recv()
print('Received: ' + data)
Alternatively, the app.add_websocket_route
method can be used instead of the
decorator:
async def feed(request, ws):
pass
app.add_websocket_route(my_websocket_handler, '/feed')
Handlers to a WebSocket route are invoked with the request as first argument, and a
WebSocket protocol object as second argument. The protocol object has send
and recv
methods to send and receive data respectively.
WebSocket support requires the websockets package by Aymeric Augustin.
About strict_slashes
You can make routes
strict to trailing slash or not, it's configurable.
# provide default strict_slashes value for all routes
app = Sanic('test_route_strict_slash', strict_slashes=True)
# you can also overwrite strict_slashes value for specific route
@app.get('/get', strict_slashes=False)
def handler(request):
return text('OK')
# It also works for blueprints
bp = Blueprint('test_bp_strict_slash', strict_slashes=True)
@bp.get('/bp/get', strict_slashes=False)
def handler(request):
return text('OK')
app.blueprint(bp)
User defined route name
A custom route name can be used by passing a name
argument while registering the route which will
override the default route name generated using the handler.__name__
attribute.
app = Sanic('test_named_route')
@app.get('/get', name='get_handler')
def handler(request):
return text('OK')
# then you need use `app.url_for('get_handler')`
# instead of # `app.url_for('handler')`
# It also works for blueprints
bp = Blueprint('test_named_bp')
@bp.get('/bp/get', name='get_handler')
def handler(request):
return text('OK')
app.blueprint(bp)
# then you need use `app.url_for('test_named_bp.get_handler')`
# instead of `app.url_for('test_named_bp.handler')`
# different names can be used for same url with different methods
@app.get('/test', name='route_test')
def handler(request):
return text('OK')
@app.post('/test', name='route_post')
def handler2(request):
return text('OK POST')
@app.put('/test', name='route_put')
def handler3(request):
return text('OK PUT')
# below url are the same, you can use any of them
# '/test'
app.url_for('route_test')
# app.url_for('route_post')
# app.url_for('route_put')
# for same handler name with different methods
# you need specify the name (it's url_for issue)
@app.get('/get')
def handler(request):
return text('OK')
@app.post('/post', name='post_handler')
def handler(request):
return text('OK')
# then
# app.url_for('handler') == '/get'
# app.url_for('post_handler') == '/post'
Build URL for static files
Sanic supports using url_for
method to build static file urls. In case if the static url
is pointing to a directory, filename
parameter to the url_for
can be ignored. q
app = Sanic('test_static')
app.static('/static', './static')
app.static('/uploads', './uploads', name='uploads')
app.static('/the_best.png', '/home/ubuntu/test.png', name='best_png')
bp = Blueprint('bp', url_prefix='bp')
bp.static('/static', './static')
bp.static('/uploads', './uploads', name='uploads')
bp.static('/the_best.png', '/home/ubuntu/test.png', name='best_png')
app.blueprint(bp)
# then build the url
app.url_for('static', filename='file.txt') == '/static/file.txt'
app.url_for('static', name='static', filename='file.txt') == '/static/file.txt'
app.url_for('static', name='uploads', filename='file.txt') == '/uploads/file.txt'
app.url_for('static', name='best_png') == '/the_best.png'
# blueprint url building
app.url_for('static', name='bp.static', filename='file.txt') == '/bp/static/file.txt'
app.url_for('static', name='bp.uploads', filename='file.txt') == '/bp/uploads/file.txt'
app.url_for('static', name='bp.best_png') == '/bp/static/the_best.png'