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
5 changes: 5 additions & 0 deletions .changeset/fix-http-server-response-body-headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Synchronize HTTP server response content headers when replacing the body.
16 changes: 2 additions & 14 deletions packages/effect/src/unstable/http/HttpClientRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import * as Stream from "../../Stream.ts"
import * as Headers from "./Headers.ts"
import * as HttpBody from "./HttpBody.ts"
import { hasBody, type HttpMethod } from "./HttpMethod.ts"
import * as bodyInternal from "./internal/httpBody.ts"
import * as Url from "./Url.ts"
import * as UrlParams from "./UrlParams.ts"

Expand Down Expand Up @@ -649,25 +650,12 @@ export const setBody: {
(body: HttpBody.HttpBody): (self: HttpClientRequest) => HttpClientRequest
(self: HttpClientRequest, body: HttpBody.HttpBody): HttpClientRequest
} = dual(2, (self: HttpClientRequest, body: HttpBody.HttpBody): HttpClientRequest => {
let headers = self.headers
if (body._tag === "Empty" || body._tag === "FormData") {
headers = Headers.remove(Headers.remove(headers, "Content-Type"), "Content-length")
} else {
if (body.contentType) {
headers = Headers.set(headers, "content-type", body.contentType)
}
if (body.contentLength !== undefined) {
headers = Headers.set(headers, "content-length", body.contentLength.toString())
} else {
headers = Headers.remove(headers, "content-length")
}
}
return makeWith(
self.method,
self.url,
self.urlParams,
self.hash,
headers,
bodyInternal.updateHeaders(self.headers, body),
body
)
})
Expand Down
8 changes: 5 additions & 3 deletions packages/effect/src/unstable/http/HttpServerResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import * as HttpClientRequest from "./HttpClientRequest.ts"
import * as HttpClientResponse from "./HttpClientResponse.ts"
import * as HttpIncomingMessage from "./HttpIncomingMessage.ts"
import type { HttpPlatform } from "./HttpPlatform.ts"
import * as bodyInternal from "./internal/httpBody.ts"
import * as Template from "./Template.ts"
import * as UrlParams from "./UrlParams.ts"

Expand Down Expand Up @@ -931,7 +932,8 @@ export const setBody: {
(self: HttpServerResponse, body: Body.HttpBody): HttpServerResponse
} = dual(
2,
(self: HttpServerResponse, body: Body.HttpBody): HttpServerResponse => makeResponse({ ...self, body })
(self: HttpServerResponse, body: Body.HttpBody): HttpServerResponse =>
makeResponse({ ...self, headers: bodyInternal.updateHeaders(self.headers, body), body })
)

/**
Expand Down Expand Up @@ -1345,13 +1347,13 @@ const makeResponse = (options: {
self.body = options.body ?? Body.empty
if (
self.body._tag !== "Empty" &&
(self.body.contentType || self.body.contentLength)
(self.body.contentType || self.body.contentLength !== undefined)
) {
const newHeaders = Headers.fromRecordUnsafe({ ...options.headers }) as any
if (self.body.contentType) {
newHeaders["content-type"] = self.body.contentType
}
if (self.body.contentLength) {
if (self.body.contentLength !== undefined) {
newHeaders["content-length"] = self.body.contentLength.toString()
}
self.headers = newHeaders
Expand Down
15 changes: 15 additions & 0 deletions packages/effect/src/unstable/http/internal/httpBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Headers from "../Headers.ts"
import type * as HttpBody from "../HttpBody.ts"

/** @internal */
export const updateHeaders = (headers: Headers.Headers, body: HttpBody.HttpBody): Headers.Headers => {
if (body._tag === "Empty" || body._tag === "FormData") {
return Headers.remove(Headers.remove(headers, "content-type"), "content-length")
}
headers = body.contentType === undefined
? Headers.remove(headers, "content-type")
: Headers.set(headers, "content-type", body.contentType)
return body.contentLength === undefined
? Headers.remove(headers, "content-length")
: Headers.set(headers, "content-length", body.contentLength.toString())
}
18 changes: 17 additions & 1 deletion packages/effect/test/unstable/http/HttpServerResponse.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assert, describe, it } from "@effect/vitest"
import { Context, Effect, Stream } from "effect"
import { HttpClientRequest, HttpClientResponse, HttpServerResponse } from "effect/unstable/http"
import { HttpBody, HttpClientRequest, HttpClientResponse, HttpServerResponse } from "effect/unstable/http"

const TestValue = Context.Reference<number>("test/TestValue", { defaultValue: () => 0 })

Expand Down Expand Up @@ -75,4 +75,20 @@ describe("HttpServerResponse", () => {
assert.strictEqual(response.status, 200)
assert.strictEqual(yield* roundTrip.text, "")
}))

it("synchronizes body metadata headers for empty and replaced bodies", () => {
Comment thread
pullfrog[bot] marked this conversation as resolved.
const emptyBytes = HttpServerResponse.uint8Array(new Uint8Array())
assert.strictEqual(emptyBytes.headers["content-length"], "0")

const replaced = HttpServerResponse.setBody(HttpServerResponse.text("abc"), HttpBody.empty)
assert.notProperty(replaced.headers, "content-type")
assert.notProperty(replaced.headers, "content-length")

const streamed = HttpServerResponse.setBody(
HttpServerResponse.text("abc"),
HttpBody.stream(Stream.empty, "application/octet-stream")
)
assert.strictEqual(streamed.headers["content-type"], "application/octet-stream")
assert.notProperty(streamed.headers, "content-length")
})
})
Loading