-
Notifications
You must be signed in to change notification settings - Fork 3
Middleware
Middleware are functions or classes that inherit chocs.Middleware
. Middlewares have access to the request object
and the next
function which can be used to control middleware stack flow. Successful middleware execution should call
the next
function which accepts a chocs.HttpRequest
instance and returns chocs.HttpReponse
.
Middlewares can perform various tasks:
- Making changes in request/response objects ending
- Validating input data
- Authenticating users
- End request-response cycle
- Connecting to external data sources
By default, if something goes wrong in your application and exception is thrown a default 500 response
is returned to the client. This behaviour can be changed with custom middleware. The following code defines simple middleware to catch all application's exceptions and returns valid json response to the client.
import chocs
import json
from typing import Callable
# middleware must always accept two parameters; HttpRequest and Callable and return HttpResponse
def handle_errors(request: chocs.HttpRequest, next: Callable) -> chocs.HttpResponse:
try:
return next(request) # we pass request further to middleware pipeline
except Exception as error: # if exception is thrown it is caught here and new response is generated instead
json_response = {
"code": error.code,
"message": str(error),
}
return chocs.HttpResponse(json.dumps(json_response), status=500, headers={"content-type": "application/json"})
# below code is registering middleware
app = chocs.Application(handle_errors)
@app.get("/hello")
def say_hello(request: chocs.HttpRequest) -> chocs.HttpResponse:
raise Exception("You will see this message in the response instead")
return chocs.HttpResponse("this one below")
chocs.serve(app)
Now let's explore the same problem with different approach (registering middleware class). This approach might be considered when middleware has to keep its state or its behaviour needs to be parametrised.
import chocs
import json
from typing import Callable
class HandleErrorsMiddleware(chocs.middleware.Middleware):
def __init__(self, response_code: int = 500):
self.response_code = response_code
def handle(self, request: chocs.HttpRequest, next: chocs.middleware.MiddlewareHandler) -> chocs.HttpResponse:
try:
return next(request)
except Exception as error:
json_response = {
"code": error.code,
"message": str(error),
}
# Response will use response_code variable to set status code's value
return chocs.HttpResponse(json.dumps(json_response), status=self.response_code, headers={"content-type": "application/json"})
# below code is registering middleware
app = chocs.Application(HandleErrorsMiddleware(501))
@app.get("/hello")
def say_hello(request: chocs.HttpRequest) -> chocs.HttpResponse:
raise NotImplemented()
chocs.serve(app)
In the above example chocs.middleware.Middleware
abstract class is being used to extend our middleware. Another difference that can be observed between middleware as function and middleware as a class is the next
parameter definition. In the middleware as a class approach we have to match the interface of chocs.middelware.Middleware
abstract class, but in both scenarios it is the same Callable
object.
- Creating new application
- Registering a controller
- Grouping controllers
- Registering middleware
- Dynamically loading modules
TBA
TBA
- Reading request's body
- Accessing request's parsed body
- Accessing request's headers
- Accessing path's parameters
- Reading client cookies
- Comparing requests objects
- API