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
11 changes: 8 additions & 3 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ Este documento estabelece as regras de design e desenvolvimento do framework Hor
* O Horse possui suporte nativo e thread-safe a ganchos de ciclo de vida (`onRequest`, `preParsing`, `preValidation`, `onSend` e `onResponse`) em cascata cooperativa (CPS).
* Ao criar novos middlewares ou funcionalidades de tratamento, utilize a infraestrutura de ganchos em vez de interceptores ad-hoc nas rotas para manter a conformidade arquitetural.

## 🟢 Desligamento Suave (Graceful Shutdown)
* O Horse gerencia o escoamento lógico de requisições ativas através das propriedades de telemetria `ActiveRequests` e `IsShuttingDown` no core (`THorseCore`).
* Para encerramentos coordenados e seguros (probes de saúde no Kubernetes/Load Balancers), use sempre `THorse.StopListenGraceful(TimeoutMS)`.
## 🟢 Ciclo de Vida do Servidor e Desligamento Suave (Server Lifecycle & Graceful Shutdown)
* O Horse gerencia o escoamento lógico de requisições ativas através das propriedades de telemetria `ActiveRequests` e `IsShuttingDown` no core (`THorseCore`) e nas instâncias locais (`THorseInstance`).
* Para encerramentos coordenados e seguros (probes de saúde no Kubernetes/Load Balancers), use sempre `THorse.StopListenGraceful(TimeoutMS)` ou `LInstance.StopListenGraceful(TimeoutMS)`.
* As propriedades `ActiveRequests` e `IsShuttingDown` estão expostas estaticamente no facade `THorse` e devem ser usadas em endpoints de `/health` ou monitoramento de observabilidade APM.
* **Providers Físicos de Rede**: Ao criar ou atualizar qualquer provedor físico de transporte no ecossistema do Horse:
* Deve-se obrigatoriamente sobrescrever a função `GetActivePort` retornando a porta física ativa (`FPort` ou `0` para acoplados externos como CGI/Apache/ISAPI) para viabilizar a resolução de instâncias no Multi-Instance.
* Deve-se acionar explicitamente `TriggerBeforeListen` no topo das rotinas de inicialização física (`InternalListen` / `Listen`).
* Deve-se acionar explicitamente `TriggerBeforeStop` no topo das rotinas de encerramento físico (`InternalStopListen` / `StopListen`).
* Deve-se sobrescrever e implementar `StopListenGraceful(const ATimeoutMS: Integer)` para suportar o escoamento de requisições ativas daquele provedor físico se o mesmo gerenciar o socket TCP.

## 🟢 Gerenciamento e Injeção de Dependências (Request Scope)
* O Horse expõe a propriedade `Services` na classe de requisição `THorseRequest`, provendo um container IoC local e thread-safe para o escopo do request.
Expand Down
38 changes: 37 additions & 1 deletion doc/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,43 @@ The two Providers are mutually exclusive (one transport per build). The legacy a

Concrete recipes (project type, code skeleton, install commands) for each shape: [Deployment Cheatsheet](./deployment.md), or the longer-form [Providers & Application types §8 (CrossSocket)](./providers.md#8-running-crosssocket-as-each-application-type) / [§9 (mORMot2)](./providers.md#9-running-mormot2-as-each-application-type).

## 7. Where to next
## 7. Structured Bootstrapping (UseStartup)

For larger corporate projects, Horse supports a structured bootstrapping pattern (similar to ASP.NET Core's *Startup* class). This allows isolating the configuration of middlewares, hooks, and routes in a dedicated class that implements the `IHorseStartup` interface:

```delphi
type
THorseStartup = class(TInterfacedObject, IHorseStartup)
public
procedure Configure(const AInstance: THorseInstance);
end;

procedure THorseStartup.Configure(const AInstance: THorseInstance);
begin
// Local configuration of the instance
AInstance.Use(Jhonson);
AInstance.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
end;
```

To inject this configuration class into the server, invoke the `UseStartup` method:

```delphi
var
LStartup: IHorseStartup;
begin
LStartup := THorseStartup.Create;
THorse.UseStartup(LStartup).Listen(9000);
end.
```

A complete executable example project is available in [samples/delphi/console_use_startup/ConsoleUseStartup.dpr](../samples/delphi/console_use_startup/ConsoleUseStartup.dpr).

## 8. Where to next

- [Routing](./routing.md) — declare endpoints, path parameters, route groups.
- [Request & Response](./request-response.md) — read input, write output.
Expand Down
38 changes: 37 additions & 1 deletion doc/getting-started.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,43 @@ Os dois Providers são mutuamente exclusivos (um transporte por build). O alias

Receitas concretas (tipo de projeto, esqueleto de código, comandos de instalação) pra cada formato: [Cheatsheet de Deploy](./deployment.pt-BR.md), ou a forma mais longa em [Providers e Tipos de aplicação §8](./providers.pt-BR.md#8-rodando-o-crosssocket-em-cada-tipo-de-aplicação).

## 7. Próximos passos
## 7. Inicialização Estruturada (UseStartup)

Para projetos corporativos maiores, o Horse suporta o padrão de inicialização estruturada (semelhante ao *Startup* do ASP.NET Core). Isso permite isolar a configuração de middlewares, ganchos e rotas em uma classe dedicada que implementa a interface `IHorseStartup`:

```delphi
type
THorseStartup = class(TInterfacedObject, IHorseStartup)
public
procedure Configure(const AInstance: THorseInstance);
end;

procedure THorseStartup.Configure(const AInstance: THorseInstance);
begin
// Configuração local da instância
AInstance.Use(Jhonson);
AInstance.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
end;
```

Para injetar essa configuração no servidor, basta invocar o método `UseStartup`:

```delphi
var
LStartup: IHorseStartup;
begin
LStartup := THorseStartup.Create;
THorse.UseStartup(LStartup).Listen(9000);
end.
```

Um projeto de exemplo executável e completo está disponível em [samples/delphi/console_use_startup/ConsoleUseStartup.dpr](../samples/delphi/console_use_startup/ConsoleUseStartup.dpr).

## 8. Próximos passos

- [Roteamento](./routing.pt-BR.md) — declarar endpoints, parâmetros de caminho, grupos de rotas.
- [Request e Response](./request-response.pt-BR.md) — ler entrada, escrever saída.
Expand Down
8 changes: 8 additions & 0 deletions doc/graceful-shutdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ To facilitate monitoring and cloud-native integration, we exposed two public sta

To perform a graceful shutdown in Horse, call the `StopListenGraceful(const ATimeoutMS: Integer = 5000)` method passing the maximum timeout in milliseconds to force the shutdown.

### Supported Providers
The graceful shutdown protocol and escoament loop are natively implemented in the following providers:
* **Console Provider** (`Horse.Provider.Console`)
* **VCL Provider** (`Horse.Provider.VCL`)
* **Daemon Provider** (`Horse.Provider.Daemon`)
* **Lazarus/LCL Provider** (`Horse.Provider.FPC.LCL`)
* **Lazarus Daemon Provider** (`Horse.Provider.FPC.Daemon`)

### Complete Example:

```delphi
Expand Down
8 changes: 8 additions & 0 deletions doc/graceful-shutdown.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ Para facilitar o monitoramento e a integração nativa em nuvem, expusemos no fa

Para realizar o desligamento suave no Horse, chame o método `StopListenGraceful(const ATimeoutMS: Integer = 5000)` passando o tempo limite máximo em milissegundos para forçar o encerramento.

### Provedores Suportados
O protocolo de desligamento suave e o ciclo de escoamento de requisições ativas estão implementados de forma nativa nos seguintes provedores:
* **Console Provider** (`Horse.Provider.Console`)
* **VCL Provider** (`Horse.Provider.VCL`)
* **Daemon Provider** (`Horse.Provider.Daemon`)
* **Lazarus/LCL Provider** (`Horse.Provider.FPC.LCL`)
* **Lazarus Daemon Provider** (`Horse.Provider.FPC.Daemon`)

### Exemplo Completo:

```delphi
Expand Down
50 changes: 50 additions & 0 deletions doc/lifecycle-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,56 @@ Since Horse handles HTTP requests concurrently using multiple worker threads (in

---

---

## 🌐 Server Lifecycle Hooks (Server Phase)

Server Lifecycle Hooks allow intercepting startup and shutdown operations of the physical HTTP socket server.

They are registered globally on the `THorse` facade or locally on a `THorseInstance`. These hooks always receive the **physical port** (`APort: Integer`) resolved by the active transport provider.

* **Signatures:**
* `THorseServerLifecycleProc = reference to procedure(APort: Integer);`
* `THorseServerLifecycleMethod = procedure(APort: Integer) of object;`

### Available Hooks

| Hook | Phase | Usage / Intent |
|---|---|---|
| `BeforeListen` | Right before the socket listener starts | Validating host configuration, starting DB pools, pre-warming caches. |
| `AfterListen` | Right after the server starts listening | Logging startup success, announcing port to registry/discovery services. |
| `BeforeStop` | Right before the socket listener closes | Initiating shutdown sequences, signaling load-balancers to remove the node. |
| `AfterStop` | Right after the server has fully stopped | Cleaning up global DB pools, releasing memory or IPC locks. |

### Example:
```delphi
THorse.AddBeforeListen(
procedure(APort: Integer)
begin
Writeln('Server is starting up on port ' + APort.ToString);
end);

THorse.AddAfterListen(
procedure(APort: Integer)
begin
Writeln('Server physical socket is listening. Accepting connections...');
end);

THorse.AddBeforeStop(
procedure(APort: Integer)
begin
Writeln('Initiating shutdown sequence for port ' + APort.ToString);
end);

THorse.AddAfterStop(
procedure(APort: Integer)
begin
Writeln('Physical server has stopped. Socket released.');
end);
```

---

## 🚀 Executable Samples

You can find ready-to-run audit projects to see hooks executing in real-time (compatible with Windows, Linux, and macOS):
Expand Down
50 changes: 50 additions & 0 deletions doc/lifecycle-hooks.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,56 @@ Como o Horse processa conexões de forma concorrente em múltiplos sockets ou lo

---

---

## 🌐 Ganchos de Ciclo de Vida do Servidor (Server Phase)

Os Ganchos de Ciclo de Vida do Servidor (*Server Lifecycle Hooks*) permitem interceptar as operações físicas de inicialização (startup) e desligamento (shutdown) do servidor de sockets HTTP.

Eles podem ser registrados globalmente na fachada `THorse` ou localmente em uma `THorseInstance` específica. Esses hooks sempre recebem a **porta física ativa** (`APort: Integer`) resolvida pelo provedor de transporte em execução.

* **Assinaturas:**
* `THorseServerLifecycleProc = reference to procedure(APort: Integer);`
* `THorseServerLifecycleMethod = procedure(APort: Integer) of object;`

### Ganchos Disponíveis

| Hook | Fase do Servidor | Uso Sugerido / Intenção |
|---|---|---|
| `BeforeListen` | Instantes antes da abertura do socket físico | Validar configurações de host/porta, iniciar pools globais de conexão de banco de dados, pré-aquecer caches de memória. |
| `AfterListen` | Imediatamente após o início da escuta | Logar sucesso de inicialização na telemetria, anunciar a porta resolvida a serviços de Service Registry / Service Discovery. |
| `BeforeStop` | Instantes antes do fechamento do socket físico | Iniciar sequências de encerramento interno, enviar sinalizadores a Load Balancers para remover o nó da rota de tráfego. |
| `AfterStop` | Imediatamente após a liberação do socket | Destruir pools de banco de dados, liberar trancas de IPC ou recursos de memória compartilhada do processo. |

### Exemplo de Uso:
```delphi
THorse.AddBeforeListen(
procedure(APort: Integer)
begin
Writeln('Servidor iniciando na porta ' + APort.ToString);
end);

THorse.AddAfterListen(
procedure(APort: Integer)
begin
Writeln('Servidor ativo e aceitando conexoes na porta ' + APort.ToString);
end);

THorse.AddBeforeStop(
procedure(APort: Integer)
begin
Writeln('Iniciando encerramento suave na porta ' + APort.ToString);
end);

THorse.AddAfterStop(
procedure(APort: Integer)
begin
Writeln('Servidor parado fisicamente e porta liberada.');
end);
```

---

## 🚀 Exemplos Práticos Executáveis

Você pode encontrar projetos prontos e auditáveis para executar e ver os hooks rodando no console em tempo real (compatíveis com Windows, Linux e macOS):
Expand Down
25 changes: 16 additions & 9 deletions doc/multi-instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,22 @@ The `THorseWebModule` processes the request and resolves the execution context:
```
3. If no instance is registered on that port, the execution falls back to the static `THorseCore` global routes.

### 🌐 Standalone Sockets vs. Managed Web Servers (ISAPI, Apache, CGI, FastCGI)

> [!NOTE]
> Under standalone local providers (like Indy, IOCP, or HttpSys), physical socket listeners are managed as global singletons. Trying to run multiple physical socket listeners *concurrently* within the same standalone process will cause socket binding collisions because those providers are not designed to host multiple parallel local listeners.
>
> However, `THorseInstance` shines in production when deployed under **Managed Web Servers** (like **IIS** via ISAPI, **Apache** via mod_delphi, **CGI**, or **FastCGI**):
> - In these environments, the external web server (IIS/Apache) handles the physical socket listening on multiple ports (e.g., port `80` for public API and `8080` for admin dashboard) and forwards all HTTP requests to the Horse DLL or process.
> - The incoming request arrives at `THorseWebModule` containing the correct `Request.ServerPort` header.
> - Horse successfully resolves and routes the request to the correct `THorseInstance` logical route tree, providing complete isolation without any socket conflicts.
### 🌐 Provider and Router Compatibility Matrix

The Multi-Instance architecture is fully compatible with all official routers and transport providers in the Horse ecosystem. However, depending on the network transport, physical behavior varies:

#### 1. Routers (Radix Router vs. Classic Router)
**Compatibility: 100% (Agnostic)**
The logical routing pipeline (both the default linear router and the high-performance Radix Tree router — `HORSE_RADIX_ROUTER`) is decoupled from the physical transport layer. Each `THorseInstance` manages its own isolated route tree in memory.

#### 2. Physical Providers (Standalone Sockets vs. Managed Web Servers)
The following matrix highlights the multi-port concurrent listening behavior across transport providers:

| Category | Providers | Listens on Distinct Physical Ports concurrently? | Architectural Behavior |
| :--- | :--- | :---: | :--- |
| **High Performance / Async** | `CrossSocket`, `mORMot2`, `HttpSys` | ✔️ Yes | Each `THorseInstance` spawns and manages its own isolated OS-level socket. |
| **Classic / Monolithic** | `Indy` (Console/VCL/Daemon), `fphttpserver` (LCL/Daemon/HTTPApplication) | ✔️ Yes | A global shared listener manages multiple network bindings transparently for all registered logical instances. |
| **Hosted / Managed** | `IIS` (ISAPI), `Apache` (Module), `CGI` / `FastCGI` | Not applicable | The external host server (IIS/Apache) owns the physical sockets and ports, forwarding incoming requests to `THorseWebModule` with the correct `Request.ServerPort` header. Horse routes them logically to the corresponding instance perfectly. |

---

Expand Down
25 changes: 16 additions & 9 deletions doc/multi-instance.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,22 @@ O módulo de controle central `THorseWebModule` intercepta a requisição e exec
```
3. Caso contrário, o fluxo é desviado para a árvore de roteamento e middlewares legados e estáticos da fachada principal `THorseCore`.

### 🌐 Sockets Standalone vs. Servidores Web Gerenciados (ISAPI, Apache, CGI, FastCGI)

> [!NOTE]
> Sob provedores locais standalone (como Indy, IOCP ou HttpSys), os listeners de sockets físicos são gerenciados como singletons globais do processo. Tentar iniciar múltiplos listeners físicos de sockets *concorrentemente* dentro do mesmo processo standalone causará colisões de bindings de portas, pois estes provedores não foram projetados para gerenciar múltiplos loops de sockets locais paralelos.
>
> Contudo, a arquitetura de `THorseInstance` brilha em produção quando implantada sob **Servidores Web Gerenciados** (como **IIS** via ISAPI, **Apache** via mod_delphi, **CGI** ou **FastCGI**):
> - Nesses ambientes, o servidor externo (IIS/Apache) é quem gerencia fisicamente as escutas das portas (ex: porta `80` para API pública e `8080` para painel administrativo) e repassa os requests para o processo ou DLL do Horse.
> - A requisição chega ao `THorseWebModule` contendo o cabeçalho correto `Request.ServerPort`.
> - O Horse resolve e direciona a requisição perfeitamente para a árvore de rotas lógicas da `THorseInstance` correspondente, provendo isolamento completo sem conflitos de socket.
### 🌐 Compatibilidade de Provedores (Sockets) e Roteadores

A arquitetura Multi-Instance é compatível com os roteadores e provedores físicos do ecossistema do Horse. Porém, dependendo da natureza do transporte de rede, o comportamento físico varia:

#### 1. Roteadores (Radix Router vs. Classic Router)
**Compatibilidade: 100% (Agnóstico)**
O roteamento lógico (tanto o roteador linear padrão quanto o roteador assíncrono baseado em Radix Tree — `HORSE_RADIX_ROUTER`) é totalmente desacoplado da camada física de transporte. Cada instância de `THorseInstance` possui sua própria árvore de rotas isolada em memória.

#### 2. Provedores Físicos (Sockets Standalone vs. Servidores Web Gerenciados)
A tabela a seguir apresenta o comportamento e suporte de escuta simultânea para cada Provedor de transporte:

| Categoria | Provedores | Escuta Portas Físicas Distintas no Mesmo Processo? | Comportamento Arquitetural |
| :--- | :--- | :---: | :--- |
| **Alta Performance / Async** | `CrossSocket`, `mORMot2`, `HttpSys` | ✔️ Sim | Cada `THorseInstance` gerencia seu próprio socket físico isolado no sistema operacional. |
| **Clássicos / Monolíticos** | `Indy` (Console/VCL/Daemon), `fphttpserver` (LCL/Daemon/HTTPApplication) | ✔️ Sim | O servidor físico global compartilha o listener, mas gerencia múltiplos bindings de rede de forma transparente para todas as instâncias lógicas registradas. |
| **Hospedados / Acoplados** | `IIS` (ISAPI), `Apache` (Module), `CGI` / `FastCGI` | Não se aplica | O servidor externo (IIS/Apache) é dono dos sockets de rede físicos e gerencia as portas de entrada, repassando o request de forma lógica ao `THorseWebModule` com o cabeçalho `Request.ServerPort` correto. O Horse faz o roteamento lógico isolado de instâncias perfeitamente. |

---

Expand Down
4 changes: 4 additions & 0 deletions doc/roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ Este documento detalha o planejamento de melhorias arquiteturais de longo prazo
* **Status:** 🟢 **Concluído e Liberado**
* **Implementação:** Desacoplado o estado estático global de roteamento, ganchos de ciclo de vida e middlewares para objetos de instância independentes de `THorseInstance`. Adicionado suporte à escuta simultânea de múltiplos servidores HTTP concorrentes em portas diferentes de forma thread-safe e com 100% de retrocompatibilidade mantendo a fachada `THorse` clássica.

### 10. Ganchos de Ciclo de Vida do Servidor (*Server Lifecycle Hooks*)
* **Status:** 🟢 **Concluído e Liberado**
* **Implementação:** Implementado suporte nativo a ganchos de ciclo de vida do servidor físico (`BeforeListen`, `AfterListen`, `BeforeStop`, `AfterStop`) de forma thread-safe tanto para o modo Multi-Instance (`THorseInstance`) quanto para o facade clássico (`THorse`). Garantido alinhamento polimórfico de portas de escuta e prevenção de travamentos não interativos de console, com testes de integração e concorrência 100% livres de memory leak e Access Violations.

---

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