http: refactor headersTimeout and requestTimeout logic

PR-URL: https://github.com/nodejs/node/pull/41263
Fixes: https://github.com/nodejs/node/issues/33440
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
This commit is contained in:
Paolo Insogna authored and GitHub committed 2022-04-13 15:47:59 +01:00
1 parent 9d6af7d1fe
commit 3caa2c1a00
33 files changed
+858 -413

No files matched your search

+262 -31
View File
@@ -60,6 +60,7 @@ using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Number;
@@ -187,7 +188,58 @@ struct StringPtr {
size_t size_;
};
class Parser;
struct ParserComparator {
bool operator()(const Parser* lhs, const Parser* rhs) const;
};
class ConnectionsList : public BaseObject {
public:
static void New(const FunctionCallbackInfo<Value>& args);
static void All(const FunctionCallbackInfo<Value>& args);
static void Idle(const FunctionCallbackInfo<Value>& args);
static void Active(const FunctionCallbackInfo<Value>& args);
static void Expired(const FunctionCallbackInfo<Value>& args);
void Push(Parser* parser) {
all_connections_.insert(parser);
}
void Pop(Parser* parser) {
all_connections_.erase(parser);
}
void PushActive(Parser* parser) {
active_connections_.insert(parser);
}
void PopActive(Parser* parser) {
active_connections_.erase(parser);
}
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(ConnectionsList)
SET_SELF_SIZE(ConnectionsList)
private:
ConnectionsList(Environment* env, Local<Object> object)
: BaseObject(env, object) {
MakeWeak();
}
std::set<Parser*, ParserComparator> all_connections_;
std::set<Parser*, ParserComparator> active_connections_;
};
class Parser : public AsyncWrap, public StreamListener {
friend class ConnectionsList;
friend struct ParserComparator;
public:
Parser(BindingData* binding_data, Local<Object> wrap)
: AsyncWrap(binding_data->env(), wrap),
@@ -205,10 +257,21 @@ class Parser : public AsyncWrap, public StreamListener {
SET_SELF_SIZE(Parser)
int on_message_begin() {
// Important: Pop from the list BEFORE resetting the last_message_start_
// otherwise std::set.erase will fail.
if (connectionsList_ != nullptr) {
connectionsList_->PopActive(this);
}
num_fields_ = num_values_ = 0;
headers_completed_ = false;
last_message_start_ = uv_hrtime();
url_.Reset();
status_message_.Reset();
header_parsing_start_time_ = uv_hrtime();
if (connectionsList_ != nullptr) {
connectionsList_->PushActive(this);
}
Local<Value> cb = object()->Get(env()->context(), kOnMessageBegin)
.ToLocalChecked();
@@ -297,8 +360,8 @@ class Parser : public AsyncWrap, public StreamListener {
int on_headers_complete() {
headers_completed_ = true;
header_nread_ = 0;
header_parsing_start_time_ = 0;
// Arguments for the on-headers-complete javascript callback. This
// list needs to be kept in sync with the actual argument list for
@@ -429,6 +492,14 @@ class Parser : public AsyncWrap, public StreamListener {
int on_message_complete() {
HandleScope scope(env()->isolate());
// Important: Pop from the list BEFORE resetting the last_message_start_
// otherwise std::set.erase will fail.
if (connectionsList_ != nullptr) {
connectionsList_->PopActive(this);
}
last_message_start_ = 0;
if (num_fields_)
Flush(); // Flush trailing HTTP headers.
@@ -486,6 +557,11 @@ class Parser : public AsyncWrap, public StreamListener {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
if (parser->connectionsList_ != nullptr) {
parser->connectionsList_->Pop(parser);
parser->connectionsList_->PopActive(parser);
}
// Since the Parser destructor isn't going to run the destroy() callbacks
// it needs to be triggered manually.
parser->EmitTraceEventDestroy();
@@ -506,7 +582,6 @@ class Parser : public AsyncWrap, public StreamListener {
}
}
// var bytesParsed = parser->execute(buffer);
static void Execute(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
@@ -545,8 +620,8 @@ class Parser : public AsyncWrap, public StreamListener {
Environment* env = Environment::GetCurrent(args);
uint64_t max_http_header_size = 0;
uint64_t headers_timeout = 0;
uint32_t lenient_flags = kLenientNone;
ConnectionsList* connectionsList = nullptr;
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsObject());
@@ -565,9 +640,9 @@ class Parser : public AsyncWrap, public StreamListener {
lenient_flags = args[3].As<Int32>()->Value();
}
if (args.Length() > 4) {
CHECK(args[4]->IsInt32());
headers_timeout = args[4].As<Int32>()->Value();
if (args.Length() > 4 && !args[4]->IsNullOrUndefined()) {
CHECK(args[4]->IsObject());
ASSIGN_OR_RETURN_UNWRAP(&connectionsList, args[4]);
}
llhttp_type_t type =
@@ -586,7 +661,21 @@ class Parser : public AsyncWrap, public StreamListener {
parser->set_provider_type(provider);
parser->AsyncReset(args[1].As<Object>());
parser->Init(type, max_http_header_size, lenient_flags, headers_timeout);
parser->Init(type, max_http_header_size, lenient_flags);
if (connectionsList != nullptr) {
parser->connectionsList_ = connectionsList;
parser->connectionsList_->Push(parser);
// This protects from a DoS attack where an attacker establishes
// the connection without sending any data on applications where
// server.timeout is left to the default value of zero.
parser->last_message_start_ = uv_hrtime();
parser->connectionsList_->PushActive(parser);
} else {
parser->connectionsList_ = nullptr;
}
}
template <bool should_pause>
@@ -644,6 +733,26 @@ class Parser : public AsyncWrap, public StreamListener {
args.GetReturnValue().Set(ret);
}
static void Duration(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
if (parser->last_message_start_ == 0) {
args.GetReturnValue().Set(0);
return;
}
double duration = (uv_hrtime() - parser->last_message_start_) / 1e6;
args.GetReturnValue().Set(duration);
}
static void HeadersCompleted(const FunctionCallbackInfo<Value>& args) {
Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
args.GetReturnValue().Set(parser->headers_completed_);
}
protected:
static const size_t kAllocBufferSize = 64 * 1024;
@@ -690,24 +799,6 @@ class Parser : public AsyncWrap, public StreamListener {
if (ret.IsEmpty())
return;
// check header parsing time
if (header_parsing_start_time_ != 0 && headers_timeout_ != 0) {
uint64_t now = uv_hrtime();
uint64_t parsing_time = (now - header_parsing_start_time_) / 1000000;
if (parsing_time > headers_timeout_) {
Local<Value> cb =
object()->Get(env()->context(), kOnTimeout).ToLocalChecked();
if (!cb->IsFunction())
return;
MakeCallback(cb.As<Function>(), 0, nullptr);
return;
}
}
Local<Value> cb =
object()->Get(env()->context(), kOnExecute).ToLocalChecked();
@@ -853,7 +944,7 @@ class Parser : public AsyncWrap, public StreamListener {
void Init(llhttp_type_t type, uint64_t max_http_header_size,
uint32_t lenient_flags, uint64_t headers_timeout) {
uint32_t lenient_flags) {
llhttp_init(&parser_, type, &settings);
if (lenient_flags & kLenientHeaders) {
@@ -873,9 +964,8 @@ class Parser : public AsyncWrap, public StreamListener {
num_values_ = 0;
have_flushed_ = false;
got_exception_ = false;
headers_completed_ = false;
max_http_header_size_ = max_http_header_size;
header_parsing_start_time_ = 0;
headers_timeout_ = headers_timeout;
}
@@ -923,11 +1013,12 @@ class Parser : public AsyncWrap, public StreamListener {
size_t current_buffer_len_;
const char* current_buffer_data_;
unsigned int execute_depth_ = 0;
bool headers_completed_ = false;
bool pending_pause_ = false;
uint64_t header_nread_ = 0;
uint64_t max_http_header_size_;
uint64_t headers_timeout_;
uint64_t header_parsing_start_time_ = 0;
uint64_t last_message_start_;
ConnectionsList* connectionsList_;
BaseObjectPtr<BindingData> binding_data_;
@@ -952,6 +1043,135 @@ class Parser : public AsyncWrap, public StreamListener {
static const llhttp_settings_t settings;
};
bool ParserComparator::operator()(const Parser* lhs, const Parser* rhs) const {
if (lhs->last_message_start_ == 0) {
return false;
} else if (rhs->last_message_start_ == 0) {
return true;
}
return lhs->last_message_start_ < rhs->last_message_start_;
}
void ConnectionsList::New(const FunctionCallbackInfo<Value>& args) {
Local<Context> context = args.GetIsolate()->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);
new ConnectionsList(env, args.This());
}
void ConnectionsList::All(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Array> all = Array::New(isolate);
ConnectionsList* list;
ASSIGN_OR_RETURN_UNWRAP(&list, args.Holder());
uint32_t i = 0;
for (auto parser : list->all_connections_) {
if (all->Set(context, i++, parser->object()).IsNothing()) {
return;
}
}
return args.GetReturnValue().Set(all);
}
void ConnectionsList::Idle(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Array> idle = Array::New(isolate);
ConnectionsList* list;
ASSIGN_OR_RETURN_UNWRAP(&list, args.Holder());
uint32_t i = 0;
for (auto parser : list->all_connections_) {
if (parser->last_message_start_ == 0) {
if (idle->Set(context, i++, parser->object()).IsNothing()) {
return;
}
}
}
return args.GetReturnValue().Set(idle);
}
void ConnectionsList::Active(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Array> active = Array::New(isolate);
ConnectionsList* list;
ASSIGN_OR_RETURN_UNWRAP(&list, args.Holder());
uint32_t i = 0;
for (auto parser : list->active_connections_) {
if (active->Set(context, i++, parser->object()).IsNothing()) {
return;
}
}
return args.GetReturnValue().Set(active);
}
void ConnectionsList::Expired(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Array> expired = Array::New(isolate);
ConnectionsList* list;
ASSIGN_OR_RETURN_UNWRAP(&list, args.Holder());
CHECK(args[0]->IsNumber());
CHECK(args[1]->IsNumber());
uint64_t headers_timeout =
static_cast<uint64_t>(args[0].As<Uint32>()->Value()) * 1000000;
uint64_t request_timeout =
static_cast<uint64_t>(args[1].As<Uint32>()->Value()) * 1000000;
if (headers_timeout == 0 && request_timeout == 0) {
return args.GetReturnValue().Set(expired);
} else if (request_timeout > 0 && headers_timeout > request_timeout) {
std::swap(headers_timeout, request_timeout);
}
const uint64_t now = uv_hrtime();
const uint64_t headers_deadline =
headers_timeout > 0 ? now - headers_timeout : 0;
const uint64_t request_deadline =
request_timeout > 0 ? now - request_timeout : 0;
uint32_t i = 0;
auto iter = list->active_connections_.begin();
auto end = list->active_connections_.end();
while (iter != end) {
Parser* parser = *iter;
iter++;
// Check for expiration.
if (
(!parser->headers_completed_ && headers_deadline > 0 &&
parser->last_message_start_ < headers_deadline) ||
(
request_deadline > 0 &&
parser->last_message_start_ < request_deadline)
) {
if (expired->Set(context, i++, parser->object()).IsNothing()) {
return;
}
list->active_connections_.erase(parser);
}
}
return args.GetReturnValue().Set(expired);
}
const llhttp_settings_t Parser::settings = {
Proxy<Call, &Parser::on_message_begin>::Raw,
Proxy<DataCall, &Parser::on_url>::Raw,
@@ -1038,8 +1258,19 @@ void InitializeHttpParser(Local<Object> target,
env->SetProtoMethod(t, "consume", Parser::Consume);
env->SetProtoMethod(t, "unconsume", Parser::Unconsume);
env->SetProtoMethod(t, "getCurrentBuffer", Parser::GetCurrentBuffer);
env->SetProtoMethod(t, "duration", Parser::Duration);
env->SetProtoMethod(t, "headersCompleted", Parser::HeadersCompleted);
env->SetConstructorFunction(target, "HTTPParser", t);
Local<FunctionTemplate> c = env->NewFunctionTemplate(ConnectionsList::New);
c->InstanceTemplate()
->SetInternalFieldCount(ConnectionsList::kInternalFieldCount);
env->SetProtoMethod(c, "all", ConnectionsList::All);
env->SetProtoMethod(c, "idle", ConnectionsList::Idle);
env->SetProtoMethod(c, "active", ConnectionsList::Active);
env->SetProtoMethod(c, "expired", ConnectionsList::Expired);
env->SetConstructorFunction(target, "ConnectionsList", c);
}
} // anonymous namespace