-
Notifications
You must be signed in to change notification settings - Fork 852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: use base node http types #723
Conversation
BREAKING CHANGE: `RequestHandler` => `RequestMiddleware`. Middleware now is expressed in terms of node core http.* types, which express req/res are compatible with. Orienting to base http.* types allows better typing accross all node servers, as eventually each implementation has these values in their servers. That is to say, express, koa, fastify, next, etc all use http.IncomingMessage/http.ServerResponse under the hood, thus the new middleware types are compatible with everyone.
BREAKING CHANGE: `RequestHandler` => `RequestMiddleware`. Middleware now is expressed in terms of node core http.* types, which express req/res are compatible with. Orienting to base http.* types allows better typing accross all node servers, as eventually each implementation has these values in their servers. That is to say, express, koa, fastify, next, etc all use http.IncomingMessage/http.ServerResponse under the hood, thus the new middleware types are compatible with everyone.
…eware into refactor/types
@chimurai , any issues, or g2g? saw tasks were cancelled, but tests were lookin good in CI |
Hi @cdaringe. The tests were running indefinitely in github-actions. I cancelled them after a while. Think it's because the server is still running after the test finished. (just found out I had to submit the review :) ) Haven't got time yet to do a thorough review. I'll try see if I can do it soon |
hmm, odd. locally, they all exit fine. i sent a patch w/ server.close. lets see if it takes. i thought you had cancelled them for other reasons 😆 |
src/types.ts
Outdated
* values are primarily decorated onto IncomingMessage by express, but are | ||
* not required for use. | ||
*/ | ||
declare module 'http' { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Augmenting http.IncomingMessage
does some assumptions on properties from certain frameworks; ie. express
.
Wondering if there is a way to provide better support (without hardcoding them?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m AFK for a day or so, but iirc, HPM mutates and/or attempts to read these values. This augment was only to satisfy HPMs existing internal usage of these fields. It does not require frameworks to implement these, luckily.
Will have to visit on a field by field basis. Probably worth going thru them and seeing if we can avoid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I had some time this morning.
I was almost able to decouple full from express, and improve typesafety (by reducing casts).
Unfortunately, there is one macro barrier--and that is that fact that internally express
mutates req.url
in a fn called trim_prefix
and exposes the desired url on originalUrl
. this is harmful behavior, and make supporting express difficult on a plain node http
, particularly because apps can app.use(...)
at many levels inside of express
.
so, we need to either need to augment http.IncomingMessage
as done above and continue to support originalUrl
, or, update the HPM createProxyMiddleware
inferface & accept a pathPrefix
(🤢 ) and use that to reconstruct original req.url
s. The latter sounds dicey, and perhaps has edge cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also posted to express here: expressjs/express#4854
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did some testing today.
Think augmenting native types is somewhat undesirable; As these augmented types will bubble up to the projects in which http-proxy-middleware is used.
Wouldn't adding back @types/express
as devDependency makes sense? And cast internal req
objects to express.Request
at places where it's needed. Instead of creating and maintaining them as internal types. This way it's also documented for which framework it's serving its purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think augmenting native types is somewhat undesirable; As these augmented types will bubble up to the projects in which http-proxy-middleware is used.
Agreed. Let's not do that.
Wouldn't adding back @types/express as devDependency makes sense? And cast internal req objects to express.Request at places where it's needed.
Ya, we can do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmm, in retrospect, if we bring them back and cast, they'd need to be a peer dependency or top level dependency for TS users who aren't skipLibCheck: true
'ing :).
the only fields that are needed are body
& originalUrl
--pretty minimal. to prevent users from having to deal with library and dependency hoopla, i was able to put in minimal types, right around the runtime places where we need to handle express.js
special cases as well. thus, we can co-locate express compile & runtime special cases simultaneously, adjacent to one another, with no dependency complexity.
let me annotate my new patches to draw attention to this to you--see if you're game
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting into a rabbit-hole... Didn't expect changes/refactoring of the internals. 😬
Need some time to review and see if it's going into the right direction.
It's a tough cookie...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting into a rabbit-hole... Didn't expect changes/refactoring of the internals. 😬
Need some time to review and see if it's going into the right direction.
It's a tough cookie...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't expect changes/refactoring of the internals
ya... sorry about that. it's much less evil than it looks! mainly just a single param drop and halt of req.url
usage (both simple!), which generated a ton of 🔴 / 🟢 in the diffs. nonetheless, it was all still on topic for de-expressing--promise! 😬 🙏
refactor: decouple from express
import type * as httpProxy from 'http-proxy'; | ||
import { getInstance } from './logger'; | ||
import { getUrl } from './url'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 as mentioned in issue discussions, we now have clarity that we cannot just use req.url
--it's mutated from express. thus, from the Request
type, I have now dropped url: string
, as it's unsafe to access in the codebase. getUrl
is a safe, express & IncomingMessage compatible accessor
export function fixRequestBody(proxyReq: http.ClientRequest, req: http.IncomingMessage): void { | ||
const requestBody = (req as Request).body; | ||
export function fixRequestBody(proxyReq: http.ClientRequest, req: Request): void { | ||
const requestBody = (req as BodyParserRequest).body; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here's an express runtime workaround, with an express type cast, all localized to the module of interest
return pathFilter(pathname, req); | ||
export function matchPathFilter(pathFilter: Filter = '/', req: Request): boolean { | ||
const url = getUrl(req); | ||
switch (typeof pathFilter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not that I love switch
vs the prior if
s, it's rather that the switch narrows pathFilter
's many types into narrow types in each block
* | ||
* Use getUrl(req) to get a x-server safe URL. | ||
*/ | ||
type ExpressCompatibleIncomingMessage = Omit<http.IncomingMessage, 'url'>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 . Here's a big change since our last review. Dropping url
out of IncomingMessage
, as it's unsafe to reference anywhere due to express support
if (typeof pathFilter === 'function') { | ||
const pathname = getUrlPathName(uri); | ||
return pathFilter(pathname, req); | ||
export function matchPathFilter(pathFilter: Filter = '/', req: Request): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the 2nd biggest change with this PR since we last reviewed. uri: string
was dropped as a param. now, url
is always extract from the req
param, safely.
a nice little bit of simplification 🧹 . this was done as part of the de-express-ificationthe
Perhaps for a different issue, but directly related to this PR's refactors. I was speaking w/ dougwilson over in express, and he made a pretty good callout here, that use of That is, HPM shouldn't handle the express special pathname case(s) it handles now at all. Currently, HPM+express does this: app.use('/api', proxy('http://localhost/'))
// then, GET /api/foo => http://localhost/api/foo but it actually should be: app.use('/api', proxy('http://localhost/'))
// then, GET /api/foo => http://localhost/foo If someone wants the original behavior, then they needed to register the HPM middleware earlier in their app middlewares, and not in the nested app mount. If they wanted something different, then they'd need to use one of HPMs many awesome rewrite APIs. I think for If agreeable, I'd refactor everything accordingly, possibly in a downstream PR, or here. |
The Think too many things are happening in #723. To make it more manageable I would suggest to break it apart:
These were actually things I already started to work on in v3 to allow better compatibility and flexibility. To provide better x-server compatibility; I created a new branch #730 and cherry picked the changes you made with the focus on improving the base types so you can use in native After that I can continue to work on the plugin system. And clean up the internals. (should make removing Removing About: This is correct. app.use('/api', proxy('http://localhost/'))
// then, GET /api/foo => http://localhost/foo Without app.use('/api', proxy('http://localhost/bar'))
// then, GET /api/foo => http://localhost/bar/foo |
I agree, there's lot's going on. It is entirely focused on 1 & 4, sans the jest patch. I'm picking up that you have some stuff in flight you wanna do w/ v3, that perhaps intersects with this work? That's fine. I'm probably at this point not going to send four PRs as you've suggested above. for me, i'd vote for one of the the following:
i wont be upset if option #1 is the pick :) |
You sensed it right. Had some plans in mind related to If you don't mind, I'll continue with option Really appreciate the time and effort you've put into it. 🙏 |
Closing PR. Superseded by #730 Will create separated PR to fix the Thanks for helping out! 💪 |
Description
Re-post of #721 against v3
http.IncomingMessage
with http-proxy-middleware values permissible onreq
, but not present by defaultBREAKING CHANGE:
RequestHandler
=>RequestMiddleware
. Middleware nowis expressed in terms of node core http.* types, which express req/res
are compatible with. Orienting to base http.* types allows better typing
across all node servers, as eventually each implementation has these
values in their servers. That is to say, express, koa, fastify, next,
etc all use http.IncomingMessage/http.ServerResponse under the hood,
thus the new middleware types are compatible with everyone.
Technically this should be backwards compatible for most users. However, exotic coupling to the previous types (e.g.
Parameters<typeof createProxyMiddleware>
could induce subtle breakages.Motivation and Context
See #719
How has this been tested?
Types of changes
Checklist: