From 04c36982b4e1fc7199d7bc58c83af6f7fb53173e Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sun, 22 Jun 2025 22:07:14 +0200 Subject: [PATCH 1/2] Document changes to sessions --- blacksheep/docs/css/extra.css | 5 ++ blacksheep/docs/sessions.md | 159 +++++++++++++++++++++++++++++++--- blacksheep/docs/settings.md | 23 +++-- 3 files changed, 165 insertions(+), 22 deletions(-) diff --git a/blacksheep/docs/css/extra.css b/blacksheep/docs/css/extra.css index c2c769c..ae4423f 100644 --- a/blacksheep/docs/css/extra.css +++ b/blacksheep/docs/css/extra.css @@ -97,6 +97,11 @@ span.task-list-indicator { --nt-color-7: #108d10; } +:root>* +{ + --md-mermaid-sequence-number-bg-color: #00363e; +} + .version-warning { margin-top: 5px !important; } diff --git a/blacksheep/docs/sessions.md b/blacksheep/docs/sessions.md index 02e8cb8..5546025 100644 --- a/blacksheep/docs/sessions.md +++ b/blacksheep/docs/sessions.md @@ -1,21 +1,87 @@ # Sessions -This page describes features to support sessions, handled with digitally signed -cookies. +Sessions are a mechanism used in web applications to persist user-specific data +across multiple requests. Since HTTP is a stateless protocol, each request from +a client is independent and does not retain information about previous +interactions. Sessions solve this by allowing the server to associate requests +with a particular user, typically by issuing a unique session identifier (often +stored in a cookie). This enables features like user authentication, shopping +carts, and personalized experiences. -/// admonition | Sessions are stored in cookies. - type: danger +This page describes built-in features to support sessions. -The built-in sessions store data in cookies on the client side. Therefore, web -applications using these features must implement [Anti-Forgery -validation](anti-request-forgery.md) to prevent Cross-Site Request Forgery -(XSRF/CSRF). +--- + +/// admonition | Sessions have been improved in `2.4.0`. + type: info + +The built-in sessions have been improved in version `2.4.0` to support any kind +of store. Older versions of BlackSheep included built-in support for sessions +but offered only built-in support for storing sessions information in cookies. +Since version `2.4.0`, any kind of store is supported, through the +`SessionStore` interface described below in this page. /// -## Enabling sessions +## Sessions flows + +The diagram below illustrates an high-level overview of how session works: + +```mermaid +sequenceDiagram + autonumber + participant Client + participant Server + + Client->>Server: HTTP Request (no session) + Server->>Server: Create session data + Server-->>Client: HTTP Response (include session information) + Client->>Server: HTTP Request (include session information) + Server->>Server: Obtain the whole session data using session information + Server-->>Client: HTTP Response (can update session information) + Note over Client,Server: Session information is exchanged via cookies
or custom request headers.
Session information can consist of the Session ID alone,
or include a signed token of the whole session data. +``` + +The session information exchanged with the client can either be a _session ID_, +—in this case the actual session data is stored server-side— or the entire +session data contained within a digitally signed token. + +### Example: session data stored in dedicated service + +The diagram below illustrates a more specific scenario, in which the server +exchanges with the client only a _session ID_, and the whole session data is +stored in a third-party service such as [PostgreSQL](https://www.postgresql.org/) +server, or [Valkey](https://github.com/valkey-io/valkey) server. + +```mermaid +sequenceDiagram + autonumber + participant Client + participant Server + participant Store as Session Store
(e.g. PostgreSQL, Valkey) + + Client->>Server: HTTP Request (no session) + Server->>Store: Create and store session data + Store-->>Server: Session ID + Server-->>Client: HTTP Response (Set-Cookie: session ID) + Client->>Server: HTTP Request (Cookie: session ID) + Server->>Store: Retrieve session data using session ID + Store-->>Server: Session data + Server-->>Client: HTTP Response (may update Set-Cookie) +``` + +In this scenario, Session ID is exchanged between client and server via +cookies. Full session data is stored in a third-party store. The server uses +the session ID from the cookie to restore session data for each request. + +In alternative to cookies, it is possible to use other ways to exchange +information between the client and the server, such as custom response and +request headers. + +## Enabling sessions, with cookie store -To enable sessions, use the `app.use_sessions` method as in the example below: +To enable sessions and have sessions stored in cookies, use the +`app.use_sessions` method as in the example below: ```python from blacksheep import Application, Request, get, text @@ -35,7 +101,10 @@ def home(request: Request): return text(session["example"]) ``` -The `use_sessions` method accepts the following parameters: +The `` is a key used to digitally sign and protect from tampering, +this key must be protected and not shared with clients. + +For this scenario, the `use_sessions` method accepts the following parameters: | Name | Description | Defaults to | | --------------- | -------------------------------------------------------------------------------------- | ------------------------------------- | @@ -48,7 +117,7 @@ The `use_sessions` method accepts the following parameters: ```python def use_sessions( self, - secret_key: str, + store: str, *, session_cookie: str = "session", serializer: Optional[SessionSerializer] = None, @@ -64,13 +133,75 @@ encrypt, and verify session cookies. Refer to [data protection](dataprotection.md) for more information on how tokens are signed and encrypted. +Before version `2.4.0`, BlackSheep offered built-in support only for sessions +stored entirely on the client side, in cookies. Using sessions with a different +store required custom code. + +## Enabling sessions, with custom store + +Starting from `2.4.0`, the built-in classes for sessions include support for +custom stores. To enable sessions and have sessions stored in the desired way, +implement a type of `SessionStore` like in the following example: + +```python +from blacksheep.sessions.abc import Session, SessionStore + + +class MySessionStore(SessionStore): + + async def load(self, request: Request) -> Session: + """Load the session for the given request.""" + ... + + async def save( + self, request: Request, response: Response, session: Session + ) -> None: + """Save the session related to the given request-response cycle.""" + ... +``` + +And then call the `app.use_sessions` passing as first argument an instance of +your custom store type. + +```python +from blacksheep import Application, Request, get, text + +from myapp.sessions import MySessionStore + +app = Application() + + +app.use_sessions(MySessionStore(...)) +``` + +When this option is used, all other parameters to the `use_sessions` method are +ignored. + +### InMemorySessionStore + +Since version `2.4.0`, an `InMemorySessionStore` is included in the +`blacksheep.sessions.memory` namespace. + +```python +from blacksheep.sessions.memory import InMemorySessionStore +``` + +This kind of store is an in-memory implementation of `SessionStore` for +managing user sessions. The session ID is transmitted in cookies, to restore +the same session information across request-response cycles. + +This session store keeps session data in a Python dictionary, mapping session +IDs to session data. It is suitable for development and testing environments, +but not recommended for production use as session data will be lost when the +application restarts and is not shared across multiple processes or servers. + ## Using sessions When sessions are enabled, they are always populated for the `request` object, and can be accessed through the `request.session` property. -The sessions middleware takes care of setting a response cookie whenever the -session is modified, session cookies are signed and encrypted by default. +The sessions middleware takes care of saving session information whenever the +session is modified. ```python @get("/") diff --git a/blacksheep/docs/settings.md b/blacksheep/docs/settings.md index ceef94b..02e0a42 100644 --- a/blacksheep/docs/settings.md +++ b/blacksheep/docs/settings.md @@ -9,14 +9,21 @@ This page describes: ## Environmental variables -| Name | Category | Description | -| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| APP_ENV | Settings | This environment variable is read to determine the environment of the application. For more information, refer to [_Defining application environment_](/blacksheep/settings/#defining-application-environment). | -| APP_SHOW_ERROR_DETAILS | Settings | If "1" or "true", configures the application to display web pages with error details in case of HTTP 500 Internal Server Error. | -| APP_MOUNT_AUTO_EVENTS | Settings | If "1" or "true", automatically binds lifecycle events of mounted apps between children and parents BlackSheep applications. | -| APP_ROUTE_PREFIX | Settings | Allows configuring a global prefix for all routes handled by the application. For more information, refer to: [Behind proxies](/blacksheep/behind-proxies/). | -| APP_SECRET_i | Secrets | Allows configuring the secrets used by the application to protect data. | -| BLACKSHEEP_SECRET_PREFIX | Secrets | Allows specifying the prefix of environment variables used to configure application secrets, defaults to "APP_SECRET" if not specified. | +| Name | Category | Description | +| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| APP_ENV | Settings | This environment variable is read to determine the environment of the application. For more information, refer to [_Defining application environment_](/blacksheep/settings/#defining-application-environment). | +| APP_DEFAULT_ROUTER | Settings | Allows disabling the use of the default router exposed by BlackSheep. To disable the default router: `APP_DEFAULT_ROUTER=0`. | +| APP_SIGNAL_HANDLER | Settings | Allows enabling a callback to the `{signal.SIGINT, signal.SIGTERM}` signals to detect when the application process is stopping. Information is used in `blacksheep.server.process.is_stopping()` and is useful when handling long-lasting connections. See _[Server-Sent Events](./server-sent-events.md)_. | +| APP_JINJA_EXTENSION | Settings | Allows configuring the file extension used to work with Jinja templates. By default it is `.jinja`. | +| APP_JINJA_PACKAGE_NAME | Settings | Allows configuring the package name used by the default Jinja templates loader. By default it is `app` (as by default views are loaded from `app/views`. | +| APP_JINJA_PACKAGE_PATH | Settings | Allows configuring the path used by the default Jinja templates loader. By default it is `views` (as by default views are loaded from `app/views`. | +| APP_JINJA_DEBUG | Settings | Allows enabling Jinja debug mode, setting this env variable to a truthy value. | +| APP_JINJA_ENABLE_ASYNC | Settings | Allows enabling Jinja async mode, setting this env variable to a truthy value. | +| APP_SHOW_ERROR_DETAILS | Settings | If "1" or "true", configures the application to display web pages with error details in case of HTTP 500 Internal Server Error. | +| APP_MOUNT_AUTO_EVENTS | Settings | If "1" or "true", automatically binds lifecycle events of mounted apps between children and parents BlackSheep applications. | +| APP_ROUTE_PREFIX | Settings | Allows configuring a global prefix for all routes handled by the application. For more information, refer to: [Behind proxies](/blacksheep/behind-proxies/). | +| APP_SECRET_i | Secrets | Allows configuring the secrets used by the application to protect data. | +| BLACKSHEEP_SECRET_PREFIX | Secrets | Allows specifying the prefix of environment variables used to configure application secrets, defaults to "APP_SECRET" if not specified. | ## Defining application environment From c41a53ac72e96bef4a02e011a715dfc41a38e5b6 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sun, 22 Jun 2025 22:44:42 +0200 Subject: [PATCH 2/2] Document encodings settings --- blacksheep/docs/settings.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/blacksheep/docs/settings.md b/blacksheep/docs/settings.md index 02e0a42..ca7ef01 100644 --- a/blacksheep/docs/settings.md +++ b/blacksheep/docs/settings.md @@ -155,6 +155,7 @@ def my_json(data: Any, status: int = 200) -> Response: ``` ### Example: applying transformations during JSON operations + The example below illustrates how to apply transformations to objects while they are serialized and deserialized. Beware that the example only illustrates this possibility, it doesn't handle objects inside lists, `@dataclass`, or @@ -201,3 +202,32 @@ json_settings.use( dumps=custom_dumps, ) ``` + +## Encodings settings + +Starting from version `2.4.0`, the framework provides settings to control how +`UnicodeDecodeError` exceptions are handled when parsing request bodies. + +By default the framework raises an exception when the client sends a payload +specifying the wrong encoding in the `Content-Type` request header, or when +the client sends a payload that is not `UTF-8` encoded and without specifying +the charset encoding. + +To customize the handling of `UnicodeDecodeError` and implement possible +fallbacks, import the `Decoder` class from the `blacksheep.settings.encodings` +namespace and create your own implementation. Subclasses of this class define a +strategy for decoding bytes into strings when a `UnicodeDecodeError` occurs +during standard decoding. You must implement the `decode` method, which +receives the bytes to decode and the original `UnicodeDecodeError`. + +```python +from blacksheep.settings.encodings import Decoder, encodings_settings + + +class MyDecoder(Decoder): + """Implements the Decoder interface.""" + def decode(self, value: bytes, decode_error: UnicodeDecodeError) -> str: ... + + +encodings_settings.use(MyDecoder()) +```