Skip to content

Conversation

@hekike
Copy link
Contributor

@hekikehekike commented Sep 22, 2017

Checklist

  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

Affected core subsystem(s)

  • http2

Motivation

Provide a simple and performant way to extend the original Http2ServerRequest and Http2ServerResponse classes in web frameworks like express or restify without messing with the original Response and Request handlers in Node core or overwriting the prototypes at every request.

Current solutions for inheritance:

Object.setPrototypeOf:

Warning: Changing the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine. The effects on performance of altering inheritance are subtle and far-flung, and are not limited to simply the time spent in obj.proto = ... statement, but may extend to any code that has access to any object whose [[Prototype]] has been altered. If you care about performance you should avoid setting the [[Prototype]] of an object. Instead, create a new object with the desired [[Prototype]] using Object.create(). - MDN

How it works

classMyServerRequestextendshttp2.Http2ServerRequest{getUserAgent(){returnthis.headers['user-agent']||'unknown';}}classMyServerResponseextendshttp2.Http2ServerResponse{status(code,contentType){this.writeHead(code,{'Content-Type': contentType});}}constserver=http2.createServer({ServerRequest: MyServerRequest,ServerResponse: MyServerResponse};

Benchmark results

Continuously increasing concurrent and streams.
See new benchmark in this PR.

./node benchmark/scatter.js --runs 30 --set benchmarker=h2load benchmark/http2/inheritance > scatter.csv

Aggregated by requests:

cat scatter.csv | Rscript benchmark/scatter.R --xaxis requests --category version --plot scatter-plot.png --log

X axis: total number of requests
Y axis: rate of operations

scatter-plot-requests-2

aggregating variable: streams requests version rate confidence.interval 5000 original 6157.151 46.30018 5000 params 6118.722 52.94544 5000 setPrototypeOf 6127.015 46.04977 10000 original 7373.277 54.71278 10000 params 7318.096 55.83396 10000 setPrototypeOf 7363.882 46.79015 20000 original 8003.976 53.02454 20000 params 8237.659 48.35231 20000 setPrototypeOf 7999.081 50.75252 50000 original 7870.669 63.91681 50000 params 8347.653 54.88024 50000 setPrototypeOf 7838.831 62.77288 

Aggregated by streams:

cat scatter.csv | Rscript benchmark/scatter.R --xaxis streams --category version --plot scatter-plot.png --log

X axis: concurrency level
Y axis: rate of operations

scatter-plot-streams-2

aggregating variable: requests streams version rate confidence.interval 100 original 7321.768 152.6445 100 params 7506.819 164.8038 100 setPrototypeOf 7317.199 143.0783 500 original 7364.365 149.7678 500 params 7566.271 174.6796 500 setPrototypeOf 7385.408 143.0759 1000 original 7372.903 140.9144 1000 params 7518.531 172.2680 1000 setPrototypeOf 7335.217 147.1282 2500 original 7331.950 145.9298 2500 params 7502.725 172.1327 2500 setPrototypeOf 7320.819 157.1094 5000 original 7385.907 145.3467 5000 params 7492.245 174.1755 5000 setPrototypeOf 7302.101 149.7285 10000 original 7330.715 155.2385 10000 params 7446.603 190.6992 10000 setPrototypeOf 7332.468 145.8346 

From the result, aggregated by requests number you can see that this new extension type (passing custom classes as options) is significantly faster for higher concurrency and request number.

Related PR

@nodejs-github-botnodejs-github-bot added the http2 Issues or PRs related to the http2 subsystem. label Sep 22, 2017
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I would use http2.Http2ServerRequest for consistency.

Copy link
Member

Choose a reason for hiding this comment

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

@hekike sorry I wasn't clear. I didn't mean to change the option name but it's type from {Http2ServerRequest} to {http2.Http2ServerRequest} because this is what it is used in the rest of the file.

Copy link
Member

Choose a reason for hiding this comment

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

Same here, http2.Http2ServerResponse.

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 it doesn't change anything but did you try using Function.prototype.bind()? It may be a little faster and easier to read.

Copy link
Contributor

@apapirovskiapapirovskiSep 24, 2017

Choose a reason for hiding this comment

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

It doesn't even need to be bound here. setupCompat has access to this[kOptions].

Edit: In fact, to correct myself, onServerStream has access to this[kOptions] so all the .calls can be eliminated.

Copy link
Member

Choose a reason for hiding this comment

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

Nit: can you please remove the line feed? The next line seems to fit here.

Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also doesn't need bind or call.

Copy link
Contributor

@apapirovskiapapirovskiSep 24, 2017

Choose a reason for hiding this comment

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

Same as above, you don't need to pass through ServerRequest and ServerResponse. onServerStream has access to this[kOptions] (with small changes to move kOptions to util & import it both in core & compat)

Copy link
Contributor

Choose a reason for hiding this comment

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

The 'original' is slower because you're having to access a prototype of an object from outside of it's scope and then you're using call on it. This should just be:

this.writeHead(200,{'Content-Type': contentType});res.end(`User Agent: ${this.headers['user-agent']||'unknown'}`);

Copy link
Contributor

Choose a reason for hiding this comment

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

To expand on this, I think you would actually not even want to run the original test & the prototype test against your modified version of the code. I think those should be tested against master while the PARAMS test case should run against your build.

Copy link
Contributor

@apapirovskiapapirovskiSep 24, 2017

Choose a reason for hiding this comment

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

This closure is harder to optimize than the one for the PARAMS option because it has to access MyServerRequest & MyServerResponse. You would probably want to declare these request handlers outside of main.

Edit: I guess that's not really possible though because of how the h2 benchmarks are created. That makes the comparison a bit tricky, I think.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I tried to do something like Express https://github.com/expressjs/express/blob/a4bd4373b2c3b2521ee4c499cb8e90e98f78bfa5/lib/middleware/init.js#L35 to demonstrate the benefits of the new approach. I'm not sure we should merge the benchmark as well, it's more like a demonstration of this PR.

@apapirovski
Copy link
Contributor

Thanks for doing this @hekike! I left some small suggestions for changes but I don't know what opinion @mcollina & @jasnell will have re: whether this is something that gets added or not.

Copy link
Contributor

@apapirovskiapapirovskiSep 24, 2017

Choose a reason for hiding this comment

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

You can just do this.on('stream', onServerStream.bind(this, this[kOptions].Http2ServerRequest, this[kOptions].Http2ServerResponse)); The rest will work as expected.

@jasnell
Copy link
Member

Definitely good with this approach in general but will do a review on Monday.

@mcollina
Copy link
Member

@dougwilson is this something that you could make some use of in Express. Do you need it also for require('http')?

Express would be the main consumer of this feature, and if do not need it I'm 👎 as it increases the API surface area.

@hekike
Copy link
ContributorAuthor

@mcollinarestify could benefit from it as well. The current approach extends the Node Core's IncomingMessage and ServerResponse prototypes. See https://github.com/restify/node-restify/blob/master/lib/request.js#L19 cc @yunong

@mcollina
Copy link
Member

Maybe it would be more important to have this PR for http1 asap, and then bring http2 up to speed. If framework authors have a use for it, I'm 👍 .

Copy link
Member

Choose a reason for hiding this comment

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

We would need to add the documentation of the constructor parameters. Those are not documented atm, but if we want to support this use case we should do it.

@apapirovski
Copy link
Contributor

If this is going to be added to http2, it will need to be allowHTTP1 aware (so accept IncomingMessage & ServerResponse) which means even larger API surface.

@apapirovski
Copy link
Contributor

ping @Fishrock123 I saw you in expressjs/express#2812 and you also did https://github.com/pillarjs/extend-proto so figured you might have some thoughts.

@hekikehekikeforce-pushed the feat/http2-request-response-extend branch from d4a9c75 to be96525CompareSeptember 25, 2017 12:16
@hekike
Copy link
ContributorAuthor

@apapirovski thanks for checking it. I fixed your suggestions, here is the latest benchmark output after the changes:

By requests:

cat scatter.csv | Rscript benchmark/scatter.R --xaxis requests --category version --plot scatter-plot.png --log

aggregating variable: streams requests version rate confidence.interval 5000 original 6017.385 103.08612 5000 params 6005.038 94.99691 5000 setPrototypeOf 5899.892 116.80510 10000 original 7066.274 130.80891 10000 params 7133.153 118.89924 10000 setPrototypeOf 7028.677 133.74257 20000 original 7684.556 144.31313 20000 params 8041.344 119.24608 20000 setPrototypeOf 7791.633 111.70869 50000 original 7754.759 116.79295 50000 params 8187.066 123.17560 50000 setPrototypeOf 7827.398 102.14100 

X axis: total number of requests
Y axis: rate of operations

scatter-plot-requests-3

By streams:

cat scatter.csv | Rscript benchmark/scatter.R --xaxis streams --category version --plot scatter-plot.png --log

aggregating variable: requests streams version rate confidence.interval 100 original 7030.053 183.8673 100 params 7270.266 232.4791 100 setPrototypeOf 6951.486 232.5603 500 original 7154.344 214.5401 500 params 7466.283 210.6520 500 setPrototypeOf 7216.214 203.2741 1000 original 7274.672 187.8303 1000 params 7414.865 200.5134 1000 setPrototypeOf 7285.713 183.2550 2500 original 7206.308 189.6652 2500 params 7301.195 216.3267 2500 setPrototypeOf 7090.802 213.5673 5000 original 7002.380 216.8112 5000 params 7312.006 192.2240 5000 setPrototypeOf 7113.685 183.5417 10000 original 7116.705 193.1916 10000 params 7285.285 214.3297 10000 setPrototypeOf 7163.500 184.8569 

X axis: concurrency level
Y axis: rate of operations

scatter-plot-streams-3

@evanlucas
Copy link
Contributor

I would be +1 on doing this for http as well as we could use it for our internal http framework too

@mcollina
Copy link
Member

Can we move the discussion on adding this for require('http')? I think we might get the best value there rather than in require('http2'). Then http2 will follow http.

@hekike
Copy link
ContributorAuthor

@mcollina good idea. The only reason why I started with http2 because I was working on restify's HTTP/2 support.

@dougwilson
Copy link
Member

@mcollina at least with Express current design, it would not be able to use this. The main things I see are

  1. This can only be set when constructing thr HTTP server instance, which is outside of Express since it sits at a higher level.
  2. The prototype Express sets is app-specific. This means that if I have sub apps, the prototype will change as the request is entering / leaving the sub apps.

@mcollina
Copy link
Member

@hekike would this means that this functionality is needed only by Restify? I'm trying to understand who needs this functionality.

@yunong
Copy link
Member

@mcollina This would be great to have for both HTTP/1 and HTTP/2. We are currently running into multiple problems in restify without this. In fact, we’ve been collaborating closely with @hekike on this issue and are in full support of this PR. 🎉

@yunong
Copy link
Member

@mcollina This is something that is generally useful for most frameworks since it doesn’t require directly modifying the prototype of the request and response objects. Sure this will require the frameworks to pick up this new paradigm, but it is much better than directly modifying the prototype, which is what both restify and express does today.

If this lands restify itself will be moving to using this approach, for both HTTP/1 and HTTP/2. This will also allow us to provide HTTP/2 support in restify quickly, as we have a PR lined up and ready to go that will use this approach.

@apapirovski
Copy link
Contributor

@yunong how do you deal with allowHTTP1: true on createSecureServer in that situation? To me that seems to be the missing piece here.

@hekike
Copy link
ContributorAuthor

@apapirovski I'm not familiar with the HTTP/1 implementation in Node, but if I understand it correctly it's not a technical difficulty "just" we need to ship them together. Right?

@apapirovski
Copy link
Contributor

apapirovski commented Sep 25, 2017

Well, there needs to be a solution for http1 but also there needs to be a solution for passing through http1 req & res prototypes through http2 to http1 when a user creates a server with allowHTTP1: true. See https://nodejs.org/dist/latest-v8.x/docs/api/http2.html#http2_http2_createsecureserver_options_onrequesthandler and

if(socket.alpnProtocol===false||socket.alpnProtocol==='http/1.1'){
if(options.allowHTTP1===true){
// Fallback to HTTP/1.1
returnhttpConnectionListener.call(this,socket);
}elseif(this.emit('unknownProtocol',socket)){
// Let event handler deal with the socket
return;
}else{
// Reject non-HTTP/2 client
returnsocket.destroy();
}
}

@mcollina
Copy link
Member

@hekike can you open up a PR for http? Then we will adjust this accordingly to support both http1 and http2.

@hekike
Copy link
ContributorAuthor

@mcollina sure, it will take a couple of days.

Copy link
Contributor

Choose a reason for hiding this comment

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

One final comment as I was going through this: to use. or to be used. Same below.

@hekikehekikeforce-pushed the feat/http2-request-response-extend branch from 32ccd60 to bdca0b7CompareFebruary 9, 2018 17:50
@hekike
Copy link
ContributorAuthor

@apapirovski thanks, fixed!

apapirovski pushed a commit that referenced this pull request Feb 9, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
@apapirovski
Copy link
Contributor

Thanks for bearing with us @hekike. This took way too long.

(FYI, I made some adjustments to the commit message to make it fit within our guidelines.)

Landed in e5f101f

@hekikehekike deleted the feat/http2-request-response-extend branch February 9, 2018 18:36
MylesBorins pushed a commit that referenced this pull request Feb 21, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
MylesBorins pushed a commit that referenced this pull request Feb 21, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
MylesBorins pushed a commit that referenced this pull request Feb 21, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
@MylesBorinsMylesBorins mentioned this pull request Feb 21, 2018
MylesBorins pushed a commit that referenced this pull request Feb 21, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
@gibfahn
Copy link
Member

Will need a backport if it should go back to v8.x (if you'd like to do a backport please change the label).

kjin pushed a commit to kjin/node that referenced this pull request May 1, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. PR-URL: nodejs#15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
MylesBorins pushed a commit that referenced this pull request May 2, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. Backport-PR-URL: #20456 PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
@MylesBorinsMylesBorins mentioned this pull request May 2, 2018
MayaLekova pushed a commit to MayaLekova/node that referenced this pull request May 8, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. PR-URL: nodejs#15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
MylesBorins pushed a commit that referenced this pull request May 15, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. Backport-PR-URL: #20456 PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
MylesBorins pushed a commit that referenced this pull request May 15, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. Backport-PR-URL: #20456 PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
MylesBorins pushed a commit that referenced this pull request May 15, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. Backport-PR-URL: #20456 PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jun 14, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. Backport-PR-URL: #20456 PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
@MylesBorinsMylesBorins mentioned this pull request Jul 9, 2018
rvagg pushed a commit that referenced this pull request Aug 16, 2018
Add optional Http2ServerRequest and Http2ServerResponse options to createServer and createSecureServer. Allows custom req & res classes that extend the default ones to be used without overriding the prototype. Backport-PR-URL: #20456 PR-URL: #15560 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author readyPRs that have at least one approval, no pending requests for changes, and a CI started.httpIssues or PRs related to the http subsystem.http2Issues or PRs related to the http2 subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

13 participants

@hekike@apapirovski@jasnell@mcollina@evanlucas@dougwilson@yunong@BridgeAR@addaleax@gibfahn@lpinca@MylesBorins@nodejs-github-bot