http2: handful of http/2 src cleanups

* inline more stuff. remove a node_http2_core.cc
* clean up debug messages
* simplify options code, and cleanup

PR-URL: https://github.com/nodejs/node/pull/14825
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
James M Snell 2017-08-14 09:05:38 -07:00
parent 2e91d8d483
commit 949aec5c32
6 changed files with 433 additions and 440 deletions

View File

@ -185,7 +185,6 @@
'src/node_contextify.cc',
'src/node_debug_options.cc',
'src/node_file.cc',
'src/node_http2_core.cc',
'src/node_http2.cc',
'src/node_http_parser.cc',
'src/node_main.cc',

View File

@ -74,6 +74,20 @@ struct http2_state {
double stream_state_buffer[IDX_STREAM_STATE_COUNT];
};
Freelist<nghttp2_data_chunk_t, FREELIST_MAX>
data_chunk_free_list;
Freelist<Nghttp2Stream, FREELIST_MAX> stream_free_list;
Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
data_chunks_free_list;
Nghttp2Session::Callbacks Nghttp2Session::callback_struct_saved[2] = {
Callbacks(false),
Callbacks(true)};
Http2Options::Http2Options(Environment* env) {
nghttp2_option_new(&options_);
@ -81,28 +95,36 @@ Http2Options::Http2Options(Environment* env) {
uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
if (flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
SetMaxDeflateDynamicTableSize(
nghttp2_option_set_max_deflate_dynamic_table_size(
options_,
buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
}
if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
SetMaxReservedRemoteStreams(
nghttp2_option_set_max_reserved_remote_streams(
options_,
buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
}
if (flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
SetMaxSendHeaderBlockLength(
nghttp2_option_set_max_send_header_block_length(
options_,
buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
}
SetPeerMaxConcurrentStreams(100); // Recommended default
// Recommended default
nghttp2_option_set_peer_max_concurrent_streams(options_, 100);
if (flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
SetPeerMaxConcurrentStreams(
nghttp2_option_set_peer_max_concurrent_streams(
options_,
buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
}
if (flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
SetPaddingStrategy(buffer[IDX_OPTIONS_PADDING_STRATEGY]);
padding_strategy_type strategy =
static_cast<padding_strategy_type>(
buffer[IDX_OPTIONS_PADDING_STRATEGY]);
SetPaddingStrategy(strategy);
}
}

View File

@ -273,31 +273,15 @@ class Http2Options {
nghttp2_option_del(options_);
}
nghttp2_option* operator*() {
nghttp2_option* operator*() const {
return options_;
}
void SetPaddingStrategy(uint32_t val) {
void SetPaddingStrategy(padding_strategy_type val) {
CHECK_LE(val, PADDING_STRATEGY_CALLBACK);
padding_strategy_ = static_cast<padding_strategy_type>(val);
}
void SetMaxDeflateDynamicTableSize(size_t val) {
nghttp2_option_set_max_deflate_dynamic_table_size(options_, val);
}
void SetMaxReservedRemoteStreams(uint32_t val) {
nghttp2_option_set_max_reserved_remote_streams(options_, val);
}
void SetMaxSendHeaderBlockLength(size_t val) {
nghttp2_option_set_max_send_header_block_length(options_, val);
}
void SetPeerMaxConcurrentStreams(uint32_t val) {
nghttp2_option_set_peer_max_concurrent_streams(options_, val);
}
padding_strategy_type GetPaddingStrategy() {
return padding_strategy_;
}

View File

@ -33,9 +33,330 @@ extern Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
extern Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
data_chunks_free_list;
#ifdef NODE_DEBUG_HTTP2
inline int Nghttp2Session::OnNghttpError(nghttp2_session* session,
const char* message,
size_t len,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %s: Error '%.*s'\n",
handle->TypeName(), len, message);
return 0;
}
#endif
// nghttp2 calls this at the beginning a new HEADERS or PUSH_PROMISE frame.
// We use it to ensure that an Nghttp2Stream instance is allocated to store
// the state.
inline int Nghttp2Session::OnBeginHeadersCallback(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id :
frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %s: beginning headers for stream %d\n",
handle->TypeName(), id);
Nghttp2Stream* stream = handle->FindStream(id);
if (stream == nullptr) {
Nghttp2Stream::Init(id, handle, frame->headers.cat);
} else {
stream->StartHeaders(frame->headers.cat);
}
return 0;
}
// nghttp2 calls this once for every header name-value pair in a HEADERS
// or PUSH_PROMISE block. CONTINUATION frames are handled automatically
// and transparently so we do not need to worry about those at all.
inline int Nghttp2Session::OnHeaderCallback(nghttp2_session* session,
const nghttp2_frame* frame,
nghttp2_rcbuf *name,
nghttp2_rcbuf *value,
uint8_t flags,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id :
frame->hd.stream_id;
Nghttp2Stream* stream = handle->FindStream(id);
nghttp2_header_list* header = header_free_list.pop();
header->name = name;
header->value = value;
nghttp2_rcbuf_incref(name);
nghttp2_rcbuf_incref(value);
LINKED_LIST_ADD(stream->current_headers, header);
return 0;
}
// When nghttp2 has completely processed a frame, it calls OnFrameReceive.
// It is our responsibility to delegate out from there. We can ignore most
// control frames since nghttp2 will handle those for us.
inline int Nghttp2Session::OnFrameReceive(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %s: complete frame received: type: %d\n",
handle->TypeName(), frame->hd.type);
bool ack;
switch (frame->hd.type) {
case NGHTTP2_DATA:
handle->HandleDataFrame(frame);
break;
case NGHTTP2_PUSH_PROMISE:
case NGHTTP2_HEADERS:
handle->HandleHeadersFrame(frame);
break;
case NGHTTP2_SETTINGS:
ack = (frame->hd.flags & NGHTTP2_FLAG_ACK) == NGHTTP2_FLAG_ACK;
handle->OnSettings(ack);
break;
case NGHTTP2_PRIORITY:
handle->HandlePriorityFrame(frame);
break;
case NGHTTP2_GOAWAY:
handle->HandleGoawayFrame(frame);
break;
default:
break;
}
return 0;
}
inline int Nghttp2Session::OnFrameNotSent(nghttp2_session *session,
const nghttp2_frame *frame,
int error_code,
void *user_data) {
Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
DEBUG_HTTP2("Nghttp2Session %s: frame type %d was not sent, code: %d\n",
handle->TypeName(), frame->hd.type, error_code);
// Do not report if the frame was not sent due to the session closing
if (error_code != NGHTTP2_ERR_SESSION_CLOSING &&
error_code != NGHTTP2_ERR_STREAM_CLOSED &&
error_code != NGHTTP2_ERR_STREAM_CLOSING)
handle->OnFrameError(frame->hd.stream_id, frame->hd.type, error_code);
return 0;
}
// Called when nghttp2 closes a stream, either in response to an RST_STREAM
// frame or the stream closing naturally on it's own
inline int Nghttp2Session::OnStreamClose(nghttp2_session *session,
int32_t id,
uint32_t code,
void *user_data) {
Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
DEBUG_HTTP2("Nghttp2Session %s: stream %d closed, code: %d\n",
handle->TypeName(), id, code);
Nghttp2Stream *stream = handle->FindStream(id);
// Intentionally ignore the callback if the stream does not exist
if (stream != nullptr)
stream->Close(code);
return 0;
}
// Called by nghttp2 to collect the data while a file response is sent.
// The buf is the DATA frame buffer that needs to be filled with at most
// length bytes. flags is used to control what nghttp2 does next.
inline ssize_t Nghttp2Session::OnStreamReadFD(nghttp2_session *session,
int32_t id,
uint8_t *buf,
size_t length,
uint32_t *flags,
nghttp2_data_source *source,
void *user_data) {
Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
DEBUG_HTTP2("Nghttp2Session %s: reading outbound file data for stream %d\n",
handle->TypeName(), id);
Nghttp2Stream *stream = handle->FindStream(id);
int fd = source->fd;
int64_t offset = stream->fd_offset_;
ssize_t numchars = 0;
if (stream->fd_length_ >= 0 &&
stream->fd_length_ < static_cast<int64_t>(length))
length = stream->fd_length_;
uv_buf_t data;
data.base = reinterpret_cast<char *>(buf);
data.len = length;
uv_fs_t read_req;
if (length > 0) {
numchars = uv_fs_read(handle->loop_,
&read_req,
fd, &data, 1,
offset, nullptr);
uv_fs_req_cleanup(&read_req);
}
// Close the stream with an error if reading fails
if (numchars < 0)
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
// Update the read offset for the next read
stream->fd_offset_ += numchars;
stream->fd_length_ -= numchars;
// if numchars < length, assume that we are done.
if (static_cast<size_t>(numchars) < length || length <= 0) {
DEBUG_HTTP2("Nghttp2Session %s: no more data for stream %d\n",
handle->TypeName(), id);
*flags |= NGHTTP2_DATA_FLAG_EOF;
GetTrailers(session, handle, stream, flags);
}
return numchars;
}
// Called by nghttp2 to collect the data to pack within a DATA frame.
// The buf is the DATA frame buffer that needs to be filled with at most
// length bytes. flags is used to control what nghttp2 does next.
inline ssize_t Nghttp2Session::OnStreamRead(nghttp2_session *session,
int32_t id,
uint8_t *buf,
size_t length,
uint32_t *flags,
nghttp2_data_source *source,
void *user_data) {
Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
DEBUG_HTTP2("Nghttp2Session %s: reading outbound data for stream %d\n",
handle->TypeName(), id);
Nghttp2Stream *stream = handle->FindStream(id);
size_t remaining = length;
size_t offset = 0;
// While there is data in the queue, copy data into buf until it is full.
// There may be data left over, which will be sent the next time nghttp
// calls this callback.
while (stream->queue_head_ != nullptr) {
DEBUG_HTTP2("Nghttp2Session %s: processing outbound data chunk\n",
handle->TypeName());
nghttp2_stream_write_queue *head = stream->queue_head_;
while (stream->queue_head_index_ < head->nbufs) {
if (remaining == 0)
goto end;
unsigned int n = stream->queue_head_index_;
// len is the number of bytes in head->bufs[n] that are yet to be written
size_t len = head->bufs[n].len - stream->queue_head_offset_;
size_t bytes_to_write = len < remaining ? len : remaining;
memcpy(buf + offset,
head->bufs[n].base + stream->queue_head_offset_,
bytes_to_write);
offset += bytes_to_write;
remaining -= bytes_to_write;
if (bytes_to_write < len) {
stream->queue_head_offset_ += bytes_to_write;
} else {
stream->queue_head_index_++;
stream->queue_head_offset_ = 0;
}
}
stream->queue_head_offset_ = 0;
stream->queue_head_index_ = 0;
stream->queue_head_ = head->next;
head->cb(head->req, 0);
delete head;
}
stream->queue_tail_ = nullptr;
end:
// If we are no longer writable and there is no more data in the queue,
// then we need to set the NGHTTP2_DATA_FLAG_EOF flag.
// If we are still writable but there is not yet any data to send, set the
// NGHTTP2_ERR_DEFERRED flag. This will put the stream into a pending state
// that will wait for data to become available.
// If neither of these flags are set, then nghttp2 will call this callback
// again to get the data for the next DATA frame.
int writable = stream->queue_head_ != nullptr || stream->IsWritable();
if (offset == 0 && writable && stream->queue_head_ == nullptr) {
DEBUG_HTTP2("Nghttp2Session %s: deferring stream %d\n",
handle->TypeName(), id);
return NGHTTP2_ERR_DEFERRED;
}
if (!writable) {
DEBUG_HTTP2("Nghttp2Session %s: no more data for stream %d\n",
handle->TypeName(), id);
*flags |= NGHTTP2_DATA_FLAG_EOF;
GetTrailers(session, handle, stream, flags);
}
assert(offset <= length);
return offset;
}
// Called by nghttp2 when it needs to determine how much padding to apply
// to a DATA or HEADERS frame
inline ssize_t Nghttp2Session::OnSelectPadding(nghttp2_session *session,
const nghttp2_frame *frame,
size_t maxPayloadLen,
void *user_data) {
Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
assert(handle->HasGetPaddingCallback());
ssize_t padding = handle->GetPadding(frame->hd.length, maxPayloadLen);
DEBUG_HTTP2("Nghttp2Session %s: using padding, size: %d\n",
handle->TypeName(), padding);
return padding;
}
// Called by nghttp2 multiple times while processing a DATA frame
inline int Nghttp2Session::OnDataChunkReceived(nghttp2_session *session,
uint8_t flags,
int32_t id,
const uint8_t *data,
size_t len,
void *user_data) {
Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
DEBUG_HTTP2("Nghttp2Session %s: buffering data chunk for stream %d, size: "
"%d, flags: %d\n", handle->TypeName(), id, len, flags);
Nghttp2Stream *stream = handle->FindStream(id);
nghttp2_data_chunk_t *chunk = data_chunk_free_list.pop();
chunk->buf = uv_buf_init(new char[len], len);
memcpy(chunk->buf.base, data, len);
if (stream->data_chunks_tail_ == nullptr) {
stream->data_chunks_head_ =
stream->data_chunks_tail_ = chunk;
} else {
stream->data_chunks_tail_->next = chunk;
stream->data_chunks_tail_ = chunk;
}
return 0;
}
inline void Nghttp2Session::GetTrailers(nghttp2_session *session,
Nghttp2Session *handle,
Nghttp2Stream *stream,
uint32_t *flags) {
if (stream->GetTrailers()) {
// Only when we are done sending the last chunk of data do we check for
// any trailing headers that are to be sent. This is the only opportunity
// we have to make this check. If there are trailers, then the
// NGHTTP2_DATA_FLAG_NO_END_STREAM flag must be set.
SubmitTrailers submit_trailers{handle, stream, flags};
handle->OnTrailers(stream, submit_trailers);
}
}
inline void Nghttp2Session::SubmitTrailers::Submit(nghttp2_nv *trailers,
size_t length) const {
if (length == 0)
return;
DEBUG_HTTP2("Nghttp2Session %s: sending trailers for stream %d, "
"count: %d\n", handle_->TypeName(), stream_->id(), length);
*flags_ |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
nghttp2_submit_trailer(handle_->session_,
stream_->id(),
trailers,
length);
}
// See: https://nghttp2.org/documentation/nghttp2_submit_shutdown_notice.html
inline void Nghttp2Session::SubmitShutdownNotice() {
DEBUG_HTTP2("Nghttp2Session %d: submitting shutdown notice\n", session_type_);
DEBUG_HTTP2("Nghttp2Session %s: submitting shutdown notice\n", TypeName());
nghttp2_submit_shutdown_notice(session_);
}
@ -44,8 +365,8 @@ inline void Nghttp2Session::SubmitShutdownNotice() {
// are no settings entries to send.
inline int Nghttp2Session::SubmitSettings(const nghttp2_settings_entry iv[],
size_t niv) {
DEBUG_HTTP2("Nghttp2Session %d: submitting settings, count: %d\n",
session_type_, niv);
DEBUG_HTTP2("Nghttp2Session %s: submitting settings, count: %d\n",
TypeName(), niv);
return nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv, niv);
}
@ -53,10 +374,12 @@ inline int Nghttp2Session::SubmitSettings(const nghttp2_settings_entry iv[],
inline Nghttp2Stream* Nghttp2Session::FindStream(int32_t id) {
auto s = streams_.find(id);
if (s != streams_.end()) {
DEBUG_HTTP2("Nghttp2Session %d: stream %d found\n", session_type_, id);
DEBUG_HTTP2("Nghttp2Session %s: stream %d found\n",
TypeName(), id);
return s->second;
} else {
DEBUG_HTTP2("Nghttp2Session %d: stream %d not found\n", session_type_, id);
DEBUG_HTTP2("Nghttp2Session %s: stream %d not found\n",
TypeName(), id);
return nullptr;
}
}
@ -80,8 +403,8 @@ inline void Nghttp2Stream::FlushDataChunks(bool done) {
// to the JS side only when the frame is fully processed.
inline void Nghttp2Session::HandleDataFrame(const nghttp2_frame* frame) {
int32_t id = frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: handling data frame for stream %d\n",
session_type_, id);
DEBUG_HTTP2("Nghttp2Session %s: handling data frame for stream %d\n",
TypeName(), id);
Nghttp2Stream* stream = this->FindStream(id);
// If the stream does not exist, something really bad happened
CHECK_NE(stream, nullptr);
@ -96,8 +419,8 @@ inline void Nghttp2Session::HandleDataFrame(const nghttp2_frame* frame) {
inline void Nghttp2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id : frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: handling headers frame for stream %d\n",
session_type_, id);
DEBUG_HTTP2("Nghttp2Session %s: handling headers frame for stream %d\n",
TypeName(), id);
Nghttp2Stream* stream = FindStream(id);
// If the stream does not exist, something really bad happened
CHECK_NE(stream, nullptr);
@ -112,8 +435,8 @@ inline void Nghttp2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
inline void Nghttp2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
nghttp2_priority priority_frame = frame->priority;
int32_t id = frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: handling priority frame for stream %d\n",
session_type_, id);
DEBUG_HTTP2("Nghttp2Session %s: handling priority frame for stream %d\n",
TypeName(), id);
// Ignore the priority frame if stream ID is <= 0
// This actually should never happen because nghttp2 should treat this as
// an error condition that terminates the session.
@ -126,7 +449,7 @@ inline void Nghttp2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
// Notifies the JS layer that a GOAWAY frame has been received
inline void Nghttp2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
nghttp2_goaway goaway_frame = frame->goaway;
DEBUG_HTTP2("Nghttp2Session %d: handling goaway frame\n", session_type_);
DEBUG_HTTP2("Nghttp2Session %s: handling goaway frame\n", TypeName());
OnGoAway(goaway_frame.last_stream_id,
goaway_frame.error_code,
@ -136,7 +459,7 @@ inline void Nghttp2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
// Prompts nghttp2 to flush the queue of pending data frames
inline void Nghttp2Session::SendPendingData() {
DEBUG_HTTP2("Nghttp2Session %d: Sending pending data\n", session_type_);
DEBUG_HTTP2("Nghttp2Session %s: Sending pending data\n", TypeName());
// Do not attempt to send data on the socket if the destroying flag has
// been set. That means everything is shutting down and the socket
// will not be usable.
@ -170,7 +493,8 @@ inline int Nghttp2Session::Init(uv_loop_t* loop,
const nghttp2_session_type type,
nghttp2_option* options,
nghttp2_mem* mem) {
DEBUG_HTTP2("Nghttp2Session %d: initializing session\n", type);
DEBUG_HTTP2("Nghttp2Session %s: initializing session\n",
SessionTypeName(type));
loop_ = loop;
session_type_ = type;
destroying_ = false;
@ -224,7 +548,7 @@ inline void Nghttp2Session::MarkDestroying() {
inline int Nghttp2Session::Free() {
assert(session_ != nullptr);
DEBUG_HTTP2("Nghttp2Session %d: freeing session\n", session_type_);
DEBUG_HTTP2("Nghttp2Session %s: freeing session\n", TypeName());
// Stop the loop
uv_prepare_stop(&prep_);
auto PrepClose = [](uv_handle_t* handle) {
@ -238,7 +562,7 @@ inline int Nghttp2Session::Free() {
nghttp2_session_del(session_);
session_ = nullptr;
loop_ = nullptr;
DEBUG_HTTP2("Nghttp2Session %d: session freed\n", session_type_);
DEBUG_HTTP2("Nghttp2Session %s: session freed\n", TypeName());
return 1;
}
@ -587,9 +911,6 @@ Nghttp2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, OnFrameNotSent);
// nghttp2_session_callbacks_set_on_invalid_frame_recv(
// callbacks, OnInvalidFrameReceived);
#ifdef NODE_DEBUG_HTTP2
nghttp2_session_callbacks_set_error_callback(
callbacks, OnNghttpError);

View File

@ -1,343 +0,0 @@
#include "node_http2_core-inl.h"
namespace node {
namespace http2 {
#ifdef NODE_DEBUG_HTTP2
int Nghttp2Session::OnNghttpError(nghttp2_session* session,
const char* message,
size_t len,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: Error '%.*s'\n",
handle->session_type_, len, message);
return 0;
}
#endif
// nghttp2 calls this at the beginning a new HEADERS or PUSH_PROMISE frame.
// We use it to ensure that an Nghttp2Stream instance is allocated to store
// the state.
int Nghttp2Session::OnBeginHeadersCallback(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id :
frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: beginning headers for stream %d\n",
handle->session_type_, id);
Nghttp2Stream* stream = handle->FindStream(id);
if (stream == nullptr) {
Nghttp2Stream::Init(id, handle, frame->headers.cat);
} else {
stream->StartHeaders(frame->headers.cat);
}
return 0;
}
// nghttp2 calls this once for every header name-value pair in a HEADERS
// or PUSH_PROMISE block. CONTINUATION frames are handled automatically
// and transparently so we do not need to worry about those at all.
int Nghttp2Session::OnHeaderCallback(nghttp2_session* session,
const nghttp2_frame* frame,
nghttp2_rcbuf *name,
nghttp2_rcbuf *value,
uint8_t flags,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id :
frame->hd.stream_id;
Nghttp2Stream* stream = handle->FindStream(id);
nghttp2_header_list* header = header_free_list.pop();
header->name = name;
header->value = value;
nghttp2_rcbuf_incref(name);
nghttp2_rcbuf_incref(value);
LINKED_LIST_ADD(stream->current_headers, header);
return 0;
}
// When nghttp2 has completely processed a frame, it calls OnFrameReceive.
// It is our responsibility to delegate out from there. We can ignore most
// control frames since nghttp2 will handle those for us.
int Nghttp2Session::OnFrameReceive(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: complete frame received: type: %d\n",
handle->session_type_, frame->hd.type);
bool ack;
switch (frame->hd.type) {
case NGHTTP2_DATA:
handle->HandleDataFrame(frame);
break;
case NGHTTP2_PUSH_PROMISE:
case NGHTTP2_HEADERS:
handle->HandleHeadersFrame(frame);
break;
case NGHTTP2_SETTINGS:
ack = (frame->hd.flags & NGHTTP2_FLAG_ACK) == NGHTTP2_FLAG_ACK;
handle->OnSettings(ack);
break;
case NGHTTP2_PRIORITY:
handle->HandlePriorityFrame(frame);
break;
case NGHTTP2_GOAWAY:
handle->HandleGoawayFrame(frame);
break;
default:
break;
}
return 0;
}
int Nghttp2Session::OnFrameNotSent(nghttp2_session* session,
const nghttp2_frame* frame,
int error_code,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: frame type %d was not sent, code: %d\n",
handle->session_type_, frame->hd.type, error_code);
// Do not report if the frame was not sent due to the session closing
if (error_code != NGHTTP2_ERR_SESSION_CLOSING &&
error_code != NGHTTP2_ERR_STREAM_CLOSED &&
error_code != NGHTTP2_ERR_STREAM_CLOSING)
handle->OnFrameError(frame->hd.stream_id, frame->hd.type, error_code);
return 0;
}
// Called when nghttp2 closes a stream, either in response to an RST_STREAM
// frame or the stream closing naturally on it's own
int Nghttp2Session::OnStreamClose(nghttp2_session *session,
int32_t id,
uint32_t code,
void *user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: stream %d closed, code: %d\n",
handle->session_type_, id, code);
Nghttp2Stream* stream = handle->FindStream(id);
// Intentionally ignore the callback if the stream does not exist
if (stream != nullptr)
stream->Close(code);
return 0;
}
// Called by nghttp2 multiple times while processing a DATA frame
int Nghttp2Session::OnDataChunkReceived(nghttp2_session *session,
uint8_t flags,
int32_t id,
const uint8_t *data,
size_t len,
void *user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: buffering data chunk for stream %d, size: "
"%d, flags: %d\n", handle->session_type_, id, len, flags);
Nghttp2Stream* stream = handle->FindStream(id);
nghttp2_data_chunk_t* chunk = data_chunk_free_list.pop();
chunk->buf = uv_buf_init(new char[len], len);
memcpy(chunk->buf.base, data, len);
if (stream->data_chunks_tail_ == nullptr) {
stream->data_chunks_head_ =
stream->data_chunks_tail_ = chunk;
} else {
stream->data_chunks_tail_->next = chunk;
stream->data_chunks_tail_ = chunk;
}
return 0;
}
// Called by nghttp2 when it needs to determine how much padding to apply
// to a DATA or HEADERS frame
ssize_t Nghttp2Session::OnSelectPadding(nghttp2_session* session,
const nghttp2_frame* frame,
size_t maxPayloadLen,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
assert(handle->HasGetPaddingCallback());
ssize_t padding = handle->GetPadding(frame->hd.length, maxPayloadLen);
DEBUG_HTTP2("Nghttp2Session %d: using padding, size: %d\n",
handle->session_type_, padding);
return padding;
}
void Nghttp2Session::GetTrailers(nghttp2_session* session,
Nghttp2Session* handle,
Nghttp2Stream* stream,
uint32_t* flags) {
if (stream->GetTrailers()) {
// Only when we are done sending the last chunk of data do we check for
// any trailing headers that are to be sent. This is the only opportunity
// we have to make this check. If there are trailers, then the
// NGHTTP2_DATA_FLAG_NO_END_STREAM flag must be set.
SubmitTrailers submit_trailers { handle, stream, flags };
handle->OnTrailers(stream, submit_trailers);
}
}
void Nghttp2Session::SubmitTrailers::Submit(nghttp2_nv* trailers,
size_t length) const {
if (length == 0) return;
DEBUG_HTTP2("Nghttp2Session %d: sending trailers for stream %d, "
"count: %d\n", handle_->session_type_, stream_->id(),
length);
*flags_ |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
nghttp2_submit_trailer(handle_->session_,
stream_->id(),
trailers,
length);
}
// Called by nghttp2 to collect the data while a file response is sent.
// The buf is the DATA frame buffer that needs to be filled with at most
// length bytes. flags is used to control what nghttp2 does next.
ssize_t Nghttp2Session::OnStreamReadFD(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: reading outbound file data for stream %d\n",
handle->session_type_, id);
Nghttp2Stream* stream = handle->FindStream(id);
int fd = source->fd;
int64_t offset = stream->fd_offset_;
ssize_t numchars = 0;
if (stream->fd_length_ >= 0 &&
stream->fd_length_ < static_cast<int64_t>(length))
length = stream->fd_length_;
uv_buf_t data;
data.base = reinterpret_cast<char*>(buf);
data.len = length;
uv_fs_t read_req;
if (length > 0) {
numchars = uv_fs_read(handle->loop_,
&read_req,
fd, &data, 1,
offset, nullptr);
uv_fs_req_cleanup(&read_req);
}
// Close the stream with an error if reading fails
if (numchars < 0)
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
// Update the read offset for the next read
stream->fd_offset_ += numchars;
stream->fd_length_ -= numchars;
// if numchars < length, assume that we are done.
if (static_cast<size_t>(numchars) < length || length <= 0) {
DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
handle->session_type_, id);
*flags |= NGHTTP2_DATA_FLAG_EOF;
GetTrailers(session, handle, stream, flags);
}
return numchars;
}
// Called by nghttp2 to collect the data to pack within a DATA frame.
// The buf is the DATA frame buffer that needs to be filled with at most
// length bytes. flags is used to control what nghttp2 does next.
ssize_t Nghttp2Session::OnStreamRead(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: reading outbound data for stream %d\n",
handle->session_type_, id);
Nghttp2Stream* stream = handle->FindStream(id);
size_t remaining = length;
size_t offset = 0;
// While there is data in the queue, copy data into buf until it is full.
// There may be data left over, which will be sent the next time nghttp
// calls this callback.
while (stream->queue_head_ != nullptr) {
DEBUG_HTTP2("Nghttp2Session %d: processing outbound data chunk\n",
handle->session_type_);
nghttp2_stream_write_queue* head = stream->queue_head_;
while (stream->queue_head_index_ < head->nbufs) {
if (remaining == 0) {
goto end;
}
unsigned int n = stream->queue_head_index_;
// len is the number of bytes in head->bufs[n] that are yet to be written
size_t len = head->bufs[n].len - stream->queue_head_offset_;
size_t bytes_to_write = len < remaining ? len : remaining;
memcpy(buf + offset,
head->bufs[n].base + stream->queue_head_offset_,
bytes_to_write);
offset += bytes_to_write;
remaining -= bytes_to_write;
if (bytes_to_write < len) {
stream->queue_head_offset_ += bytes_to_write;
} else {
stream->queue_head_index_++;
stream->queue_head_offset_ = 0;
}
}
stream->queue_head_offset_ = 0;
stream->queue_head_index_ = 0;
stream->queue_head_ = head->next;
head->cb(head->req, 0);
delete head;
}
stream->queue_tail_ = nullptr;
end:
// If we are no longer writable and there is no more data in the queue,
// then we need to set the NGHTTP2_DATA_FLAG_EOF flag.
// If we are still writable but there is not yet any data to send, set the
// NGHTTP2_ERR_DEFERRED flag. This will put the stream into a pending state
// that will wait for data to become available.
// If neither of these flags are set, then nghttp2 will call this callback
// again to get the data for the next DATA frame.
int writable = stream->queue_head_ != nullptr || stream->IsWritable();
if (offset == 0 && writable && stream->queue_head_ == nullptr) {
DEBUG_HTTP2("Nghttp2Session %d: deferring stream %d\n",
handle->session_type_, id);
return NGHTTP2_ERR_DEFERRED;
}
if (!writable) {
DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
handle->session_type_, id);
*flags |= NGHTTP2_DATA_FLAG_EOF;
GetTrailers(session, handle, stream, flags);
}
assert(offset <= length);
return offset;
}
Freelist<nghttp2_data_chunk_t, FREELIST_MAX>
data_chunk_free_list;
Freelist<Nghttp2Stream, FREELIST_MAX> stream_free_list;
Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
data_chunks_free_list;
Nghttp2Session::Callbacks Nghttp2Session::callback_struct_saved[2] = {
Callbacks(false),
Callbacks(true)
};
} // namespace http2
} // namespace node

View File

@ -105,6 +105,16 @@ class Nghttp2Session {
return destroying_;
}
inline const char* TypeName(nghttp2_session_type type) {
switch (type) {
case NGHTTP2_SESSION_SERVER: return "server";
case NGHTTP2_SESSION_CLIENT: return "client";
default:
// This should never happen
ABORT();
}
}
// Returns the pointer to the identified stream, or nullptr if
// the stream does not exist
inline Nghttp2Stream* FindStream(int32_t id);
@ -173,7 +183,7 @@ class Nghttp2Session {
class SubmitTrailers {
public:
void Submit(nghttp2_nv* trailers, size_t length) const;
inline void Submit(nghttp2_nv* trailers, size_t length) const;
private:
inline SubmitTrailers(Nghttp2Session* handle,
@ -197,63 +207,63 @@ class Nghttp2Session {
inline void HandleDataFrame(const nghttp2_frame* frame);
inline void HandleGoawayFrame(const nghttp2_frame* frame);
static void GetTrailers(nghttp2_session* session,
Nghttp2Session* handle,
Nghttp2Stream* stream,
uint32_t* flags);
static inline void GetTrailers(nghttp2_session* session,
Nghttp2Session* handle,
Nghttp2Stream* stream,
uint32_t* flags);
/* callbacks for nghttp2 */
#ifdef NODE_DEBUG_HTTP2
static int OnNghttpError(nghttp2_session* session,
const char* message,
size_t len,
void* user_data);
static inline int OnNghttpError(nghttp2_session* session,
const char* message,
size_t len,
void* user_data);
#endif
static int OnBeginHeadersCallback(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data);
static int OnHeaderCallback(nghttp2_session* session,
const nghttp2_frame* frame,
nghttp2_rcbuf* name,
nghttp2_rcbuf* value,
uint8_t flags,
void* user_data);
static int OnFrameReceive(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data);
static int OnFrameNotSent(nghttp2_session* session,
const nghttp2_frame* frame,
int error_code,
void* user_data);
static int OnStreamClose(nghttp2_session* session,
int32_t id,
uint32_t code,
void* user_data);
static int OnDataChunkReceived(nghttp2_session* session,
uint8_t flags,
int32_t id,
const uint8_t *data,
size_t len,
void* user_data);
static ssize_t OnStreamReadFD(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data);
static ssize_t OnStreamRead(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data);
static ssize_t OnSelectPadding(nghttp2_session* session,
const nghttp2_frame* frame,
size_t maxPayloadLen,
void* user_data);
static inline int OnBeginHeadersCallback(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data);
static inline int OnHeaderCallback(nghttp2_session* session,
const nghttp2_frame* frame,
nghttp2_rcbuf* name,
nghttp2_rcbuf* value,
uint8_t flags,
void* user_data);
static inline int OnFrameReceive(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data);
static inline int OnFrameNotSent(nghttp2_session* session,
const nghttp2_frame* frame,
int error_code,
void* user_data);
static inline int OnStreamClose(nghttp2_session* session,
int32_t id,
uint32_t code,
void* user_data);
static inline int OnDataChunkReceived(nghttp2_session* session,
uint8_t flags,
int32_t id,
const uint8_t *data,
size_t len,
void* user_data);
static inline ssize_t OnStreamReadFD(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data);
static inline ssize_t OnStreamRead(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data);
static inline ssize_t OnSelectPadding(nghttp2_session* session,
const nghttp2_frame* frame,
size_t maxPayloadLen,
void* user_data);
struct Callbacks {
inline explicit Callbacks(bool kHasGetPaddingCallback);