---
myst:
html_meta:
"description": ""
"property=og:description": ""
"property=og:title": ""
"keywords": ""
---
# Routing
Routing is the mechanism which allows our application to call different
parts according to the requested URL.
Until now only saw applications that always give the same response to any
requested URL.
## Using `PATH_INFO`
The requested URL contains a `PATH_INFO` which is passed to our WSGI
application via the `environment` dictionary.
We can write our application as a giant case switch to match a specific
`PATH_INFO` to a specific behavior:
```python
def giant_wsgi_case_app(environ, start_response):
status = '200 OK' # HTTP Status
# HTTP Headers
headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, headers)
# The returned object is going to be printed
if environ['PATH_INFO'] == '/hello':
return [b"Hello World"]
elif environ['PATH_INFO'] == '/bye':
return [b"Good bye"]
elif ...
...
else:
start_response('404 Not Found', headers)
return [b"Not found"]
```
This would be very un-pythonic and cumbersome to extend. Essentially, this
problem is solved by all web framework with some kind of a routing
middleware. But before we examine how it is done by some of the most famous
WSGI frameworks, we implement a primitive routing middleware on our own.
### Exercise 4
A small improvement would be to replace the giant `if ... elif ... else`
with a dictionary and map a `PATH_INFO` to a callable. The middleware should use this mapping to call the correct WSGI callable.
````{dropdown} Solution
:animate: fade-in-slide-down
:icon: question
```python
def not_found(environ, start_response):
start_response('404 Not Found', [('Content-Type', 'text/plain')])
return [b'404 Not Found']
def hello_world(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return [b"Hello World!\n"]
def greet_user(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
user = environ.get('USER')
return ["Welcome {}!\n".format(user).encode()] # response must contain bytes
URLS = {
"/": hello_world,
"/greeter": greet_user,
}
def app(environ, start_response):
handler = URLS.get(environ.get('PATH_INFO')) or not_found
return handler(environ, start_response)
```
````
While this solution is pretty primitive it is understand and extend.
Essentially, many WSGI framework have some kind of a `Mapping`
class which is responsible for this mechanism.
For example, in Django one defines in `urls.py` a list of patters,
which are a regular expression and callable `view`. Here is an
example from the most venerable Django polls tutorial:
```python
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'),
...
]
```
The `url` items are then matched by [django.urls.resolvers.ResolverMatch]
A similar approach is also taken by the more modern `aiohttp` (an
honorable reference, even though it's not a WSGI framework):
```python
from aiohttp import web
...
app = web.Application()
app.router.add_get('/', handle)
```
Pyramid does this too:
```python
with Configurator() as config:
config.add_route('hello', '/hello/{name}')
config.add_view(hello_world, route_name='hello')
app = config.make_wsgi_app()
```
Here `add_route` creates an association between a `route_name` and
a pattern. `add_view` connects the callable `hello_world` with the route
just created.
`Flask` and `Bottle` have an implicit way of adding `route` items to
the `Mapping`:
```python
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
```
`app.route` adds the wrapped callable to the internal mapping inside the
`Flask` instance. In a later part of this course, we will examine later
how this decorator works.
## Working with URL parameters
So far, we have a simple routing middleware. But it can't work with
parameters, as seen in the Django and Pyramid examples above.
A middleware can modify the response or the environment. Modifying the latter,
we can pass new objects via the environment dictionary to the callable.
### Exercise 5
Modify the main app matching mechanism to use regular expression groups,
to match certain URL parts as groups. These groups are the URL args,
the application can make use of. For example, calling `/hello/` should return
`hello wolrd!`. Calling `/hello/frank` should return `/hello/frank!`.
```python
def hello(environ, start_response):
"""Like the example above, but it uses the name specified in the URL."""
# get the name from the url if it was specified there.
args = environ['myapp.url_args']
if args:
subject = escape(args[0])
else:
subject = 'World'
start_response('200 OK', [('Content-Type', 'text/html')])
return ['''Hello {}!'''.format(subject).encode()]
```
````{dropdown} Solution
:animate: fade-in-slide-down
:icon: question
```python
urls = [
(r'^$', index),
(r'hello/?$', hello),
(r'hello/(.+)/$', hello),
]
def application(environ, start_response):
path = environ.get('PATH_INFO', '').lstrip('/')
for regex, callback in urls:
match = re.search(regex, path)
if match:
environ['myapp.url_args'] = match.groups()
return callback(environ, start_response)
return not_found(environ, start_response)
```
````
[django.urls.resolvers.resolvermatch]: https://github.com/django/django/blob/f0ffa3f4ea277f9814285085fde20baff60fc386/django/urls/resolvers.py#L29