Skip to content

Server

The Web Server module is designed to handle both HTML content and RESTful API requests.

It serves HTML content to clients, enabling the delivery of web pages and resources.

Additionally, it supports RESTful API endpoints, allowing clients to interact with the server using standard HTTP methods such as GET, POST, PUT, and DELETE.

This dual functionality makes the server versatile, catering to both traditional web applications and modern web services. The server ensures efficient handling of requests, providing a robust platform for web development and API integration.

Starlette

The Web Server module is built on top of the Starlette framework, a lightweight ASGI framework/toolkit for building high-performance asyncio services.

Homepage: https://www.starlette.io/

Documentation: https://www.starlette.io/applications/

Installation

To install the server module with all dependencies, you can use the following command:

pip3 install everysk-beta[starlette]


Quick Start Guide for running a RESTful API

from everysk.server.applications import create_application
from everysk.server.endpoints import JSONEndpoint
from everysk.server.routing import Route, RouteLazy

class TestPublicEndpoint(JSONEndpoint):
    rest_key_name: str = None
    rest_key_value: str = None

    async def get(self):
        return {'message': 'Hello, World!'}

class TestPrivateEndpoint(JSONEndpoint):
    rest_key_name: str = 'X-Api-Key'
    rest_key_value: str = '123456'

    async def get(self):
        return {'message': 'Hello, World Private!'}

routes = [
    RouteLazy(path='/', endpoint='everysk.server.example_api.TestPublicEndpoint'),
    Route(path='/private', endpoint=TestPrivateEndpoint)
]

app = create_application(routes=routes)

The Web Server - DEV

To be able to run this code, you need to have a web server installed on your machine. For the development environment, you can use the uvicorn. We chose uvicorn because it is a lightning-fast ASGI server implementation, using uvloop and httptools and is the only ASGI server that works with breakpoints in debug mode.

To install uvicorn, you can use the following command:

pip3 install uvicorn

To run the server, you can use the following command:

uvicorn --host "0.0.0.0" --port "<APPLICATION_PORT>" --workers 1 --reload --access-log <FILE>:<APP_OBJECT_NAME>

where <FILE> is the path to the Python file containing the application object using python syntax and <APP_OBJECT_NAME> is the name of the application object. Let's say you have the above code in the app.py file and wanna run the server on port 8000, you can use the following command:

uvicorn --host "0.0.0.0" --port "8000" --workers 1 --reload --access-log api:app


The Web Server - PROD

To run the server in production, we need a different server implementation, we choose the unit. Unit is a dynamic web and application server, designed to run applications in multiple languages and in conjunction with Nginx we could use it to run the HTTP2 protocol. This implementation is more complex than the development one and does not support breakpoints in debug mode. Remember that if the flag for HTTP2 is enabled, the server will only accept HTTPS requests inside Google, locally it will not work. To see how to install and configure the unit server, you can access the Nginx/Unit installation.


The Server Structure

flowchart TB
    classDef orangeBorder fill:#1A1A1A,color:#E2E2E2,stroke:#FF9933,stroke-width:2px;

    ServerDir(["Server"]):::orangeBorder
    ApplicationsDir(["Applications"]):::orangeBorder
    EndpointsDir(["Endpoints"]):::orangeBorder
    RoutesDir(["Routes"]):::orangeBorder
    MiddlewaresDir(["Middlewares"]):::orangeBorder
    ServerDir --> ApplicationsDir
    ServerDir --> EndpointsDir
    ServerDir --> RoutesDir
    ServerDir --> MiddlewaresDir

Application

Every server needs at least one application. An application is a collection of routes that define the server's behavior. It is created using the create_application function from the everysk.server.applications module.

from everysk.server.applications import create_application

app = create_application(
    routes: list[Routes],
    debug: bool = True | False,
    middlewares: list[Middlewares] = None,
    exception_handlers: dict[int | Exception, callable] = None
)


Endpoints

Endpoints are the handlers for the server's routes, they define the behavior of the server when a client makes a request. It's in the endpoints that you define the logic of your server, like what to do when a client makes a request to a specific route.

Every endpoint is a class that inherits from the ones that are in the everysk.server.endpoints module. Normally you only need to implement the desired HTTP methods, like get, post, put, delete, etc.

BaseEndpoint

BaseEndpoint is a class that is used to create endpoints, we created it based on the Starlette HTTPEndpoint class. Use this class when you want to create an endpoint that returns a response that is not JSON or to create your own base class for your endpoints.

Example

from everysk.server.endpoints import BaseEndpoint
from everysk.server.responses import Response

class MyEndpoint(BaseEndpoint):
    async def get(self):
        return Response('<b>Hello, World!</b>')

Attributes

Private attributes - _request_class: Request -> The class that is used to create the request object. - _response_class: Response -> The class that is used to create the response object.

Public attributes - receive: Receive -> Starlette's Receive object. - request: Request -> Object that represents the request created by the _request_class. - scope: Scope -> Starlette's Scope object. - send: Send -> Starlette's Send object.

The dispatch method

The dispatch method is the method that is called when a client makes a request to the endpoint, it receives the request and returns a response. We changed it to insert the headers inside the log context, so you can see the chained logs in GCP. ** Please, don't override/touch the dispatch method **.

Payload content

The payload content is the content that is sent in the request, it can be a JSON, a form, a file, etc. To access the payload content, you can use the self.get_http_payload method, the type of the return could vary depending on the content type of the request and the implementation of the method.

If by any reason we receive the payload content in Gzip format, we automatically decompress it and set the cleaned content in the return of the self.get_http_payload.

JSONEndpoint

JSONEndpoint is a class that is used to create endpoints that return JSON responses, you can use it to create RESTful APIs.

Example

from everysk.server.endpoints import JSONEndpoint

class TestPrivateEndpoint(JSONEndpoint):
    rest_key_name: str = 'X-Api-Key'
    rest_key_value: str = '123456'

    async def get(self):
        return {'message': 'Hello, World Private!'}

Attributes

  • rest_key_name: str -> The name of the header that will be used to check the API key in the request.
  • rest_key_value: str -> The value of the header that will be used to check the API key in the request.

If the rest_key_name and rest_key_value are set, the endpoint will check if the request has the header with the name rest_key_name and the value rest_key_value, if it doesn't have it, the endpoint will return a 401 status code.

To create an endpoint that is public, you can set the rest_key_name and rest_key_value to None.

To avoid setting the rest_key_name and rest_key_value in every endpoint, you can create a base class that inherits from JSONEndpoint and set the values there or use settings.EVERYSK_SERVER_REST_KEY_NAME and settings.EVERYSK_SERVER_REST_KEY_VALUE to set the values globally.

The return of the methods

For the JSONEndpoint, the return of the methods should be a dictionary that will be converted to a JSON response or we could return a Response object.

from everysk.server.endpoints import JSONEndpoint
from everysk.server.responses import JSONResponse

class TestPrivateEndpoint(JSONEndpoint):
    rest_key_name: str = 'X-Api-Key'
    rest_key_value: str = '123456'

    async def get(self):
        return JSONResponse({'message': 'Hello, World Private!'})

RedirectEndpoint

RedirectEndpoint is a class that is used to create endpoints that return a redirect response to another host. It acts like a proxy, it receives a request and redirects it to another host and returns the response. It's useful to be used directly in the Routes with the RouteLazy method.

Example

RouteLazy('/anbima_feed_funds/{call_id}', endpoint='everysk.server.endpoints.RedirectEndpoint')

Attributes

  • host_url: str -> The host that the endpoint will redirect the request.
  • timeout: int -> The timeout in seconds that the endpoint will wait for the response from the redirected host, defaults to 600 seconds.

The host_url attribute is mandatory, if it's not set, the endpoint will raise an exception. You can inherit from the RedirectEndpoint and set the host_url in the class or set it in settings.EVERYSK_SERVER_REDIRECT_URL.

HealthCheckEndpoint

HealthCheckEndpoint is a class that is used to create an endpoint that returns a health check response. It's used directly in the Routes with the RouteLazy method.

Example

RouteLazy('/health_check', endpoint='everysk.server.endpoints.HealthCheckEndpoint')


Routes

Routes are the paths that clients can access on the server, they define the URL map of the server. Inside the routing module, you can find the Route class that is used to create the routes of the server and the RouteLazy class that is used to create the routes of the server lazily.

Example using Route

endpoint.py

from everysk.server.endpoints import JSONEndpoint

class TestPrivateEndpoint(JSONEndpoint):
    rest_key_name: str = 'X-Api-Key'
    rest_key_value: str = '123456'

    async def get(self):
        return {'message': 'Hello, World Private!'}

main.py

from endpoint import TestPrivateEndpoint
from everysk.server.routing import Route

routes = [
    Route(path='/private', endpoint=TestPrivateEndpoint)
]

Example using RouteLazy

When we use the RouteLazy class, we can pass the endpoint as a string, this way python will import the route only when a request arrives.

main.py

from everysk.server.routing import Route

routes = [
    Route(path='/private', endpoint='endpoint.TestPrivateEndpoint')
]


Middlewares

Middlewares are functions that can intercept requests and responses, allowing you to modify them before they reach the endpoint.

When you create an application you can pass a list of middlewares that will be executed in the order they are passed. We automatically add the GZipMiddleware and SecurityHeadersMiddleware to the middlewares list. Where GZipMiddleware is used to compress the response and SecurityHeadersMiddleware is used to add security headers to the response.

Creating your own middleware

To create your own middleware, you need to create a class that inherits from the everysk.server.middlewares.BaseMiddleware class and implement the dispatch method.

from everysk.server.middlewares import BaseMiddleware
from everysk.server.requests import Request

class MyMiddleware(BaseHTTPMiddleware):

    async def dispatch(self, request: Request, call_next: callable):
        response = await call_next(request)
        # Do something with the response
        .....
        return response