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
21 changes: 21 additions & 0 deletions doc/epoll.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ ulimit -n
ulimit -n 65535
```

## 🚀 Production Tuning for Linux

For extreme loads and high-performance setups, tune the Linux kernel parameters in `/etc/sysctl.conf`:

```ini
# Increase max queued connections backlog
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# Enable fast reuse of TIME_WAIT sockets
net.ipv4.tcp_tw_reuse = 1

# Increase ephemeral outbound port range
net.ipv4.ip_local_port_range = 1024 65535

# Increase max open file descriptors limit globally
fs.file-max = 2097152
```

Run `sysctl -p` to apply kernel configurations immediately.

---

## ⚡ Quick Start
Expand Down
21 changes: 21 additions & 0 deletions doc/epoll.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ ulimit -n
ulimit -n 65535
```

## 🚀 Tuning do Linux para Produção

Para extrair o máximo de desempenho sob carga de trabalho intensa, configure o kernel do Linux com os seguintes parâmetros no arquivo `/etc/sysctl.conf`:

```ini
# Aumenta a fila de conexões TCP pendentes (backlog)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# Permite a reutilização rápida de conexões em estado TIME_WAIT
net.ipv4.tcp_tw_reuse = 1

# Aumenta a faixa de portas efêmeras disponíveis
net.ipv4.ip_local_port_range = 1024 65535

# Aumenta o limite máximo de descritores de arquivos abertos globalmente
fs.file-max = 2097152
```

Execute `sysctl -p` para aplicar as configurações de kernel imediatamente.

---

## ⚡ Início Rápido (Quick Start)
Expand Down
17 changes: 17 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ Welcome. This is the documentation hub for [Horse](https://github.com/HashLoad/h

If you're new, start with [Getting Started](./getting-started.md). If you have a working server and want to make a specific change, jump straight to the relevant topic below.

## Middleware Execution Flow

When an HTTP request reaches the Horse server, it flows through the middleware layers in the following precedence order:

```mermaid
graph TD
A[HTTP Request] --> B[Global Middlewares<br>ex: CORS, Johnson]
B --> C{Belongs to a Group?}
C -- Yes --> D[Group Middlewares<br>ex: Restricted Auth]
C -- No --> E{Has Route-level Middlewares?}
D --> E
E -- Yes --> F[Route-level Middlewares<br>ex: Logs, Custom Checks]
E -- No --> G[Final Route Handler<br>Executes logic]
F --> G
G --> H[HTTP Response]
```

---

## Reading order for newcomers
Expand Down
17 changes: 17 additions & 0 deletions doc/index.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ Bem-vindo. Este é o índice da documentação do [Horse](https://github.com/Has

Se você é novo por aqui, comece por [Primeiros passos](./getting-started.pt-BR.md). Se já tem um servidor rodando e quer fazer uma alteração específica, vá direto ao tópico relevante abaixo.

## Fluxo de Execução de Middlewares

Quando uma requisição HTTP chega ao servidor Horse, ela passa pelas camadas de middlewares na seguinte ordem de precedência:

```mermaid
graph TD
A[Requisição HTTP] --> B[Middlewares Globais<br>ex: CORS, Johnson]
B --> C{Pertence a um Grupo?}
C -- Sim --> D[Middlewares de Grupo<br>ex: Auth Restrita]
C -- Não --> E{Tem Middlewares na Rota?}
D --> E
E -- Sim --> F[Middlewares Locais da Rota<br>ex: Log, Validações]
E -- Não --> G[Handler Final da Rota<br>Executa a Lógica]
F --> G
G --> H[Resposta HTTP]
```

---

## Ordem de leitura para iniciantes
Expand Down
26 changes: 15 additions & 11 deletions doc/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,32 @@ The `try / finally` pattern is the canonical way to wrap the entire request —

## Registration

`THorse.Use(...)` accepts:
You can register middlewares at different scopes in Horse:

1. **Global**: Registered via `THorse.Use(...)` affecting all routes (or wildcard paths).
2. **Group-level**: Registered via `.Use(...)` inside a route group (`THorse.Group`).
3. **Route-level (Local)**: Passed as an array (`array of THorseCallback`) directly into the HTTP verb of the route.

```delphi
THorse.Use(MyMiddleware); // applies to every request
THorse.Use('/api', MyMiddleware); // applies only to /api/*
THorse.Use(MyGlobalMiddleware); // Global

THorse.Group.Prefix('/admin')
.Use(RequireAuth) // applies only to /admin/*
.Get('/users', ListUsers);
.Use(MyGroupMiddleware) // Group-level
.Get('/users', [MyRouteMiddleware], ListUsers); // Route-level (local)
```

**Registration order matters.** Middleware runs in the order it was registered, in a nested onion model:
**Registration order matters.** Middleware runs in the order it was registered/mapped, in a nested onion model:

```
THorse.Use(A); // outermost
THorse.Use(B);
THorse.Use(C); // innermost
THorse.Get('/x', Handler);
THorse.Use(A); // Global (outermost)
THorse.Group.Prefix('/admin')
.Use(B) // Group-level
.Get('/x', [C], Handler); // Route-level (innermost)
```

Request flow:
```
A → B → C → Handler → C → B → A
A (Global) → B (Group) → C (Route) → Handler → C → B → A
```

…where the right-hand side of each arrow is the code that runs after `Next()` returns. So `A` runs first and gets the last word; `C` wraps the handler most tightly.
Expand Down
26 changes: 15 additions & 11 deletions doc/middleware.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,32 @@ O padrão `try / finally` é a forma canônica de envolver a requisição inteir

## Registro

`THorse.Use(...)` aceita:
Você pode registrar middlewares em diferentes escopos no Horse:

1. **Globais**: Registrados via `THorse.Use(...)` afetando todas as rotas (ou caminhos wildcard).
2. **De Grupo**: Registrados via `.Use(...)` dentro de um grupo de rotas (`THorse.Group`).
3. **Locais**: Passados como um array (`array of THorseCallback`) diretamente no verbo HTTP da rota.

```delphi
THorse.Use(MyMiddleware); // aplica a toda requisição
THorse.Use('/api', MyMiddleware); // aplica apenas a /api/*
THorse.Use(MyGlobalMiddleware); // Global

THorse.Group.Prefix('/admin')
.Use(RequireAuth) // aplica apenas a /admin/*
.Get('/users', ListUsers);
.Use(MyGroupMiddleware) // De Grupo
.Get('/users', [MyRouteMiddleware], ListUsers); // Local (de rota)
```

**A ordem de registro importa.** O middleware roda na ordem em que foi registrado, num modelo cebola aninhado:
**A ordem de registro importa.** O middleware roda na ordem em que foi registrado/mapeado, num modelo cebola aninhado:

```
THorse.Use(A); // mais externo
THorse.Use(B);
THorse.Use(C); // mais interno
THorse.Get('/x', Handler);
THorse.Use(A); // Global (mais externo)
THorse.Group.Prefix('/admin')
.Use(B) // De Grupo
.Get('/x', [C], Handler); // Local de Rota (mais interno)
```

Fluxo da requisição:
```
A → B → C → Handler → C → B → A
A (Global) → B (Grupo) → C (Rota) → Handler → C → B → A
```

…onde o lado direito de cada seta é o código que roda após o `Next()` retornar. Então `A` roda primeiro e tem a última palavra; `C` envolve o handler mais de perto.
Expand Down
30 changes: 30 additions & 0 deletions doc/request-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,36 @@ The framework converts the exception to a JSON error response. Any other uncaugh

To intercept exceptions globally, register the `handle-exception` middleware ([`HashLoad/handle-exception`](https://github.com/HashLoad/handle-exception)) — it formats your errors consistently.

## Chunked Transfer Encoding (Streaming Responses)

When you need to send large payloads (like large report files or live video/audio streams) without loading the entire content into memory at once, use **Chunked Transfer Encoding**:

```delphi
THorse.Get('/data/heavy',
procedure(Req: THorseRequest; Res: THorseResponse)
var
Stream: TStringStream;
begin
// Indicated chunked transfer encoding
Res.AddHeader('Transfer-Encoding', 'chunked');
Res.ContentType('text/plain');

// Write portions of data
Stream := TStringStream.Create('First chunk of text...');
try
Res.Send(Stream.DataString);
// Horse handles the underlying socket chunking automatically based on the Provider
finally
Stream.Free;
end;
end);
```

## Best Practices with Large Payloads (Upload/Download)

* **Avoid loading entire files into String or MemoryStream**: Always use a `TFileStream` to read from or write to disk directly, passing the stream reference to `Res.SendFile` or `Res.Download`. Horse will stream the file in small chunks (usually 8KB), keeping the server's memory consumption flat.
* **Request body size limits**: On Indy (default provider), you can configure max content length or timeouts directly in the underlying server instance (`THorse.RawWebserver`).

---

## Provider-specific notes
Expand Down
30 changes: 30 additions & 0 deletions doc/request-response.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,36 @@ O framework converte a exception numa resposta JSON de erro. Qualquer outra exce

Para interceptar exceptions globalmente, registre o middleware `handle-exception` ([`HashLoad/handle-exception`](https://github.com/HashLoad/handle-exception)) — ele formata seus erros de forma consistente.

## Envio de Respostas Fragmentadas (Chunked Transfer)

Quando você precisa enviar grandes volumes de dados (como relatórios massivos ou streams de áudio/vídeo) sem carregar todo o conteúdo na memória de uma só vez, você deve utilizar o envio fragmentado (**Chunked Transfer Encoding**):

```delphi
THorse.Get('/dados/pesados',
procedure(Req: THorseRequest; Res: THorseResponse)
var
Stream: TStringStream;
begin
// Indica que o conteúdo será fragmentado
Res.AddHeader('Transfer-Encoding', 'chunked');
Res.ContentType('text/plain');

// Você pode fazer loops escrevendo pedaços de dados na resposta
Stream := TStringStream.Create('Primeiro pedaço de texto...');
try
Res.Send(Stream.DataString);
// O Horse gerencia o envio fragmentado de forma transparente dependendo do Provider
finally
Stream.Free;
end;
end);
```

## Boas Práticas com Grandes Payloads (Upload/Download)

* **Evite carregar arquivos inteiros em String/MemoryStream**: Use `TFileStream` para ler diretamente do disco ou gravar no disco, passando o stream para `Res.SendFile` ou `Res.Download`. O Horse fará o streaming do arquivo em blocos pequenos (geralmente 8KB), mantendo o consumo de memória do servidor constante.
* **Limites de tamanho de requisição**: No Indy (provider padrão), você pode configurar o tamanho máximo de requisição ou timeout diretamente nas propriedades do servidor subjacente (`THorse.RawWebserver`).

---

## Notas específicas de provider
Expand Down
13 changes: 8 additions & 5 deletions doc/roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ Este documento detalha o planejamento de melhorias arquiteturais de longo prazo
* Redução geral de alocação de memória no heap (*zero-allocation response mapping*).
* Todos os providers do ecossistema (incluindo Indy legado) passariam a se beneficiar de envio zero-copy automaticamente, sem precisar reimplementar essa lógica individualmente.

### 3. Cadeias de Middlewares por Rota (*Route-level Middleware Chains*)
* **Descrição:** Permitir declarar arrays de middlewares específicos diretamente na definição de um endpoint ou grupo de rotas.
* **Ganhos:**
* Elimina a necessidade de registrar middlewares globais ou criar múltiplos controllers.
* Possibilita isolar autenticações (JWT/BasicAuth) a nível de rota com Fluent API: `THorse.Get('/secure', [Auth, Logger], Handler)`.

### 4. Desligamento Suave (*Graceful Shutdown*)
* **Descrição:** Implementar encerramento coordenado de conexões. O servidor encerra a escuta de novos sockets mas conclui requisições ativas dentro de um tempo limite de segurança.
Expand Down Expand Up @@ -76,10 +71,18 @@ Este documento detalha o planejamento de melhorias arquiteturais de longo prazo
### 13. Servidor de Arquivos Estáticos Otimizado com Suporte a Range (*Static File Streaming*)
* **Descrição:** Middleware robusto e otimizado para entrega de arquivos físicos locais, incluindo cabeçalhos de controle de cache (`Cache-Control`, `ETags`) e suporte a requisições parciais (HTTP 206 para streaming de mídia).


## ✅ Evolução Arquitetural Entregue (Concluído)

### 1. Cadeias de Middlewares por Rota (*Route-level Middleware Chains*)
* **Status:** 🟢 **Concluído e Liberado**
* **Implementação:** Permitida a declaração de múltiplos middlewares locais de rotas via Open Arrays (`array of THorseCallback`) em formato estático e fluente. Compatibilidade total de retrocompatibilidade e compilação multiplataforma.

---

## ✅ Entregas Recentes de Testes & CI/CD (Concluído)


### 1. Testes de Conexões Persistentes (HTTP Keep-Alive)
* **Status:** 🟢 **Concluído e Liberado**
* **Implementação:** Desenvolvida a unit `Tests.Integration.KeepAlive.pas` validando a persistência e a conformidade dos cabeçalhos do protocolo HTTP/1.1 sob requisições sucessivas sobre o mesmo socket de conexão física.
Expand Down
2 changes: 1 addition & 1 deletion doc/roadmap/prioritization_matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Esta tabela classifica as 13 melhorias pendentes do roadmap técnico do Horse co

| # | Funcionalidade | Categoria | Impacto (1-5) | Dificuldade (1-5) | ROI (Imp/Dif) | Impacto Usuário Final | Classificação / Ação |
|---|---|---|:---:|:---:|:---:|---|---|
| 3 | **Cadeia de Middlewares por Rota** | DX / Legibilidade | 5 | 2 | **2.50** | ➕ **Novo Recurso** (Opcional) | 🚀 **Quick Win** (Alto impacto, baixo esforço) |
| 3 | **Cadeia de Middlewares por Rota** | DX / Legibilidade | 5 | 2 | **2.50** | ➕ **Novo Recurso** (Opcional) | 🟢 **Concluído** (Implementado e Liberado) |
| 5 | **Pipeline Global de Erros (OnError)** | DX / Robustez | 4 | 2 | **2.00** | ➕ **Novo Recurso** (Opcional) | 🚀 **Quick Win** (Implementação direta) |
| 12 | **Middleware de Rate Limiting** | Segurança | 4 | 2 | **2.00** | ➕ **Novo Middleware** (Opcional) | 🚀 **Quick Win** (Excelente ganho de segurança) |
| 11 | **Middleware de Compressão (Gzip)** | Otimização | 4 | 3 | **1.33** | ➕ **Novo Middleware** (Opcional) | 📅 **Planejar execução** (Uso do `System.ZLib`) |
Expand Down
20 changes: 20 additions & 0 deletions doc/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ THorse.Group
.Post('/audit', WriteAudit);
```

## Route-level Middlewares (Local)

You can pass an array of route-specific middlewares (`array of THorseCallback`) to apply checks only to a single endpoint:

```delphi
// Static Syntax with array of middlewares
THorse.Get('/admin/dashboard', [AuthMiddleware, LoggerMiddleware],
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('Admin Dashboard');
end);

// Fluent Route Syntax with array of middlewares
THorse.Route('/reports')
.Get([AuthMiddleware, LoggerMiddleware], GetReportsHandler)
.Post([AuthMiddleware], PostReportHandler);
```

Route-level middlewares execute after global middlewares and after any group-level middlewares, but right before the final route callback (handler) executes.

## Wildcard middleware

`THorse.Use(...)` registers middleware that runs on every request, regardless of path:
Expand Down
20 changes: 20 additions & 0 deletions doc/routing.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ THorse.Group
.Post('/audit', WriteAudit);
```

## Middlewares por Rota (Locais)

Você pode passar uma cadeia de middlewares específicos para uma única rota em formato de array (`array of THorseCallback`):

```delphi
// Sintaxe Estática com array de middlewares
THorse.Get('/admin/dashboard', [MiddlewareAutenticacao, MiddlewareLogAcesso],
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('Painel Administrativo');
end);

// Sintaxe Fluente de Rota com array de middlewares
THorse.Route('/relatorios')
.Get([MiddlewareAutenticacao, MiddlewareLogAcesso], GetRelatorioHandler)
.Post([MiddlewareAutenticacao], PostRelatorioHandler);
```

Os middlewares de rota são executados após os middlewares globais e após os middlewares do grupo da rota, mas antes do callback final (handler) da rota.

## Middleware wildcard

`THorse.Use(...)` registra middleware que roda em toda requisição, independentemente do caminho:
Expand Down
16 changes: 16 additions & 0 deletions doc/skills/horse-middlewares/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ end;

---

## Middleware Scope & Execution Flow
Middlewares can be declared at three levels:
1. **Global** (via `THorse.Use`): Runs on every request.
2. **Group-level** (via `THorseGroup.Use`): Runs on all routes inside a specific group prefix.
3. **Route-level (Local)**: Runs only on that specific route (defined as an array `[Middleware1, Middleware2]` in the verb method).

### Execution Flow:
The execution order follows the nested onion pattern:
```
Global Middlewares → Group Middlewares → Route-level Middlewares → Final Route Handler
```

Always design middlewares to call `Next()` to pass control, or skip it to short-circuit the request (e.g., unauthorized requests).

---

## The Johnson Middleware (Critical)
The **`Jhonson`** middleware automatically handles JSON parsing (`TJSONObject` / `TJSONArray`) for requests and responses.

Expand Down
Loading