Skip to content

Routing

Dawid Kraczkowski edited this page Feb 24, 2021 · 9 revisions

Routing is chocs' component in charge of translating each incoming HTTP request to a controller. Every HTTP request has a URL. A URL identifies a resource. The following "diagram" describes different parts of a URL:

    GET http://www.example.org:8080/pets/categories?order_by=creation_date&order=asc
    \_/ \__/   \_____________/ \__/\     \_______// \______________________________/
     |   |           |          |   \_____segment/               |
  method |         host       port       |                  query string
       scheme                          path

When chocs receives requests when the scheme is http (or https) and the host refers to a machine where the application is running. Therefore application cares about method, path and the query_string. Those attributes are available in the request object passed to your controller. Before a controller is called, method and path parts are used by router. Router, which contains list of registered routes and corresponding controllers starts translating a request to a controller. The process is called `routing.

Component responsible for routing is called router, and chocs implementation can be found in chocs.HttpRouter class.

Registering a route

import chocs

http = chocs.Application()

@http.get("/hello")
def hello(req: chocs.HttpRequest) -> chocs.HttpResponse:
    ...

The above example will register hello function to handle a GET /hello request.

First we initialise new application and store it in http variable. Application class defines decorator methods, all decorator method's name corresponds to a different http verb. The following list contains supported methods by chocs library:

  • delete
  • get
  • head
  • options
  • patch
  • post
  • put
  • trace

Those methods accept an argument which is called a route. Route is a pattern which application will use to identify corresponding controller. The decorated function is your controller. A Controller should always accept chocs.HttpRequest parameter and return chocs.HttpResponse object.

Parametrising routes

Routes can contain parameterised parts. Parameters must be enclosed within { and }. A path parameter captures the value from a path segment and can be retrieved from the request object.

import chocs

http = Application()


@http.get("/pet/{id}")
def get_pet(request: chocs.HttpRequest) -> chocs.HttpResponse:
    pet_id = request.path_parameters.get("id")
    ...

The above application will handle the following requests:

  • GET /pet/1 (pet_id will be 1 in the current scenario)
  • GET /pet/abc (pet_id will be abc in the current scenario)
  • GET /pet/abc1 (pet_id will be abc1 in the current scenario)

Wildcard routes

A route pattern may contain at the end a special 'match-all' token, the asterisk (*). This token allows for any remaining request's path to be matched, regardless of its contents or length.

Keep in mind that routes which do not contain wildcards are prioritised over routes with wildcards.

import chocs

http = chocs.Application()

@http.get("/pet/*")
def get_pet(request: chocs.HttpRequest) -> chocs.HttpResponse:
    ...

The above application will handle the following requests:

  • /pet/a
  • /pet/a/b/c
  • /pet/1
  • /pet/1/2/3
  • /pet/can/go/like/that/forever

Route groups

Chocs supports route groups. Route groups is implemented through context lib interface. If you need to split your application in smaller chunks with standalone req/res handlers this feature may come in very handy:

from threading import Thread

from chocs.wsgi import serve 
from chocs import Application
from chocs import HttpRequest
from chocs import HttpResponse

main_app = Application()

with main_app.group("/users/{id}") as user_module:
    
    @user_module.post("/profile_picture")  # POST /users/{id}/profile_pictures
    def create_profile_picture(request: HttpRequest) -> HttpResponse:
        ...
    
    @user_module.get("/profile_picture")  # GET /users/{id}/profile_pictures
    def get_profile_picture(request: HttpRequest) -> HttpResponse:
        ...
    
    @user_module.get("/badges") # GET /users/{id}/badges
    def badges(request: HttpRequest) -> HttpResponse:
        ...

with main_app.group("/payments") as payment_module:

    @payment_module.get("/analytics") # GET /payments/analytics
    def get_analytics(request: HttpRequest) -> HttpResponse:
        ...

if __name__ == '__main__':
    def wsgi_user_module():
        serve(user_module, port=8081)
    def wsgi_payment_module():
        serve(payment_module, port=8082)

    Thread(target=wsgi_user_module).start()
    payment_module()

The above example shows how to run two different modules, which support their own routes on two different ports in the one process.

API