From 80fe40aabfa2993e60ffd458bda045ae34057007 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 14 Aug 2017 09:05:38 -0700 Subject: [PATCH] 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 --- node.gyp | 1 - src/node_http2.cc | 34 +++- src/node_http2.h | 20 +-- src/node_http2_core-inl.h | 359 ++++++++++++++++++++++++++++++++++++-- src/node_http2_core.cc | 343 ------------------------------------ src/node_http2_core.h | 116 ++++++------ 6 files changed, 433 insertions(+), 440 deletions(-) delete mode 100644 src/node_http2_core.cc diff --git a/node.gyp b/node.gyp index 040e38223daeda..14eb9886785a7f 100644 --- a/node.gyp +++ b/node.gyp @@ -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', diff --git a/src/node_http2.cc b/src/node_http2.cc index ee6e604a851984..16c727034272d9 100755 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -74,6 +74,20 @@ struct http2_state { double stream_state_buffer[IDX_STREAM_STATE_COUNT]; }; +Freelist + data_chunk_free_list; + +Freelist stream_free_list; + +Freelist header_free_list; + +Freelist + 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( + buffer[IDX_OPTIONS_PADDING_STRATEGY]); + SetPaddingStrategy(strategy); } } diff --git a/src/node_http2.h b/src/node_http2.h index 0290ac43fa1be8..3ff2d33a7b2603 100755 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -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(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_; } diff --git a/src/node_http2_core-inl.h b/src/node_http2_core-inl.h index eae76430f46725..9bf471ccbc30cb 100755 --- a/src/node_http2_core-inl.h +++ b/src/node_http2_core-inl.h @@ -33,9 +33,330 @@ extern Freelist header_free_list; extern Freelist 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(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(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(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(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(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(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(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(length)) + length = stream->fd_length_; + + uv_buf_t data; + data.base = reinterpret_cast(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(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(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(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(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); diff --git a/src/node_http2_core.cc b/src/node_http2_core.cc deleted file mode 100644 index 3ec6529f98145d..00000000000000 --- a/src/node_http2_core.cc +++ /dev/null @@ -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(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(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(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(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(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(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(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(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(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(length)) - length = stream->fd_length_; - - uv_buf_t data; - data.base = reinterpret_cast(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(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(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 - data_chunk_free_list; - -Freelist stream_free_list; - -Freelist header_free_list; - -Freelist - data_chunks_free_list; - -Nghttp2Session::Callbacks Nghttp2Session::callback_struct_saved[2] = { - Callbacks(false), - Callbacks(true) -}; - -} // namespace http2 -} // namespace node diff --git a/src/node_http2_core.h b/src/node_http2_core.h index b3ccabfbbc9c6c..e9f393f079f32e 100755 --- a/src/node_http2_core.h +++ b/src/node_http2_core.h @@ -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);