Previously, in server side, we used closed streams to detect the error
that the misbehaving client sends a frame on the incoming stream it
explicitly closed. With this commit, we make a further step, and
detect one more error case. Since we retain closed streams as long as
the sum of its size and the number of opened streams are equal or less
than max concurrent streams, we can safely say that if we get a frame
which is sent on the stream that is not found in either closed or
opened stream, it is already closed or has not existed. Then we can
send GOAWAY.
The previous code shrinks closed streams when we closed another
stream, but now it is removed. It is enough to adjust closed streams
when new incoming stream is created.
While creating this commit, we noticed that
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS is defined as INT32_MAX. But
since SETTINGS can contain value up to UINT32_MAX, it is not enough.
However, since the stream ID space is limited to INT32_MAX, it is high
enough. We could keep this value, but this time we deprecate
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS macro. While it is in public
header, the effect of deprecating it is negligible because of the
reason we wrote above, and usually application sets much smaller value
(say, 100) as SETTINGS_MAX_CONCURRENT_STREAMS.
https://tools.ietf.org/html/rfc7540#section-5.3.3 explains how to
transform dependency tree to avoid circular dependency. Previously,
we wrongly always moved the dependent stream under the root stream.
The correct destination is the parent stream of the stream to
reprioritize. This commit fixes this bug.
nghttp2_on_invalid_header_callback is similar to
nghttp2_on_header_callback, but the former is only called when the
invalid header field is received which is silently ignored when the
callback is not set. With this callback, application inspects the
incoming invalid field, and it also can reset stream from this
callback by returning NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE, or using
nghttp2_submit_rst_stream() directly with the error code of choice.
We also added nghttp2_on_invalid_header_callback2, which uses
reference counted header fields.
This function sets the maximum length of header block (a set of header
fields per HEADERS frame) to send. The length of given set of header
fields is calculated using nghttp2_hd_deflate_bound(). Previously,
this is hard-coded, and is 64KiB.
This option prevents the nghttp2 library from sending PING frame with
ACK flag set in the reply to incoming PING frame. To allow the
application to send PING with ACK flag set, nghttp2_submit_ping() now
recognizes NGHTTP2_FLAG_PING in its flags parameter.
Since v0.6.2-7-g1d138ac ("Unify DATA and other frames in
nghttp2_outbound_item and save malloc()"), the macros are unused and the
builds fails on -Werror=unused-macros.
Disallow request from server, and response from client respectively.
When the violation is detected, return NGHTTP2_ERR_PROTO from
nghttp2_submit_request, nghttp2_submit_response,
nghttp2_submit_headers.
We also did some refactoring, and now self-dependency detection is
placed where it is only required.
Previously, we use session->next_stream_id to detect that given stream
ID was idle or not. But this was suboptimal, since it was updated
when stream ID was assigned, and it did not necessarily mean that it
actually has been sent to the peer. Now we introduced
session->sent_stream_id, which only updated when HEADERS/PUSH_PROMISE
has sent. Using sent_stream_id instead of next_stream_id tightens
idle stream detection, and misbehaved peer which sends frame with
stream ID that has not been generated.
This commit also overhauls test code which involves opening streams.
Now we have some wrapper functions for nghttp2_session_open_stream()
which also take care of updating next_stream_id and
last_recv_stream_id. They are crucial for some tests.
Return NGHTTP2_ERR_INVALID_ARGUMENT from nghttp2_submit_headers() if
given stream ID and pri_spec->stream_id are the same (thus trying to
depend on itself).
Also return NGHTTP2_ERR_INVALID_ARGUMENT from nghttp2_submit_request()
and nghttp2_submit_headers() with stream_id == 1, when new stream ID
equals to pri_spec->stream_id.
Previously, these cases are not checked, and just sent to peer.
With the presence of idle stream related API (e.g.,
nghttp2_create_idle_stream()), it is more predictable for client to
create idle streams with its dependency to another idle stream.
Previously, we didn't create complete parent idle stream in this case.
Now we create idle streams as we do on server side.
Previously, stream object for pushed resource was not created during
nghttp2_submit_push_promise(). It was created just before
nghttp2_before_frame_send_callback was called for that PUSH_PROMISE
frame. This means that application could not call
nghttp2_submit_response for the pushed resource before
nghttp2_before_frame_send_callback was called. This could be solved
by callback chaining, but for web server with back pressure from
backend stream, it is a bit unnecessarily hard to use.
This commit changes nghttp2_submit_push_promise() behaviour so that
stream object is created during that call. It makes application call
nghttp2_submit_response right after successful
nghttp2_submit_push_promise call.
Previously, nghttp2_session_end_request_headers_received assumes
stream is still writable (in other words, local endpoint has not sent
END_STREAM). But this assumption is false, because application can
send response in nghttp2_on_begin_frame_callback. Probably, this
assumption was made before the callback was introduced. This commit
addresses this issue. Since all
nghttp2_session_end_*_headers_received functions are identical, we
refactored them into one function.
Previously, nghttp2_session_find_stream(session, 0) returned NULL
despite the fact that documentation said that it should return root
stream. Now it is corrected, and it returns root stream as
documented.
It has no usecase at the moment. It is most likely that applications
know the flags when it submitted extension frame, no need to modify it
later. Possibly feature bloat.
To validate actual response body length against the value declared in
content-length response header field, we first check request method.
If request method is HEAD, respose body must be 0 regardless of the
value in content-length. nghttp2_session_upgrade() has no parameter
to indicate the request method is HEAD, so we failed to validate
response body if HEAD is used with HTTP Upgrade. New
nghttp2_session_upgrade2() accepts new parameter to indicate that
request method is HEAD or not to fix this issue. Although, this issue
affects client side only, we deprecate nghttp2_session_upgrade() in
favor of nghttp2_session_upgrade2() for both client and server side.
By default, we check the length of response body matches
content-length. For HEAD request, this is not necessarily true, so we
sniff request method, and if it is HEAD, make sure that response body
length is 0. But this does not work for HTTP Upgrade, since
nghttp2_session_upgrade() has no parameter to tell the request method
was HEAD. This commit disables this response body length validation
for the stream upgraded by HTTP Upgrade. We will add new version of
nghttp2_session_upgrade with the parameter to pass the request method
information so that we can handle this situation properly.
The encoder is not required to send dynamic table size update if the
table size is not changed from the previous value after accepting new
maximum value.
This change adds new return error code from nghttp2_session_mem_recv
and nghttp2_session_recv functions, namely NGHTTP2_ERR_FLOODED. It is
fatal error, and is returned when flooding was detected.
RFC 7540 does not enforce any limit on the number of incoming reserved
streams (in RFC 7540 terms, streams in reserved (remote) state). This
only affects client side, since only server can push streams.
Malicious server can push arbitrary number of streams, and make
client's memory exhausted. The new option,
nghttp2_set_max_reserved_remote_streams, can set the maximum number of
such incoming streams to avoid possible memory exhaustion. If this
option is set, and pushed streams are automatically closed on
reception, without calling user provided callback, if they exceed the
given limit. The default value is 200. If session is configured as
server side, this option has no effect. Server can control the number
of streams to push.
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.
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.
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.
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.
For "http" or "https" URIs, :path header field must start with "/".
The only exception is OPTIONS method, which can contain "*" to
represent system-wide OPTIONS request.
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.