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..ca7ef01 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
@@ -148,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
@@ -194,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())
+```