The intention of this stream API is give server application about
stream dependency information, so that it can utilize it for better
scheduling of stream processing. We have no plan to add object
oriented API based on stream object.
We now use priority queue per stream, which contains the stream which
has ready to send a frame, or one of its descendants have a frame to
send. We maintain invariant that if a stream is queued, then its
ancestors are also queued (except for root). When we re-schedule
stream after transmission, we re-schedule all ancestors, so that
streams on the other path can get a chance to send. This is basically
the same mechanism h2o project uses, but there are differences in the
details.
Previously, the number of stream in one dependency tree (not including
root) is limited to 120. This is due to the fact that we use
recursive calls to traverse trees. Now we replaced recursive calls
with loop, we can remove this limitation. Also now all streams are
descendant of root stream, rather than linked list of individual
subtree root.
This commit fixes the bug that DATA is not consumed if
nghttp2_http_on_data_chunk is failed. It also simplify the handling
of missing stream in NGHTTP2_IB_READ_DATA state.
This commit documents NGHTTP2_ERR_DATA_EXIST also occurs if HEADERS
has been already attached to stream too. This commit also fixes
possible assertion error, and now nghttp2_submit_headers() and
nghttp2_submit_response() may return NGHTTP2_ERR_DATA_EXIST. But we
recommend to use nghttp2_submit_request() and
nghttp2_submit_response(), and using them will avoid this error.
Previously, we did not handle PRIORITY frame which depends on itself
and for idle stream. As a result, nghttp2_session_mem_recv (or
nghttp2_session_recv) returne NGHTTP2_ERR_NOMEM. The error code was
still misleading. It was not out of memory, and we failed to insert
hash map because of duplicated key, which was treated as out of
memory. This commit fixes this issue, by explicitly checking
dependency for incoming PRIORITY for all cases.
When we know that stream is closed at time we read DATA frame header,
we use NGHTTP2_IB_IGN_DATA, and consume data for connection if
nghttp2_option_set_no_auto_window_update() is used. However, if
stream is closed while we are in NGHTTP2_IB_READ_DATA, those bytes are
not consumed for connection, nor notified to application via callback,
so it eventually fills up connection window and connection will
freeze. This commit fixes this issue by consuming these data for
connection when stream is closed or does not exist.
The private global variable nghttp2_enable_strict_preface is also
marked as NGHTTP2_EXTERN, but it is test purpose only (test with
.dll), and not part of public API. It could be removed in the future
release.
After reviewing codebase, only queue for DATA frames requires
priorities. Other frames can be replaced multiple linear queues.
Replacing priority queue with linear queue allows us to simplify
codebase a bit; for example, now nghttp2_session.next_seq is gone.
Since application most likely allocates the stream object in
nghttp2_on_begin_headers_callback, it is desirable to handle its
failure as stream error. But previously it only signals success or
fatal error. Submitting RST_STREAM does not prevent
nghttp2_on_header_callback from being invoked. This commit improves
this situation by allowing NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE from
nghttp2_on_begin_headers_callback. If that value is returned, library
submits RST_STREAM with error code INTERNAL_ERROR, and
nghttp2_on_header_callback and nghttp2_on_frame_recv_callback for that
frame are not invoked. Note that for PUSH_PROMISE frame, the stream
to be reset is promised stream.
We rewrite static header table handling in nghttp2_hd.c. We expand
nghttp2_token to include all static header table entries, and fully
use them in header compression and decompression. The lookup function
is now located in nghttp2_hd.c. We add new nghttp2_hd_inflate_hd2()
function to export token value for header name, then we pass it to
nghttp2_http_on_header function, so that we don't have to look up
token there. We carefully set enum value of token to static table
index, so looking up static table is now O(1), assuming we have token.
The existing nghttp2_session_consume() affects both connection and
stream level flow control windows. The new functions only affects
either connection or stream. There is some interesting use cases.
For example, we may want to pause a stream by not sending
WINDOW_UPDATE, meanwhile we want to continue to process other streams.
In this case, we use nghttp2_session_consume_connection() to tell
library that only connection level window is recovered. The relevant
discussion: https://code.google.com/p/chromium/issues/detail?id=473259
Previously nghttp2_session_send() and nghttp2_session_mem_send() did
not send 24 bytes client magic byte string (MAGIC). We made
nghttp2_session_recv() and nghttp2_session_mem_recv() process MAGIC by
default, so it is natural to make library send MAGIC as well. This
commit makes nghttp2_session_send() and nghttp2_session_mem_send()
send MAGIC. This commit also replace "connection preface" with
"client magic", since we call MAGIC as "connection preface" but it is
just a part of connection preface. NGHTTP2_CLIENT_CONNECTION_PREFACE
macro was replaced with NGHTTP2_CLIENT_MAGIC. The already deprecated
NGHTTP2_CLIENT_CONNECTION_HEADER macro was removed permanently.
nghttp2_option_set_no_recv_client_preface() was renamed as
nghttp2_option_set_no_recv_client_magic(). NGHTTP2_ERR_BAD_PREFACE
was renamed as NGHTTP2_ERR_BAD_CLIENT_MAGIC.
Since HTTP/2 spec requires for client to send connection preface, it
is reasonable to make this option enabled by default. It is still a
use case to disable this, so replace this option with
nghttp2_option_set_no_recv_client_preface().
To avoid buffer copy in nghttp2_data_source_read_callback, this commit
introduces NGHTTP2_DATA_FLAG_NO_COPY and nghttp2_send_data_callback.
By using NGHTTP2_DATA_FLAG_NO_COPY in
nghttp2_data_source_read_callback, application can avoid to copy
application data to given buffer. Instead, application has to
implement nghttp2_send_data_callback to send complete DATA frame by
itself. We see noticeable performance increase in nghttpd and
tiny-nghttpd using this new feature. On the other hand, nghttpx does
not show such difference, probably because buffer copy is not
bottleneck. Using nghttp2_send_data_callback adds complexity, so it
is recommended to measure the performance to see whether this extra
complexity worth it.
nghttp2_submit_request and nghttp2_submit_response will set
NGHTTP2_FLAG_END_STREAM after all given data is sent (data could be
0). This means we have no way to send trailers. In this commit, we
added NGHTTP2_DATA_FLAG_NO_END_STREAM flag. The application can set
this flag in *data_flags inside nghttp2_data_source_read_callback. If
NGHTTP2_DATA_FLAG_EOF is set, library automatically set
NGHTTP2_FLAG_END_STREAM. But if both NGHTTP2_DATA_FLAG_EOF and
NGHTTP2_DATA_FLAG_NO_END_STREAM are set, NGHTTP2_FLAG_END_STREAM will
not set by library. Then application can use new
nghttp2_submit_trailer() to send trailers. nghttp2_submit_trailer()
will set NGHTTP2_FLAG_END_STREAM and it is actually thing wrapper of
nghttp2_submit_headers().
After we sent SETTINGS including ENABLE_PUSH = 0, peer may already
issue PUSH_PROMISE before receiving our SETTINGS and react it to
SETTINGS ACK. Previously we accept this PUSH_PROMISE. In this
commit, we check the pending ENABLE_PUSH value and if it means
disabling push, we refuse PUSH_PROMISE with RST_STREAM of error
REFUSED_STREAM.
This commit only affects the library behaviour unless
nghttp2_option_set_no_http_messaging() is used.
We like strict validation against header field name and value against
RFC 7230, but we have already so much web sites and libraries in
public internet which do not obey these rules. Simply just
terminating stream because of this may break web sites and it is too
disruptive. So we decided that we should be conservative here so
those header fields containing illegal characters are just ignored.
But we are conservative only for regular headers. We are strict for
pseudo headers since it is new to HTTP/2 and new implementations
should know the rules better.
This API function with nonzero |val| parameter disables HTTP Messaging
validation in nghttp2 library, so that application can use nghttp2
library for non-HTTP use.
Previously we did not check HTTP semantics and it is left out for
application. Although checking is relatively easy, but they are
scattered and error prone. We have implemented these checks in our
applications and also feel they are tedious. To make application
development a bit easier, this commit adds basic HTTP semantics
validation to library code. We do following checks:
server:
* HEADERS is either request header or trailer header. Other type of
header is disallowed.
client:
* HEADERS is either zero or more non-final response header or final
response header or trailer header. Other type of header is
disallowed.
For both:
* Check mandatory pseudo header fields.
* Make sure that content-length matches the amount of DATA we
received.
If validation fails, RST_STREAM of type PROTOCOL_ERROR is issued.
Previously we did not handle the situation where RST_STREAM is
submitted against a stream while requet HEADERS which opens that
stream is still in queue. Due to max concurrent streams limit,
RST_STREAM is sent first, and then request HEADERS, which effectively
voids RST_STREAM.
In this commit, we checks RST_STREAM against currently pending request
HEADERS in queue and if stream ID matches, we mark that HEADERS as
canceled and RST_STREAM is not sent in this case. The library will
call on_frame_not_sent_callback for the canceled HEADERS with error
code from RST_STREAM.
Previously we treat stream in NGHTTP2_STREAM_RESERVED state specially,
that is we don't increment or decrement streams counts if stream is in
that state. Because of this, we don't change the stream state to
NGHTTP2_STREAM_CLOSING if stream is in NGHTTP2_STREAM_RESERVED. But
it turns out that it causes a problem. If client canceled pushed
stream before push response HEADERS, stream is still in
NGHTTP2_STREAM_RESERVED state. If push response HEADERS arrived in
this state, library happily accepts it and passed to application.
With this commit, this bug was corrected. We now change stream state
to NGHTTP2_STREAM_CLOSING even if it was in NGHTTP2_STREAM_RESERVED
state. We now use NGHTTP2_STREAM_FLAG_PUSH to determine whether we
have to increase/decrase stream count.
nghttp2_submit_shutdown_notice() is used to notify the client that
graceful shutdown is started. We expect that after this call, the
server application should send another GOAWAY using
nghttp2_submit_goaway() with appropriate last_stream_id. In this
commit, we also added nghttp2_session_get_last_proc_stream_id(), which
can be used as last_stream_id parameter.
This commit implements graceful shutdown in nghttpx. The integration
test for graceful shutdown is also added.
This change makes sure that GOAWAY which terminates session is
immediately sent without blocked by other frames.
NGHTTP2_ERR_SESSION_CLOSING library error code was added to indicate
this situation to callback.
Initially, we use nghttp2_stream.data_item to refer only item with
DATA frame. But recently we use it to refer HEADERS frame as well.
So it is better to call just item rather than data_item. This applies
to all related functions.
Previously session_after_frame_sent is called after we detected all
data is sent. In nghttp2_session_mem_send, we only detect it in the
next call of the function. It means that if a frame data bearing
END_STREAM is on flight to the peer as a result of
nghttp2_session_mem_send, peer may get that data and knows the stream
closure and issues new stream. We may receive this new stream before
the next nghttp2_session_mem_send call, which means that we may
incorrectly assumes that peer violates maximum concurrent stream
limit. To fix this issue, we separate session_after_frame_sent into 2
functions: session_after_frame_sent1 and session_after_frame_sent2.
session_after_frame_sent1 handles on_frame_send_callback and stream
closure and we call this early in nghttp2_session_mem_send. This
makes number of streams are synchronized correctly with peer.
Previously we handle idle streams as closed streams. We only keeps
sum of closed streams and active streams under max concurrent streams
limit, idle streams gets deleted earlier than client expects.
In this change, idle streams are kept in separate list and not handled
as closed streams. To mitigate possible attack vector to make
unlimited idle streams, we cap the number of idle streams in a half of
max concurrent streams. This is arbitrary choice. It may be adjusted
in the future when we have interop experience.
nghttp2_mem structure is introduced to hold custom memory allocator
functions and user supplied pointer. nghttp2_mem object can be passed
to nghttp2_session_client_new3(), nghttp2_session_server_new3(),
nghttp2_hd_deflate_new2() and nghttp2_hd_inflate_new2() to replace
standard malloc(), free(), calloc() and realloc(). nghttp2_mem
structure has user supplied pointer mem_user_data which can be used as
per session/object memory pool.
This change will utilize last_stream_id in GOAWAY extensively. When
GOAWAY is received with a last_stream_id, library closes all outgoing
streams whose stream_id > received last_stream_id.
nghttp2_on_stream_callback is called for each stream to be closed.
When GOAWAY is sent with a last_stream_id, library closes all incoming
streams whose stream_id > sent last_stream_id.
nghttp2_on_stream_callback is called for each stream to be closed.
If stream ID is not idle, it might be valid HEADERS. If stream ID is
idle, it is invalid regardless stream ID is even or odd, since client
is not expected to recieve request from server. nghttp2 library
historically allows this, but now we forbids this.
We make following HEADERS under priority control:
* push response HEADERS
* HEADERS submitted by nghttp2_submit_response
Currently, HEADERS submitted by nghttp2_submit_headers is not attached
to stream. This is because it may be used as non-final response
header and application may submit final response using
nghttp2_submit_response without checking non-final response header
transmission.
Allowing PRIORITY frame at anytime so that PRIORITY frame to idle
stream can create anchor node in dependency tree. In this change, we
open stream with new NGHTTP2_STREAM_IDLE state, which is linked in
session->closed_stream_head and is treated as if it is closed stream.
One difference is that if the stream is opened, we remove it from
linked list and change the state to the appropriate one. To O(1)
removal from linked list, we change session->closed_stream_head to
doubly linked list.
This change fixes the bug that stream is out of dependency tree if the
number of nodes in a dependency tree which we add new node to is
already maximum (NGHTTP2_MAX_DEP_TREE_LENGTH) and the number of
maximum concurrent streams is more than more than
NGHTTP2_MAX_DEP_TREE_LENGTH.
* Add NGHTTP2_HTTP_1_1_REQUIRED error code
* Allow transmission of WINDOW_UPDATE on reserved (remote)
* Allow reception of WINDOW_UPDATE on reserved (local)
* Treat frame larger than MAX_FRAME_SIZE as FRAME_SIZE_ERROR
ALPN identifier is still h2-14 to continue interop, since draft-14 and
-15 are binary compatible. The new error code was added in draft-15,
but HTTP/2 allows extensions can freely add new error code, so it is
not a problem.
Previously when nghttp2_stream_resume_deferred_data() is called,
deferred flags in stream->flags are all cleared. This is not ideal
because if application returned NGHTTP2_ERR_DEFERRED, and also that
stream is deferred by flow control, then all flags are cleared and
read callback will be invoked again. This commit fixes this issue.
This changes error condition of nghttp2_session_resume_data().
Previously we return error if stream was deferred by flow control.
Now we don't return error in this case. We just clear
NGHTTP2_FLAG_DEFERRED_USER and if still
NGHTTP2_FLAG_DEFERRED_FLOW_CONTROL is set, just return 0.
Previously when connection level remote flow control window gets 0, we
mark the stream having DATA frame with
NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL. When connection level
WINDOW_UPDATE is received, we checks all existing streams, including
closed ones, and call nghttp2_stream_resume_deferred_data(). The
profiler shows this is expensive.
Now we prepare dedicated priority queue for DATA frames. And we don't
mark stream with NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL when DATA
cannot be sent solely due to connection level flow control. Instead,
we just queue DATA item to queue. We won't pop DATA item from queue
when connection level remote window size is 0. This way, we avoid the
expensive operation for all streams when WINDOW_UPDATE is arrived.
Previously we missed the case where stream->data_item is not deleted
and it caused leak. Now stream->data_item is properly deleted when
session is deleted. We decided not to delete data_item in
nghttp2_stream_free() since we need nghttp2_session to decide whether
data_item should be deleted or not there.
By default, nghttp2 library only handles HTTP/2 frames and does not
recognize first 24 bytes of client connection preface. This design
choice is done due to the fact that server may want to detect the
application protocol based on first few bytes on clear text
communication. But for simple servers which only speak HTTP/2, it is
easier for developers if nghttp2 library takes care of client
connection preface.
If this option is used with nonzero val, nghttp2 library checks first
24 bytes client connection preface. If it is not a valid one,
nghttp2_session_recv() and nghttp2_session_mem_recv() will return
error NGHTTP2_ERR_BAD_PREFACE, which is fatal error.
This commit moves frame_type parameter of
nghttp2_data_soruce_read_length_callback in front of stream_id
parameter. The motivation is that other callback is generally put
frame related parameters first. To make it consistent, we move
frame_type, which is frame ralted parameter, to the left.
Previously we always call on_frame_send_callback before calling
nghttp2_stream_detach_data() after sending DATA frame. As a result,
even if DATA frame has END_STREAM, application cannot call
nghttp2_submit_data() in on_frame_send_callback because previous data
is still attached. This commit makes a change so that
nghttp2_stream_detach_data() is called before on_frame_send_callback
so that application can issue nghttp2_submit_data() in the callback.
Now it returns only stream's available remote window size, without
considering connection level window size. For connection-level window
size, nghttp2_session_get_remote_window_size() is added by this
commit. To get old behavior of
nghttp2_session_get_stream_remote_window_size() is use
min(nghttp2_session_get_stream_remote_window_size(),
nghttp2_session_get_remote_window_size()). The reason of this change
is that it is desirable to know just stream level window size without
taking into connection level window size. This is useful for
debugging purpose.
h2-14 now allows extensions to define new error codes. To allow
application callback to access such error codes, we uses uint32_t as
error_code type for structs and function parameters. Previously we
treated unknown error code as INTERNAL_ERROR, but this change removes
this and unknown error code is passed to application callback as is.
Motivation:
The send window size is currently fixed by a macro at compile time.
In order for users of the library to impact the send window size they
would have to change a macro at compile time. The window size may be dynamic
depending on the environment and deployment scheme. The library users
currently have no way to change this parameter.
Modifications:
Add a new optional callback method which is called before data is sent to
obtain the desired send window size. The callback return value will be
subject to a range check for the current session, stream, and settings
limits defined by flow control.
Result:
Library users have control over their send sizes.
Previously returning NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE from
on_header_callback moves input offset badly and it causes header
decompression error on the subsequent frames. This commit fix this
bug.
This commit makes handling of outgoing HEADERS and PUSH_PROMISE in the
same priority of other frames on the stream, so these frames are
processed in the order they are submitted. This allows application to
submit frames to a stream returned by nghttp2_submit_{request,
headers, push_promise} immediately. The only exception is
WINDOW_UPDATA frame, which requires nghttp2_stream object, which is
not created yet.
The application should be responsible for the size of incoming header
block size. Framing layer just passes everything (we have size limit
for one header/field though) to application.
Reworked no automatic WINDOW_UPDATE feature. We added new API
nghttp2_session_consume() which tells the library how many bytes are
consumed by the application. Instead of submitting WINDOW_UPDATE by
the application, the library is now responsible to submit
WINDOW_UPDATE based on consumed bytes. This is more reliable method,
since it enables us to properly send WINDOW_UPDATE for stream and
connection individually. The previous implementation of nghttpx had
broken connection window management.
Previously we just assumed that if same settings ID is found in
SETTINGS, it is enough to process last seen entry. But it turns out
it is not enough for SETTINGS_HEADER_TABLE_SIZE. If we have 0 and
4096 for SETTINGS_HEADER_TABLE_SIZE in one SETTINGS, we must first
shrink dynamic table to 0 and then enlarge it to 4096. This means
that we have to remember the minimum value and last value.
Since we do not call on_data_chunk_recv_callback for ignored DATA
chunk, if nghttp2_option_set_no_auto_connection_window_update is used,
application may not have a chance to send connection WINDOW_UPDATE.
To fix this, we accumulate those received bytes, and if it exceeds
certain number, we automatically send connection-level WINDOW_UPDATE.
This commits changes the upper bound of one header field size (the sum
of the length of name and value) to 64KiB by default. We may add an
option to change this upper bound in the future.
Add last_stream_id parameter to nghttp2_submit_goaway(). To terminate
connection immediately with application chosen last stream ID,
nghttp2_session_terminate_session2() was added.
ALTSVC and BLOCKED frames are now extension frames. To add new
extension frame without modifying nghttp2_frame union, which causes so
name bump, we separated extension frames from core frames.
nghttp2_frame includes generic nghttp2_extension. The payload member
of nghttp2_extension will point to the structure of extension frame
payload. The frame types of extension frames are defined in
nghttp2_ext_frame_type.
pending_local_max_concurrent_stream is, once local settings applied,
becomes NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS, which is very large
number. When adjusting number of streams, we have to take min of
local effective SETTINGS_MAX_CONCURRENT_STREAMS and pending one.
We simulate resource sharing by decreasing weight. The thing is if
weight is wrapped, that item continues to send DATA until its weight
gets lowered under the other items. This commits fix this issue.
Previously stream ID was assigned just before HEADERS or PUSH_PROMISE
was serialized and nghttp2_submit_{request, headers, push_promise} did
not return stream ID. The application has to check assigned stream ID
using before_frame_send_callback. Now it is apparent that priority is
meant to DATA transfer only. Also application can reorder the
requests if it wants. Therefore we can assign stream ID in
nghttp2_submit_* functions and return stream ID from them. With this
change, now application does not have to check stream ID using
before_frame_send_callback and its code will be simplified.
Currently, nghttpd server only compresses files whose extensions are
one of .html, .js, .css and .txt. nghttp advertises its support of
per-frame compression in SETTINGS frame. To implement this feature,
we added 2 public API: nghttp2_session_get_remote_settings() and
nghttp2_gzip_inflate_finished().
Callback function invoked to adjust priority value for request
HEADERS.
Since the application doesn’t know stream ID when it submits
requests, it may not be able to add correct priority value to HEADERS
frame and forced to use follwing PRIORITY frame. The purpose of this
callback is give the chance to the application to adjust priority
value with the latest information it has just before transmission so
that correct priority is included in HEADERS frame and it doesn’t
have to send additional PRIORITY frame.
The library interface supports compressed DATA. The library does not
deflate nor inflate data payload. When sending data, an application
has to compress data and set NGHTTP2_DATA_FLAG_COMPRESSED to
data_flags parameter in nghttp2_data_source_read_callback. On
receiving, flags parameter in nghttp2_on_data_chunk_recv_callback
includes NGHTTP2_FLAG_COMPRESSED. An application should check the
flags and inflate data as necessary. Since compression context is per
frame, when DATA is seen in nghttp2_on_frame_recv_callback, an
application should reset compression context.
To make adding new option easier, we decided to make the details of
option struct private and hide it from public API. We provide
functions to set individual option value.