-
-
Couldn't load subscription status.
- Fork 33.6k
http: fix incorrect headersTimeout measurement #30184
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
Changes from all commits
f7281ca
8cfd1b8
aa83993
845d4bb
01c2228
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -142,6 +142,7 @@ const STATUS_CODES = { | |
| }; | ||
|
|
||
| const kOnExecute = HTTPParser.kOnExecute | 0; | ||
| const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0; | ||
|
|
||
| class HTTPServerAsyncResource { | ||
| constructor(type, socket) { | ||
|
|
@@ -428,8 +429,6 @@ function connectionListenerInternal(server, socket) { | |
| isLenient() : server.insecureHTTPParser, | ||
| ); | ||
| parser.socket = socket; | ||
|
|
||
| // We are starting to wait for our headers. | ||
| parser.parsingHeadersStart = nowDate(); | ||
| socket.parser = parser; | ||
|
|
||
|
|
@@ -481,6 +480,7 @@ function connectionListenerInternal(server, socket) { | |
| } | ||
| parser[kOnExecute] = | ||
| onParserExecute.bind(undefined, server, socket, parser, state); | ||
| parser[kOnMessageBegin] = onParserMessageBegin.bind(undefined, parser); | ||
|
|
||
| socket._paused = false; | ||
| } | ||
|
|
@@ -568,11 +568,17 @@ function socketOnData(server, socket, parser, state, d) { | |
| onParserExecuteCommon(server, socket, parser, state, ret, d); | ||
| } | ||
|
|
||
| function onParserMessageBegin(parser) { | ||
| // We are starting to wait for the headers. | ||
| parser.parsingHeadersStart = nowDate(); | ||
|
||
| } | ||
|
|
||
| function onParserExecute(server, socket, parser, state, ret) { | ||
| socket._unrefTimer(); | ||
| const start = parser.parsingHeadersStart; | ||
|
|
||
| debug('SERVER socketOnParserExecute %d', ret); | ||
|
|
||
| const start = parser.parsingHeadersStart; | ||
| // If we have not parsed the headers, destroy the socket | ||
| // after server.headersTimeout to protect from DoS attacks. | ||
| // start === 0 means that we have parsed headers. | ||
|
|
@@ -720,10 +726,6 @@ function emitCloseNT(self) { | |
| function parserOnIncoming(server, socket, state, req, keepAlive) { | ||
| resetSocketTimeout(server, socket, state); | ||
|
|
||
| if (server.keepAliveTimeout > 0) { | ||
| req.on('end', resetHeadersTimeoutOnReqEnd); | ||
| } | ||
|
|
||
| // Set to zero to communicate that we have finished parsing. | ||
| socket.parser.parsingHeadersStart = 0; | ||
|
|
||
|
|
@@ -851,17 +853,6 @@ function generateSocketListenerWrapper(originalFnName) { | |
| }; | ||
| } | ||
|
|
||
| function resetHeadersTimeoutOnReqEnd() { | ||
| debug('resetHeadersTimeoutOnReqEnd'); | ||
|
|
||
| const parser = this.socket.parser; | ||
| // Parser can be null if the socket was destroyed | ||
| // in that case, there is nothing to do. | ||
| if (parser) { | ||
| parser.parsingHeadersStart = nowDate(); | ||
| } | ||
| } | ||
|
|
||
| module.exports = { | ||
| STATUS_CODES, | ||
| Server, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,6 +74,7 @@ const uint32_t kOnHeadersComplete = 1; | |
| const uint32_t kOnBody = 2; | ||
| const uint32_t kOnMessageComplete = 3; | ||
| const uint32_t kOnExecute = 4; | ||
| const uint32_t kOnMessageBegin = 5; | ||
| // Any more fields than this will be flushed into JS | ||
| const size_t kMaxHeaderFieldsCount = 32; | ||
|
|
||
|
|
@@ -181,6 +182,22 @@ class Parser : public AsyncWrap, public StreamListener { | |
| num_fields_ = num_values_ = 0; | ||
| url_.Reset(); | ||
| status_message_.Reset(); | ||
|
|
||
| Local<Object> obj = object(); | ||
| Local<Value> cb = obj->Get(env()->context(), | ||
| kOnMessageBegin).ToLocalChecked(); | ||
|
|
||
| if (!cb->IsFunction()) | ||
|
||
| return 0; | ||
|
|
||
| MaybeLocal<Value> r = MakeCallback(cb.As<Function>(), 0, nullptr); | ||
|
|
||
| if (r.IsEmpty()) { | ||
| got_exception_ = true; | ||
| llhttp_set_error_reason(&parser_, "HPE_JS_EXCEPTION:JS Exception"); | ||
| return HPE_USER; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
|
|
@@ -890,6 +907,8 @@ void InitializeHttpParser(Local<Object> target, | |
| Integer::NewFromUnsigned(env->isolate(), kOnMessageComplete)); | ||
| t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnExecute"), | ||
| Integer::NewFromUnsigned(env->isolate(), kOnExecute)); | ||
| t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnMessageBegin"), | ||
| Integer::NewFromUnsigned(env->isolate(), kOnMessageBegin)); | ||
|
|
||
| Local<Array> methods = Array::New(env->isolate()); | ||
| #define V(num, name, string) \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| 'use strict'; | ||
|
||
|
|
||
| const common = require('../common'); | ||
| const http = require('http'); | ||
| const net = require('net'); | ||
| const { finished } = require('stream'); | ||
|
|
||
| const headers = | ||
| 'GET / HTTP/1.1\r\n' + | ||
| 'Host: localhost\r\n' + | ||
| 'Connection: keep-alive\r\n' + | ||
| 'Agent: node\r\n'; | ||
|
|
||
| const baseTimeout = 1000; | ||
|
|
||
| const server = http.createServer(common.mustCall((req, res) => { | ||
| req.resume(); | ||
| res.writeHead(200); | ||
| res.end(); | ||
| }, 2)); | ||
|
|
||
| server.keepAliveTimeout = 10 * baseTimeout; | ||
| server.headersTimeout = baseTimeout; | ||
|
|
||
| server.once('timeout', common.mustNotCall((socket) => { | ||
| socket.destroy(); | ||
| })); | ||
|
|
||
| server.listen(0, () => { | ||
| const client = net.connect(server.address().port); | ||
|
|
||
| // first request | ||
| client.write(headers); | ||
| client.write('\r\n'); | ||
|
|
||
| setTimeout(() => { | ||
| // second request | ||
| client.write(headers); | ||
| // `headersTimeout` doesn't seem to fire if request headers | ||
| // are sent in one packet. | ||
| setTimeout(() => { | ||
| client.write('\r\n'); | ||
| client.end(); | ||
| }, 10); | ||
| }, baseTimeout + 10); | ||
|
|
||
| finished(client, common.mustCall((err) => { | ||
| server.close(); | ||
| })); | ||
| }); | ||
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.
my understanding was that onMessageBegin will be called when the parser starts parsing a new request's headers.