The HTTP Resource Class
Jivago uses Resource classes to define HTTP routes. Routable classes should be annotated with the @Resource
decorator with an URL path on which it should be mounted. Each routing method should then be annotated with one of the HTTP verbs (@GET
, @POST
, etc.) to make them known to the framework, and can optionally define a subpath using the @Path
annotation.
from jivago.wsgi.annotations import Resource, Path
from jivago.wsgi.invocation.parameters import PathParam, QueryParam
from jivago.wsgi.methods import GET, POST, PUT
from jivago.wsgi.request.request import Request
from jivago.wsgi.request.response import Response
@Resource("/hello")
class HelloWorldResource(object):
@GET
def get_hello(self) -> str:
return "Hello"
@POST
@Path("/{name}")
def post_hello(self, name: PathParam[str]) -> str:
return "name: {}".format(name)
@Path("/request/json")
@POST
def read_request_body_from_dict(self, body: dict) -> dict:
return {"the body": body}
@GET
@Path("/query")
def with_query(self, name: QueryParam[str]) -> str:
return "Hello {}!".format(name)
@GET
@Path("/request/raw")
def read_raw_request(self, request: Request) -> Response:
return Response(200, {}, "body")
Allowed Parameter Types
When handling a specific request, Jivago reads declared parameter types before invoking the routing function. Passed arguments can come from the query itself, from the body, from the raw request or a combination. Below are all the allowed parameter types :
QueryParam[T] : Reads the parameter matching the variable name from the query string. T should be either
str
,int
orfloat
.OptionalQueryParam[T] : Identical to the above, except that it allows
None
values to be passed in place of a missing one.PathParam[T] : Reads the parameter from the url path. The variable name should match the declared name in the
@Path
or the@Resource
annotation. Route definitions use the{path-parameter-name}
to declare these parameters.dict : The request body which has been deserialized to a dictionary. Requires the body to be deserializable to a dictionary. (e.g. JSON).
A user-defined DTO : Any declared
@Serializable
class will be instantiated before invoking. This effectively acts as a JSON-schema validation.Request : The raw
Request
object, as handled by Jivago. Useful when direct access to headers, query strings or the body is required.Headers : The raw
Headers
object, containing all request headers. This class is simply a case-insensitive dictionary.
Manual Route Registration
Additionnal URL routes can be registered by creating a new RoutingTable
which references classes and their methods. Note that the appropriate classes should be imported beforehand. The referenced resource class can be either an instance, or the actual class. In that case, it will be instantiated by the ServiceLocator, and should therefore be registered manually in the configure_service_locator
context method.
from jivago.wsgi.methods import GET, POST
from jivago.wsgi.routing.table.tree_routing_table import TreeRoutingTable
my_routing_table = TreeRoutingTable()
my_routing_table.register_route(GET, "/hello", MyResourceClass, MyResourceClass.get_hello)
my_routing_table.register_route(POST, "/hello", MyResourceClass, MyResourceClass.get_hello)
This new RoutingTable
can then be used to configure the Router
object, which is used to serve all requests. The recommended way of configuring your application is by inheriting from the ProductionJivagoContext
class, and then overriding the create_router_config
method.
from jivago.config.production_jivago_context import ProductionJivagoContext
from jivago.config.router.router_builder import RouterBuilder
from jivago.jivago_application import JivagoApplication
from jivago.wsgi.routing.routing_rule import RoutingRule
class MyApplicationContext(ProductionJivagoContext):
def create_router_config(self) -> RouterBuilder:
return super().create_router_config() \
.add_rule(RoutingRule("/", my_routing_table))
app = JivagoApplication(my_package, context=MyApplicationContext)
Serving static files
While it is not generally recommended to serve static files from a WSGI application for performance reasons, Jivago supports static file serving. The StaticFileRoutingTable
dynamically defines routes for serving files.
from jivago.config.production_jivago_context import ProductionJivagoContext
from jivago.config.router.router_builder import RouterBuilder
from jivago.lang.annotations import Override
from jivago.wsgi.routing.routing_rule import RoutingRule
from jivago.wsgi.routing.serving.static_file_routing_table import StaticFileRoutingTable
class MyApplicationContext(ProductionJivagoContext):
@Override
def create_router_config(self) -> RouterBuilder:
return super().create_router_config() \
.add_rule(RoutingRule("/", StaticFileRoutingTable("/var/www"))) \
.add_rule(RoutingRule("/", StaticFileRoutingTable("/var/www", allowed_extensions=['.html', '.xml'])))
The StaticFileRoutingTable
can also be used with a allowed_extensions
parameter to explicitly allow or disallow specific file types.
HTTP Streaming responses
In cases where a streaming response is desired, Jivago provides the StreamingResponseBody
object. Returning an instance of StreamingResponseBody
will cause the Transfer-Encoding
header to be automatically set to chunked
. A StreamingResponseBody
object requires an Iterable[bytes]
object.
from jivago.wsgi.annotations import Resource
from jivago.wsgi.methods import GET
from jivago.wsgi.request.headers import Headers
from jivago.wsgi.request.response import Response
from jivago.wsgi.request.streaming_response_body import StreamingResponseBody
@Resource("/stream")
class MyStreamingResource(object):
@GET
def get_stream(self) -> StreamingResponseBody:
# Returning the body object automatically sets the status code to 200 OK
return StreamingResponseBody(self.generate_bytes())
@GET
def get_stream(self) -> Response:
# A Response object can also be manually created to provide further control over transport parameters.
return Response(202, Headers(), StreamingResponseBody(self.generate_bytes()))
def generate_bytes(self) -> bytes:
for i in range(0, 5):
yield b"my bytes"
Note that chunked (streaming) requests and responses may not be supported by every wsgi server. Jivago has been tested with gunicorn
.
HTTP Streaming requests
Similarly, requests using Transfer-Encoding: chunked
will be mapped automatically to a StreamingRequestBody
instance.
from jivago.wsgi.annotations import Resource
from jivago.wsgi.methods import POST
from jivago.wsgi.request.request import Request
from jivago.wsgi.request.streaming_request_body import StreamingRequestBody
@Resource("/stream")
class MyStreamingResource(object):
@POST
def post_stream(self, body: StreamingRequestBody):
content = body.read()
print(content)
return "OK"
@POST
def post_stream2(self, request: Request):
# When Transfer-Encoding is set to 'chunked', the request body will be an instance of StreamingRequestBody
if isinstance(request.body, StreamingRequestBody):
print(request.body.read())
return "OK"
- language:
python
Additional router configuration options, including specific filter and CORS rules, can be found at Router Configuration.