diff --git a/packages/server/src/plugins/response-compression.test.ts b/packages/server/src/plugins/response-compression.test.ts index 6ca850320..3e912bff3 100644 --- a/packages/server/src/plugins/response-compression.test.ts +++ b/packages/server/src/plugins/response-compression.test.ts @@ -670,6 +670,40 @@ describe('responseCompressionHandlerPlugin', () => { expect(matched).toBe(true) expect(response!.headers.has('content-encoding')).toBe(false) }) + + it.each([ + ['text/csv; charset=utf-8', 'text/plain', 'gzip'], + ['text/html; charset=utf-8', '', 'gzip'], + ['image/png', 'text/plain', null], + [[], 'text/plain', null], + ])('uses content-type header %j over blob type %j', async (contentType, blobType, encoding) => { + const handler = new RPCHandler(os.handler(() => new Blob(['large content'.repeat(100)], { type: blobType })), { + plugins: [new ResponseCompressionHandlerPlugin({ threshold: 100 })], + routingInterceptors: [async ({ next }) => { + const result = await next() + + return result.matched + ? { ...result, response: { ...result.response, headers: { ...result.response.headers, 'content-type': contentType } } } + : result + }], + }) + + const handle = (acceptEncoding: string) => handler.handle(new Request('http://localhost', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'accept-encoding': acceptEncoding, + }, + body: JSON.stringify({ json: null }), + })) + + const { response } = await handle('gzip') + const { response: uncompressed } = await handle('identity') + + expect(response!.headers.get('content-encoding')).toBe(encoding) + // Same content-type the adapter sends when the response is not compressed + expect(response!.headers.get('content-type')).toBe(uncompressed!.headers.get('content-type')) + }) }) describe('readable stream body', () => { diff --git a/packages/server/src/plugins/response-compression.ts b/packages/server/src/plugins/response-compression.ts index 230ac8c40..e61aea7dd 100644 --- a/packages/server/src/plugins/response-compression.ts +++ b/packages/server/src/plugins/response-compression.ts @@ -102,9 +102,12 @@ export class ResponseCompressionHandlerPlugin implements Stan } else if (body instanceof Blob) { + // Adapters only fall back to the blob type when no Content-Type header is set + const contentType = headers['content-type'] ?? body.type + if ( (!Number.isFinite(body.size) || body.size >= this.threshold) - && this.isCompressibleContentType(body.type, interceptorOptions) + && this.isCompressibleContentType(flattenStandardHeader(contentType), interceptorOptions) ) { const contentDisposition = headers['content-disposition'] ?? generateContentDisposition( body instanceof File ? body.name : 'blob', @@ -118,7 +121,7 @@ export class ResponseCompressionHandlerPlugin implements Stan headers: { ...toCompressedHeaders(headers, encoding), 'standard-server': 'file' satisfies StandardBodyHint, - 'content-type': body.type, + 'content-type': contentType, 'content-disposition': contentDisposition, }, },