fix(server): compress responses with correct content types (#44321)

This commit is contained in:
Brendan Allan 2026-08-23 14:18:20 +08:00 committed by GitHub
parent b7167aaab0
commit b8fb894ec7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 7 deletions

View file

@ -42,7 +42,9 @@ function serveUI(request: HttpServerRequest.HttpServerRequest, url: URL, assets:
"x-content-type-options": "nosniff",
}
return Effect.succeed(
request.method === "HEAD" ? HttpServerResponse.empty({ headers }) : HttpServerResponse.raw(file, { headers }),
request.method === "HEAD"
? HttpServerResponse.empty({ headers })
: HttpServerResponse.raw(file, { headers, contentType: headers["content-type"] }),
)
}

View file

@ -55,6 +55,7 @@ describe("web UI", () => {
const script = yield* Effect.promise(() => fetch(`${origin}/app.js`))
expect(yield* Effect.promise(() => script.text())).toBe("console.log('embedded')")
expect(script.headers.get("content-type")).toContain("javascript")
expect(script.headers.get("cache-control")).toBe("public, max-age=31536000, immutable")
const worker = yield* Effect.promise(() => fetch(`${origin}/sw.js`))
@ -64,6 +65,7 @@ describe("web UI", () => {
expect(registration.headers.get("cache-control")).toBe("no-cache")
const font = yield* Effect.promise(() => fetch(`${origin}/font.woff2`))
expect(font.headers.get("content-type")).toBe("font/woff2")
expect(new Uint8Array(yield* Effect.promise(() => font.arrayBuffer()))).toEqual(
new Uint8Array([0, 1, 2, 255]),
)

View file

@ -4,7 +4,14 @@ import { NodeHttpServer } from "@effect/platform-node"
import { SessionRestart } from "@opencode-ai/core/session/execution/restart"
import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty"
import { Cause, Context, Effect, Exit, Latch, Layer, Option, Ref, Scope } from "effect"
import { HttpMiddleware, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import {
HttpMiddleware,
HttpPlatform,
HttpRouter,
HttpServer,
HttpServerRequest,
HttpServerResponse,
} from "effect/unstable/http"
import { createServer } from "node:http"
import { ServerAuth } from "./auth"
import { isAllowedCorsOrigin } from "./cors"
@ -90,7 +97,7 @@ export const start = Effect.fn("ServerProcess.start")(function* <E, R>(
const host = address.family === "IPv6" ? `[${address.address}]` : address.address
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname)
},
).pipe(Layer.provide(NodeHttpServer.layerHttpServices)),
).pipe(Layer.provideMerge(NodeHttpServer.layerHttpServices)),
applicationScope,
)
if (lifecycle) {
@ -98,7 +105,12 @@ export const start = Effect.fn("ServerProcess.start")(function* <E, R>(
Effect.provideService(Scope.Scope, applicationScope),
)
}
const app = Context.get(context, HttpRouter.HttpRouter).asHttpEffect()
const app = Context.get(context, HttpRouter.HttpRouter)
.asHttpEffect()
.pipe(
HttpMiddleware.compression(),
Effect.provideService(HttpPlatform.HttpPlatform, Context.get(context, HttpPlatform.HttpPlatform)),
)
yield* Ref.set(application, Option.some(transform ? transform(app) : app))
yield* status.ready
return { address: bound.http.address, shutdown: shutdown.await }

View file

@ -6,6 +6,7 @@ import { ServerProcess } from "../src/process"
it.live("allows browser preflight requests without credentials", () =>
Effect.gen(function* () {
const fallback = "fallback".repeat(256)
const server = yield* ServerProcess.start<never, never>(
{
hostname: "127.0.0.1",
@ -19,7 +20,7 @@ it.live("allows browser preflight requests without credentials", () =>
api.pipe(
Effect.catchIf(
(error) => error instanceof HttpServerError.HttpServerError && error.reason._tag === "RouteNotFound",
() => Effect.succeed(HttpServerResponse.text("fallback")),
() => Effect.succeed(HttpServerResponse.raw(fallback, { contentType: "text/plain" })),
),
),
)
@ -51,12 +52,30 @@ it.live("allows browser preflight requests without credentials", () =>
expect(health.headers.get("access-control-allow-origin")).toBe("http://localhost:3000")
expect(yield* Effect.promise(() => health.json())).toMatchObject({ version: "test-version" })
const event = yield* Effect.promise(() =>
fetch(new URL("/api/event", HttpServer.formatAddress(server.address)), {
headers: {
"accept-encoding": "br",
authorization: `Basic ${btoa("opencode:secret")}`,
},
}),
)
expect(event.status).toBe(200)
expect(event.headers.get("content-encoding")).toBeNull()
yield* Effect.promise(() => event.body?.cancel() ?? Promise.resolve())
const missing = yield* Effect.promise(() =>
fetch(new URL("/missing", HttpServer.formatAddress(server.address)), {
headers: { authorization: `Basic ${btoa("opencode:secret")}` },
headers: {
"accept-encoding": "br",
authorization: `Basic ${btoa("opencode:secret")}`,
},
}),
)
expect(missing.status).toBe(200)
expect(yield* Effect.promise(() => missing.text())).toBe("fallback")
expect(missing.headers.get("content-encoding")).toBe("br")
expect(missing.headers.get("content-type")).toBe("text/plain")
expect(missing.headers.get("vary")?.toLowerCase()).toContain("accept-encoding")
expect(yield* Effect.promise(() => missing.text())).toBe(fallback)
}),
)