From c8ad1542344f4f8ec1cb558af2f6957a0b8403bb Mon Sep 17 00:00:00 2001 From: Taku Amano Date: Mon, 19 Aug 2024 09:37:51 +0900 Subject: [PATCH] fix: return response from res.body if internal data is not ready to be returned directly (#188) --- src/listener.ts | 29 +++++++++++++++++++---------- test/server.test.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/listener.ts b/src/listener.ts index a9d46c2..9ea890f 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -92,18 +92,27 @@ const responseViaResponseObject = async ( // eslint-disable-next-line @typescript-eslint/no-explicit-any const internalBody = getInternalBody(res as any) if (internalBody) { - if (internalBody.length) { - resHeaderRecord['content-length'] = internalBody.length - } - outgoing.writeHead(res.status, resHeaderRecord) - if (typeof internalBody.source === 'string' || internalBody.source instanceof Uint8Array) { - outgoing.end(internalBody.source) - } else if (internalBody.source instanceof Blob) { - outgoing.end(new Uint8Array(await internalBody.source.arrayBuffer())) + const { length, source, stream } = internalBody + if (source instanceof Uint8Array && source.byteLength !== length) { + // maybe `source` is detached, so we should send via res.body } else { - await writeFromReadableStream(internalBody.stream, outgoing) + // send via internal raw data + if (length) { + resHeaderRecord['content-length'] = length + } + outgoing.writeHead(res.status, resHeaderRecord) + if (typeof source === 'string' || source instanceof Uint8Array) { + outgoing.end(source) + } else if (source instanceof Blob) { + outgoing.end(new Uint8Array(await source.arrayBuffer())) + } else { + await writeFromReadableStream(stream, outgoing) + } + return } - } else if (res.body) { + } + + if (res.body) { /** * If content-encoding is set, we assume that the response should be not decoded. * Else if transfer-encoding is set, we assume that the response should be streamed. diff --git a/test/server.test.ts b/test/server.test.ts index 5aa39c6..f7489fd 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -5,6 +5,7 @@ import { Response as PonyfillResponse } from '@whatwg-node/fetch' import { Hono } from 'hono' import { basicAuth } from 'hono/basic-auth' import { compress } from 'hono/compress' +import { etag } from 'hono/etag' import { poweredBy } from 'hono/powered-by' import { stream } from 'hono/streaming' import request from 'supertest' @@ -143,6 +144,20 @@ describe('via internal body', () => { }) return new Response(stream) }) + app.get('/buffer', () => { + const response = new Response(Buffer.from('Hello Hono!'), { + headers: { 'content-type': 'text/plain' }, + }) + return response + }) + + app.use('/etag/*', etag()) + app.get('/etag/buffer', () => { + const response = new Response(Buffer.from('Hello Hono!'), { + headers: { 'content-type': 'text/plain' }, + }) + return response + }) const server = createAdaptorServer(app) @@ -186,6 +201,23 @@ describe('via internal body', () => { expect(res.headers['content-length']).toBeUndefined() expect(expectedChunks.length).toBe(0) // all chunks are received }) + + it('Should return 200 response - GET /buffer', async () => { + const res = await request(server).get('/buffer') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.headers['content-length']).toMatch('11') + expect(res.text).toBe('Hello Hono!') + }) + + it('Should return 200 response - GET /etag/buffer', async () => { + const res = await request(server).get('/etag/buffer') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.headers['etag']).toMatch('"7e03b9b8ed6156932691d111c81c34c3c02912f9"') + expect(res.headers['content-length']).toMatch('11') + expect(res.text).toBe('Hello Hono!') + }) }) describe('Routing', () => {