diff --git a/README.md b/README.md index 1727307..cbaf610 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,33 @@ type Http2Bindings = { } ``` +## Direct response from Node.js API + +You can directly respond to the client from the Node.js API. +In that case, the response from Hono should be ignored, so return RESPONSE_ALREADY_SENT. + +> [!NOTE] +> This feature can be used when migrating existing Node.js applications to Hono, but we recommend using Hono's API for new applications. + +```ts +import { serve } from '@hono/node-server' +import type { HttpBindings } from '@hono/node-server' +import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response' +import { Hono } from 'hono' + +const app = new Hono<{ Bindings: HttpBindings }>() + +app.get('/', (c) => { + const { outgoing } = c.env + outgoing.writeHead(200, { 'Content-Type': 'text/plain' }) + outgoing.end('Hello World\n') + + return RESPONSE_ALREADY_SENT +}) + +serve(app) +``` + ## Related projects - Hono - diff --git a/package.json b/package.json index 3d27357..2ad8064 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,11 @@ "types": "./dist/vercel.d.ts", "require": "./dist/vercel.js", "import": "./dist/vercel.mjs" + }, + "./utils/*": { + "types": "./dist/utils/*.d.ts", + "require": "./dist/utils/*.js", + "import": "./dist/utils/*.mjs" } }, "typesVersions": { @@ -34,6 +39,9 @@ ], "vercel": [ "./dist/vercel.d.ts" + ], + "utils/*": [ + "./dist/utils/*.d.ts" ] } }, diff --git a/src/listener.ts b/src/listener.ts index d8af24f..0957347 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -4,6 +4,7 @@ import { getAbortController, newRequest } from './request' import { cacheKey, getInternalBody } from './response' import type { CustomErrorHandler, FetchCallback, HttpBindings } from './types' import { writeFromReadableStream, buildOutgoingHttpHeaders } from './utils' +import { X_ALREADY_SENT } from './utils/response/constants' import './globals' const regBuffer = /^no$/i @@ -128,6 +129,8 @@ const responseViaResponseObject = async ( outgoing.writeHead(res.status, resHeaderRecord) outgoing.end(new Uint8Array(buffer)) } + } else if (resHeaderRecord[X_ALREADY_SENT]) { + // do nothing, the response has already been sent } else { outgoing.writeHead(res.status, resHeaderRecord) outgoing.end() diff --git a/src/utils/response.ts b/src/utils/response.ts new file mode 100644 index 0000000..69b57b8 --- /dev/null +++ b/src/utils/response.ts @@ -0,0 +1,4 @@ +import { X_ALREADY_SENT } from './response/constants' +export const RESPONSE_ALREADY_SENT = new Response(null, { + headers: { [X_ALREADY_SENT]: 'true' }, +}) diff --git a/src/utils/response/constants.ts b/src/utils/response/constants.ts new file mode 100644 index 0000000..0dc0003 --- /dev/null +++ b/src/utils/response/constants.ts @@ -0,0 +1 @@ +export const X_ALREADY_SENT = 'x-hono-already-sent' diff --git a/test/utils/response.test.ts b/test/utils/response.test.ts new file mode 100644 index 0000000..5ae3e38 --- /dev/null +++ b/test/utils/response.test.ts @@ -0,0 +1,23 @@ +import { Hono } from 'hono' +import request from 'supertest' +import type { HttpBindings } from '../../src/' +import { createAdaptorServer } from '../../src/server' +import { RESPONSE_ALREADY_SENT } from '../../src/utils/response' + +describe('RESPONSE_ALREADY_SENT', () => { + const app = new Hono<{ Bindings: HttpBindings }>() + app.get('/', (c) => { + const { outgoing } = c.env + outgoing.writeHead(200, { 'Content-Type': 'text/plain' }) + outgoing.end('Hono!') + return RESPONSE_ALREADY_SENT + }) + const server = createAdaptorServer(app) + + it('Should return 200 response - GET /', async () => { + const res = await request(server).get('/') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.text).toBe('Hono!') + }) +})