Skip to content

Commit 6649c06

Browse files
authored
feat(hono): support for hono with createHonoProxyMiddleware (#1193)
1 parent 3945773 commit 6649c06

10 files changed

Lines changed: 238 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- fix(fixRequestBody): support content-encoding on request body (#1142)
1616
- fix: prevent TypeError when ws enabled but server is undefined (#1163)
1717
- fix: applyPathRewrite logs old req.url instead of rewritten path (#1157)
18+
- feat(hono): support for hono with createHonoProxyMiddleware
1819

1920
## [v3.0.5](https://github.com/chimurai/http-proxy-middleware/releases/tag/v3.0.5)
2021

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Known Vulnerabilities](https://snyk.io/test/github/chimurai/http-proxy-middleware/badge.svg)](https://snyk.io/test/github/chimurai/http-proxy-middleware)
66
[![npm](https://img.shields.io/npm/v/http-proxy-middleware?color=%23CC3534&style=flat-square&logo=npm)](https://www.npmjs.com/package/http-proxy-middleware)
77

8-
Node.js proxying made simple. Configure proxy middleware with ease for [connect](https://github.com/senchalabs/connect), [express](https://github.com/expressjs/express), [next.js](https://github.com/vercel/next.js) and [many more](#compatible-servers).
8+
Node.js proxying made simple. Configure proxy middleware with ease for [connect](https://github.com/senchalabs/connect), [express](https://github.com/expressjs/express), [next.js](https://github.com/vercel/next.js), [hono](https://github.com/honojs/hono) and [many more](#compatible-servers).
99

1010
Powered by [`httpxy`](https://github.com/unjs/httpxy). A maintained version of [http-proxy](https://github.com/http-party/node-http-proxy).
1111

@@ -592,6 +592,7 @@ View the [recipes](https://github.com/chimurai/http-proxy-middleware/tree/master
592592

593593
- [connect](https://www.npmjs.com/package/connect)
594594
- [express](https://www.npmjs.com/package/express)
595+
- [hono](https://www.npmjs.com/package/@hono/node-server)
595596
- [next.js](https://www.npmjs.com/package/next)
596597
- [fastify](https://www.npmjs.com/package/fastify)
597598
- [browser-sync](https://www.npmjs.com/package/browser-sync)

examples/hono/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @ts-check
2+
/**
3+
* Module dependencies.
4+
*/
5+
import { serve } from '@hono/node-server';
6+
import { Hono } from 'hono';
7+
import open from 'open';
8+
9+
import { createHonoProxyMiddleware } from '../../dist/index.js';
10+
11+
const app = new Hono();
12+
13+
app.use(
14+
'/users',
15+
createHonoProxyMiddleware({
16+
target: 'http://jsonplaceholder.typicode.com',
17+
changeOrigin: true, // for vhosted sites, changes host header to match to target's host
18+
logger: console,
19+
}),
20+
);
21+
22+
console.log('Server is running on http://localhost:3000');
23+
24+
const server = serve(app);
25+
26+
console.log('[DEMO] Server: listening on port 3000');
27+
console.log('[DEMO] Opening: http://localhost:3000/users');
28+
29+
open('http://localhost:3000/users');
30+
31+
process.on('SIGINT', () => server.close());
32+
process.on('SIGTERM', () => server.close());

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"@commitlint/cli": "20.5.0",
6868
"@commitlint/config-conventional": "20.5.0",
6969
"@eslint/js": "10.0.1",
70+
"@hono/node-server": "1.19.12",
7071
"@trivago/prettier-plugin-sort-imports": "6.0.2",
7172
"@types/debug": "4.1.13",
7273
"@types/eslint": "9.6.1",
@@ -82,6 +83,7 @@
8283
"express": "5.2.1",
8384
"get-port": "7.2.0",
8485
"globals": "17.4.0",
86+
"hono": "4.12.10",
8587
"husky": "9.1.7",
8688
"lint-staged": "16.4.0",
8789
"mockttp": "4.3.0",

recipes/servers.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Missing a server? Feel free to extend this list of examples.
88
- [Express](#express)
99
- [Connect](#connect)
1010
- [Next.js](#nextjs)
11+
- [Hono](#hono)
1112
- [fastify](#fastify)
1213
- [Browser-Sync](#browser-sync)
1314
- [Polka](#polka)
@@ -132,6 +133,32 @@ export const config = {
132133
// curl http://localhost:3000/api/users
133134
```
134135

136+
## Hono
137+
138+
<https://github.com/honojs/hono> [![GitHub stars](https://img.shields.io/github/stars/honojs/hono.svg?style=social&label=Star)](https://github.com/honojs/hono)
139+
![hono downloads](https://img.shields.io/npm/dm/hono)
140+
141+
```javascript
142+
import { serve } from '@hono/node-server';
143+
import { Hono } from 'hono';
144+
import { createHonoProxyMiddleware } from 'http-proxy-middleware';
145+
146+
const app = new Hono();
147+
148+
app.use(
149+
'/users',
150+
createHonoProxyMiddleware({
151+
target: 'http://jsonplaceholder.typicode.com',
152+
changeOrigin: true, // for vhosted sites, changes host header to match to target's host
153+
logger: console,
154+
}),
155+
);
156+
157+
const server = serve(app);
158+
159+
// curl http://localhost:3000/users
160+
```
161+
135162
## fastify
136163

137164
<https://github.com/fastify/fastify> [![GitHub stars](https://img.shields.io/github/stars/fastify/fastify.svg?style=social&label=Star)](https://github.com/fastify/fastify)

src/factory-hono.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { HttpBindings } from '@hono/node-server';
2+
import type { MiddlewareHandler } from 'hono';
3+
4+
import { type Options, createProxyMiddleware } from './index.js';
5+
import { getLogger } from './logger.js';
6+
7+
/**
8+
* Creates a Hono middleware that proxies requests using http-proxy-middleware.
9+
*
10+
* `@remarks`
11+
* This middleware requires Hono to be running on Node.js via `@hono/node-server`.
12+
* It uses `c.env.incoming` and `c.env.outgoing` which are only available with `HttpBindings`.
13+
*
14+
* `@experimental` This API is experimental and may change without a major version bump.
15+
*
16+
* `@example`
17+
* ```ts
18+
* import { serve } from '@hono/node-server';
19+
* import { Hono } from 'hono';
20+
* import { createHonoProxyMiddleware } from 'http-proxy-middleware';
21+
*
22+
* const app = new Hono();
23+
* app.use('/api', createHonoProxyMiddleware({ target: 'http://example.com', changeOrigin: true }));
24+
* serve(app);
25+
*/
26+
export function createHonoProxyMiddleware(
27+
options: Options,
28+
): MiddlewareHandler<{ Bindings: HttpBindings }> {
29+
const proxy = createProxyMiddleware(options);
30+
const logger = getLogger(options);
31+
32+
return (c, next) => {
33+
return new Promise<void>((resolve, reject) => {
34+
proxy(c.env.incoming, c.env.outgoing, (err) => {
35+
if (err) {
36+
reject(err);
37+
} else {
38+
resolve();
39+
}
40+
});
41+
})
42+
.then(() => next())
43+
.catch((err) => {
44+
logger.error('Proxy error:', err);
45+
return c.text('Proxy Error', 500);
46+
});
47+
};
48+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './factory.js';
2+
export * from './factory-hono.js';
23

34
export * from './handlers/index.js';
45

test/e2e/hono.spec.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { HttpBindings, ServerType, serve } from '@hono/node-server';
2+
import getPort from 'get-port';
3+
import { Hono } from 'hono';
4+
import { Mockttp, getLocal } from 'mockttp';
5+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6+
7+
import { createHonoProxyMiddleware } from './test-kit.js';
8+
9+
describe('E2E Hono', () => {
10+
describe('http-proxy-middleware in actual Hono server', () => {
11+
let app: Hono<{ Bindings: HttpBindings }>;
12+
13+
let mockTargetServer: Mockttp;
14+
15+
beforeEach(async () => {
16+
mockTargetServer = getLocal();
17+
await mockTargetServer.start();
18+
});
19+
20+
afterEach(async () => {
21+
await mockTargetServer.stop();
22+
});
23+
24+
describe('basic setup, requests to target', () => {
25+
let server: ServerType;
26+
let serverPort: number;
27+
28+
beforeEach(async () => {
29+
app = new Hono<{ Bindings: HttpBindings }>();
30+
serverPort = await getPort();
31+
32+
app.use(
33+
'/api',
34+
createHonoProxyMiddleware({
35+
target: `http://localhost:${mockTargetServer.port}`,
36+
pathFilter: '/api',
37+
}),
38+
);
39+
40+
server = serve({
41+
fetch: app.fetch,
42+
port: serverPort,
43+
});
44+
});
45+
46+
afterEach(() => {
47+
server.close();
48+
});
49+
50+
it('should have response body: "HELLO WEB"', async () => {
51+
await mockTargetServer.forGet('/api').thenReply(200, 'HELLO WEB');
52+
const response = await fetch(`http://127.0.0.1:${serverPort}/api`);
53+
expect(response.status).toBe(200);
54+
await expect(response.text()).resolves.toBe('HELLO WEB');
55+
});
56+
57+
it('should handle connection reset', async () => {
58+
await mockTargetServer.forGet('/api').thenResetConnection();
59+
const response = await fetch(`http://127.0.0.1:${serverPort}/api`);
60+
expect(response.status).toBe(504);
61+
await expect(response.text()).resolves.toContain('Error occurred while trying to proxy:');
62+
});
63+
});
64+
65+
describe('pathFilter matching', () => {
66+
let server: ServerType;
67+
let serverPort: number;
68+
69+
beforeEach(async () => {
70+
app = new Hono<{ Bindings: HttpBindings }>();
71+
serverPort = await getPort();
72+
73+
app.use(
74+
createHonoProxyMiddleware({
75+
target: `http://localhost:${mockTargetServer.port}`,
76+
pathFilter: '/api',
77+
}),
78+
);
79+
80+
app.get('/other', (c) => c.text('DOWNSTREAM HANDLER'));
81+
82+
server = serve({
83+
fetch: app.fetch,
84+
port: serverPort,
85+
});
86+
});
87+
88+
afterEach(() => {
89+
server.close();
90+
});
91+
92+
it('should proxy requests when request url matches pathFilter', async () => {
93+
await mockTargetServer.forGet('/api').thenReply(200, 'HELLO API');
94+
95+
const response = await fetch(`http://127.0.0.1:${serverPort}/api`);
96+
97+
expect(response.status).toBe(200);
98+
await expect(response.text()).resolves.toBe('HELLO API');
99+
});
100+
101+
it('should not proxy requests when request url does not match pathFilter', async () => {
102+
const response = await fetch(`http://127.0.0.1:${serverPort}/other`);
103+
104+
expect(response.status).toBe(200);
105+
await expect(response.text()).resolves.toBe('DOWNSTREAM HANDLER');
106+
});
107+
});
108+
});
109+
});

test/e2e/test-kit.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import express, { type Express, type RequestHandler } from 'express';
22

3-
export { createProxyMiddleware, responseInterceptor, fixRequestBody } from '../../src/index.js';
3+
export {
4+
createProxyMiddleware,
5+
createHonoProxyMiddleware,
6+
responseInterceptor,
7+
fixRequestBody,
8+
} from '../../src/index.js';
49

510
export function createApp(...middlewares): Express {
611
const app = express();

yarn.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,11 @@
389389
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
390390
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
391391

392+
"@hono/node-server@1.19.12":
393+
version "1.19.12"
394+
resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.12.tgz#dae075247959b6d7d2dba4c8bdc8c452ca0c7b40"
395+
integrity sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==
396+
392397
"@httptoolkit/httpolyglot@^3.0.1":
393398
version "3.0.1"
394399
resolved "https://registry.yarnpkg.com/@httptoolkit/httpolyglot/-/httpolyglot-3.0.1.tgz#b76598a852232c59fcab98d717c22bef512e169f"
@@ -2426,6 +2431,11 @@ hasown@^2.0.2:
24262431
dependencies:
24272432
function-bind "^1.1.2"
24282433

2434+
hono@4.12.10:
2435+
version "4.12.10"
2436+
resolved "https://registry.yarnpkg.com/hono/-/hono-4.12.10.tgz#1d95bd5b3d2fd0c75e84725a087ac50a16ed8b9e"
2437+
integrity sha512-mx/p18PLy5og9ufies2GOSUqep98Td9q4i/EF6X7yJgAiIopxqdfIO3jbqsi3jRgTgw88jMDEzVKi+V2EF+27w==
2438+
24292439
html-escaper@^2.0.0:
24302440
version "2.0.2"
24312441
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"

0 commit comments

Comments
 (0)