Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ The full guide lives in [`doc/`](./doc/index.md) — a small wiki that complemen
| Defining routes, route params, route groups, query strings | [Routing](./doc/routing.md) |
| `THorseRequest` / `THorseResponse` — body, headers, cookies, sessions, status, streaming | [Request & Response](./doc/request-response.md) |
| Using middleware, registration order, the `Next` proc | [Middleware](./doc/middleware.md) |
| Request lifecycle hooks — onRequest, preParsing, preValidation, onSend, onResponse | [Lifecycle Hooks](./doc/lifecycle-hooks.md) |
| **Writing & publishing your own middleware** — skeleton, thread safety, Provider neutrality, Boss packaging | [**Writing a Middleware**](./doc/writing-middleware.md) |
| **Choosing a transport provider** — Indy (default), CrossSocket, mORMot2, ICS, HttpSys, Apache, ISAPI, CGI, daemons | [**Providers**](./doc/providers.md) |
| **Deploy** as Console / VCL / Daemon / Windows Service / LCL / HTTPApplication — one-page recipe | [**Deployment Cheatsheet**](./doc/deployment.md) |
Expand Down
3 changes: 2 additions & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ O guia completo fica em [`doc/`](./doc/index.pt-BR.md) — um pequeno wiki que c
| Definir rotas, parâmetros de rota, grupos de rotas, query strings | [Roteamento](./doc/routing.pt-BR.md) |
| `THorseRequest` / `THorseResponse` — body, headers, cookies, sessions, status, streaming | [Request e Response](./doc/request-response.pt-BR.md) |
| Usar middleware, ordem de registro, o `Next` proc | [Middleware](./doc/middleware.pt-BR.md) |
| **Criar e publicar seu próprio middleware** — esqueleto, thread safety, neutralidade a Provider, empacotamento Boss | [**Criando um Middleware**](./doc/writing-middleware.pt-BR.md) |
| Ganchos de Ciclo de Vida — onRequest, preParsing, preValidation, onSend, onResponse | [Ganchos de Ciclo de Vida](./doc/lifecycle-hooks.pt-BR.md) |
| **Criar e publicar o seu próprio middleware** — esqueleto, thread safety, neutralidade de Provider, empacotamento Boss | [**Criando um Middleware**](./doc/writing-middleware.pt-BR.md) |
| **Escolher um provider de transporte** — Indy (padrão), CrossSocket, mORMot2, ICS, HttpSys, Apache, ISAPI, CGI, daemons | [**Providers**](./doc/providers.pt-BR.md) |
| **Deploy** como Console / VCL / Daemon / Serviço Windows / LCL / HTTPApplication — receita de uma página | [**Cheatsheet de Deploy**](./doc/deployment.pt-BR.md) |
| Catálogo completo de middlewares com descrições estendidas | [Ecossistema de Middlewares](./doc/middleware-ecosystem.pt-BR.md) |
Expand Down
2 changes: 2 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ graph TD
| [Routing](./routing.md) | `THorse.Get` / `Post` / `Put` / `Delete` / `Patch` / `Head` / `Use`; path params; route groups; wildcards; HTTP method enum. |
| [Request & Response](./request-response.md) | `THorseRequest` (body, params, query, headers, cookies, sessions, multipart). `THorseResponse` (`Send`, `Status`, `ContentType`, `AddHeader`, `RedirectTo`, `SendFile`, `Download`, `RawWebResponse`). |
| [Middleware](./middleware.md) | The `Next` proc model; built-in vs custom; registration order; per-route vs global. |
| [Lifecycle Hooks](./lifecycle-hooks.md) | Request lifecycle hooks (`onRequest`, `preParsing`, `preValidation`, `onSend`, `onResponse`) to extend and intercept request/response pipelines. |
| [Writing a Middleware](./writing-middleware.md) | Authoring a production-quality middleware: skeleton, configuration patterns, thread safety, Provider-neutral coding, cross-compiler pitfalls, testing matrix, Boss packaging, publishing. |
| [Providers & Application types](./providers.md) | The two-axis model: **Provider** (transport — Indy default; CrossSocket, mORMot2, ICS optional; HttpSys, epoll and IOCP built-in) × **Application type** (Console / VCL / Daemon / LCL / HTTPApplication, plus host-managed Apache / ISAPI / CGI / FCGI). Compatibility matrix and selection guidance. |
| [Middleware Ecosystem](./middleware-ecosystem.md) | Official `HashLoad/*` packages and the community-maintained list. |
Expand All @@ -59,6 +60,7 @@ doc/
├── routing.md ← URL → handler binding
├── request-response.md ← THorseRequest and THorseResponse API
├── middleware.md ← chaining handlers
├── lifecycle-hooks.md ← request lifecycle hooks (onRequest, etc.)
├── providers.md ← choosing a transport
├── iocp.md ← Windows async I/O completion ports
├── epoll.md ← Linux async event loop
Expand Down
2 changes: 2 additions & 0 deletions doc/index.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ graph TD
| [Roteamento](./routing.pt-BR.md) | `THorse.Get` / `Post` / `Put` / `Delete` / `Patch` / `Head` / `Use`; parâmetros de caminho; grupos de rotas; wildcards; enum de método HTTP. |
| [Request e Response](./request-response.pt-BR.md) | `THorseRequest` (body, params, query, headers, cookies, sessions, multipart). `THorseResponse` (`Send`, `Status`, `ContentType`, `AddHeader`, `RedirectTo`, `SendFile`, `Download`, `RawWebResponse`). |
| [Middleware](./middleware.pt-BR.md) | O modelo `Next` proc; built-in vs custom; ordem de registro; por-rota vs global. |
| [Ganchos de Ciclo de Vida](./lifecycle-hooks.pt-BR.md) | Ganchos de ciclo de vida (`onRequest`, `preParsing`, `preValidation`, `onSend`, `onResponse`) para estender e interceptar requisições. |
| [Criando um Middleware](./writing-middleware.pt-BR.md) | Criando um middleware de qualidade de produção: esqueleto, padrões de configuração, thread safety, código neutro a Provider, armadilhas entre compiladores, matriz de testes, empacotamento Boss, publicação. |
| [Providers e Tipos de aplicação](./providers.pt-BR.md) | O modelo de dois eixos: **Provider** (transporte — Indy padrão; CrossSocket, mORMot2, ICS opcionais; HttpSys, epoll e IOCP embutidos) × **Tipo de aplicação** (Console / VCL / Daemon / LCL / HTTPApplication, mais host-managed Apache / ISAPI / CGI / FCGI). Matriz de compatibilidade e guia de escolha. |
| [Ecossistema de Middlewares](./middleware-ecosystem.pt-BR.md) | Pacotes oficiais `HashLoad/*` e a lista mantida pela comunidade. |
Expand All @@ -59,6 +60,7 @@ doc/
├── routing.*.md ← ligação URL → handler
├── request-response.*.md ← API de THorseRequest e THorseResponse
├── middleware.*.md ← encadeamento de handlers
├── lifecycle-hooks.*.md ← ganchos de ciclo de vida (onRequest, etc.)
├── providers.*.md ← escolha de transporte
├── iocp.*.md ← portas de conclusão assíncronas (Windows)
├── epoll.*.md ← laço de eventos assíncronos (Linux)
Expand Down
181 changes: 181 additions & 0 deletions doc/lifecycle-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Lifecycle Hooks

*Read this in [English](./lifecycle-hooks.md) or [Português (BR)](./lifecycle-hooks.pt-BR.md).*

The **Lifecycle Hooks** in Horse provide standardized, guaranteed extension points throughout the lifecycle of an HTTP request.

Unlike traditional middleware, hooks run at precise architectural phases, allowing you to intercept and manipulate request/response data without relying on the ordering of middlewares in the `Next` chain.

---

## 🗺️ Request Lifecycle Flow

When an HTTP request hits the Horse server, the request pipeline strictly follows this sequence:

```mermaid
sequenceDiagram
autonumber
participant Client
participant Core as Horse Core
participant Router as Router / Radix
participant Hooks as Local Hooks

Client->>Core: HTTP Request
Note over Core: Phase 1: onRequest
Core->>Core: Run global onRequest hooks

Note over Core: Phase 2: preParsing
Core->>Core: Run global preParsing hooks

Core->>Router: Resolve Routing
Note over Router: Phase 3: preValidation
Router->>Router: Run preValidation hooks (before Endpoint)

Router->>Hooks: Run route middleware & endpoint
Hooks->>Hooks: Res.Send(Payload)

Note over Hooks: Phase 4: onSend
Hooks->>Hooks: Run onSend hooks (modify Payload)
Hooks->>Client: Send Physical HTTP Response

Note over Core: Phase 5: onResponse (Guaranteed in finally)
Core->>Core: Run onResponse hooks (Audit/Cleanup)
```

---

## 🔌 1. onRequest (Entry Phase)

The `onRequest` hook is executed at the very beginning of the request handling, **before routing** and before checking any route path segments.

* **Signature:** `THorseCallback`
* **Use Cases:**
* Simple firewalls (WAF) or IP blacklisting.
* Fast global validation that shouldn't pay the performance price of a complex router lookup.
* Modifying or injecting early request headers.

### Example:
```delphi
THorse.AddOnRequest(
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
// Block requests without a corporate ID header
if Req.Headers['X-Corporate-ID'] = '' then
Res.Send('Unauthorized').Status(THTTPStatus.Unauthorized)
else
Next; // Continue to the next phase
end);
```

---

## 📦 2. preParsing (Raw Payload Phase)

The `preParsing` hook runs after `onRequest` but before any body parsing middleware (like the `Jhonson` JSON parser) reads or interprets the request `Body`.

* **Signature:** `THorseCallback`
* **Use Cases:**
* Decrypting incoming payloads. If the request body is encrypted, you can decrypt and re-inject it so that downstream JSON parser middlewares see the clean, unencrypted JSON.
* Uncompressing custom input compression formats.

### Example:
```delphi
THorse.AddPreParsing(
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
// Decrypt the raw body received from the client before parsing it to JSON
var RawEncrypted := Req.Body;
var DecryptedJSON := MyCryptoHelper.Decrypt(RawEncrypted);
Req.Body(DecryptedJSON); // Replace the body in the request
Next;
end);
```

---

## 🛡️ 3. preValidation (Rules Phase)

The `preValidation` hook is executed once the active route is resolved, but **before** executing the first route-level middleware or the endpoint handler.

* **Signature:** `THorseCallback`
* **Use Cases:**
* Declarative validation (such as DTO schema validation).
* Validating permissions and authentication tokens specific to the active route.

### Example:
```delphi
THorse.AddPreValidation(
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
// Example: JWT token validation specific to the resolved route
if not IsTokenValidForRoute(Req.MatchedRoute, Req.Headers['Authorization']) then
Res.Send('Forbidden').Status(THTTPStatus.Forbidden)
else
Next;
end);
```

---

## ✉️ 4. onSend (Send Phase)

The `onSend` hook intercepts calls to `Res.Send(string)` and `Res.Send(TBytes)` right before the payload is physically written to the client socket or socket provider. It allows modifying the response body "in transit".

* **Signature:**
* `THorseOnSendString = reference to procedure(const Req: THorseRequest; const Res: THorseResponse; var AContent: string);`
* `THorseOnSendBytes = reference to procedure(const Req: THorseRequest; const Res: THorseResponse; var AContent: TBytes);`
* **Use Cases:**
* Auto-encrypting outgoing responses.
* Automatically appending digital signatures, watermarks, or formatting the payload right before sending.

### Example (String):
```delphi
THorse.AddOnSend(
procedure(const Req: THorseRequest; const Res: THorseResponse; var AContent: string)
begin
// Encrypt the outgoing response JSON transparently before sending to the client
AContent := MyCryptoHelper.Encrypt(AContent);
end);
```

---

## 🏁 5. onResponse (Exit / Guaranteed Phase)

The `onResponse` hook executes at the very exit of the request lifecycle. It is wrapped inside a `try..finally` block at the transport layer of the Provider, which guarantees that it **always runs**, regardless of any controller exceptions (like Access Violations or Database connection errors).

* **Signature:** `THorseCallback`
* **Use Cases:**
* Auditing requests (logging the final physical HTTP status code).
* Colecting performance metrics (Telemetry/OpenTelemetry).
* Request-scoped resource cleanup (releasing database connections or transaction scopes allocated for the current thread).

### Example:
```delphi
THorse.AddOnResponse(
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
try
// Always close/release the database connection opened for this specific thread
ReleaseConnectionForCurrentThread;
finally
Next;
end;
end);
```

---

## 🧵 Thread Safety and Concurrency

Since Horse handles HTTP requests concurrently using multiple worker threads (in socket providers like Indy, epoll, or IOCP):
* All hooks run under the context of the worker thread processing the active request.
* You can pass state safely between different hooks using the thread-safe request dictionary (`Req.State`).

---

## 🚀 Executable Samples

You can find ready-to-run audit projects to see hooks executing in real-time (compatible with Windows, Linux, and macOS):
* **Delphi (Windows/Linux):** [samples/delphi/console_lifecycle_hooks/ConsoleLifecycleHooks.dpr](file:///d:/Delphi/horse/samples/delphi/console_lifecycle_hooks/ConsoleLifecycleHooks.dpr)
* **Lazarus/FPC (Windows/Linux/macOS):** [samples/lazarus/console_lifecycle_hooks/ConsoleLifecycleHooks.lpr](file:///d:/Delphi/horse/samples/lazarus/console_lifecycle_hooks/ConsoleLifecycleHooks.lpr)
Loading