Skip to content
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

Remove direct http dependency #3213

Open
wants to merge 1 commit into
base: 5.x
Choose a base branch
from

Conversation

wesleytodd
Copy link
Member

I think this might resolve the issues brought up in #2812 and #3206, and also some of what we talked about at the most recent TC meeting for compat in the browser. The goal here is that you can do:

var express = require('express')
var uws = require('uws')
var app = express({
  reqProto: uws.http.getRequestPrototype(),
  resProto: uws.http.getResponsePrototype(),
  createServer: uws.http.createServer
})

In the long run, I think this is also what I need for my Nighthawk project as well, so IMO it is a good way forward. But let me know your thoughts!

CC: @dougwilson, @alexhultman, @blakeembrey

@ghost
Copy link

ghost commented Feb 20, 2017

I've removed uws.http.getRequestPrototype() and uws.http.getResponsePrototype() and instead I provide var app = uws.http.getExpressApp(express) so this won't work anymore

@wesleytodd
Copy link
Member Author

wesleytodd commented Feb 20, 2017

Notes:

  1. I tried to apply the "standard" style where I made changes, but it results in mixed files, so I can change that back if we want.
  2. I applied the setPrototypeOf module where we were using __proto__ like we did in the router, this is also something we could discuss, especially if we drop support for older nodes and browsers.
  3. This does nothing to help the larger issue of NOT modifying the prototypes of all req/res, but I think that moving forward with this should not change anything we do there.
  4. Also, not sure if this helps or hinders the efforts for http2 support, so please comment to that regard, thanks!

@wesleytodd
Copy link
Member Author

@alexhultman Hmm, well it seems to me to be a better api for your project to not have to know about express, and for express to just support arbitrary extensions like in this PR. With getExpressApp you are now relying on express supporting what you use, but inverting the responsibility here removes that need.

@wesleytodd
Copy link
Member Author

wesleytodd commented Feb 20, 2017

Lol, tests that hit google.com and rely on a specific html response? Should I fix these brittle tests? Remove them? Or ignore the failures for this PR?

@ghost
Copy link

ghost commented Feb 20, 2017

I'll add them back then.

@wesleytodd
Copy link
Member Author

@alexhultman You might want to wait until we get some feedback from the greater @expressjs team and community, the above is merely my opinion :)

@ghost
Copy link

ghost commented Feb 20, 2017

Too late I already added them again

@dougwilson
Copy link
Contributor

The test failures are just from a conflict with 2f8ac67 in which the test is expecting the same code names from the http Node.js module but this moved them to use the statuses module. I think the change to use statuses could probably be extracted to a PR (maybe even against 4.x?).

@wesleytodd
Copy link
Member Author

wesleytodd commented Feb 20, 2017

Updated to move the status stuff into a separate PR, but left the commit in here just so it is clear the intent. Now that I look I see you did say against 4, so I will update that.

One other thing we should have open for discussion is the idea of passing options to createApplication. Doug mentioned that the current way was a preference of @tj, but that we could bring it up for discussion again. My thoughts are that offering these kind of options while creating the application is nice. There is also no reason we could not offer both api's, but that increases the test and maintenance surface. Also, there are some types of settings that should not be changed after startup, because middleware might rely on them working in a certain way, like query string parsing, so IMO these should not be changeable throughout the lifetime of the app like they are now.

@wesleytodd
Copy link
Member Author

Last comment, I think this PR might also be able to land of 4.x. It passes all the tests, and is really just a minor point addition for the new options api.

Copy link
Member

@LinusU LinusU left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat 👍

lib/express.js Outdated
app.response = { __proto__: res, app: app };
app.createServer = opts.createServer
app.request = setPrototypeOf({ app: app }, req(opts.reqProto))
app.response = setPrototypeOf({ app: app }, res(opts.resProto))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these two lines could just be Object.create(proto, descriptorsWithJustApp), right?

app.response = Object.create(res(opts.resProto), {
  app: { configurable: true, enumerable: true, writable: true, value: app }
})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

lib/express.js Outdated
@@ -32,16 +57,22 @@ exports = module.exports = createApplication;
* @api public
*/

function createApplication() {
function createApplication(opts) {
opts = opts || {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't want to reassign values linked to arguments, since it silently alters arguments[0]. Usually I would just name the argument options and this line as var opts = options || {}

$ node -pe '(function(foo){foo=foo||{};return arguments[0] === null}(null))'
false

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, fixed.

lib/express.js Outdated
opts = opts || {}
opts.reqProto = opts.reqProto || http.IncomingMessage.prototype
opts.resProto = opts.resProto || http.ServerResponse.prototype
opts.createServer = opts.createServer || http.createServer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually not best practice to alter the object someone is passing in; the object could potentially be frozen, even, causing an exception here. var createServer = opts.createServer || http.createServer is probably fine (and then changing the vars below).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@wesleytodd wesleytodd force-pushed the remove-http-dep branch 2 times, most recently from cab07fb to d66bba4 Compare February 24, 2017 15:01
@mjsalinger
Copy link

mjsalinger commented May 4, 2017

@dougwilson Any way we can get this merged?

@dougwilson
Copy link
Contributor

H @mjsalinger before merging, the CI should at least pass for the PR.

@kibertoad
Copy link

@mjsalinger Could you review those CI failures and address if needed?

@wesleytodd
Copy link
Member Author

I have not had a chance to look at the failures in this PR, which I think needs a bit of revision in respect to the discussion here.

@LucyMaber
Copy link

what the time line on this

@wesleytodd
Copy link
Member Author

Hey @LucyMaber, I know this is likely not the timeline you hoped for, but we are now trying to organize an effort to land these before the final v5 release (see the issue mention right above this). If you are still looking for this, I would love if you could post why you wanted it. It is hard enough to pick up work we thought was valuable back then, but it is even harder to decide if it is still worth doing when most of the comments do not contain enough information about why folks asked for this.

@wesleytodd
Copy link
Member Author

@kibertoad I started scrolling up and should have waited to post that to also mention you! Can you help give a bit of context on what this change helps with? I know it was something which helped me in 2017, but I am not sure I really understand why this might still be helpful in 2024.

@kibertoad
Copy link

@wesleytodd If I'm not mistaken, Node.js wasn't able to remove certain http warts precisely because express relied on them. If that helps to unblock Node.js, I'm all for it.

@wesleytodd
Copy link
Member Author

This particular change was a bit unrelated to that. It was more about enabling non-node core (but api compatible) implementations. Sadly, until we can get the api's in place and stop monkey-patching req/res it does not help node core move forward yet.

I will work on the other pending todo topics first, and hopefully we can make a decision on if landing this is a good idea in the mean time.

@wesleytodd
Copy link
Member Author

I have decided this is worth landing. The main reason I think so is because it gives us an interesting opportunity to experiment with the http-next effort ongoing in the Node.js Web Server Frameworks Team. We could use this to provide new compatibility layers to allow using that work within express which might help us validate that work. Just wanted to post that update here and give folks one last chance to oppose landing this. I am going to get the branch into a conflict free updated state now and then if I don't hear back I will merge early next week.

@wesleytodd wesleytodd force-pushed the remove-http-dep branch 4 times, most recently from bf3b980 to 6e32dae Compare May 17, 2024 21:08
Copy link
Member

@blakeembrey blakeembrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this requires more thought before we land it for 5.x because I don't think it's the ideal API to achieve the goal, and I wouldn't want to churn it again for 6.x 🤷

exports.request = req;
exports.response = res;
exports.request = req.proto
exports.response = res.proto
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these exports are meaningless now because they're just empty objects?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah probably, hard to remember if I had even looked at that since it was so long ago, but I think you are right. I will hold off on addressing these because the other review comments are much more important for us to address first imo.


var req = Object.create(http.IncomingMessage.prototype)
exports = module.exports = function (proto) {
return setPrototypeOf(req, proto)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's almost functionally the same for a single server, but this would be clearer by making all of req a function that returns the existing behavior (and can have multiple instances created). My main concern with setPrototypeOf here is that you'd be changing the prototype if you actually intended to use this feature to run two versions of e.g. HTTP1 and HTTP2 servers. This seems like a bit of a foot gun for anyone running tests, especially if they tried to parallelize or something.

Edit: This isn't required if you instead do the mixin approach in createApplication instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree with this including the edit. The use case of multiple servers was always bad with express patching things though so I am not sure this makes it worse, but the mixin way seems like it would make it better in general. I am +1 for going that route and calling these other comments resolved.

var options = opts || {}
options.reqProto = options.reqProto || http.IncomingMessage.prototype
options.resProto = options.resProto || http.ServerResponse.prototype
options.createServer = options.createServer || http.createServer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of making the http object above, why not just require('http') here instead? You'd avoid three requires. Instead, since all 3 of these options should be expected to be set together (why would you mix http native with implementation?), just make them all required or fallback on something like var options = opts || getHttpOptions().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea and honestly any use cases where you might use node's server but custom req/res is probably not really that viable anyway. Maybe to nitpick the name idea though, what about overrideHttp() or something in that vien? My idea here is that it is an it should be clear you shouldn't use this unless you really want to override something which is more of a power user. I was trying to think of a name that sends that message more without being over done.

@@ -607,7 +606,7 @@ app.render = function render(name, options, callback) {
*/

app.listen = function listen () {
var server = http.createServer(this)
var server = this.createServer(this)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I think this should just deprecate this in favor of people explicitly writing http.createServer(app).listen(...). It also removes one more option from the createApplication options which is only ever used once here. It took me a while to follow the indirect assignments around and I think that's bad for developers and users to understanding what's going on.

But I do understand why it's done after 30 minutes of review and won't block on this issue. I would be in favor of refactoring this to assign app.listen within the create function instead. That way there's less indirect dependencies that's hard to reason about and createServer is used right where it's defined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be in favor of refactoring this to assign app.listen within the create function instead.

Of the options to achieve this I think this one is my preference. I believe any of these loose the once behavior, but I am not sure it makes a big difference but I agree simplifying things like this is good. I honestly cannot remember if there used to be cases where the server could emit error events more than once, but maybe that was an old node behavior?

// expose the prototype that will get set on requests
app.request = Object.create(req, {
app.request = Object.create(req(options.reqProto), {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not Object.create(options.reqProto, ...) and then mixin(app.request, req) instead? Seems easier to follow and similar performance implications since this is only initialized once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 no idea what I was thinking when I wrote this. I agree that feels more clear to read. I cannot make time to make these changes now but I will when I get time to revisit this pr.

Copy link
Member

@blakeembrey blakeembrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need some tests for any of this behavior, including the concerns around prototype re-assignment not allowing multiple app instances to exist.

Can we get at least https tests and/or a simple proxy example of how someone would use this with e.g. one method working?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants