| /** | |
| * 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. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import { Clock } from "../../Clock.js"; | |
| import * as Context from "../../Context.js"; | |
| import * as Effect from "../../Effect.js"; | |
| import { constant, constFalse } from "../../Function.js"; | |
| import * as internalEffect from "../../internal/effect.js"; | |
| import * as Layer from "../../Layer.js"; | |
| import * as Option from "../../Option.js"; | |
| import { TracerEnabled } from "../../References.js"; | |
| import { ParentSpan } from "../../Tracer.js"; | |
| import * as Headers from "./Headers.js"; | |
| import { causeResponseStripped, exitResponse } from "./HttpServerError.js"; | |
| import { HttpServerRequest } from "./HttpServerRequest.js"; | |
| import * as Request from "./HttpServerRequest.js"; | |
| import * as Response from "./HttpServerResponse.js"; | |
| import * as TraceContext from "./HttpTraceContext.js"; | |
| import { appendPreResponseHandlerUnsafe } from "./internal/preResponseHandler.js"; | |
| /** | |
| * Defines an `HttpMiddleware` while preserving its precise type. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const make = middleware => middleware; | |
| const loggerDisabledRequests = /*#__PURE__*/new WeakSet(); | |
| const stripSearchAndHash = url => { | |
| const queryIndex = url.indexOf("?"); | |
| const hashIndex = url.indexOf("#"); | |
| if (queryIndex === -1) { | |
| return hashIndex === -1 ? url : url.slice(0, hashIndex); | |
| } | |
| if (hashIndex === -1) { | |
| return url.slice(0, queryIndex); | |
| } | |
| return url.slice(0, Math.min(queryIndex, hashIndex)); | |
| }; | |
| /** | |
| * Runs an effect with HTTP response logging disabled for the current server request. | |
| * | |
| * @category Logger | |
| * @since 4.0.0 | |
| */ | |
| export const withLoggerDisabled = self => Effect.withFiber(fiber => { | |
| const request = Context.getUnsafe(fiber.context, HttpServerRequest); | |
| loggerDisabledRequests.add(request.source); | |
| return self; | |
| }); | |
| /** | |
| * Context reference for a predicate that disables server-side tracing for matching requests. | |
| * | |
| * @category Tracer | |
| * @since 4.0.0 | |
| */ | |
| export const TracerDisabledWhen = /*#__PURE__*/Context.Reference("effect/http/HttpMiddleware/TracerDisabledWhen", { | |
| defaultValue: () => constFalse | |
| }); | |
| /** | |
| * Creates a layer that disables server-side tracing for requests whose URL exactly matches one of the supplied URLs. | |
| * | |
| * @category Tracer | |
| * @since 4.0.0 | |
| */ | |
| export const layerTracerDisabledForUrls = urls => Layer.succeed(TracerDisabledWhen)(req => urls.includes(req.url)); | |
| /** | |
| * Context reference for generating server span names from HTTP server requests. | |
| * | |
| * @category Tracer | |
| * @since 4.0.0 | |
| */ | |
| export const SpanNameGenerator = /*#__PURE__*/Context.Reference("@effect/platform/HttpMiddleware/SpanNameGenerator", { | |
| defaultValue: () => request => `http.server ${request.method}` | |
| }); | |
| /** | |
| * Middleware that logs sent HTTP responses with request method, request URL, and response status annotations. | |
| * | |
| * @category Logger | |
| * @since 4.0.0 | |
| */ | |
| export const logger = /*#__PURE__*/make(httpApp => Effect.withFiber(fiber => { | |
| const request = Context.getUnsafe(fiber.context, HttpServerRequest); | |
| const path = stripSearchAndHash(request.url); | |
| return Effect.withLogSpan(Effect.flatMap(Effect.exit(httpApp), exit => { | |
| if (loggerDisabledRequests.has(request.source)) { | |
| return exit; | |
| } else if (exit._tag === "Failure") { | |
| const [response, cause] = causeResponseStripped(exit.cause); | |
| return Effect.andThen(Effect.annotateLogs(Effect.log(Option.getOrElse(cause, () => "Sent HTTP Response")), { | |
| "http.method": request.method, | |
| "http.url": path, | |
| "http.status": response.status | |
| }), exit); | |
| } | |
| return Effect.andThen(Effect.annotateLogs(Effect.log("Sent HTTP response"), { | |
| "http.method": request.method, | |
| "http.url": path, | |
| "http.status": exit.value.status | |
| }), exit); | |
| }), "http.span"); | |
| })); | |
| /** | |
| * Middleware that creates a server trace span for each request and records request and response HTTP attributes. | |
| * | |
| * @category Tracer | |
| * @since 4.0.0 | |
| */ | |
| export const tracer = /*#__PURE__*/make(httpApp => Effect.withFiber(fiber => { | |
| const request = Context.getUnsafe(fiber.context, HttpServerRequest); | |
| const disabled = !fiber.getRef(TracerEnabled) || fiber.getRef(TracerDisabledWhen)(request); | |
| if (disabled) { | |
| return httpApp; | |
| } | |
| const nameGenerator = fiber.getRef(SpanNameGenerator); | |
| const span = internalEffect.makeSpanUnsafe(fiber, nameGenerator(request), { | |
| parent: Option.getOrUndefined(TraceContext.fromHeaders(request.headers)), | |
| kind: "server" | |
| }); | |
| const prevServices = fiber.context; | |
| fiber.setContext(Context.add(fiber.context, ParentSpan, span)); | |
| return Effect.onExitPrimitive(httpApp, exit => { | |
| fiber.setContext(prevServices); | |
| const endTime = fiber.getRef(Clock).currentTimeNanosUnsafe(); | |
| fiber.currentDispatcher.scheduleTask(() => { | |
| const url = Request.toURL(request); | |
| if (Option.isSome(url) && (url.value.username !== "" || url.value.password !== "")) { | |
| url.value.username = "REDACTED"; | |
| url.value.password = "REDACTED"; | |
| } | |
| const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames); | |
| const requestHeaders = Headers.redact(request.headers, redactedHeaderNames); | |
| span.attribute("http.request.method", request.method); | |
| if (Option.isSome(url)) { | |
| span.attribute("url.full", url.value.toString()); | |
| span.attribute("url.path", url.value.pathname); | |
| const query = url.value.search.slice(1); | |
| if (query !== "") { | |
| span.attribute("url.query", url.value.search.slice(1)); | |
| } | |
| span.attribute("url.scheme", url.value.protocol.slice(0, -1)); | |
| } | |
| if (request.headers["user-agent"] !== undefined) { | |
| span.attribute("user_agent.original", request.headers["user-agent"]); | |
| } | |
| for (const name in requestHeaders) { | |
| span.attribute(`http.request.header.${name}`, String(requestHeaders[name])); | |
| } | |
| if (Option.isSome(request.remoteAddress)) { | |
| span.attribute("client.address", request.remoteAddress.value); | |
| } | |
| const response = exitResponse(exit); | |
| span.attribute("http.response.status_code", response.status); | |
| const responseHeaders = Headers.redact(response.headers, redactedHeaderNames); | |
| for (const name in responseHeaders) { | |
| span.attribute(`http.response.header.${name}`, String(responseHeaders[name])); | |
| } | |
| span.end(endTime, exit); | |
| }, 0); | |
| return undefined; | |
| }, true); | |
| })); | |
| /** | |
| * Middleware that trusts `X-Forwarded-Host` and `X-Forwarded-For`, updating the request host header and remote address. | |
| * | |
| * @category Proxying | |
| * @since 4.0.0 | |
| */ | |
| export const xForwardedHeaders = /*#__PURE__*/make(httpApp => Effect.updateService(httpApp, HttpServerRequest, request => request.headers["x-forwarded-host"] ? request.modify({ | |
| headers: Headers.set(request.headers, "host", request.headers["x-forwarded-host"]), | |
| remoteAddress: Option.fromNullishOr(request.headers["x-forwarded-for"]?.split(",")[0].trim()) | |
| }) : request)); | |
| /** | |
| * Middleware that parses the current request URL's search parameters and provides them as `ParsedSearchParams`. | |
| * | |
| * @category search params | |
| * @since 4.0.0 | |
| */ | |
| export const searchParamsParser = httpApp => Effect.withFiber(fiber => { | |
| const services = fiber.context; | |
| const request = Context.getUnsafe(services, HttpServerRequest); | |
| const params = Request.searchParamsFromURL(new URL(request.originalUrl)); | |
| return Effect.provideService(httpApp, Request.ParsedSearchParams, params); | |
| }); | |
| /** | |
| * Middleware that handles CORS preflight requests and adds configured CORS headers to HTTP responses. | |
| * | |
| * @category CORS | |
| * @since 4.0.0 | |
| */ | |
| export const cors = options => { | |
| const opts = { | |
| allowedOrigins: options?.allowedOrigins ?? [], | |
| allowedMethods: options?.allowedMethods ?? ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"], | |
| allowedHeaders: options?.allowedHeaders ?? [], | |
| exposedHeaders: options?.exposedHeaders ?? [], | |
| credentials: options?.credentials ?? false, | |
| maxAge: options?.maxAge | |
| }; | |
| const isAllowedOrigin = typeof opts.allowedOrigins === "function" ? opts.allowedOrigins : origin => opts.allowedOrigins.includes(origin); | |
| const allowOrigin = typeof opts.allowedOrigins === "function" || opts.allowedOrigins.length > 1 ? originHeader => { | |
| if (!isAllowedOrigin(originHeader)) return undefined; | |
| return { | |
| "access-control-allow-origin": originHeader, | |
| vary: "Origin" | |
| }; | |
| } : opts.allowedOrigins.length === 0 ? constant({ | |
| "access-control-allow-origin": "*" | |
| }) : constant({ | |
| "access-control-allow-origin": opts.allowedOrigins[0], | |
| vary: "Origin" | |
| }); | |
| const allowMethods = opts.allowedMethods.length > 0 ? { | |
| "access-control-allow-methods": opts.allowedMethods.join(", ") | |
| } : undefined; | |
| const allowCredentials = opts.credentials ? { | |
| "access-control-allow-credentials": "true" | |
| } : undefined; | |
| const allowHeaders = accessControlRequestHeaders => { | |
| if (opts.allowedHeaders.length === 0 && accessControlRequestHeaders) { | |
| return { | |
| vary: "Access-Control-Request-Headers", | |
| "access-control-allow-headers": accessControlRequestHeaders | |
| }; | |
| } | |
| if (opts.allowedHeaders) { | |
| return { | |
| "access-control-allow-headers": opts.allowedHeaders.join(",") | |
| }; | |
| } | |
| return undefined; | |
| }; | |
| const exposeHeaders = opts.exposedHeaders.length > 0 ? { | |
| "access-control-expose-headers": opts.exposedHeaders.join(",") | |
| } : undefined; | |
| const maxAge = opts.maxAge ? { | |
| "access-control-max-age": opts.maxAge.toString() | |
| } : undefined; | |
| const headersFromRequest = request => { | |
| const origin = request.headers["origin"]; | |
| return Headers.fromRecordUnsafe({ | |
| ...allowOrigin(origin), | |
| ...allowCredentials, | |
| ...exposeHeaders | |
| }); | |
| }; | |
| const headersFromRequestOptions = request => { | |
| const origin = request.headers["origin"]; | |
| const accessControlRequestHeaders = request.headers["access-control-request-headers"]; | |
| return Headers.fromRecordUnsafe({ | |
| ...allowOrigin(origin), | |
| ...allowCredentials, | |
| ...exposeHeaders, | |
| ...allowMethods, | |
| ...allowHeaders(accessControlRequestHeaders), | |
| ...maxAge | |
| }); | |
| }; | |
| const preResponseHandler = (request, response) => Effect.succeed(Response.setHeaders(response, headersFromRequest(request))); | |
| return httpApp => Effect.withFiber(fiber => { | |
| const request = Context.getUnsafe(fiber.context, HttpServerRequest); | |
| if (request.method === "OPTIONS") { | |
| return Effect.succeed(Response.empty({ | |
| status: 204, | |
| headers: headersFromRequestOptions(request) | |
| })); | |
| } | |
| appendPreResponseHandlerUnsafe(request, preResponseHandler); | |
| return httpApp; | |
| }); | |
| }; | |
| //# sourceMappingURL=HttpMiddleware.js.map |
Xet Storage Details
- Size:
- 11.2 kB
- Xet hash:
- 51dfffe0353f14da84a1a94f77f7de2edbe30ecb758b07bca1073e0375031e9f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.