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:
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:
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:
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¶
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¶
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.