Middleware Pipeline

Read time 3 minutes
On this page

This page explains how the MiddlewarePipeline fits into the Nalix request path and how to leverage it for application-level policy and observability.

The Middleware Model

Nalix utilizes a single, high-performance middleware layer powered by the MiddlewarePipeline. This pipeline executes after packet deserialization and metadata resolution, giving you full context for every request.

Architecture Diagram
flowchart LR
    A["Socket frame"] --> B["FramePipeline (Low-level)"]
    B --> C["Deserialize packet"]
    C --> D["Resolve handler metadata"]
    D --> E["MiddlewarePipeline"]
    E --> F["Handler"]
    F --> G["Return handler / reply"]

Middleware Pipeline

The MiddlewarePipeline runs with the full packet execution context. Middleware usually consumes that state through IPacketContext<TPacket>, while the runtime backs it with a pooled PacketContext<TPacket> instance carrying the deserialized packet, connection state, and cached handler metadata.

Use it when:

  • You need permission checks
  • You need timeout enforcement
  • You need rate limiting or concurrency limiting
  • You need audit logging with handler context
  • You need tenant or product policy checks

Tradeoff: Because it runs after deserialization, the packet has already been allocated. For early-stage rejection of malformed traffic, rely on ConnectionGuard or Protocol validation.

Low-Level Transformations

Low-level buffer operations such as decryption, decompression, and raw frame validation are integrated into the FramePipeline. This pipeline is executed directly by the Listeners (TCP/UDP) at the transport layer before the packet reaches runtime middleware.

Execution Order

Middleware ordering is attribute-driven, not raw registration order. The pipeline reads MiddlewareOrderAttribute and MiddlewareStageAttribute from each middleware type. If MiddlewareOrderAttribute is absent, the order defaults to 0. If MiddlewareStageAttribute is absent, the stage defaults to Inbound.

Stage bucketSource behavior
InboundSorted by MiddlewareOrder ascending.
OutboundSorted by MiddlewareOrder descending.
Outbound with AlwaysExecute=trueStored in a separate outbound-always bucket, sorted descending, and executed after the handler even when normal outbound is skipped.
Architecture Diagram
flowchart LR
    I1["Inbound low order"] --> I2["Inbound high order"]
    I2 --> H["Handler"]
    H --> A1["Outbound Always high order"]
    A1 --> O1["Outbound high order"]
    O1 --> O2["Outbound low order"]

Registration example:

PacketDispatchChannel dispatch = new(options =>
{
    // Register middleware into the MiddlewarePipeline
    options.WithMiddleware(new PermissionMiddleware());
    options.WithMiddleware(new RateLimitMiddleware());
    options.WithMiddleware(new AuditMiddleware());
});

How Metadata Fits In

Middleware becomes powerful because the dispatcher resolves metadata before the pipeline runs.

Middleware can read:

  • PacketOpcode — the handler’s opcode
  • Permission rules ([PacketPermission])
  • Timeout rules ([PacketTimeout])
  • Rate limit rules ([PacketRateLimit])
  • Custom attributes collected by the PacketHandlerGenerator source generator

The typical flow is:

  1. Declare attributes on handler methods
  2. The PacketHandlerGenerator collects them at compile time into PacketMetadata
  3. Read the resolved metadata inside middleware via context.Attributes

Decision Guide

NeedBest fit
Reject a malformed frame before packet creationProtocol / ConnectionGuard
Decrypt or decompress a frameFramePipeline (Transport)
Block a packet by permission levelMiddlewarePipeline
Apply per-handler timeout rulesMiddlewarePipeline
Read a custom tenant tag from metadataMiddlewarePipeline
Rate limit by opcode or endpointMiddlewarePipeline
Audit handler invocationsMiddlewarePipeline

Common Pitfalls

See it in action