Skip to content

Commit

Permalink
ensure HttpMiddleware is only initialized once (#3808)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored Oct 22, 2024
1 parent 6bfab7e commit e7afc47
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changeset/large-singers-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/platform": patch
---

ensure HttpMiddleware is only initialized once
110 changes: 67 additions & 43 deletions packages/platform/src/HttpApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as Exit from "effect/Exit"
import * as FiberRef from "effect/FiberRef"
import type * as FiberRef from "effect/FiberRef"
import * as Layer from "effect/Layer"
import type * as Option from "effect/Option"
import * as Runtime from "effect/Runtime"
import * as Scope from "effect/Scope"
import { unify } from "effect/Unify"
import type { HttpMiddleware } from "./HttpMiddleware.js"
import * as ServerError from "./HttpServerError.js"
import * as ServerRequest from "./HttpServerRequest.js"
Expand All @@ -32,6 +33,8 @@ export type HttpApp<A = ServerResponse.HttpServerResponse, E = never, R = never>
*/
export type Default<E = never, R = never> = HttpApp<ServerResponse.HttpServerResponse, E, R>

const handledSymbol = Symbol.for("@effect/platform/HttpApp/handled")

/**
* @since 1.0.0
* @category combinators
Expand All @@ -45,53 +48,74 @@ export const toHandled = <E, R, _, EH, RH>(
middleware?: HttpMiddleware | undefined
): Effect.Effect<void, never, Exclude<R | RH | ServerRequest.HttpServerRequest, Scope.Scope>> => {
const responded = Effect.withFiberRuntime<
void,
never,
ServerResponse.HttpServerResponse,
E | EH | ServerError.ResponseError,
R | RH | ServerRequest.HttpServerRequest
>((fiber) => {
let handled = false
const request = Context.unsafeGet(fiber.getFiberRef(FiberRef.currentContext), ServerRequest.HttpServerRequest)
const preprocessResponse = (response: ServerResponse.HttpServerResponse) => {
>((fiber) =>
Effect.flatMap(self, (response) => {
const request = Context.unsafeGet(fiber.currentContext, ServerRequest.HttpServerRequest)
const handler = fiber.getFiberRef(currentPreResponseHandlers)
return handler._tag === "Some" ? handler.value(request, response) : Effect.succeed(response)
}
const responded = Effect.matchCauseEffect(self, {
onFailure: (cause) =>
Effect.flatMap(ServerError.causeResponse(cause), ([response, cause]) =>
preprocessResponse(response).pipe(
Effect.flatMap((response) => {
handled = true
if (handler._tag === "None") {
;(request as any)[handledSymbol] = true
return Effect.as(handleResponse(request, response), response)
}
return Effect.tap(handler.value(request, response), (response) => {
;(request as any)[handledSymbol] = true
return handleResponse(request, response)
})
})
)

const withErrorHandling = Effect.catchAllCause(
responded,
(cause) =>
Effect.withFiberRuntime<
ServerResponse.HttpServerResponse,
E | EH | ServerError.ResponseError,
ServerRequest.HttpServerRequest | RH
>((fiber) =>
Effect.flatMap(ServerError.causeResponse(cause), ([response, cause]) => {
const request = Context.unsafeGet(fiber.currentContext, ServerRequest.HttpServerRequest)
const handler = fiber.getFiberRef(currentPreResponseHandlers)
if (handler._tag === "None") {
;(request as any)[handledSymbol] = true
return Effect.zipRight(handleResponse(request, response), Effect.failCause(cause))
}
return Effect.zipRight(
Effect.tap(handler.value(request, response), (response) => {
;(request as any)[handledSymbol] = true
return handleResponse(request, response)
}),
Effect.zipRight(Effect.failCause(cause))
)),
onSuccess: (response) =>
Effect.tap(
preprocessResponse(response),
(response) => {
handled = true
return handleResponse(request, response)
}
)
})
const withTracer = internalMiddleware.tracer(responded)
if (middleware === undefined) {
return withTracer as any
}
return Effect.matchCauseEffect(middleware(withTracer), {
onFailure: (cause): Effect.Effect<void, EH, RH> => {
if (handled) {
return Effect.void
}
return Effect.matchCauseEffect(ServerError.causeResponse(cause), {
onFailure: (_cause) => handleResponse(request, ServerResponse.empty({ status: 500 })),
onSuccess: ([response]) => handleResponse(request, response)
Effect.failCause(cause)
)
})
},
onSuccess: (response): Effect.Effect<void, EH, RH> => handled ? Effect.void : handleResponse(request, response)
})
})
return Effect.uninterruptible(Effect.scoped(responded))
)
)

const withMiddleware = unify(
middleware === undefined ?
internalMiddleware.tracer(withErrorHandling) :
Effect.matchCauseEffect(middleware(internalMiddleware.tracer(withErrorHandling)), {
onFailure: (cause): Effect.Effect<void, EH, RH> =>
Effect.withFiberRuntime((fiber) => {
const request = Context.unsafeGet(fiber.currentContext, ServerRequest.HttpServerRequest)
if (handledSymbol in request) {
return Effect.void
}
return Effect.matchCauseEffect(ServerError.causeResponse(cause), {
onFailure: (_cause) => handleResponse(request, ServerResponse.empty({ status: 500 })),
onSuccess: ([response]) => handleResponse(request, response)
})
}),
onSuccess: (response): Effect.Effect<void, EH, RH> =>
Effect.withFiberRuntime((fiber) => {
const request = Context.unsafeGet(fiber.currentContext, ServerRequest.HttpServerRequest)
return handledSymbol in request ? Effect.void : handleResponse(request, response)
})
})
)

return Effect.uninterruptible(Effect.scoped(withMiddleware)) as any
}

/**
Expand Down
9 changes: 3 additions & 6 deletions packages/platform/src/internal/httpMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ export const withTracerDisabledForUrls = dual<
export const logger = make((httpApp) => {
let counter = 0
return Effect.withFiberRuntime((fiber) => {
const context = fiber.getFiberRef(FiberRef.currentContext)
const request = Context.unsafeGet(context, ServerRequest.HttpServerRequest)
const request = Context.unsafeGet(fiber.currentContext, ServerRequest.HttpServerRequest)
return Effect.withLogSpan(
Effect.flatMap(Effect.exit(httpApp), (exit) => {
if (fiber.getFiberRef(loggerDisabled)) {
Expand Down Expand Up @@ -110,8 +109,7 @@ export const logger = make((httpApp) => {
/** @internal */
export const tracer = make((httpApp) =>
Effect.withFiberRuntime((fiber) => {
const context = fiber.getFiberRef(FiberRef.currentContext)
const request = Context.unsafeGet(context, ServerRequest.HttpServerRequest)
const request = Context.unsafeGet(fiber.currentContext, ServerRequest.HttpServerRequest)
const disabled = fiber.getFiberRef(currentTracerDisabledWhen)(request)
if (disabled) {
return httpApp
Expand Down Expand Up @@ -303,8 +301,7 @@ export const cors = (options?: {

return <E, R>(httpApp: App.Default<E, R>): App.Default<E, R> =>
Effect.withFiberRuntime((fiber) => {
const context = fiber.getFiberRef(FiberRef.currentContext)
const request = Context.unsafeGet(context, ServerRequest.HttpServerRequest)
const request = Context.unsafeGet(fiber.currentContext, ServerRequest.HttpServerRequest)
if (request.method === "OPTIONS") {
return Effect.succeed(ServerResponse.empty({
status: 204,
Expand Down

0 comments on commit e7afc47

Please sign in to comment.