/** * Wraps HTTP server apps with request and response behavior. * * A middleware is a function from one HTTP server app effect to another. The app * runs with the current `HttpServerRequest` in its context, so middleware can * inspect or rewrite the request, provide request-scoped services, attach hooks * before the response is sent, or observe the app exit. This module includes * middleware for response logging, server tracing, forwarded proxy headers, * parsed search parameters, and CORS response headers. * * @stability unstable * @since 4.0.0 */ import * as ByteSize from "../ByteSize.ts"; import * as Context from "../Context.ts"; import * as Effect from "../Effect.ts"; import * as Layer from "../Layer.ts"; import * as IpNetwork from "../net/IpNetwork.ts"; import * as NetAddress from "../net/NetAddress.ts"; import type { Predicate } from "../Predicate.ts"; import type { CompressionAlgorithm } from "./HttpPlatform.ts"; import { HttpPlatform } from "./HttpPlatform.ts"; import { HttpServerRequest } from "./HttpServerRequest.ts"; import * as Request from "./HttpServerRequest.ts"; import * as Response from "./HttpServerResponse.ts"; import type { HttpServerResponse } from "./HttpServerResponse.ts"; /** * Middleware that transforms an HTTP server app effect into another HTTP server app effect. * * @stability unstable * @category models * @since 4.0.0 */ export interface HttpMiddleware { (self: Effect.Effect): Effect.Effect; } /** * Namespace containing types associated with `HttpMiddleware`. * * @stability unstable * @since 4.0.0 */ export declare namespace HttpMiddleware { /** * Callable type representing middleware already specialized to a particular transformed app type. * * @stability unstable * @category models * @since 4.0.0 */ interface Applied, E, R> { (self: Effect.Effect): A; } } /** * Defines an `HttpMiddleware` while preserving its precise type. * * **Gotchas** * * TypeScript cannot infer the `app` parameter of an `Effect.fn` or * `Effect.fnUntraced` generator passed directly to `make`, so `app` becomes * `any` and the middleware loses the app's error and service types. Prefer an * arrow function, as in the first example. To use `Effect.fn`, declare `E` * and `R` type parameters and annotate `app` with them. * * **Example** (Defining middleware) * * ```ts import.meta.vitest * import { Effect } from "effect" * import { HttpMiddleware, HttpServerResponse } from "effect/http" * * const withHeader = HttpMiddleware.make((app) => * Effect.gen(function*() { * const response = yield* app * return HttpServerResponse.setHeader(response, "x-service", "example") * }) * ) * * const app: Effect.Effect = * Effect.fail("Unauthorized") * * // Effect * const withHeaderApp = withHeader(app) * ``` * * **Example** (Defining middleware with Effect.fn) * * ```ts import.meta.vitest * import { Effect } from "effect" * import { HttpMiddleware, HttpServerResponse } from "effect/http" * * const withHeader = HttpMiddleware.make( * Effect.fn("withHeader")(function*( * app: Effect.Effect * ) { * const response = yield* app * return HttpServerResponse.setHeader(response, "x-service", "example") * }) * ) * * const app: Effect.Effect = * Effect.fail("Unauthorized") * * // Effect * const withHeaderApp = withHeader(app) * ``` * * @stability unstable * @category constructors * @since 4.0.0 */ export declare const make: (middleware: M) => M; /** * Runs an effect with HTTP response logging disabled for the current server request. * * @stability unstable * @category logging * @since 4.0.0 */ export declare const withLoggerDisabled: (self: Effect.Effect) => Effect.Effect; /** * Context reference for a predicate that disables server-side tracing for matching requests. * * @stability unstable * @category services * @since 4.0.0 */ export declare const TracerDisabledWhen: Context.Reference>; /** * Creates a layer that disables server-side tracing for requests whose URL exactly matches one of the supplied URLs. * * @stability unstable * @category layers * @since 4.0.0 */ export declare const layerTracerDisabledForUrls: (urls: ReadonlyArray) => Layer.Layer; /** * Context reference for generating server span names from HTTP server requests. * * **Details** * * Defaults to the request method unchanged. * * @stability unstable * @category services * @since 4.0.0 */ export declare const SpanNameGenerator: Context.Reference<(request: HttpServerRequest) => string>; /** * Context reference for filtering request and response headers added to server spans. * * **Details** * * Header capture is opt-in: the default filter records no headers. Captured * headers listed in `Headers.CurrentRedactedNames` are recorded as ``. * * @stability unstable * @category services * @since 4.0.0 */ export declare const TracerHeaderFilter: Context.Reference<(headerName: string, phase: "request" | "response") => boolean>; /** * Middleware that logs sent HTTP responses with request method, request URL, and response status annotations. * * @stability unstable * @category logging * @since 4.0.0 */ export declare const logger: (httpApp: Effect.Effect) => Effect.Effect; /** * Middleware that creates a server trace span for each request and records request and response HTTP attributes. * * @stability unstable * @category tracing * @since 4.0.0 */ export declare const tracer: (httpApp: Effect.Effect) => Effect.Effect; /** * Options for `xForwardedHeaders`. * * **Details** * * `trustedProxies` is a list of networks or a predicate that decides whether an * address belongs to a proxy you operate. IPv4-mapped IPv6 addresses are * converted to IPv4 before matching. * * @stability unstable * @category proxying * @since 4.0.0 */ export interface TrustedProxyOptions { readonly trustedProxies: ReadonlyArray | Predicate; } /** * Creates middleware that applies `X-Forwarded-For`, `X-Forwarded-Host`, and * `X-Forwarded-Proto` sent by trusted proxies. * * **When to use** * * Use when the app runs behind proxies that append to `X-Forwarded-For`, such * as most load balancers and TLS terminators. * * **Details** * * Headers are ignored unless the original transport peer * (`request.peerAddress`) is trusted. `X-Forwarded-For` is then walked from * the right, skipping trusted proxies. The first untrusted address becomes * `remoteAddress`. When every address is trusted, the leftmost one is used. An * entry that is not an IP address, such as `unknown`, stops the walk and * leaves the request unchanged. * * `X-Forwarded-Host` and `X-Forwarded-Proto` are only used when they list one * value per `X-Forwarded-For` entry; the value at the selected position * replaces the `host` header and sets `protocol` (`http` or `https`), which * `HttpServerRequest.toURL` uses. Otherwise the transport values are kept. * * `request.peerAddress` keeps the transport peer after `remoteAddress` is * replaced. * * **Example** (Reading the client address behind a load balancer) * * ```ts import.meta.vitest * import { Effect, Option } from "effect" * import { HttpMiddleware, HttpServerRequest, HttpServerResponse } from "effect/http" * import { IpNetwork } from "effect/net" * * const app = HttpServerRequest.HttpServerRequest.use((request) => * Effect.succeed(HttpServerResponse.text(Option.getOrElse(request.remoteAddress, () => "unknown"))) * ).pipe( * HttpMiddleware.xForwardedHeaders({ * trustedProxies: [IpNetwork.fromStringUnsafe("10.0.0.0/8")] * }) * ) * * // The load balancer at 10.0.0.1 appended the client address to a spoofed entry * const request = HttpServerRequest.fromWeb( * new Request("http://app.test/", { headers: { "x-forwarded-for": "6.6.6.6, 198.51.100.7" } }) * ).modify({ remoteAddress: Option.some("10.0.0.1") }) * * const response = await Effect.runPromise( * Effect.provideService(app, HttpServerRequest.HttpServerRequest, request) * ) * await HttpServerResponse.toWeb(response).text() // => "198.51.100.7" * ``` * * @stability unstable * @category proxying * @since 4.0.0 */ export declare const xForwardedHeaders: (options: TrustedProxyOptions) => HttpMiddleware; /** * Middleware that parses the current request URL's search parameters and provides them as `ParsedSearchParams`. * * @stability unstable * @category parsing * @since 4.0.0 */ export declare const searchParamsParser: (httpApp: Effect.Effect) => Effect.Effect>; /** * Middleware that handles CORS preflight requests and adds configured CORS headers to HTTP responses. * * @stability unstable * @category middleware * @since 4.0.0 */ export declare const cors: (options?: { readonly allowedOrigins?: ReadonlyArray | Predicate | undefined; readonly allowedMethods?: ReadonlyArray | undefined; readonly allowedHeaders?: ReadonlyArray | undefined; readonly exposedHeaders?: ReadonlyArray | undefined; readonly maxAge?: number | undefined; readonly credentials?: boolean | undefined; }) => (httpApp: Effect.Effect) => Effect.Effect; /** * Middleware that compresses HTTP response bodies based on the request's * `Accept-Encoding` header. * * **Details** * * Content negotiation follows RFC 9110: the first algorithm in server * preference order that the client accepts with a positive q-value and the * platform supports is used. When no algorithm is acceptable the response is * sent uncompressed. * * The body transform is performed by the `HttpPlatform` service. * * Responses are skipped when the status is 1xx, 204, 206, or 304, when a * `Content-Encoding` is already present, when `Cache-Control: no-transform` * is set, when the content type is absent or not compressible, when a known * body length is below `minSize`, or when the body is empty or `FormData`. A * response carrying `Content-Encoding: identity` opts out of compression and * has the header stripped before sending. * * `Vary: Accept-Encoding` is set on every response that was eligible by * status and content type, including ones skipped by negotiation or * `minSize`. * * Do not combine this middleware with Deno's automatic response compression. * On edge runtimes that already apply automatic compression, the middleware * is unnecessary. Platforms backed by Web `CompressionStream` also cannot * explicitly flush each input chunk, so incremental delivery depends on the * runtime's implementation. * * **Gotchas** * * Compression can expose secrets through BREACH-style attacks when one * response contains both secret data and attacker-controlled input and an * attacker can observe the compressed response length. For affected routes, * disable compression with `Content-Encoding: identity` or * `Cache-Control: no-transform`, or use `compressible` to restrict which * response content types can be compressed. * * @stability unstable * @category compression * @since 4.0.0 */ export declare const compression: (options?: { /** * Server preference order. Negotiation picks the first accepted algorithm * supported by the platform. Defaults to `["br", "gzip", "deflate"]`; * `zstd` must be explicitly opted into. */ readonly algorithms?: ReadonlyArray | undefined; /** * Minimum body size in bytes when the length is known. Unknown-length bodies * are always compressed. Defaults to `1024`. */ readonly minSize?: number | undefined; /** Replaces the default content-type predicate. */ readonly compressible?: ((contentType: string) => boolean) | undefined; /** Per-algorithm levels. Platforms without a level knob ignore them. */ readonly levels?: { readonly gzip?: number | undefined; readonly deflate?: number | undefined; readonly br?: number | undefined; readonly zstd?: number | undefined; } | undefined; } | undefined) => (httpApp: Effect.Effect) => Effect.Effect; /** * Middleware that decompresses request bodies sent with a `Content-Encoding` * of `gzip` or `deflate`. * * **Details** * * The request service is replaced with a view whose body accessors (`stream`, * `text`, `json`, `arrayBuffer`, `urlParamsBody`, `multipart`) yield the * decompressed body, and whose headers no longer include `Content-Encoding` * or `Content-Length`. Decompression is lazy and only happens when the body * is read. Stacked codings such as `deflate, gzip` are undone in reverse * order, and `identity` members are ignored. * * Requests whose `Content-Encoding` includes any other coding are rejected * with `415 Unsupported Media Type` and `Accept-Encoding: gzip, deflate`, * without running the app. Malformed compressed data fails the body read * with a `RequestParseError`, or a `MultipartError` with reason `Parse` when * reading multipart data. * * The decompressed size is limited to `maxDecompressedSize`, falling back to * the `MaxBodySize` reference and then to 10 MiB. Exceeding it fails the body * read with a `RequestParseError`, or a `MultipartError` with reason * `BodyTooLarge` when reading multipart data. The limit applies to every body * accessor, including `stream`. * * Decompression uses the global `DecompressionStream`. `deflate` means the * zlib format (RFC 1950); raw deflate data is rejected. * * **Gotchas** * * `request.source` is still the original, compressed request. * `HttpServerRequest.toWeb` returns that compressed request only when the * source is a Web `Request`. On other platforms, such as Node, it builds a new * `Request` from the decompressed body and the stripped headers. * * @stability unstable * @category compression * @since 4.0.0 */ export declare const decompression: (options?: { /** * Maximum number of decompressed body bytes. Defaults to the * `MaxBodySize` reference, or 10 MiB when that is unset. */ readonly maxDecompressedSize?: ByteSize.Input | undefined; } | undefined) => (httpApp: Effect.Effect) => Effect.Effect; //# sourceMappingURL=HttpMiddleware.d.ts.map