diff --git a/packages/http-server-js/src/http/server/index.ts b/packages/http-server-js/src/http/server/index.ts index 9eca8ed4316..6f020fe21e8 100644 --- a/packages/http-server-js/src/http/server/index.ts +++ b/packages/http-server-js/src/http/server/index.ts @@ -641,24 +641,30 @@ function* emitResultProcessingForType( if (body) { const bodyCase = parseCase(body.name); - const serializationRequired = isSerializationRequired( - ctx, - module, - body.type, - "application/json", - ); - requireSerialization(ctx, body.type, "application/json"); - yield `${names.ctx}.response.setHeader("content-type", "application/json");`; - - if (serializationRequired) { - const typeReference = emitTypeReference(ctx, body.type, body, module, { - altName: namer.getAltName("Body"), - requireDeclaration: true, - }); - yield `${names.ctx}.response.end(globalThis.JSON.stringify(${typeReference}.toJsonObject(${names.result}.${bodyCase.camelCase})))`; + if (ctx.program.checker.isStdType(body.type, "bytes")) { + // Binary response: content-type already set by @header above, send bytes directly. + yield `${names.ctx}.response.end(${names.result}.${bodyCase.camelCase});`; } else { - yield `${names.ctx}.response.end(globalThis.JSON.stringify(${names.result}.${bodyCase.camelCase}));`; + const serializationRequired = isSerializationRequired( + ctx, + module, + body.type, + "application/json", + ); + requireSerialization(ctx, body.type, "application/json"); + + yield `${names.ctx}.response.setHeader("content-type", "application/json");`; + + if (serializationRequired) { + const typeReference = emitTypeReference(ctx, body.type, body, module, { + altName: namer.getAltName("Body"), + requireDeclaration: true, + }); + yield `${names.ctx}.response.end(globalThis.JSON.stringify(${typeReference}.toJsonObject(${names.result}.${bodyCase.camelCase})))`; + } else { + yield `${names.ctx}.response.end(globalThis.JSON.stringify(${names.result}.${bodyCase.camelCase}));`; + } } } else if (isArrayModelType(target)) { const itemType = target.indexer.value; diff --git a/packages/http-server-js/test/scalar.test.ts b/packages/http-server-js/test/scalar.test.ts index b3c786dadf6..c7c6e28da6e 100644 --- a/packages/http-server-js/test/scalar.test.ts +++ b/packages/http-server-js/test/scalar.test.ts @@ -209,6 +209,27 @@ describe("scalar", () => { expect(serverRaw).toMatch(/response\.end\(globalThis\.JSON\.stringify\(__result_\d+\)\);/); }); + it("emits direct bytes send for binary body with custom content-type", async () => { + const { outputs } = await HttpServerEmitterTester.compile(` + @service(#{ title: "Example" }) + @route("/") + namespace Example { + @post op download(): { + @statusCode statusCode: 200; + @header contentType: "application/zip"; + @body content: bytes; + }; + } + `); + + const serverRaw = outputs["src/generated/http/operations/server-raw.ts"]; + + expect(serverRaw).toBeDefined(); + expect(serverRaw).not.toContain('"application/json"'); + expect(serverRaw).not.toContain("Uint8Array.toJsonObject"); + expect(serverRaw).toMatch(/response\.end\(__result_\d+\.content\);/); + }); + describe("date/time/duration types", () => { describe("mode: temporal", () => { const options: JsEmitterOptions = {