Merge branch 'master' into simple-extensions

This commit is contained in:
Tatsuhiro Tsujikawa 2015-11-14 22:31:52 +09:00
commit eb4e402aae
67 changed files with 1118 additions and 462 deletions

16
contrib/usr.sbin.nghttpx Normal file
View File

@ -0,0 +1,16 @@
#include <tunables/global>
/usr/sbin/nghttpx {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
capability setgid,
capability setuid,
/usr/sbin/nghttpx rmix, # allow to run itself
/etc/nghttpx/nghttpx.conf r, # allow to read the config file
/etc/ssl/** r, # give access to ssl keys
/{,var/}run/nghttpx.pid lw, # allow to store a pid file
}

View File

@ -108,7 +108,7 @@ APIDOCS= \
nghttp2_session_mem_recv.rst \ nghttp2_session_mem_recv.rst \
nghttp2_session_mem_send.rst \ nghttp2_session_mem_send.rst \
nghttp2_session_recv.rst \ nghttp2_session_recv.rst \
nghttp2_session_request_allowed.rst \ nghttp2_session_check_request_allowed.rst \
nghttp2_session_resume_data.rst \ nghttp2_session_resume_data.rst \
nghttp2_session_send.rst \ nghttp2_session_send.rst \
nghttp2_session_server_new.rst \ nghttp2_session_server_new.rst \
@ -119,6 +119,7 @@ APIDOCS= \
nghttp2_session_terminate_session.rst \ nghttp2_session_terminate_session.rst \
nghttp2_session_terminate_session2.rst \ nghttp2_session_terminate_session2.rst \
nghttp2_session_upgrade.rst \ nghttp2_session_upgrade.rst \
nghttp2_session_upgrade2.rst \
nghttp2_session_want_read.rst \ nghttp2_session_want_read.rst \
nghttp2_session_want_write.rst \ nghttp2_session_want_write.rst \
nghttp2_stream_get_first_child.rst \ nghttp2_stream_get_first_child.rst \

View File

@ -22,7 +22,7 @@ starts with ``/**``. In other words, it only processes the comment
block starting ``/**``. The comment block must end with ``*/``. The block starting ``/**``. The comment block must end with ``*/``. The
``mkapiref.py`` requires that which type of the object this comment ``mkapiref.py`` requires that which type of the object this comment
block refers to. To specify the type of the object, the next line block refers to. To specify the type of the object, the next line
must contain the so-caled action keyword. Currently, the following must contain the so-called action keyword. Currently, the following
action keywords are supported: ``@function``, ``@functypedef``, action keywords are supported: ``@function``, ``@functypedef``,
``@enum``, ``@struct`` and ``@union``. The following sections ``@enum``, ``@struct`` and ``@union``. The following sections
describes each action keyword. describes each action keyword.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "H2LOAD" "1" "October 25, 2015" "1.4.0" "nghttp2" .TH "H2LOAD" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
.SH NAME .SH NAME
h2load \- HTTP/2 benchmarking tool h2load \- HTTP/2 benchmarking tool
. .
@ -288,14 +288,18 @@ The number of status code h2load received.
.TP .TP
.B total .B total
The number of bytes received from the server "on the wire". If The number of bytes received from the server "on the wire". If
requests were made via TLS, this value is the number of decrpyted requests were made via TLS, this value is the number of decrypted
bytes. bytes.
.TP .TP
.B headers .B headers
The number of response header bytes from the server without The number of response header bytes from the server without
decompression. For HTTP/2, this is the sum of the payload of decompression. The \fBspace savings\fP shows efficiency of header
HEADERS frame. For SPDY, this is the sum of the payload of compression. Let \fBdecompressed(headers)\fP to the number of bytes
SYN_REPLY frame. used for header fields after decompression. The \fBspace savings\fP
is calculated by (1 \- \fBheaders\fP / \fBdecompressed(headers)\fP) *
100. For HTTP/1.1, this is usually 0.00%, since it does not have
header compression. For HTTP/2 and SPDY, it shows some insightful
numbers.
.TP .TP
.B data .B data
The number of response body bytes received from the server. The number of response body bytes received from the server.

View File

@ -239,13 +239,17 @@ status codes
traffic traffic
total total
The number of bytes received from the server "on the wire". If The number of bytes received from the server "on the wire". If
requests were made via TLS, this value is the number of decrpyted requests were made via TLS, this value is the number of decrypted
bytes. bytes.
headers headers
The number of response header bytes from the server without The number of response header bytes from the server without
decompression. For HTTP/2, this is the sum of the payload of decompression. The ``space savings`` shows efficiency of header
HEADERS frame. For SPDY, this is the sum of the payload of compression. Let ``decompressed(headers)`` to the number of bytes
SYN_REPLY frame. used for header fields after decompression. The ``space savings``
is calculated by (1 - ``headers`` / ``decompressed(headers)``) *
100. For HTTP/1.1, this is usually 0.00%, since it does not have
header compression. For HTTP/2 and SPDY, it shows some insightful
numbers.
data data
The number of response body bytes received from the server. The number of response body bytes received from the server.

View File

@ -30,13 +30,17 @@ status codes
traffic traffic
total total
The number of bytes received from the server "on the wire". If The number of bytes received from the server "on the wire". If
requests were made via TLS, this value is the number of decrpyted requests were made via TLS, this value is the number of decrypted
bytes. bytes.
headers headers
The number of response header bytes from the server without The number of response header bytes from the server without
decompression. For HTTP/2, this is the sum of the payload of decompression. The ``space savings`` shows efficiency of header
HEADERS frame. For SPDY, this is the sum of the payload of compression. Let ``decompressed(headers)`` to the number of bytes
SYN_REPLY frame. used for header fields after decompression. The ``space savings``
is calculated by (1 - ``headers`` / ``decompressed(headers)``) *
100. For HTTP/1.1, this is usually 0.00%, since it does not have
header compression. For HTTP/2 and SPDY, it shows some insightful
numbers.
data data
The number of response body bytes received from the server. The number of response body bytes received from the server.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTP" "1" "October 25, 2015" "1.4.0" "nghttp2" .TH "NGHTTP" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
.SH NAME .SH NAME
nghttp \- HTTP/2 client nghttp \- HTTP/2 client
. .
@ -58,7 +58,7 @@ Discard downloaded data.
.TP .TP
.B \-O, \-\-remote\-name .B \-O, \-\-remote\-name
Save download data in the current directory. The Save download data in the current directory. The
filename is dereived from URI. If URI ends with \(aq\fI/\fP\(aq, filename is derived from URI. If URI ends with \(aq\fI/\fP\(aq,
\(aqindex.html\(aq is used as a filename. Not implemented \(aqindex.html\(aq is used as a filename. Not implemented
yet. yet.
.UNINDENT .UNINDENT

View File

@ -36,7 +36,7 @@ OPTIONS
.. option:: -O, --remote-name .. option:: -O, --remote-name
Save download data in the current directory. The Save download data in the current directory. The
filename is dereived from URI. If URI ends with '*/*', filename is derived from URI. If URI ends with '*/*',
'index.html' is used as a filename. Not implemented 'index.html' is used as a filename. Not implemented
yet. yet.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPD" "1" "October 25, 2015" "1.4.0" "nghttp2" .TH "NGHTTPD" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
.SH NAME .SH NAME
nghttpd \- HTTP/2 server nghttpd \- HTTP/2 server
. .
@ -172,6 +172,14 @@ Send back uploaded content if method is POST or PUT.
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B \-\-mime\-types\-file=<PATH>
Path to file that contains MIME media types and the
extensions that represent them.
.sp
Default: \fB/etc/mime.types\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-version .B \-\-version
Display version information and exit. Display version information and exit.
.UNINDENT .UNINDENT

View File

@ -132,6 +132,13 @@ OPTIONS
Send back uploaded content if method is POST or PUT. Send back uploaded content if method is POST or PUT.
.. option:: --mime-types-file=<PATH>
Path to file that contains MIME media types and the
extensions that represent them.
Default: ``/etc/mime.types``
.. option:: --version .. option:: --version
Display version information and exit. Display version information and exit.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPX" "1" "October 25, 2015" "1.4.0" "nghttp2" .TH "NGHTTPX" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
.SH NAME .SH NAME
nghttpx \- HTTP/2 proxy nghttpx \- HTTP/2 proxy
. .
@ -267,7 +267,7 @@ is done for each pattern group.
Set maximum number of backend concurrent HTTP/1 Set maximum number of backend concurrent HTTP/1
connections per origin host. This option is meaningful connections per origin host. This option is meaningful
when \fI\%\-s\fP option is used. The origin host is determined when \fI\%\-s\fP option is used. The origin host is determined
by authority portion of requset URI (or :authority by authority portion of request URI (or :authority
header field for HTTP/2). To limit the number of header field for HTTP/2). To limit the number of
connections per frontend for default mode, use connections per frontend for default mode, use
\fI\%\-\-backend\-http1\-connections\-per\-frontend\fP\&. \fI\%\-\-backend\-http1\-connections\-per\-frontend\fP\&.
@ -1377,7 +1377,7 @@ Clear all existing response header fields.
Return custom response \fIbody\fP to a client. When this method Return custom response \fIbody\fP to a client. When this method
is called in request phase hook, the request is not forwarded is called in request phase hook, the request is not forwarded
to the backend, and response phase hook for this request will to the backend, and response phase hook for this request will
not be invoked. When this method is called in resonse phase not be invoked. When this method is called in response phase
hook, response from backend server is canceled and discarded. hook, response from backend server is canceled and discarded.
The status code and response header fields should be set The status code and response header fields should be set
before using this method. To set status code, use :rb:meth To before using this method. To set status code, use :rb:meth To
@ -1395,7 +1395,7 @@ It is an error to call this method twice for a given request.
.UNINDENT .UNINDENT
.SS MRUBY EXAMPLES .SS MRUBY EXAMPLES
.sp .sp
Modify requet path: Modify request path:
.INDENT 0.0 .INDENT 0.0
.INDENT 3.5 .INDENT 3.5
.sp .sp

View File

@ -236,7 +236,7 @@ Performance
Set maximum number of backend concurrent HTTP/1 Set maximum number of backend concurrent HTTP/1
connections per origin host. This option is meaningful connections per origin host. This option is meaningful
when :option:`-s` option is used. The origin host is determined when :option:`-s` option is used. The origin host is determined
by authority portion of requset URI (or :authority by authority portion of request URI (or :authority
header field for HTTP/2). To limit the number of header field for HTTP/2). To limit the number of
connections per frontend for default mode, use connections per frontend for default mode, use
:option:`--backend-http1-connections-per-frontend`\. :option:`--backend-http1-connections-per-frontend`\.
@ -1248,7 +1248,7 @@ respectively.
Return custom response *body* to a client. When this method Return custom response *body* to a client. When this method
is called in request phase hook, the request is not forwarded is called in request phase hook, the request is not forwarded
to the backend, and response phase hook for this request will to the backend, and response phase hook for this request will
not be invoked. When this method is called in resonse phase not be invoked. When this method is called in response phase
hook, response from backend server is canceled and discarded. hook, response from backend server is canceled and discarded.
The status code and response header fields should be set The status code and response header fields should be set
before using this method. To set status code, use :rb:meth To before using this method. To set status code, use :rb:meth To
@ -1266,7 +1266,7 @@ respectively.
MRUBY EXAMPLES MRUBY EXAMPLES
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
Modify requet path: Modify request path:
.. code-block:: ruby .. code-block:: ruby

View File

@ -354,7 +354,7 @@ respectively.
Return custom response *body* to a client. When this method Return custom response *body* to a client. When this method
is called in request phase hook, the request is not forwarded is called in request phase hook, the request is not forwarded
to the backend, and response phase hook for this request will to the backend, and response phase hook for this request will
not be invoked. When this method is called in resonse phase not be invoked. When this method is called in response phase
hook, response from backend server is canceled and discarded. hook, response from backend server is canceled and discarded.
The status code and response header fields should be set The status code and response header fields should be set
before using this method. To set status code, use :rb:meth To before using this method. To set status code, use :rb:meth To
@ -372,7 +372,7 @@ respectively.
MRUBY EXAMPLES MRUBY EXAMPLES
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
Modify requet path: Modify request path:
.. code-block:: ruby .. code-block:: ruby

View File

@ -27,7 +27,7 @@ We use clang-format to format source code consistently. The
clang-format configuration file .clang-format is located at the root clang-format configuration file .clang-format is located at the root
directory. Since clang-format produces slightly different results directory. Since clang-format produces slightly different results
between versions, we currently use clang-format which comes with between versions, we currently use clang-format which comes with
clang-3.5. clang-3.6.
To detect any violation to the coding style, we recommend to setup git To detect any violation to the coding style, we recommend to setup git
pre-commit hook to check coding style of the changes you introduced. pre-commit hook to check coding style of the changes you introduced.
@ -35,7 +35,7 @@ The pre-commit file is located at the root directory. Copy it under
.git/hooks and make sure that it is executable. The pre-commit script .git/hooks and make sure that it is executable. The pre-commit script
uses clang-format-diff.py to detect any style errors. If it is not in uses clang-format-diff.py to detect any style errors. If it is not in
your PATH or it exists under different name (e.g., your PATH or it exists under different name (e.g.,
clang-format-diff-3.5 in debian), either add it to PATH variable or clang-format-diff-3.6 in debian), either add it to PATH variable or
add git option ``clangformatdiff.binary`` to point to the script. add git option ``clangformatdiff.binary`` to point to the script.
For emacs users, integrating clang-format to emacs is very easy. For emacs users, integrating clang-format to emacs is very easy.

View File

@ -459,12 +459,11 @@ static void ctl_poll(struct pollfd *pollfd, struct Connection *connection) {
static void submit_request(struct Connection *connection, struct Request *req) { static void submit_request(struct Connection *connection, struct Request *req) {
int32_t stream_id; int32_t stream_id;
/* Make sure that the last item is NULL */ /* Make sure that the last item is NULL */
const nghttp2_nv nva[] = {MAKE_NV(":method", "GET"), const nghttp2_nv nva[] = {
MAKE_NV_CS(":path", req->path), MAKE_NV(":method", "GET"), MAKE_NV_CS(":path", req->path),
MAKE_NV(":scheme", "https"), MAKE_NV(":scheme", "https"), MAKE_NV_CS(":authority", req->hostport),
MAKE_NV_CS(":authority", req->hostport), MAKE_NV("accept", "*/*"),
MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)};
MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)};
stream_id = nghttp2_submit_request(connection->session, NULL, nva, stream_id = nghttp2_submit_request(connection->session, NULL, nva,
sizeof(nva) / sizeof(nva[0]), NULL, req); sizeof(nva) / sizeof(nva[0]), NULL, req);

View File

@ -434,7 +434,19 @@ typedef enum {
* Header Field never Indexed" representation must be used in HPACK * Header Field never Indexed" representation must be used in HPACK
* encoding). Other implementation calls this bit as "sensitive". * encoding). Other implementation calls this bit as "sensitive".
*/ */
NGHTTP2_NV_FLAG_NO_INDEX = 0x01 NGHTTP2_NV_FLAG_NO_INDEX = 0x01,
/**
* This flag is set solely by application. If this flag is set, the
* library does not make a copy of header field name. This could
* improve performance.
*/
NGHTTP2_NV_FLAG_NO_COPY_NAME = 0x02,
/**
* This flag is set solely by application. If this flag is set, the
* library does not make a copy of header field value. This could
* improve performance.
*/
NGHTTP2_NV_FLAG_NO_COPY_VALUE = 0x04
} nghttp2_nv_flag; } nghttp2_nv_flag;
/** /**
@ -446,17 +458,27 @@ typedef struct {
/** /**
* The |name| byte string. If this struct is presented from library * The |name| byte string. If this struct is presented from library
* (e.g., :type:`nghttp2_on_frame_recv_callback`), |name| is * (e.g., :type:`nghttp2_on_frame_recv_callback`), |name| is
* guaranteed to be NULL-terminated. When application is * guaranteed to be NULL-terminated. For some callbacks
* constructing this struct, |name| is not required to be * (:type:`nghttp2_before_frame_send_callback`,
* :type:`nghttp2_on_frame_send_callback`, and
* :type:`nghttp2_on_frame_not_send_callback`), it may not be
* NULL-terminated if header field is passed from application with
* the flag :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME`). When application
* is constructing this struct, |name| is not required to be
* NULL-terminated. * NULL-terminated.
*/ */
uint8_t *name; uint8_t *name;
/** /**
* The |value| byte string. If this struct is presented from * The |value| byte string. If this struct is presented from
* library (e.g., :type:`nghttp2_on_frame_recv_callback`), |value| * library (e.g., :type:`nghttp2_on_frame_recv_callback`), |value|
* is guaranteed to be NULL-terminated. When application is * is guaranteed to be NULL-terminated. For some callbacks
* constructing this struct, |value| is not required to be * (:type:`nghttp2_before_frame_send_callback`,
* NULL-terminated. * :type:`nghttp2_on_frame_send_callback`, and
* :type:`nghttp2_on_frame_not_send_callback`), it may not be
* NULL-terminated if header field is passed from application with
* the flag :enum:`NGHTTP2_NV_FLAG_NO_COPY_VALUE`). When
* application is constructing this struct, |value| is not required
* to be NULL-terminated.
*/ */
uint8_t *value; uint8_t *value;
/** /**
@ -2455,8 +2477,8 @@ NGHTTP2_EXTERN int nghttp2_session_send(nghttp2_session *session);
* buffer up small chunks of data as necessary to avoid this * buffer up small chunks of data as necessary to avoid this
* situation. * situation.
*/ */
NGHTTP2_EXTERN ssize_t nghttp2_session_mem_send(nghttp2_session *session, NGHTTP2_EXTERN ssize_t
const uint8_t **data_ptr); nghttp2_session_mem_send(nghttp2_session *session, const uint8_t **data_ptr);
/** /**
* @function * @function
@ -2654,7 +2676,7 @@ nghttp2_session_set_stream_user_data(nghttp2_session *session,
* include the deferred DATA frames. * include the deferred DATA frames.
*/ */
NGHTTP2_EXTERN size_t NGHTTP2_EXTERN size_t
nghttp2_session_get_outbound_queue_size(nghttp2_session *session); nghttp2_session_get_outbound_queue_size(nghttp2_session *session);
/** /**
* @function * @function
@ -2670,8 +2692,9 @@ NGHTTP2_EXTERN size_t
* *
* This function returns -1 if it fails. * This function returns -1 if it fails.
*/ */
NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_recv_data_length( NGHTTP2_EXTERN int32_t
nghttp2_session *session, int32_t stream_id); nghttp2_session_get_stream_effective_recv_data_length(nghttp2_session *session,
int32_t stream_id);
/** /**
* @function * @function
@ -2683,8 +2706,9 @@ NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_recv_data_length(
* *
* This function returns -1 if it fails. * This function returns -1 if it fails.
*/ */
NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_local_window_size( NGHTTP2_EXTERN int32_t
nghttp2_session *session, int32_t stream_id); nghttp2_session_get_stream_effective_local_window_size(nghttp2_session *session,
int32_t stream_id);
/** /**
* @function * @function
@ -2701,7 +2725,7 @@ NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_local_window_size(
* This function returns -1 if it fails. * This function returns -1 if it fails.
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_session_get_effective_recv_data_length(nghttp2_session *session); nghttp2_session_get_effective_recv_data_length(nghttp2_session *session);
/** /**
* @function * @function
@ -2714,7 +2738,7 @@ NGHTTP2_EXTERN int32_t
* This function returns -1 if it fails. * This function returns -1 if it fails.
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_session_get_effective_local_window_size(nghttp2_session *session); nghttp2_session_get_effective_local_window_size(nghttp2_session *session);
/** /**
* @function * @function
@ -2731,8 +2755,8 @@ NGHTTP2_EXTERN int32_t
* This function returns -1 if it fails. * This function returns -1 if it fails.
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_session_get_stream_remote_window_size(nghttp2_session *session, nghttp2_session_get_stream_remote_window_size(nghttp2_session *session,
int32_t stream_id); int32_t stream_id);
/** /**
* @function * @function
@ -2742,7 +2766,7 @@ NGHTTP2_EXTERN int32_t
* This function always succeeds. * This function always succeeds.
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_session_get_remote_window_size(nghttp2_session *session); nghttp2_session_get_remote_window_size(nghttp2_session *session);
/** /**
* @function * @function
@ -2868,8 +2892,8 @@ NGHTTP2_EXTERN int nghttp2_submit_shutdown_notice(nghttp2_session *session);
* :enum:`nghttp2_settings_id`. * :enum:`nghttp2_settings_id`.
*/ */
NGHTTP2_EXTERN uint32_t NGHTTP2_EXTERN uint32_t
nghttp2_session_get_remote_settings(nghttp2_session *session, nghttp2_session_get_remote_settings(nghttp2_session *session,
nghttp2_settings_id id); nghttp2_settings_id id);
/** /**
* @function * @function
@ -2898,7 +2922,7 @@ NGHTTP2_EXTERN int nghttp2_session_set_next_stream_id(nghttp2_session *session,
* function returns 1 << 31. * function returns 1 << 31.
*/ */
NGHTTP2_EXTERN uint32_t NGHTTP2_EXTERN uint32_t
nghttp2_session_get_next_stream_id(nghttp2_session *session); nghttp2_session_get_next_stream_id(nghttp2_session *session);
/** /**
* @function * @function
@ -2974,6 +2998,17 @@ NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session,
* be called from both client and server, but the behavior is very * be called from both client and server, but the behavior is very
* different in each other. * different in each other.
* *
* .. warning::
*
* This function is deprecated in favor of
* `nghttp2_session_upgrade2()`, because this function lacks the
* parameter to tell the library the request method used in the
* original HTTP request. This information is required for client
* to validate actual response body length against content-length
* header field (see `nghttp2_option_set_no_http_messaging()`). If
* HEAD is used in request, the length of response body must be 0
* regardless of value included in content-length header field.
*
* If called from client side, the |settings_payload| must be the * If called from client side, the |settings_payload| must be the
* value sent in ``HTTP2-Settings`` header field and must be decoded * value sent in ``HTTP2-Settings`` header field and must be decoded
* by base64url decoder. The |settings_payloadlen| is the length of * by base64url decoder. The |settings_payloadlen| is the length of
@ -3008,6 +3043,51 @@ NGHTTP2_EXTERN int nghttp2_session_upgrade(nghttp2_session *session,
size_t settings_payloadlen, size_t settings_payloadlen,
void *stream_user_data); void *stream_user_data);
/**
* @function
*
* Performs post-process of HTTP Upgrade request. This function can
* be called from both client and server, but the behavior is very
* different in each other.
*
* If called from client side, the |settings_payload| must be the
* value sent in ``HTTP2-Settings`` header field and must be decoded
* by base64url decoder. The |settings_payloadlen| is the length of
* |settings_payload|. The |settings_payload| is unpacked and its
* setting values will be submitted using `nghttp2_submit_settings()`.
* This means that the client application code does not need to submit
* SETTINGS by itself. The stream with stream ID=1 is opened and the
* |stream_user_data| is used for its stream_user_data. The opened
* stream becomes half-closed (local) state.
*
* If called from server side, the |settings_payload| must be the
* value received in ``HTTP2-Settings`` header field and must be
* decoded by base64url decoder. The |settings_payloadlen| is the
* length of |settings_payload|. It is treated as if the SETTINGS
* frame with that payload is received. Thus, callback functions for
* the reception of SETTINGS frame will be invoked. The stream with
* stream ID=1 is opened. The |stream_user_data| is ignored. The
* opened stream becomes half-closed (remote).
*
* If the request method is HEAD, pass nonzero value to
* |head_request|. Otherwise, pass 0.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |settings_payload| is badly formed.
* :enum:`NGHTTP2_ERR_PROTO`
* The stream ID 1 is already used or closed; or is not available.
*/
NGHTTP2_EXTERN int nghttp2_session_upgrade2(nghttp2_session *session,
const uint8_t *settings_payload,
size_t settings_payloadlen,
int head_request,
void *stream_user_data);
/** /**
* @function * @function
* *
@ -3030,8 +3110,8 @@ NGHTTP2_EXTERN int nghttp2_session_upgrade(nghttp2_session *session,
* The provided |buflen| size is too small to hold the output. * The provided |buflen| size is too small to hold the output.
*/ */
NGHTTP2_EXTERN ssize_t NGHTTP2_EXTERN ssize_t
nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen, nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen,
const nghttp2_settings_entry *iv, size_t niv); const nghttp2_settings_entry *iv, size_t niv);
/** /**
* @function * @function
@ -3098,7 +3178,15 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
* *
* This function creates copies of all name/value pairs in |nva|. It * This function creates copies of all name/value pairs in |nva|. It
* also lower-cases all names in |nva|. The order of elements in * also lower-cases all names in |nva|. The order of elements in
* |nva| is preserved. * |nva| is preserved. For header fields with
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME` and
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, header field name
* and value are not copied respectively. With
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME`, application is responsible to
* pass header field name in lowercase. The application should
* maintain the references to them until
* :type:`nghttp2_on_frame_send_callback` or
* :type:`nghttp2_on_frame_not_send_callback` is called.
* *
* HTTP/2 specification has requirement about header fields in the * HTTP/2 specification has requirement about header fields in the
* request HEADERS. See the specification for more details. * request HEADERS. See the specification for more details.
@ -3134,11 +3222,11 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
* *
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_submit_request(nghttp2_session *session, nghttp2_submit_request(nghttp2_session *session,
const nghttp2_priority_spec *pri_spec, const nghttp2_priority_spec *pri_spec,
const nghttp2_nv *nva, size_t nvlen, const nghttp2_nv *nva, size_t nvlen,
const nghttp2_data_provider *data_prd, const nghttp2_data_provider *data_prd,
void *stream_user_data); void *stream_user_data);
/** /**
* @function * @function
@ -3154,7 +3242,15 @@ NGHTTP2_EXTERN int32_t
* *
* This function creates copies of all name/value pairs in |nva|. It * This function creates copies of all name/value pairs in |nva|. It
* also lower-cases all names in |nva|. The order of elements in * also lower-cases all names in |nva|. The order of elements in
* |nva| is preserved. * |nva| is preserved. For header fields with
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME` and
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, header field name
* and value are not copied respectively. With
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME`, application is responsible to
* pass header field name in lowercase. The application should
* maintain the references to them until
* :type:`nghttp2_on_frame_send_callback` or
* :type:`nghttp2_on_frame_not_send_callback` is called.
* *
* HTTP/2 specification has requirement about header fields in the * HTTP/2 specification has requirement about header fields in the
* response HEADERS. See the specification for more details. * response HEADERS. See the specification for more details.
@ -3211,7 +3307,15 @@ nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
* *
* This function creates copies of all name/value pairs in |nva|. It * This function creates copies of all name/value pairs in |nva|. It
* also lower-cases all names in |nva|. The order of elements in * also lower-cases all names in |nva|. The order of elements in
* |nva| is preserved. * |nva| is preserved. For header fields with
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME` and
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, header field name
* and value are not copied respectively. With
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME`, application is responsible to
* pass header field name in lowercase. The application should
* maintain the references to them until
* :type:`nghttp2_on_frame_send_callback` or
* :type:`nghttp2_on_frame_not_send_callback` is called.
* *
* For server, trailer must be followed by response HEADERS or * For server, trailer must be followed by response HEADERS or
* response DATA. The library does not check that response HEADERS * response DATA. The library does not check that response HEADERS
@ -3287,7 +3391,15 @@ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session,
* *
* This function creates copies of all name/value pairs in |nva|. It * This function creates copies of all name/value pairs in |nva|. It
* also lower-cases all names in |nva|. The order of elements in * also lower-cases all names in |nva|. The order of elements in
* |nva| is preserved. * |nva| is preserved. For header fields with
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME` and
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, header field name
* and value are not copied respectively. With
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME`, application is responsible to
* pass header field name in lowercase. The application should
* maintain the references to them until
* :type:`nghttp2_on_frame_send_callback` or
* :type:`nghttp2_on_frame_not_send_callback` is called.
* *
* The |stream_user_data| is a pointer to an arbitrary data which is * The |stream_user_data| is a pointer to an arbitrary data which is
* associated to the stream this frame will open. Therefore it is * associated to the stream this frame will open. Therefore it is
@ -3325,11 +3437,10 @@ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session,
* *
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
int32_t stream_id, int32_t stream_id, const nghttp2_priority_spec *pri_spec,
const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen,
const nghttp2_nv *nva, size_t nvlen, void *stream_user_data);
void *stream_user_data);
/** /**
* @function * @function
@ -3485,7 +3596,15 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session,
* *
* This function creates copies of all name/value pairs in |nva|. It * This function creates copies of all name/value pairs in |nva|. It
* also lower-cases all names in |nva|. The order of elements in * also lower-cases all names in |nva|. The order of elements in
* |nva| is preserved. * |nva| is preserved. For header fields with
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME` and
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, header field name
* and value are not copied respectively. With
* :enum:`NGHTTP2_NV_FLAG_NO_COPY_NAME`, application is responsible to
* pass header field name in lowercase. The application should
* maintain the references to them until
* :type:`nghttp2_on_frame_send_callback` or
* :type:`nghttp2_on_frame_not_send_callback` is called.
* *
* The |promised_stream_user_data| is a pointer to an arbitrary data * The |promised_stream_user_data| is a pointer to an arbitrary data
* which is associated to the promised stream this frame will open and * which is associated to the promised stream this frame will open and
@ -3524,9 +3643,9 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session,
* *
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags, nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const nghttp2_nv *nva, int32_t stream_id, const nghttp2_nv *nva,
size_t nvlen, void *promised_stream_user_data); size_t nvlen, void *promised_stream_user_data);
/** /**
* @function * @function
@ -3615,7 +3734,7 @@ NGHTTP2_EXTERN int nghttp2_submit_goaway(nghttp2_session *session,
* This function always succeeds. * This function always succeeds.
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_session_get_last_proc_stream_id(nghttp2_session *session); nghttp2_session_get_last_proc_stream_id(nghttp2_session *session);
/** /**
* @function * @function
@ -3632,7 +3751,8 @@ NGHTTP2_EXTERN int32_t
* may return error. Or, request is failed to sent, and * may return error. Or, request is failed to sent, and
* :type:`nghttp2_on_stream_close_callback` is called. * :type:`nghttp2_on_stream_close_callback` is called.
*/ */
NGHTTP2_EXTERN int nghttp2_session_request_allowed(nghttp2_session *session); NGHTTP2_EXTERN int
nghttp2_session_check_request_allowed(nghttp2_session *session);
/** /**
* @function * @function
@ -3930,8 +4050,8 @@ nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
* The provided |buflen| size is too small to hold the output. * The provided |buflen| size is too small to hold the output.
*/ */
NGHTTP2_EXTERN ssize_t NGHTTP2_EXTERN ssize_t
nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf,
size_t buflen, const nghttp2_nv *nva, size_t nvlen); size_t buflen, const nghttp2_nv *nva, size_t nvlen);
/** /**
* @function * @function
@ -4289,7 +4409,7 @@ typedef enum {
* :enum:`NGHTTP2_STREAM_STATE_IDLE`. * :enum:`NGHTTP2_STREAM_STATE_IDLE`.
*/ */
NGHTTP2_EXTERN nghttp2_stream_proto_state NGHTTP2_EXTERN nghttp2_stream_proto_state
nghttp2_stream_get_state(nghttp2_stream *stream); nghttp2_stream_get_state(nghttp2_stream *stream);
/** /**
* @function * @function
@ -4352,7 +4472,7 @@ NGHTTP2_EXTERN int32_t nghttp2_stream_get_weight(nghttp2_stream *stream);
* Returns the sum of the weight for |stream|'s children. * Returns the sum of the weight for |stream|'s children.
*/ */
NGHTTP2_EXTERN int32_t NGHTTP2_EXTERN int32_t
nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream); nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -750,21 +750,26 @@ void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen) {
int nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, const nghttp2_nv *nva, int nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, const nghttp2_nv *nva,
size_t nvlen, nghttp2_mem *mem) { size_t nvlen, nghttp2_mem *mem) {
size_t i; size_t i;
uint8_t *data; uint8_t *data = NULL;
size_t buflen = 0; size_t buflen = 0;
nghttp2_nv *p; nghttp2_nv *p;
for (i = 0; i < nvlen; ++i) {
/* + 2 for null-termination */
buflen += nva[i].namelen + nva[i].valuelen + 2;
}
if (nvlen == 0) { if (nvlen == 0) {
*nva_ptr = NULL; *nva_ptr = NULL;
return 0; return 0;
} }
for (i = 0; i < nvlen; ++i) {
/* + 1 for null-termination */
if ((nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_NAME) == 0) {
buflen += nva[i].namelen + 1;
}
if ((nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_VALUE) == 0) {
buflen += nva[i].valuelen + 1;
}
}
buflen += sizeof(nghttp2_nv) * nvlen; buflen += sizeof(nghttp2_nv) * nvlen;
*nva_ptr = nghttp2_mem_malloc(mem, buflen); *nva_ptr = nghttp2_mem_malloc(mem, buflen);
@ -779,17 +784,29 @@ int nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, const nghttp2_nv *nva,
for (i = 0; i < nvlen; ++i) { for (i = 0; i < nvlen; ++i) {
p->flags = nva[i].flags; p->flags = nva[i].flags;
memcpy(data, nva[i].name, nva[i].namelen); if (nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_NAME) {
p->name = data; p->name = nva[i].name;
p->namelen = nva[i].namelen; p->namelen = nva[i].namelen;
data[p->namelen] = '\0'; } else {
nghttp2_downcase(p->name, p->namelen); memcpy(data, nva[i].name, nva[i].namelen);
data += nva[i].namelen + 1; p->name = data;
memcpy(data, nva[i].value, nva[i].valuelen); p->namelen = nva[i].namelen;
p->value = data; data[p->namelen] = '\0';
p->valuelen = nva[i].valuelen; nghttp2_downcase(p->name, p->namelen);
data[p->valuelen] = '\0'; data += nva[i].namelen + 1;
data += nva[i].valuelen + 1; }
if (nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_VALUE) {
p->value = nva[i].value;
p->valuelen = nva[i].valuelen;
} else {
memcpy(data, nva[i].value, nva[i].valuelen);
p->value = data;
p->valuelen = nva[i].valuelen;
data[p->valuelen] = '\0';
data += nva[i].valuelen + 1;
}
++p; ++p;
} }
return 0; return 0;

View File

@ -41,7 +41,7 @@
#define lstreq(A, B, N) ((sizeof((A)) - 1) == (N) && memcmp((A), (B), (N)) == 0) #define lstreq(A, B, N) ((sizeof((A)) - 1) == (N) && memcmp((A), (B), (N)) == 0)
#define nghttp2_struct_of(ptr, type, member) \ #define nghttp2_struct_of(ptr, type, member) \
((type *)(void *)((char *)(ptr) - offsetof(type, member))) ((type *)(void *)((char *)(ptr)-offsetof(type, member)))
/* /*
* Copies 2 byte unsigned integer |n| in host byte order to |buf| in * Copies 2 byte unsigned integer |n| in host byte order to |buf| in

View File

@ -381,7 +381,8 @@ int nghttp2_http_on_response_headers(nghttp2_stream *stream) {
if (!expect_response_body(stream)) { if (!expect_response_body(stream)) {
stream->content_length = 0; stream->content_length = 0;
} else if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { } else if (stream->http_flags & (NGHTTP2_HTTP_FLAG_METH_CONNECT |
NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND)) {
stream->content_length = -1; stream->content_length = -1;
} }

View File

@ -615,10 +615,9 @@ void nghttp2_session_del(nghttp2_session *session) {
nghttp2_mem_free(mem, session); nghttp2_mem_free(mem, session);
} }
int int nghttp2_session_reprioritize_stream(
nghttp2_session_reprioritize_stream(nghttp2_session *session, nghttp2_session *session, nghttp2_stream *stream,
nghttp2_stream *stream, const nghttp2_priority_spec *pri_spec_in) {
const nghttp2_priority_spec *pri_spec_in) {
int rv; int rv;
nghttp2_stream *dep_stream = NULL; nghttp2_stream *dep_stream = NULL;
nghttp2_priority_spec pri_spec_default; nghttp2_priority_spec pri_spec_default;
@ -1327,7 +1326,7 @@ static int session_predicate_for_stream_send(nghttp2_session *session,
return 0; return 0;
} }
int nghttp2_session_request_allowed(nghttp2_session *session) { int nghttp2_session_check_request_allowed(nghttp2_session *session) {
return !session->server && session->next_stream_id <= INT32_MAX && return !session->server && session->next_stream_id <= INT32_MAX &&
(session->goaway_flags & (session->goaway_flags &
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) == 0; (NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) == 0;
@ -6662,10 +6661,10 @@ uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
assert(0); assert(0);
} }
int nghttp2_session_upgrade(nghttp2_session *session, static int nghttp2_session_upgrade_internal(nghttp2_session *session,
const uint8_t *settings_payload, const uint8_t *settings_payload,
size_t settings_payloadlen, size_t settings_payloadlen,
void *stream_user_data) { void *stream_user_data) {
nghttp2_stream *stream; nghttp2_stream *stream;
nghttp2_frame frame; nghttp2_frame frame;
nghttp2_settings_entry *iv; nghttp2_settings_entry *iv;
@ -6722,6 +6721,58 @@ int nghttp2_session_upgrade(nghttp2_session *session,
return 0; return 0;
} }
int nghttp2_session_upgrade(nghttp2_session *session,
const uint8_t *settings_payload,
size_t settings_payloadlen,
void *stream_user_data) {
int rv;
nghttp2_stream *stream;
rv = nghttp2_session_upgrade_internal(session, settings_payload,
settings_payloadlen, stream_user_data);
if (rv != 0) {
return rv;
}
stream = nghttp2_session_get_stream(session, 1);
assert(stream);
/* We have no information about request header fields when Upgrade
was happened. So we don't know the request method here. If
request method is HEAD, we have a trouble because we may have
nonzero content-length header field in response headers, and we
will going to check it against the actual DATA frames, but we may
get mismatch because HEAD response body must be empty. Because
of this reason, nghttp2_session_upgrade() was deprecated in favor
of nghttp2_session_upgrade2(), which has |head_request| parameter
to indicate that request method is HEAD or not. */
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND;
return 0;
}
int nghttp2_session_upgrade2(nghttp2_session *session,
const uint8_t *settings_payload,
size_t settings_payloadlen, int head_request,
void *stream_user_data) {
int rv;
nghttp2_stream *stream;
rv = nghttp2_session_upgrade_internal(session, settings_payload,
settings_payloadlen, stream_user_data);
if (rv != 0) {
return rv;
}
stream = nghttp2_session_get_stream(session, 1);
assert(stream);
if (head_request) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
}
return 0;
}
int nghttp2_session_get_stream_local_close(nghttp2_session *session, int nghttp2_session_get_stream_local_close(nghttp2_session *session,
int32_t stream_id) { int32_t stream_id) {
nghttp2_stream *stream; nghttp2_stream *stream;

View File

@ -114,8 +114,7 @@ static int stream_subtree_active(nghttp2_stream *stream) {
*/ */
static uint64_t stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) { static uint64_t stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
return last_cycle + return last_cycle +
(stream->last_writelen + 1) * NGHTTP2_MAX_WEIGHT / stream->last_writelen * NGHTTP2_MAX_WEIGHT / (uint32_t)stream->weight;
(uint32_t)stream->weight;
} }
static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) { static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {

View File

@ -116,19 +116,21 @@ typedef enum {
NGHTTP2_HTTP_FLAG_METH_CONNECT = 1 << 7, NGHTTP2_HTTP_FLAG_METH_CONNECT = 1 << 7,
NGHTTP2_HTTP_FLAG_METH_HEAD = 1 << 8, NGHTTP2_HTTP_FLAG_METH_HEAD = 1 << 8,
NGHTTP2_HTTP_FLAG_METH_OPTIONS = 1 << 9, NGHTTP2_HTTP_FLAG_METH_OPTIONS = 1 << 9,
NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND = 1 << 10,
NGHTTP2_HTTP_FLAG_METH_ALL = NGHTTP2_HTTP_FLAG_METH_CONNECT | NGHTTP2_HTTP_FLAG_METH_ALL = NGHTTP2_HTTP_FLAG_METH_CONNECT |
NGHTTP2_HTTP_FLAG_METH_HEAD | NGHTTP2_HTTP_FLAG_METH_HEAD |
NGHTTP2_HTTP_FLAG_METH_OPTIONS, NGHTTP2_HTTP_FLAG_METH_OPTIONS |
NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND,
/* :path category */ /* :path category */
/* path starts with "/" */ /* path starts with "/" */
NGHTTP2_HTTP_FLAG_PATH_REGULAR = 1 << 10, NGHTTP2_HTTP_FLAG_PATH_REGULAR = 1 << 11,
/* path "*" */ /* path "*" */
NGHTTP2_HTTP_FLAG_PATH_ASTERISK = 1 << 11, NGHTTP2_HTTP_FLAG_PATH_ASTERISK = 1 << 12,
/* scheme */ /* scheme */
/* "http" or "https" scheme */ /* "http" or "https" scheme */
NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 12, NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 13,
/* set if final response is expected */ /* set if final response is expected */
NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 13 NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 14
} nghttp2_http_flag; } nghttp2_http_flag;
struct nghttp2_stream { struct nghttp2_stream {

View File

@ -59,13 +59,13 @@ void session_impl::start_resolve(const std::string &host,
resolver_.async_resolve({host, service}, resolver_.async_resolve({host, service},
[this](const boost::system::error_code &ec, [this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) { tcp::resolver::iterator endpoint_it) {
if (ec) { if (ec) {
not_connected(ec); not_connected(ec);
return; return;
} }
start_connect(endpoint_it); start_connect(endpoint_it);
}); });
} }
void session_impl::connected(tcp::resolver::iterator endpoint_it) { void session_impl::connected(tcp::resolver::iterator endpoint_it) {
@ -462,9 +462,9 @@ const request *session_impl::submit(boost::system::error_code &ec,
[](nghttp2_session *session, int32_t stream_id, uint8_t *buf, [](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
size_t length, uint32_t *data_flags, nghttp2_data_source *source, size_t length, uint32_t *data_flags, nghttp2_data_source *source,
void *user_data) -> ssize_t { void *user_data) -> ssize_t {
auto strm = static_cast<stream *>(source->ptr); auto strm = static_cast<stream *>(source->ptr);
return strm->request().impl().call_on_read(buf, length, data_flags); return strm->request().impl().call_on_read(buf, length, data_flags);
}; };
prdptr = &prd; prdptr = &prd;
} }

View File

@ -41,13 +41,13 @@ void session_tcp_impl::start_connect(tcp::resolver::iterator endpoint_it) {
boost::asio::async_connect(socket_, endpoint_it, boost::asio::async_connect(socket_, endpoint_it,
[this](const boost::system::error_code &ec, [this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) { tcp::resolver::iterator endpoint_it) {
if (ec) { if (ec) {
not_connected(ec); not_connected(ec);
return; return;
} }
connected(endpoint_it); connected(endpoint_it);
}); });
} }
tcp::socket &session_tcp_impl::socket() { return socket_; } tcp::socket &session_tcp_impl::socket() { return socket_; }

View File

@ -23,6 +23,7 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#include "asio_client_session_tls_impl.h" #include "asio_client_session_tls_impl.h"
#include "asio_common.h"
namespace nghttp2 { namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
@ -44,24 +45,31 @@ session_tls_impl::session_tls_impl(boost::asio::io_service &io_service,
session_tls_impl::~session_tls_impl() {} session_tls_impl::~session_tls_impl() {}
void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) { void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) {
boost::asio::async_connect(socket(), endpoint_it, boost::asio::async_connect(
[this](const boost::system::error_code &ec, socket(), endpoint_it, [this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) { tcp::resolver::iterator endpoint_it) {
if (ec) { if (ec) {
not_connected(ec); not_connected(ec);
return; return;
} }
socket_.async_handshake( socket_.async_handshake(
boost::asio::ssl::stream_base::client, boost::asio::ssl::stream_base::client,
[this, endpoint_it](const boost::system::error_code &ec) { [this, endpoint_it](const boost::system::error_code &ec) {
if (ec) { if (ec) {
not_connected(ec); not_connected(ec);
return; return;
} }
connected(endpoint_it);
}); if (!tls_h2_negotiated(socket_)) {
}); not_connected(make_error_code(
NGHTTP2_ASIO_ERR_TLS_NO_APP_PROTO_NEGOTIATED));
return;
}
connected(endpoint_it);
});
});
} }
tcp::socket &session_tls_impl::socket() { return socket_.next_layer(); } tcp::socket &session_tls_impl::socket() { return socket_.next_layer(); }

View File

@ -56,6 +56,12 @@ configure_tls_context(boost::system::error_code &ec,
SSL_CTX_set_next_proto_select_cb(ctx, client_select_next_proto_cb, nullptr); SSL_CTX_set_next_proto_select_cb(ctx, client_select_next_proto_cb, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
auto proto_list = util::get_default_alpn();
SSL_CTX_set_alpn_protos(ctx, proto_list.data(), proto_list.size());
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
return ec; return ec;
} }

View File

@ -48,6 +48,31 @@ boost::system::error_code make_error_code(nghttp2_error ev) {
return boost::system::error_code(static_cast<int>(ev), nghttp2_category()); return boost::system::error_code(static_cast<int>(ev), nghttp2_category());
} }
class nghttp2_asio_category_impl : public boost::system::error_category {
public:
const char *name() const noexcept { return "nghttp2_asio"; }
std::string message(int ev) const {
switch (ev) {
case NGHTTP2_ASIO_ERR_NO_ERROR:
return "no error";
case NGHTTP2_ASIO_ERR_TLS_NO_APP_PROTO_NEGOTIATED:
return "tls: no application protocol negotiated";
default:
return "unknown";
}
}
};
const boost::system::error_category &nghttp2_asio_category() noexcept {
static nghttp2_asio_category_impl cat;
return cat;
}
boost::system::error_code make_error_code(nghttp2_asio_error ev) {
return boost::system::error_code(static_cast<int>(ev),
nghttp2_asio_category());
}
generator_cb string_generator(std::string data) { generator_cb string_generator(std::string data) {
auto strio = std::make_shared<std::pair<std::string, size_t>>(std::move(data), auto strio = std::make_shared<std::pair<std::string, size_t>>(std::move(data),
data.size()); data.size());
@ -65,8 +90,9 @@ generator_cb string_generator(std::string data) {
} }
generator_cb deferred_generator() { generator_cb deferred_generator() {
return [](uint8_t *buf, size_t len, return [](uint8_t *buf, size_t len, uint32_t *data_flags) {
uint32_t *data_flags) { return NGHTTP2_ERR_DEFERRED; }; return NGHTTP2_ERR_DEFERRED;
};
} }
template <typename F, typename... T> template <typename F, typename... T>
@ -89,20 +115,20 @@ generator_cb file_generator_from_fd(int fd) {
return [fd, d](uint8_t *buf, size_t len, uint32_t *data_flags) return [fd, d](uint8_t *buf, size_t len, uint32_t *data_flags)
-> generator_cb::result_type { -> generator_cb::result_type {
ssize_t n; ssize_t n;
while ((n = read(fd, buf, len)) == -1 && errno == EINTR) while ((n = read(fd, buf, len)) == -1 && errno == EINTR)
; ;
if (n == -1) { if (n == -1) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
if (n == 0) { if (n == 0) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF; *data_flags |= NGHTTP2_DATA_FLAG_EOF;
} }
return n; return n;
}; };
} }
bool check_path(const std::string &path) { return util::check_path(path); } bool check_path(const std::string &path) { return util::check_path(path); }
@ -144,5 +170,23 @@ boost::system::error_code host_service_from_uri(boost::system::error_code &ec,
return ec; return ec;
} }
bool tls_h2_negotiated(ssl_socket &socket) {
auto ssl = socket.native_handle();
const unsigned char *next_proto = nullptr;
unsigned int next_proto_len = 0;
SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
if (next_proto == nullptr) {
SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
}
if (next_proto == nullptr) {
return false;
}
return util::check_h2_is_selected(next_proto, next_proto_len);
}
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -39,6 +39,8 @@ namespace asio_http2 {
boost::system::error_code make_error_code(nghttp2_error ev); boost::system::error_code make_error_code(nghttp2_error ev);
boost::system::error_code make_error_code(nghttp2_asio_error ev);
generator_cb string_generator(std::string data); generator_cb string_generator(std::string data);
// Returns generator_cb, which just returns NGHTTP2_ERR_DEFERRED // Returns generator_cb, which just returns NGHTTP2_ERR_DEFERRED
@ -58,6 +60,12 @@ void split_path(uri_ref &dst, InputIt first, InputIt last) {
dst.raw_query.assign(query_first, last); dst.raw_query.assign(query_first, last);
} }
using boost::asio::ip::tcp;
using ssl_socket = boost::asio::ssl::stream<tcp::socket>;
bool tls_h2_negotiated(ssl_socket &socket);
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -37,6 +37,7 @@
#include "asio_server.h" #include "asio_server.h"
#include "asio_server_connection.h" #include "asio_server_connection.h"
#include "asio_common.h"
#include "util.h" #include "util.h"
namespace nghttp2 { namespace nghttp2 {
@ -122,38 +123,46 @@ void server::start_accept(boost::asio::ssl::context &tls_context,
auto new_connection = std::make_shared<connection<ssl_socket>>( auto new_connection = std::make_shared<connection<ssl_socket>>(
mux, io_service_pool_.get_io_service(), tls_context); mux, io_service_pool_.get_io_service(), tls_context);
acceptor.async_accept(new_connection->socket().lowest_layer(), acceptor.async_accept(
[this, &tls_context, &acceptor, &mux, new_connection]( new_connection->socket().lowest_layer(),
const boost::system::error_code &e) { [this, &tls_context, &acceptor, &mux, new_connection](
if (!e) { const boost::system::error_code &e) {
new_connection->socket().lowest_layer().set_option(tcp::no_delay(true)); if (!e) {
new_connection->socket().async_handshake( new_connection->socket().lowest_layer().set_option(
boost::asio::ssl::stream_base::server, tcp::no_delay(true));
[new_connection](const boost::system::error_code &e) { new_connection->socket().async_handshake(
if (!e) { boost::asio::ssl::stream_base::server,
new_connection->start(); [new_connection](const boost::system::error_code &e) {
} if (e) {
}); return;
} }
start_accept(tls_context, acceptor, mux); if (!tls_h2_negotiated(new_connection->socket())) {
}); return;
}
new_connection->start();
});
}
start_accept(tls_context, acceptor, mux);
});
} }
void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) { void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) {
auto new_connection = std::make_shared<connection<tcp::socket>>( auto new_connection = std::make_shared<connection<tcp::socket>>(
mux, io_service_pool_.get_io_service()); mux, io_service_pool_.get_io_service());
acceptor.async_accept(new_connection->socket(), acceptor.async_accept(
[this, &acceptor, &mux, new_connection]( new_connection->socket(), [this, &acceptor, &mux, new_connection](
const boost::system::error_code &e) { const boost::system::error_code &e) {
if (!e) { if (!e) {
new_connection->socket().set_option(tcp::no_delay(true)); new_connection->socket().set_option(tcp::no_delay(true));
new_connection->start(); new_connection->start();
} }
start_accept(acceptor, mux); start_accept(acceptor, mux);
}); });
} }
void server::stop() { io_service_pool_.stop(); } void server::stop() { io_service_pool_.stop(); }

View File

@ -83,29 +83,30 @@ public:
void do_read() { void do_read() {
auto self = this->shared_from_this(); auto self = this->shared_from_this();
socket_.async_read_some(boost::asio::buffer(buffer_), socket_.async_read_some(
[this, self](const boost::system::error_code &e, boost::asio::buffer(buffer_),
std::size_t bytes_transferred) { [this, self](const boost::system::error_code &e,
if (!e) { std::size_t bytes_transferred) {
if (handler_->on_read(buffer_, bytes_transferred) != 0) { if (!e) {
return; if (handler_->on_read(buffer_, bytes_transferred) != 0) {
} return;
}
do_write(); do_write();
if (!writing_ && handler_->should_stop()) { if (!writing_ && handler_->should_stop()) {
return; return;
} }
do_read(); do_read();
} }
// If an error occurs then no new asynchronous operations are // If an error occurs then no new asynchronous operations are
// started. This means that all shared_ptr references to the // started. This means that all shared_ptr references to the
// connection object will disappear and the object will be // connection object will disappear and the object will be
// destroyed automatically after this handler returns. The // destroyed automatically after this handler returns. The
// connection class's destructor closes the socket. // connection class's destructor closes the socket.
}); });
} }
void do_write() { void do_write() {

View File

@ -332,9 +332,9 @@ int http2_handler::start_response(stream &strm) {
[](nghttp2_session *session, int32_t stream_id, uint8_t *buf, [](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
size_t length, uint32_t *data_flags, nghttp2_data_source *source, size_t length, uint32_t *data_flags, nghttp2_data_source *source,
void *user_data) -> ssize_t { void *user_data) -> ssize_t {
auto &strm = *static_cast<stream *>(source->ptr); auto &strm = *static_cast<stream *>(source->ptr);
return strm.response().impl().call_read(buf, length, data_flags); return strm.response().impl().call_read(buf, length, data_flags);
}; };
prd_ptr = &prd; prd_ptr = &prd;
} }
rv = nghttp2_submit_response(session_, strm.get_stream_id(), nva.data(), rv = nghttp2_submit_response(session_, strm.get_stream_id(), nva.data(),

View File

@ -42,6 +42,19 @@ std::vector<unsigned char> &get_alpn_token() {
} }
} // namespace } // namespace
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
namespace {
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
if (!util::select_h2(out, outlen, in, inlen)) {
return SSL_TLSEXT_ERR_NOACK;
}
return SSL_TLSEXT_ERR_OK;
}
} // namespace
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
boost::system::error_code boost::system::error_code
configure_tls_context_easy(boost::system::error_code &ec, configure_tls_context_easy(boost::system::error_code &ec,
boost::asio::ssl::context &tls_context) { boost::asio::ssl::context &tls_context) {
@ -81,6 +94,11 @@ configure_tls_context_easy(boost::system::error_code &ec,
}, },
nullptr); nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// ALPN selection callback
SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, nullptr);
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
return ec; return ec;
} }

View File

@ -95,8 +95,8 @@ RequestStat::RequestStat() : data_offset(0), completed(false) {}
Stats::Stats(size_t req_todo) Stats::Stats(size_t req_todo)
: req_todo(0), req_started(0), req_done(0), req_success(0), : req_todo(0), req_started(0), req_done(0), req_success(0),
req_status_success(0), req_failed(0), req_error(0), req_timedout(0), req_status_success(0), req_failed(0), req_error(0), req_timedout(0),
bytes_total(0), bytes_head(0), bytes_body(0), status(), bytes_total(0), bytes_head(0), bytes_head_decomp(0), bytes_body(0),
req_stats(req_todo) {} status(), req_stats(req_todo) {}
Stream::Stream() : status_success(-1) {} Stream::Stream() : status_success(-1) {}
@ -1171,12 +1171,11 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
if (util::select_protocol(const_cast<const unsigned char **>(out), outlen, in, if (util::select_protocol(const_cast<const unsigned char **>(out), outlen, in,
inlen, config.npn_list)) { inlen, config.npn_list)) {
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} else if (inlen == 0) {
std::cout
<< "Server does not support NPN. Fallback behaviour may be activated."
<< std::endl;
} }
return SSL_TLSEXT_ERR_OK;
// OpenSSL will terminate handshake with fatal alert if we return
// NOACK. So there is no way to fallback.
return SSL_TLSEXT_ERR_NOACK;
} }
} // namespace } // namespace
@ -2073,6 +2072,7 @@ int main(int argc, char **argv) {
stats.req_error += s.req_error; stats.req_error += s.req_error;
stats.bytes_total += s.bytes_total; stats.bytes_total += s.bytes_total;
stats.bytes_head += s.bytes_head; stats.bytes_head += s.bytes_head;
stats.bytes_head_decomp += s.bytes_head_decomp;
stats.bytes_body += s.bytes_body; stats.bytes_body += s.bytes_body;
for (size_t i = 0; i < stats.status.size(); ++i) { for (size_t i = 0; i < stats.status.size(); ++i) {
@ -2102,7 +2102,13 @@ int main(int argc, char **argv) {
bps = stats.bytes_total / secd.count(); bps = stats.bytes_total / secd.count();
} }
std::cout << R"( double header_space_savings = 0.;
if (stats.bytes_head_decomp > 0) {
header_space_savings =
1. - static_cast<double>(stats.bytes_head) / stats.bytes_head_decomp;
}
std::cout << std::fixed << std::setprecision(2) << R"(
finished in )" << util::format_duration(duration) << ", " << rps << " req/s, " finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
<< util::utos_with_funit(bps) << R"(B/s << util::utos_with_funit(bps) << R"(B/s
requests: )" << stats.req_todo << " total, " << stats.req_started requests: )" << stats.req_todo << " total, " << stats.req_started
@ -2113,7 +2119,8 @@ requests: )" << stats.req_todo << " total, " << stats.req_started
status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, " status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
<< stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
<< " bytes headers, " << stats.bytes_body << R"( bytes data << " bytes headers (space savings " << header_space_savings * 100
<< "%), " << stats.bytes_body << R"( bytes data
min max mean sd +/- sd min max mean sd +/- sd
time for request: )" << std::setw(10) << util::format_duration(ts.request.min) time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
<< " " << std::setw(10) << util::format_duration(ts.request.max) << " " << std::setw(10) << util::format_duration(ts.request.max)

View File

@ -166,8 +166,12 @@ struct Stats {
// The number of bytes received on the "wire". If SSL/TLS is used, // The number of bytes received on the "wire". If SSL/TLS is used,
// this is the number of decrypted bytes the application received. // this is the number of decrypted bytes the application received.
int64_t bytes_total; int64_t bytes_total;
// The number of bytes received in HEADERS frame payload. // The number of bytes received for header fields. This is
// compressed version.
int64_t bytes_head; int64_t bytes_head;
// The number of bytes received for header fields after they are
// decompressed.
int64_t bytes_head_decomp;
// The number of bytes received in DATA frame. // The number of bytes received in DATA frame.
int64_t bytes_body; int64_t bytes_body;
// The number of each HTTP status category, status[i] is status code // The number of each HTTP status category, status[i] is status code

View File

@ -99,6 +99,7 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto client = session->get_client(); auto client = session->get_client();
client->worker->stats.bytes_head += len; client->worker->stats.bytes_head += len;
client->worker->stats.bytes_head_decomp += len;
return 0; return 0;
} }
} // namespace } // namespace
@ -109,6 +110,7 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto client = session->get_client(); auto client = session->get_client();
client->worker->stats.bytes_head += len; client->worker->stats.bytes_head += len;
client->worker->stats.bytes_head_decomp += len;
return 0; return 0;
} }
} // namespace } // namespace

View File

@ -51,6 +51,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0; return 0;
} }
client->on_header(frame->hd.stream_id, name, namelen, value, valuelen); client->on_header(frame->hd.stream_id, name, namelen, value, valuelen);
client->worker->stats.bytes_head_decomp += namelen + valuelen;
return 0; return 0;
} }
} // namespace } // namespace
@ -63,7 +64,9 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
return 0; return 0;
} }
client->worker->stats.bytes_head += frame->hd.length; client->worker->stats.bytes_head +=
frame->hd.length - frame->headers.padlen -
((frame->hd.flags & NGHTTP2_FLAG_PRIORITY) ? 5 : 0);
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
client->record_ttfb(); client->record_ttfb();
} }
@ -210,7 +213,7 @@ void Http2Session::on_connect() {
} }
int Http2Session::submit_request(RequestStat *req_stat) { int Http2Session::submit_request(RequestStat *req_stat) {
if (nghttp2_session_request_allowed(session_) == 0) { if (nghttp2_session_check_request_allowed(session_) == 0) {
return -1; return -1;
} }

View File

@ -65,11 +65,18 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
for (auto p = frame->syn_reply.nv; *p; p += 2) { for (auto p = frame->syn_reply.nv; *p; p += 2) {
auto name = *p; auto name = *p;
auto value = *(p + 1); auto value = *(p + 1);
auto namelen = strlen(name);
auto valuelen = strlen(value);
client->on_header(frame->syn_reply.stream_id, client->on_header(frame->syn_reply.stream_id,
reinterpret_cast<const uint8_t *>(name), strlen(name), reinterpret_cast<const uint8_t *>(name), namelen,
reinterpret_cast<const uint8_t *>(value), strlen(value)); reinterpret_cast<const uint8_t *>(value), valuelen);
client->worker->stats.bytes_head_decomp += namelen + valuelen;
} }
client->worker->stats.bytes_head += frame->syn_reply.hd.length;
// Strictly speaking, we have to subtract 2 (unused field) if SPDY
// version is 2. But it is already deprecated, and we don't do
// extra work for it.
client->worker->stats.bytes_head += frame->syn_reply.hd.length - 4;
if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) { if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) {
client->record_ttfb(); client->record_ttfb();

View File

@ -132,6 +132,108 @@ std::string get_status_string(unsigned int status_code) {
} }
} }
const char *stringify_status(unsigned int status_code) {
switch (status_code) {
case 100:
return "100";
case 101:
return "101";
case 200:
return "200";
case 201:
return "201";
case 202:
return "202";
case 203:
return "203";
case 204:
return "204";
case 205:
return "205";
case 206:
return "206";
case 300:
return "300";
case 301:
return "301";
case 302:
return "302";
case 303:
return "303";
case 304:
return "304";
case 305:
return "305";
// case 306: return "306";
case 307:
return "307";
case 308:
return "308";
case 400:
return "400";
case 401:
return "401";
case 402:
return "402";
case 403:
return "403";
case 404:
return "404";
case 405:
return "405";
case 406:
return "406";
case 407:
return "407";
case 408:
return "408";
case 409:
return "409";
case 410:
return "410";
case 411:
return "411";
case 412:
return "412";
case 413:
return "413";
case 414:
return "414";
case 415:
return "415";
case 416:
return "416";
case 417:
return "417";
case 421:
return "421";
case 426:
return "426";
case 428:
return "428";
case 429:
return "429";
case 431:
return "431";
case 500:
return "500";
case 501:
return "501";
case 502:
return "502";
case 503:
return "503";
case 504:
return "504";
case 505:
return "505";
case 511:
return "511";
default:
return nullptr;
}
}
void capitalize(DefaultMemchunks *buf, const std::string &s) { void capitalize(DefaultMemchunks *buf, const std::string &s) {
buf->append(util::upcase(s[0])); buf->append(util::upcase(s[0]));
for (size_t i = 1; i < s.size(); ++i) { for (size_t i = 1; i < s.size(); ++i) {
@ -207,17 +309,34 @@ bool non_empty_value(const Headers::value_type *nv) {
return nv && !nv->value.empty(); return nv && !nv->value.empty();
} }
nghttp2_nv make_nv(const std::string &name, const std::string &value, namespace {
bool no_index) { nghttp2_nv make_nv_internal(const std::string &name, const std::string &value,
bool no_index, uint8_t nv_flags) {
uint8_t flags; uint8_t flags;
flags = no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE; flags =
nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE);
return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
value.size(), flags}; value.size(), flags};
} }
} // namespace
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) { nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index) {
return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
}
nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool no_index) {
return make_nv_internal(name, value, no_index,
NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE);
}
namespace {
void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
const Headers &headers, uint8_t nv_flags) {
for (auto &kv : headers) { for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') { if (kv.name.empty() || kv.name[0] == ':') {
continue; continue;
@ -238,9 +357,20 @@ void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
case HD_X_FORWARDED_PROTO: case HD_X_FORWARDED_PROTO:
continue; continue;
} }
nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); nva.push_back(make_nv_internal(kv.name, kv.value, kv.no_index, nv_flags));
} }
} }
} // namespace
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE);
}
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const Headers &headers) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE);
}
void build_http1_headers_from_headers(DefaultMemchunks *buf, void build_http1_headers_from_headers(DefaultMemchunks *buf,
const Headers &headers) { const Headers &headers) {

View File

@ -70,6 +70,10 @@ namespace http2 {
std::string get_status_string(unsigned int status_code); std::string get_status_string(unsigned int status_code);
// Returns string version of |status_code|. This function can handle
// only predefined status code. Otherwise, returns nullptr.
const char *stringify_status(unsigned int status_code);
void capitalize(DefaultMemchunks *buf, const std::string &s); void capitalize(DefaultMemchunks *buf, const std::string &s);
// Returns true if |value| is LWS // Returns true if |value| is LWS
@ -110,26 +114,41 @@ bool non_empty_value(const Headers::value_type *nv);
nghttp2_nv make_nv(const std::string &name, const std::string &value, nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index = false); bool no_index = false);
nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool no_index = false);
// Create nghttp2_nv from string literal |name| and |value|. // Create nghttp2_nv from string literal |name| and |value|.
template <size_t N, size_t M> template <size_t N, size_t M>
constexpr nghttp2_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) { constexpr nghttp2_nv make_nv_ll(const char(&name)[N], const char(&value)[M]) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1, return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
NGHTTP2_NV_FLAG_NONE}; NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
} }
// Create nghttp2_nv from string literal |name| and c-string |value|. // Create nghttp2_nv from string literal |name| and c-string |value|.
template <size_t N> template <size_t N>
nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) { nghttp2_nv make_nv_lc(const char(&name)[N], const char *value) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
NGHTTP2_NV_FLAG_NONE}; NGHTTP2_NV_FLAG_NO_COPY_NAME};
}
template <size_t N>
nghttp2_nv make_nv_lc_nocopy(const char(&name)[N], const char *value) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
} }
// Create nghttp2_nv from string literal |name| and std::string // Create nghttp2_nv from string literal |name| and std::string
// |value|. // |value|.
template <size_t N> template <size_t N>
nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) { nghttp2_nv make_nv_ls(const char(&name)[N], const std::string &value) {
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
NGHTTP2_NV_FLAG_NONE}; NGHTTP2_NV_FLAG_NO_COPY_NAME};
}
template <size_t N>
nghttp2_nv make_nv_ls_nocopy(const char(&name)[N], const std::string &value) {
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
} }
// Appends headers in |headers| to |nv|. |headers| must be indexed // Appends headers in |headers| to |nv|. |headers| must be indexed
@ -138,6 +157,11 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
// which require special handling (i.e. via), are not copied. // which require special handling (i.e. via), are not copied.
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers); void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
// Just like copy_headers_to_nva(), but this adds
// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE.
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const Headers &headers);
// Appends HTTP/1.1 style header lines to |buf| from headers in // Appends HTTP/1.1 style header lines to |buf| from headers in
// |headers|. |headers| must be indexed before this call (its // |headers|. |headers| must be indexed before this call (its
// element's token field is assigned). Certain headers, which // element's token field is assigned). Certain headers, which

View File

@ -151,10 +151,26 @@ auto headers =
} // namespace } // namespace
void test_http2_copy_headers_to_nva(void) { void test_http2_copy_headers_to_nva(void) {
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
std::vector<nghttp2_nv> nva; std::vector<nghttp2_nv> nva;
http2::copy_headers_to_nva_nocopy(nva, headers);
CU_ASSERT(7 == nva.size());
for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);
if (ans[i] == 0) {
CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE |
NGHTTP2_NV_FLAG_NO_INDEX) == nva[i].flags);
} else {
CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE) == nva[i].flags);
}
}
nva.clear();
http2::copy_headers_to_nva(nva, headers); http2::copy_headers_to_nva(nva, headers);
CU_ASSERT(7 == nva.size()); CU_ASSERT(7 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
for (size_t i = 0; i < ans.size(); ++i) { for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]); check_nv(headers[ans[i]], &nva[i]);

View File

@ -38,16 +38,6 @@
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>
namespace boost {
namespace system {
template <> struct is_error_code_enum<nghttp2_error> {
BOOST_STATIC_CONSTANT(bool, value = true);
};
} // namespace system
} // namespace boost
namespace nghttp2 { namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
@ -99,8 +89,8 @@ typedef std::function<void(uint32_t)> close_cb;
// are not available right now, return NGHTTP2_ERR_DEFERRED. In case // are not available right now, return NGHTTP2_ERR_DEFERRED. In case
// of the error and request/response must be closed, return // of the error and request/response must be closed, return
// NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE. // NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE.
typedef std::function< typedef std::function<ssize_t(uint8_t *buf, std::size_t len,
ssize_t(uint8_t *buf, std::size_t len, uint32_t *data_flags)> generator_cb; uint32_t *data_flags)> generator_cb;
// Convenient function to create function to read file denoted by // Convenient function to create function to read file denoted by
// |path|. This can be passed to response::end(). // |path|. This can be passed to response::end().
@ -132,8 +122,29 @@ boost::system::error_code host_service_from_uri(boost::system::error_code &ec,
std::string &service, std::string &service,
const std::string &uri); const std::string &uri);
enum nghttp2_asio_error {
NGHTTP2_ASIO_ERR_NO_ERROR = 0,
NGHTTP2_ASIO_ERR_TLS_NO_APP_PROTO_NEGOTIATED = 1,
};
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2
namespace boost {
namespace system {
template <> struct is_error_code_enum<nghttp2_error> {
BOOST_STATIC_CONSTANT(bool, value = true);
};
template <> struct is_error_code_enum<nghttp2::asio_http2::nghttp2_asio_error> {
BOOST_STATIC_CONSTANT(bool, value = true);
};
} // namespace system
} // namespace boost
#endif // ASIO_HTTP2_H #endif // ASIO_HTTP2_H

View File

@ -161,7 +161,7 @@ template <typename Memchunk> struct Memchunks {
return count; return count;
} }
template <size_t N> size_t append(const char (&s)[N]) { template <size_t N> size_t append(const char(&s)[N]) {
return append(s, N - 1); return append(s, N - 1);
} }
size_t append(const std::string &s) { return append(s.c_str(), s.size()); } size_t append(const std::string &s) { return append(s.c_str(), s.size()); }

View File

@ -226,8 +226,7 @@ void test_peek_memchunks_append(void) {
std::array<uint8_t, 32> b{{ std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '5', '6', '7', '8', '9', '0', '1', }},
}},
d; d;
pchunks.append(b.data(), b.size()); pchunks.append(b.data(), b.size());
@ -261,8 +260,7 @@ void test_peek_memchunks_disable_peek_drain(void) {
std::array<uint8_t, 32> b{{ std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '5', '6', '7', '8', '9', '0', '1', }},
}},
d; d;
pchunks.append(b.data(), b.size()); pchunks.append(b.data(), b.size());
@ -289,8 +287,7 @@ void test_peek_memchunks_disable_peek_no_drain(void) {
std::array<uint8_t, 32> b{{ std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '5', '6', '7', '8', '9', '0', '1', }},
}},
d; d;
pchunks.append(b.data(), b.size()); pchunks.append(b.data(), b.size());
@ -317,8 +314,7 @@ void test_peek_memchunks_reset(void) {
std::array<uint8_t, 32> b{{ std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '5', '6', '7', '8', '9', '0', '1', }},
}},
d; d;
pchunks.append(b.data(), b.size()); pchunks.append(b.data(), b.size());

View File

@ -89,8 +89,7 @@ enum {
namespace { namespace {
constexpr auto anchors = std::array<Anchor, 5>{{ constexpr auto anchors = std::array<Anchor, 5>{{
{3, 0, 201}, {5, 0, 101}, {7, 0, 1}, {9, 7, 1}, {11, 3, 1}, {3, 0, 201}, {5, 0, 101}, {7, 0, 1}, {9, 7, 1}, {11, 3, 1}, }};
}};
} // namespace } // namespace
Config::Config() Config::Config()
@ -392,6 +391,11 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
} }
auto method = http2::get_header(build_headers, ":method");
assert(method);
req->method = method->value;
std::string trailer_names; std::string trailer_names;
if (!config.trailer.empty()) { if (!config.trailer.empty()) {
trailer_names = config.trailer[0].name; trailer_names = config.trailer[0].name;
@ -549,10 +553,11 @@ int HttpClient::initiate_connection() {
// If the user overrode the :authority or host header, use that // If the user overrode the :authority or host header, use that
// value for the SNI extension // value for the SNI extension
const char *host_string = nullptr; const char *host_string = nullptr;
auto i = std::find_if(std::begin(config.headers), auto i =
std::end(config.headers), [](const Header &nv) { std::find_if(std::begin(config.headers), std::end(config.headers),
return ":authority" == nv.name || "host" == nv.name; [](const Header &nv) {
}); return ":authority" == nv.name || "host" == nv.name;
});
if (i != std::end(config.headers)) { if (i != std::end(config.headers)) {
host_string = (*i).value.c_str(); host_string = (*i).value.c_str();
} else { } else {
@ -808,27 +813,42 @@ int HttpClient::on_upgrade_connect() {
base64::encode(std::begin(settings_payload), base64::encode(std::begin(settings_payload),
std::begin(settings_payload) + settings_payloadlen); std::begin(settings_payload) + settings_payloadlen);
util::to_token68(token68); util::to_token68(token68);
std::string req; std::string req;
if (reqvec[0]->data_prd) { if (reqvec[0]->data_prd) {
// If the request contains upload data, use OPTIONS * to upgrade // If the request contains upload data, use OPTIONS * to upgrade
req = "OPTIONS *"; req = "OPTIONS *";
} else { } else {
req = "GET "; auto meth = std::find_if(
std::begin(config.headers), std::end(config.headers),
[](const Header &kv) { return util::streq_l(":method", kv.name); });
if (meth == std::end(config.headers)) {
req = "GET ";
reqvec[0]->method = "GET";
} else {
req = (*meth).value;
req += " ";
reqvec[0]->method = (*meth).value;
}
req += reqvec[0]->make_reqpath(); req += reqvec[0]->make_reqpath();
} }
auto headers = Headers{{"Host", hostport}, auto headers = Headers{{"host", hostport},
{"Connection", "Upgrade, HTTP2-Settings"}, {"connection", "Upgrade, HTTP2-Settings"},
{"Upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID}, {"upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID},
{"HTTP2-Settings", token68}, {"http2-settings", token68},
{"Accept", "*/*"}, {"accept", "*/*"},
{"User-Agent", "nghttp2/" NGHTTP2_VERSION}}; {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
auto initial_headerslen = headers.size(); auto initial_headerslen = headers.size();
for (auto &kv : config.headers) { for (auto &kv : config.headers) {
size_t i; size_t i;
if (kv.name.empty() || kv.name[0] == ':') {
continue;
}
for (i = 0; i < initial_headerslen; ++i) { for (i = 0; i < initial_headerslen; ++i) {
if (util::strieq(kv.name, headers[i].name)) { if (kv.name == headers[i].name) {
headers[i].value = kv.value; headers[i].value = kv.value;
break; break;
} }
@ -836,9 +856,7 @@ int HttpClient::on_upgrade_connect() {
if (i < initial_headerslen) { if (i < initial_headerslen) {
continue; continue;
} }
if (kv.name.size() != 0 && kv.name[0] != ':') { headers.emplace_back(kv.name, kv.value, kv.no_index);
headers.emplace_back(kv.name, kv.value, kv.no_index);
}
} }
req += " HTTP/1.1\r\n"; req += " HTTP/1.1\r\n";
@ -858,9 +876,10 @@ int HttpClient::on_upgrade_connect() {
std::cout << " HTTP Upgrade request\n" << req << std::endl; std::cout << " HTTP Upgrade request\n" << req << std::endl;
} }
// record request time if this is GET request
if (!reqvec[0]->data_prd) { if (!reqvec[0]->data_prd) {
// record request time if this is a part of real request.
reqvec[0]->record_request_start_time(); reqvec[0]->record_request_start_time();
reqvec[0]->req_nva = std::move(headers);
} }
on_writefn = &HttpClient::noop; on_writefn = &HttpClient::noop;
@ -979,8 +998,12 @@ int HttpClient::connection_made() {
if (!reqvec[0]->data_prd) { if (!reqvec[0]->data_prd) {
stream_user_data = reqvec[0].get(); stream_user_data = reqvec[0].get();
} }
rv = nghttp2_session_upgrade(session, settings_payload.data(), // If HEAD is used, that is only when user specified it with -H
settings_payloadlen, stream_user_data); // option.
auto head_request = stream_user_data && stream_user_data->method == "HEAD";
rv = nghttp2_session_upgrade2(session, settings_payload.data(),
settings_payloadlen, head_request,
stream_user_data);
if (rv != 0) { if (rv != 0) {
std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: " std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
<< nghttp2_strerror(rv) << std::endl; << nghttp2_strerror(rv) << std::endl;
@ -1378,13 +1401,6 @@ void HttpClient::output_har(FILE *outfile) {
auto request = json_object(); auto request = json_object();
json_object_set_new(entry, "request", request); json_object_set_new(entry, "request", request);
auto method_ptr = http2::get_header(req->req_nva, ":method");
const char *method = "GET";
if (method_ptr) {
method = (*method_ptr).value.c_str();
}
auto req_headers = json_array(); auto req_headers = json_array();
json_object_set_new(request, "headers", req_headers); json_object_set_new(request, "headers", req_headers);
@ -1396,7 +1412,7 @@ void HttpClient::output_har(FILE *outfile) {
json_object_set_new(hd, "value", json_string(nv.value.c_str())); json_object_set_new(hd, "value", json_string(nv.value.c_str()));
} }
json_object_set_new(request, "method", json_string(method)); json_object_set_new(request, "method", json_string(req->method.c_str()));
json_object_set_new(request, "url", json_string(req->uri.c_str())); json_object_set_new(request, "url", json_string(req->uri.c_str()));
json_object_set_new(request, "httpVersion", json_string("HTTP/2.0")); json_object_set_new(request, "httpVersion", json_string("HTTP/2.0"));
json_object_set_new(request, "cookies", json_array()); json_object_set_new(request, "cookies", json_array());
@ -1917,12 +1933,12 @@ void print_stats(const HttpClient &client) {
std::sort(std::begin(reqs), std::end(reqs), std::sort(std::begin(reqs), std::end(reqs),
[](const Request *lhs, const Request *rhs) { [](const Request *lhs, const Request *rhs) {
const auto &ltiming = lhs->timing; const auto &ltiming = lhs->timing;
const auto &rtiming = rhs->timing; const auto &rtiming = rhs->timing;
return ltiming.response_end_time < rtiming.response_end_time || return ltiming.response_end_time < rtiming.response_end_time ||
(ltiming.response_end_time == rtiming.response_end_time && (ltiming.response_end_time == rtiming.response_end_time &&
ltiming.request_start_time < rtiming.request_start_time); ltiming.request_start_time < rtiming.request_start_time);
}); });
std::cout << R"( std::cout << R"(
Request timing: Request timing:
@ -2011,7 +2027,8 @@ namespace {
int communicate( int communicate(
const std::string &scheme, const std::string &host, uint16_t port, const std::string &scheme, const std::string &host, uint16_t port,
std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>> std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
requests, const nghttp2_session_callbacks *callbacks) { requests,
const nghttp2_session_callbacks *callbacks) {
int result = 0; int result = 0;
auto loop = EV_DEFAULT; auto loop = EV_DEFAULT;
SSL_CTX *ssl_ctx = nullptr; SSL_CTX *ssl_ctx = nullptr;
@ -2382,7 +2399,7 @@ Options:
Discard downloaded data. Discard downloaded data.
-O, --remote-name -O, --remote-name
Save download data in the current directory. The Save download data in the current directory. The
filename is dereived from URI. If URI ends with '/', filename is derived from URI. If URI ends with '/',
'index.html' is used as a filename. Not implemented 'index.html' is used as a filename. Not implemented
yet. yet.
-t, --timeout=<DURATION> -t, --timeout=<DURATION>

View File

@ -137,6 +137,7 @@ struct Request {
Headers res_nva; Headers res_nva;
Headers req_nva; Headers req_nva;
std::string method;
// URI without fragment // URI without fragment
std::string uri; std::string uri;
http_parser_url u; http_parser_url u;

View File

@ -1213,7 +1213,7 @@ Performance:
Set maximum number of backend concurrent HTTP/1 Set maximum number of backend concurrent HTTP/1
connections per origin host. This option is meaningful connections per origin host. This option is meaningful
when -s option is used. The origin host is determined when -s option is used. The origin host is determined
by authority portion of requset URI (or :authority by authority portion of request URI (or :authority
header field for HTTP/2). To limit the number of header field for HTTP/2). To limit the number of
connections per frontend for default mode, use connections per frontend for default mode, use
--backend-http1-connections-per-frontend. --backend-http1-connections-per-frontend.

View File

@ -288,7 +288,12 @@ std::pair<std::string, std::string> parse_header(const char *optarg) {
for (; *value == '\t' || *value == ' '; ++value) for (; *value == '\t' || *value == ' '; ++value)
; ;
return {std::string(optarg, colon), std::string(value, strlen(value))}; auto p = std::make_pair(std::string(optarg, colon),
std::string(value, strlen(value)));
util::inp_strlower(p.first);
util::inp_strlower(p.second);
return p;
} }
template <typename T> template <typename T>

View File

@ -175,12 +175,12 @@ int ConnectionHandler::create_single_worker() {
, ,
nb_.get() nb_.get()
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );
auto cl_ssl_ctx = ssl::setup_client_ssl_context( auto cl_ssl_ctx = ssl::setup_client_ssl_context(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
nb_.get() nb_.get()
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );
if (cl_ssl_ctx) { if (cl_ssl_ctx) {
all_ssl_ctx_.push_back(cl_ssl_ctx); all_ssl_ctx_.push_back(cl_ssl_ctx);
@ -207,12 +207,12 @@ int ConnectionHandler::create_worker_thread(size_t num) {
, ,
nb_.get() nb_.get()
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );
auto cl_ssl_ctx = ssl::setup_client_ssl_context( auto cl_ssl_ctx = ssl::setup_client_ssl_context(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
nb_.get() nb_.get()
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );
if (cl_ssl_ctx) { if (cl_ssl_ctx) {
all_ssl_ctx_.push_back(cl_ssl_ctx); all_ssl_ctx_.push_back(cl_ssl_ctx);
@ -401,8 +401,8 @@ void ConnectionHandler::accept_pending_connection() {
} }
} }
void void ConnectionHandler::set_ticket_keys(
ConnectionHandler::set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys) { std::shared_ptr<TicketKeys> ticket_keys) {
ticket_keys_ = std::move(ticket_keys); ticket_keys_ = std::move(ticket_keys);
if (single_worker_) { if (single_worker_) {
single_worker_->set_ticket_keys(ticket_keys_); single_worker_->set_ticket_keys(ticket_keys_);
@ -740,8 +740,8 @@ void ConnectionHandler::on_tls_ticket_key_get_success(
set_ticket_keys_to_worker(ticket_keys); set_ticket_keys_to_worker(ticket_keys);
} }
void void ConnectionHandler::schedule_next_tls_ticket_key_memcached_get(
ConnectionHandler::schedule_next_tls_ticket_key_memcached_get(ev_timer *w) { ev_timer *w) {
ev_timer_set(w, get_config()->tls_ticket_key_memcached_interval, 0.); ev_timer_set(w, get_config()->tls_ticket_key_memcached_interval, 0.);
ev_timer_start(loop_, w); ev_timer_start(loop_, w);
} }

View File

@ -150,6 +150,9 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
http2::init_hdidx(request_hdidx_); http2::init_hdidx(request_hdidx_);
http2::init_hdidx(response_hdidx_); http2::init_hdidx(response_hdidx_);
request_headers_.reserve(16);
response_headers_.reserve(32);
} }
Downstream::~Downstream() { Downstream::~Downstream() {
@ -277,8 +280,33 @@ void Downstream::assemble_request_cookie() {
} }
} }
Headers Downstream::crumble_request_cookie() { size_t Downstream::count_crumble_request_cookie() {
Headers cookie_hdrs; size_t n = 0;
for (auto &kv : request_headers_) {
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq_l("cooki", kv.name.c_str(), 5)) {
continue;
}
size_t last = kv.value.size();
for (size_t j = 0; j < last;) {
j = kv.value.find_first_not_of("\t ;", j);
if (j == std::string::npos) {
break;
}
j = kv.value.find(';', j);
if (j == std::string::npos) {
j = last;
}
++n;
}
}
return n;
}
void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
for (auto &kv : request_headers_) { for (auto &kv : request_headers_) {
if (kv.name.size() != 6 || kv.name[5] != 'e' || if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq_l("cooki", kv.name.c_str(), 5)) { !util::streq_l("cooki", kv.name.c_str(), 5)) {
@ -298,11 +326,13 @@ Headers Downstream::crumble_request_cookie() {
j = last; j = last;
} }
cookie_hdrs.push_back( nva.push_back({(uint8_t *)"cookie", (uint8_t *)kv.value.c_str() + first,
Header("cookie", kv.value.substr(first, j - first), kv.no_index)); str_size("cookie"), j - first,
(uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE |
(kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))});
} }
} }
return cookie_hdrs;
} }
const std::string &Downstream::get_assembled_request_cookie() const { const std::string &Downstream::get_assembled_request_cookie() const {

View File

@ -97,9 +97,11 @@ public:
// downstream request API // downstream request API
const Headers &get_request_headers() const; const Headers &get_request_headers() const;
Headers &get_request_headers(); Headers &get_request_headers();
// Crumbles (split cookie by ";") in request_headers_ and returns // Count number of crumbled cookies
// them. Headers::no_index is inherited. size_t count_crumble_request_cookie();
Headers crumble_request_cookie(); // Crumbles (split cookie by ";") in request_headers_ and adds them
// in |nva|. Headers::no_index is inherited.
void crumble_request_cookie(std::vector<nghttp2_nv> &nva);
void assemble_request_cookie(); void assemble_request_cookie();
const std::string &get_assembled_request_cookie() const; const std::string &get_assembled_request_cookie() const;
// Lower the request header field names and indexes request headers. // Lower the request header field names and indexes request headers.

View File

@ -108,13 +108,29 @@ void test_downstream_crumble_request_cookie(void) {
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1); reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
d.add_request_header("cookie", ";delta"); d.add_request_header("cookie", ";delta");
d.add_request_header("cookie", "echo"); d.add_request_header("cookie", "echo");
auto cookies = d.crumble_request_cookie();
std::vector<nghttp2_nv> nva;
d.crumble_request_cookie(nva);
auto num_cookies = d.count_crumble_request_cookie();
CU_ASSERT(5 == nva.size());
CU_ASSERT(5 == num_cookies);
Headers cookies;
std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies),
[](const nghttp2_nv &nv) {
return Header(std::string(nv.name, nv.name + nv.namelen),
std::string(nv.value, nv.value + nv.valuelen),
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
});
Headers ans = {{"cookie", "alpha"}, Headers ans = {{"cookie", "alpha"},
{"cookie", "bravo"}, {"cookie", "bravo"},
{"cookie", "charlie"}, {"cookie", "charlie"},
{"cookie", "delta"}, {"cookie", "delta"},
{"cookie", "echo"}}; {"cookie", "echo"}};
CU_ASSERT(ans == cookies); CU_ASSERT(ans == cookies);
CU_ASSERT(cookies[0].no_index); CU_ASSERT(cookies[0].no_index);
CU_ASSERT(cookies[1].no_index); CU_ASSERT(cookies[1].no_index);

View File

@ -205,6 +205,8 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
if (!trailers.empty()) { if (!trailers.empty()) {
std::vector<nghttp2_nv> nva; std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size()); nva.reserve(trailers.size());
// We cannot use nocopy version, since nva may be touched after
// Downstream object is deleted.
http2::copy_headers_to_nva(nva, trailers); http2::copy_headers_to_nva(nva, trailers);
if (!nva.empty()) { if (!nva.empty()) {
rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
@ -273,17 +275,13 @@ int Http2DownstreamConnection::push_request_headers() {
authority = req_authority.c_str(); authority = req_authority.c_str();
} }
if (!authority) {
authority = downstream_hostport;
}
downstream_->set_request_downstream_host(authority); downstream_->set_request_downstream_host(authority);
auto nheader = downstream_->get_request_headers().size(); auto nheader = downstream_->get_request_headers().size();
Headers cookies; size_t num_cookies = 0;
if (!get_config()->http2_no_cookie_crumbling) { if (!get_config()->http2_no_cookie_crumbling) {
cookies = downstream_->crumble_request_cookie(); num_cookies = downstream_->count_crumble_request_cookie();
} }
// 8 means: // 8 means:
@ -296,29 +294,30 @@ int Http2DownstreamConnection::push_request_headers() {
// 7. x-forwarded-proto (optional) // 7. x-forwarded-proto (optional)
// 8. te (optional) // 8. te (optional)
auto nva = std::vector<nghttp2_nv>(); auto nva = std::vector<nghttp2_nv>();
nva.reserve(nheader + 8 + cookies.size() + nva.reserve(nheader + 8 + num_cookies +
get_config()->add_request_headers.size()); get_config()->add_request_headers.size());
nva.push_back(http2::make_nv_lc(":method", http2::to_method_string(method))); nva.push_back(
http2::make_nv_lc_nocopy(":method", http2::to_method_string(method)));
auto &scheme = downstream_->get_request_http2_scheme(); auto &scheme = downstream_->get_request_http2_scheme();
nva.push_back(http2::make_nv_lc(":authority", authority)); nva.push_back(http2::make_nv_lc_nocopy(":authority", authority));
if (method != HTTP_CONNECT) { if (method != HTTP_CONNECT) {
assert(!scheme.empty()); assert(!scheme.empty());
nva.push_back(http2::make_nv_ls(":scheme", scheme)); nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
auto &path = downstream_->get_request_path(); auto &path = downstream_->get_request_path();
if (method == HTTP_OPTIONS && path.empty()) { if (method == HTTP_OPTIONS && path.empty()) {
nva.push_back(http2::make_nv_ll(":path", "*")); nva.push_back(http2::make_nv_ll(":path", "*"));
} else { } else {
nva.push_back(http2::make_nv_ls(":path", path)); nva.push_back(http2::make_nv_ls_nocopy(":path", path));
} }
} }
http2::copy_headers_to_nva(nva, downstream_->get_request_headers()); http2::copy_headers_to_nva_nocopy(nva, downstream_->get_request_headers());
bool chunked_encoding = false; bool chunked_encoding = false;
auto transfer_encoding = auto transfer_encoding =
@ -328,8 +327,8 @@ int Http2DownstreamConnection::push_request_headers() {
chunked_encoding = true; chunked_encoding = true;
} }
for (auto &nv : cookies) { if (!get_config()->http2_no_cookie_crumbling) {
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index)); downstream_->crumble_request_cookie(nva);
} }
std::string xff_value; std::string xff_value;
@ -343,20 +342,20 @@ int Http2DownstreamConnection::push_request_headers() {
downstream_->get_upstream()->get_client_handler()->get_ipaddr(); downstream_->get_upstream()->get_client_handler()->get_ipaddr();
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value)); nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) { } else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value)); nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value));
} }
if (!get_config()->http2_proxy && !get_config()->client_proxy && if (!get_config()->http2_proxy && !get_config()->client_proxy &&
downstream_->get_request_method() != HTTP_CONNECT) { downstream_->get_request_method() != HTTP_CONNECT) {
// We use same protocol with :scheme header field // We use same protocol with :scheme header field
nva.push_back(http2::make_nv_ls("x-forwarded-proto", scheme)); nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", scheme));
} }
std::string via_value; std::string via_value;
auto via = downstream_->get_request_header(http2::HD_VIA); auto via = downstream_->get_request_header(http2::HD_VIA);
if (get_config()->no_via) { if (get_config()->no_via) {
if (via) { if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value)); nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
} }
} else { } else {
if (via) { if (via) {
@ -377,7 +376,7 @@ int Http2DownstreamConnection::push_request_headers() {
} }
for (auto &p : get_config()->add_request_headers) { for (auto &p : get_config()->add_request_headers) {
nva.push_back(http2::make_nv(p.first, p.second)); nva.push_back(http2::make_nv_nocopy(p.first, p.second));
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {

View File

@ -553,8 +553,8 @@ void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) {
dconns_.append(dconn); dconns_.append(dconn);
} }
void void Http2Session::remove_downstream_connection(
Http2Session::remove_downstream_connection(Http2DownstreamConnection *dconn) { Http2DownstreamConnection *dconn) {
dconns_.remove(dconn); dconns_.remove(dconn);
dconn->detach_stream_data(); dconn->detach_stream_data();
} }
@ -1569,13 +1569,6 @@ int Http2Session::connected() {
conn_.rlimit.startw(); conn_.rlimit.startw();
if (conn_.tls.ssl) {
read_ = &Http2Session::tls_handshake;
write_ = &Http2Session::tls_handshake;
return do_write();
}
read_ = &Http2Session::read_clear; read_ = &Http2Session::read_clear;
write_ = &Http2Session::write_clear; write_ = &Http2Session::write_clear;
@ -1583,6 +1576,13 @@ int Http2Session::connected() {
return do_write(); return do_write();
} }
if (conn_.tls.ssl) {
read_ = &Http2Session::tls_handshake;
write_ = &Http2Session::tls_handshake;
return do_write();
}
if (connection_made() != 0) { if (connection_made() != 0) {
state_ = CONNECT_FAILING; state_ = CONNECT_FAILING;
return -1; return -1;

View File

@ -108,9 +108,10 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
auto settings_payload = auto settings_payload =
base64::decode(std::begin(http2_settings), std::end(http2_settings)); base64::decode(std::begin(http2_settings), std::end(http2_settings));
rv = nghttp2_session_upgrade( rv = nghttp2_session_upgrade2(
session_, reinterpret_cast<const uint8_t *>(settings_payload.c_str()), session_, reinterpret_cast<const uint8_t *>(settings_payload.c_str()),
settings_payload.size(), nullptr); settings_payload.size(),
http->get_downstream()->get_request_method() == HTTP_HEAD, nullptr);
if (rv != 0) { if (rv != 0) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "nghttp2_session_upgrade() returned error: " ULOG(INFO, this) << "nghttp2_session_upgrade() returned error: "
@ -1259,7 +1260,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
if (!trailers.empty()) { if (!trailers.empty()) {
std::vector<nghttp2_nv> nva; std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size()); nva.reserve(trailers.size());
http2::copy_headers_to_nva(nva, trailers); http2::copy_headers_to_nva_nocopy(nva, trailers);
if (!nva.empty()) { if (!nva.empty()) {
rv = nghttp2_submit_trailer(session, stream_id, nva.data(), rv = nghttp2_submit_trailer(session, stream_id, nva.data(),
nva.size()); nva.size());
@ -1295,13 +1296,20 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
data_prd_ptr = &data_prd; data_prd_ptr = &data_prd;
} }
auto status_code_str = util::utos(downstream->get_response_http_status());
auto &headers = downstream->get_response_headers(); auto &headers = downstream->get_response_headers();
auto nva = std::vector<nghttp2_nv>(); auto nva = std::vector<nghttp2_nv>();
// 2 for :status and server // 2 for :status and server
nva.reserve(2 + headers.size()); nva.reserve(2 + headers.size());
nva.push_back(http2::make_nv_ls(":status", status_code_str)); std::string status_code_str;
auto response_status_const =
http2::stringify_status(downstream->get_response_http_status());
if (response_status_const) {
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const));
} else {
status_code_str = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", status_code_str));
}
for (auto &kv : headers) { for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') { if (kv.name.empty() || kv.name[0] == ':') {
@ -1316,11 +1324,12 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
case http2::HD_UPGRADE: case http2::HD_UPGRADE:
continue; continue;
} }
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
} }
if (!downstream->get_response_header(http2::HD_SERVER)) { if (!downstream->get_response_header(http2::HD_SERVER)) {
nva.push_back(http2::make_nv_lc("server", get_config()->server_name)); nva.push_back(
http2::make_nv_lc_nocopy("server", get_config()->server_name));
} }
rv = nghttp2_submit_response(session_, downstream->get_stream_id(), rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
@ -1356,14 +1365,20 @@ int Http2Upstream::error_reply(Downstream *downstream,
auto lgconf = log_config(); auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now()); lgconf->update_tstamp(std::chrono::system_clock::now());
auto response_status_const = http2::stringify_status(status_code);
auto content_length = util::utos(html.size()); auto content_length = util::utos(html.size());
auto status_code_str = util::utos(status_code);
auto nva = std::string status_code_str;
make_array(http2::make_nv_ls(":status", status_code_str),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"), auto nva = make_array(
http2::make_nv_lc("server", get_config()->server_name), response_status_const
http2::make_nv_ls("content-length", content_length), ? http2::make_nv_lc_nocopy(":status", response_status_const)
http2::make_nv_ls("date", lgconf->time_http_str)); : http2::make_nv_ls(":status",
(status_code_str = util::utos(status_code))),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
http2::make_nv_lc_nocopy("server", get_config()->server_name),
http2::make_nv_ls("content-length", content_length),
http2::make_nv_ls("date", lgconf->time_http_str));
rv = nghttp2_submit_response(session_, downstream->get_stream_id(), rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
nva.data(), nva.size(), &data_prd); nva.data(), nva.size(), &data_prd);
@ -1376,8 +1391,8 @@ int Http2Upstream::error_reply(Downstream *downstream,
return 0; return 0;
} }
void void Http2Upstream::add_pending_downstream(
Http2Upstream::add_pending_downstream(std::unique_ptr<Downstream> downstream) { std::unique_ptr<Downstream> downstream) {
downstream_queue_.add_pending(std::move(downstream)); downstream_queue_.add_pending(std::move(downstream));
} }
@ -1445,12 +1460,20 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
// field. // field.
nva.reserve(nheader + 4 + get_config()->add_response_headers.size()); nva.reserve(nheader + 4 + get_config()->add_response_headers.size());
std::string via_value; std::string via_value;
auto response_status = util::utos(downstream->get_response_http_status()); std::string response_status;
nva.push_back(http2::make_nv_ls(":status", response_status));
http2::copy_headers_to_nva(nva, downstream->get_response_headers()); auto response_status_const =
http2::stringify_status(downstream->get_response_http_status());
if (response_status_const) {
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const));
} else {
response_status = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", response_status));
}
if (downstream->get_non_final_response()) { if (downstream->get_non_final_response()) {
http2::copy_headers_to_nva(nva, downstream->get_response_headers());
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
log_response_headers(downstream, nva); log_response_headers(downstream, nva);
} }
@ -1469,19 +1492,22 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
return 0; return 0;
} }
http2::copy_headers_to_nva_nocopy(nva, downstream->get_response_headers());
if (!get_config()->http2_proxy && !get_config()->client_proxy) { if (!get_config()->http2_proxy && !get_config()->client_proxy) {
nva.push_back(http2::make_nv_lc("server", get_config()->server_name)); nva.push_back(
http2::make_nv_lc_nocopy("server", get_config()->server_name));
} else { } else {
auto server = downstream->get_response_header(http2::HD_SERVER); auto server = downstream->get_response_header(http2::HD_SERVER);
if (server) { if (server) {
nva.push_back(http2::make_nv_ls("server", (*server).value)); nva.push_back(http2::make_nv_ls_nocopy("server", (*server).value));
} }
} }
auto via = downstream->get_response_header(http2::HD_VIA); auto via = downstream->get_response_header(http2::HD_VIA);
if (get_config()->no_via) { if (get_config()->no_via) {
if (via) { if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value)); nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
} }
} else { } else {
if (via) { if (via) {
@ -1494,7 +1520,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
} }
for (auto &p : get_config()->add_response_headers) { for (auto &p : get_config()->add_response_headers) {
nva.push_back(http2::make_nv(p.first, p.second)); nva.push_back(http2::make_nv_nocopy(p.first, p.second));
} }
if (downstream->get_stream_id() % 2 == 0) { if (downstream->get_stream_id() % 2 == 0) {
@ -1646,9 +1672,8 @@ int Http2Upstream::consume(int32_t stream_id, size_t len) {
return 0; return 0;
} }
void void Http2Upstream::log_response_headers(
Http2Upstream::log_response_headers(Downstream *downstream, Downstream *downstream, const std::vector<nghttp2_nv> &nva) const {
const std::vector<nghttp2_nv> &nva) const {
std::stringstream ss; std::stringstream ss;
for (auto &nv : nva) { for (auto &nv : nva) {
ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n"; ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
@ -1797,7 +1822,7 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
case http2::HD_CACHE_CONTROL: case http2::HD_CACHE_CONTROL:
case http2::HD_HOST: case http2::HD_HOST:
case http2::HD_USER_AGENT: case http2::HD_USER_AGENT:
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
break; break;
} }
} }

View File

@ -184,7 +184,7 @@ std::pair<OutputIterator, size_t> copy(const std::string &src, size_t avail,
namespace { namespace {
template <size_t N, typename OutputIterator> template <size_t N, typename OutputIterator>
std::pair<OutputIterator, size_t> copy_l(const char (&src)[N], size_t avail, std::pair<OutputIterator, size_t> copy_l(const char(&src)[N], size_t avail,
OutputIterator oitr) { OutputIterator oitr) {
return copy(src, N - 1, avail, oitr); return copy(src, N - 1, avail, oitr);
} }

View File

@ -52,8 +52,8 @@ static LogConfig *config = new LogConfig();
LogConfig *log_config(void) { return config; } LogConfig *log_config(void) { return config; }
#endif // NOTHREADS #endif // NOTHREADS
void void LogConfig::update_tstamp(
LogConfig::update_tstamp(const std::chrono::system_clock::time_point &now) { const std::chrono::system_clock::time_point &now) {
auto t0 = std::chrono::system_clock::to_time_t(time_str_updated_); auto t0 = std::chrono::system_clock::to_time_t(time_str_updated_);
auto t1 = std::chrono::system_clock::to_time_t(now); auto t1 = std::chrono::system_clock::to_time_t(now);
if (t0 == t1) { if (t0 == t1) {

View File

@ -888,13 +888,11 @@ int SpdyUpstream::error_reply(Downstream *downstream,
std::string content_length = util::utos(html.size()); std::string content_length = util::utos(html.size());
std::string status_string = http2::get_status_string(status_code); std::string status_string = http2::get_status_string(status_code);
const char *nv[] = {":status", status_string.c_str(), const char *nv[] = {":status", status_string.c_str(), ":version", "http/1.1",
":version", "http/1.1", "content-type", "text/html; charset=UTF-8", "server",
"content-type", "text/html; charset=UTF-8", get_config()->server_name, "content-length",
"server", get_config()->server_name, content_length.c_str(), "date",
"content-length", content_length.c_str(), lgconf->time_http_str.c_str(), nullptr};
"date", lgconf->time_http_str.c_str(),
nullptr};
rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv, rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv,
&data_prd); &data_prd);

View File

@ -930,7 +930,10 @@ int check_cert(SSL *ssl, const DownstreamAddr *addr) {
std::vector<std::string> dns_names; std::vector<std::string> dns_names;
std::vector<std::string> ip_addrs; std::vector<std::string> ip_addrs;
get_altnames(cert, dns_names, ip_addrs, common_name); get_altnames(cert, dns_names, ip_addrs, common_name);
if (verify_hostname(addr->host.get(), &addr->addr, dns_names, ip_addrs, auto hostname = get_config()->backend_tls_sni_name
? get_config()->backend_tls_sni_name.get()
: addr->host.get();
if (verify_hostname(hostname, &addr->addr, dns_names, ip_addrs,
common_name) != 0) { common_name) != 0) {
LOG(ERROR) << "Certificate verification failed: hostname does not match"; LOG(ERROR) << "Certificate verification failed: hostname does not match";
return -1; return -1;
@ -1142,7 +1145,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
auto ssl_ctx = ssl::create_ssl_context(get_config()->private_key_file.get(), auto ssl_ctx = ssl::create_ssl_context(get_config()->private_key_file.get(),
get_config()->cert_file.get() get_config()->cert_file.get()
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
, ,
nb nb
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );
@ -1163,7 +1166,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
auto ssl_ctx = auto ssl_ctx =
ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str() ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str()
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
, ,
nb nb
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );

View File

@ -43,13 +43,12 @@ void test_shrpx_ssl_create_lookup_tree(void) {
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method())}; SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method())};
const char *hostnames[] = {"example.com", "www.example.org", const char *hostnames[] = {
"*www.example.org", "x*.host.domain", "example.com", "www.example.org", "*www.example.org", "x*.host.domain",
"*yy.host.domain", "nghttp2.sourceforge.net", "*yy.host.domain", "nghttp2.sourceforge.net", "sourceforge.net",
"sourceforge.net", "sourceforge.net", // duplicate
"sourceforge.net", // duplicate "*.foo.bar", // oo.bar is suffix of *.foo.bar
"*.foo.bar", // oo.bar is suffix of *.foo.bar "oo.bar"};
"oo.bar"};
int num = array_size(ctxs); int num = array_size(ctxs);
for (int i = 0; i < num; ++i) { for (int i = 0; i < num; ++i) {
tree->add_cert(ctxs[i], hostnames[i], strlen(hostnames[i])); tree->add_cert(ctxs[i], hostnames[i], strlen(hostnames[i]));

View File

@ -60,11 +60,11 @@ make_array(T &&... t) {
sizeof...(T)>{{std::forward<T>(t)...}}; sizeof...(T)>{{std::forward<T>(t)...}};
} }
template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) { template <typename T, size_t N> constexpr size_t array_size(T(&)[N]) {
return N; return N;
} }
template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) { template <typename T, size_t N> constexpr size_t str_size(T(&)[N]) {
return N - 1; return N - 1;
} }
@ -76,8 +76,8 @@ template <typename F, typename... T> struct Defer {
Defer(Defer &&o) : f(std::move(o.f)) {} Defer(Defer &&o) : f(std::move(o.f)) {}
~Defer() { f(); } ~Defer() { f(); }
using ResultType = typename std::result_of< using ResultType = typename std::result_of<typename std::decay<F>::type(
typename std::decay<F>::type(typename std::decay<T>::type...)>::type; typename std::decay<T>::type...)>::type;
std::function<ResultType()> f; std::function<ResultType()> f;
}; };

View File

@ -433,15 +433,15 @@ std::string format_hex(const unsigned char *s, size_t len) {
void to_token68(std::string &base64str) { void to_token68(std::string &base64str) {
std::transform(std::begin(base64str), std::end(base64str), std::transform(std::begin(base64str), std::end(base64str),
std::begin(base64str), [](char c) { std::begin(base64str), [](char c) {
switch (c) { switch (c) {
case '+': case '+':
return '-'; return '-';
case '/': case '/':
return '_'; return '_';
default: default:
return c; return c;
} }
}); });
base64str.erase(std::find(std::begin(base64str), std::end(base64str), '='), base64str.erase(std::find(std::begin(base64str), std::end(base64str), '='),
std::end(base64str)); std::end(base64str));
} }
@ -449,15 +449,15 @@ void to_token68(std::string &base64str) {
void to_base64(std::string &token68str) { void to_base64(std::string &token68str) {
std::transform(std::begin(token68str), std::end(token68str), std::transform(std::begin(token68str), std::end(token68str),
std::begin(token68str), [](char c) { std::begin(token68str), [](char c) {
switch (c) { switch (c) {
case '-': case '-':
return '+'; return '+';
case '_': case '_':
return '/'; return '/';
default: default:
return c; return c;
} }
}); });
if (token68str.size() & 0x3) { if (token68str.size() & 0x3) {
token68str.append(4 - (token68str.size() & 0x3), '='); token68str.append(4 - (token68str.size() & 0x3), '=');
} }

View File

@ -220,7 +220,7 @@ std::string quote_string(const std::string &target);
std::string format_hex(const unsigned char *s, size_t len); std::string format_hex(const unsigned char *s, size_t len);
template <size_t N> std::string format_hex(const unsigned char (&s)[N]) { template <size_t N> std::string format_hex(const unsigned char(&s)[N]) {
return format_hex(s, N); return format_hex(s, N);
} }
@ -366,11 +366,11 @@ inline bool strieq(const char *a, const std::string &b) {
} }
template <typename InputIt, size_t N> template <typename InputIt, size_t N>
bool strieq_l(const char (&a)[N], InputIt b, size_t blen) { bool strieq_l(const char(&a)[N], InputIt b, size_t blen) {
return strieq(a, N - 1, b, blen); return strieq(a, N - 1, b, blen);
} }
template <size_t N> bool strieq_l(const char (&a)[N], const std::string &b) { template <size_t N> bool strieq_l(const char(&a)[N], const std::string &b) {
return strieq(a, N - 1, std::begin(b), b.size()); return strieq(a, N - 1, std::begin(b), b.size());
} }
@ -400,10 +400,14 @@ inline bool streq(const char *a, const char *b) {
} }
template <typename InputIt, size_t N> template <typename InputIt, size_t N>
bool streq_l(const char (&a)[N], InputIt b, size_t blen) { bool streq_l(const char(&a)[N], InputIt b, size_t blen) {
return streq(a, N - 1, b, blen); return streq(a, N - 1, b, blen);
} }
template <size_t N> bool streq_l(const char(&a)[N], const std::string &b) {
return streq(a, N - 1, std::begin(b), b.size());
}
bool strifind(const char *a, const char *b); bool strifind(const char *a, const char *b);
template <typename InputIt> void inp_strlower(InputIt first, InputIt last) { template <typename InputIt> void inp_strlower(InputIt first, InputIt last) {

View File

@ -140,7 +140,7 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_send_push_promise) || test_nghttp2_session_send_push_promise) ||
!CU_add_test(pSuite, "session_is_my_stream_id", !CU_add_test(pSuite, "session_is_my_stream_id",
test_nghttp2_session_is_my_stream_id) || test_nghttp2_session_is_my_stream_id) ||
!CU_add_test(pSuite, "session_upgrade", test_nghttp2_session_upgrade) || !CU_add_test(pSuite, "session_upgrade2", test_nghttp2_session_upgrade2) ||
!CU_add_test(pSuite, "session_reprioritize_stream", !CU_add_test(pSuite, "session_reprioritize_stream",
test_nghttp2_session_reprioritize_stream) || test_nghttp2_session_reprioritize_stream) ||
!CU_add_test( !CU_add_test(
@ -301,6 +301,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_http_record_request_method) || test_nghttp2_http_record_request_method) ||
!CU_add_test(pSuite, "http_push_promise", !CU_add_test(pSuite, "http_push_promise",
test_nghttp2_http_push_promise) || test_nghttp2_http_push_promise) ||
!CU_add_test(pSuite, "http_head_method_upgrade_workaround",
test_nghttp2_http_head_method_upgrade_workaround) ||
!CU_add_test(pSuite, "frame_pack_headers", !CU_add_test(pSuite, "frame_pack_headers",
test_nghttp2_frame_pack_headers) || test_nghttp2_frame_pack_headers) ||
!CU_add_test(pSuite, "frame_pack_headers_frame_too_large", !CU_add_test(pSuite, "frame_pack_headers_frame_too_large",

View File

@ -1007,25 +1007,20 @@ void test_nghttp2_hd_deflate_inflate(void) {
nghttp2_hd_deflater deflater; nghttp2_hd_deflater deflater;
nghttp2_hd_inflater inflater; nghttp2_hd_inflater inflater;
nghttp2_nv nv1[] = { nghttp2_nv nv1[] = {
MAKE_NV(":status", "200 OK"), MAKE_NV(":status", "200 OK"), MAKE_NV("access-control-allow-origin", "*"),
MAKE_NV("access-control-allow-origin", "*"),
MAKE_NV("cache-control", "private, max-age=0, must-revalidate"), MAKE_NV("cache-control", "private, max-age=0, must-revalidate"),
MAKE_NV("content-length", "76073"), MAKE_NV("content-length", "76073"), MAKE_NV("content-type", "text/html"),
MAKE_NV("content-type", "text/html"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
MAKE_NV("expires", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("expires", "Sat, 27 Jul 2013 06:22:12 GMT"),
MAKE_NV("server", "Apache"), MAKE_NV("server", "Apache"), MAKE_NV("vary", "foobar"),
MAKE_NV("vary", "foobar"),
MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
MAKE_NV("x-cache", "MISS from alphabravo"), MAKE_NV("x-cache", "MISS from alphabravo"),
MAKE_NV("x-cache-action", "MISS"), MAKE_NV("x-cache-action", "MISS"), MAKE_NV("x-cache-age", "0"),
MAKE_NV("x-cache-age", "0"),
MAKE_NV("x-cache-lookup", "MISS from alphabravo:3128"), MAKE_NV("x-cache-lookup", "MISS from alphabravo:3128"),
MAKE_NV("x-lb-nocache", "true"), MAKE_NV("x-lb-nocache", "true"),
}; };
nghttp2_nv nv2[] = { nghttp2_nv nv2[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=56682045"), MAKE_NV("cache-control", "max-age=56682045"),
MAKE_NV("content-type", "text/css"), MAKE_NV("content-type", "text/css"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1036,8 +1031,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache", "HIT from alphabravo"), MAKE_NV("x-cache", "HIT from alphabravo"),
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128")}; MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128")};
nghttp2_nv nv3[] = { nghttp2_nv nv3[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=56682072"), MAKE_NV("cache-control", "max-age=56682072"),
MAKE_NV("content-type", "text/css"), MAKE_NV("content-type", "text/css"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1049,8 +1043,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
}; };
nghttp2_nv nv4[] = { nghttp2_nv nv4[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=56682022"), MAKE_NV("cache-control", "max-age=56682022"),
MAKE_NV("content-type", "text/css"), MAKE_NV("content-type", "text/css"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1062,8 +1055,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
}; };
nghttp2_nv nv5[] = { nghttp2_nv nv5[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=4461139"), MAKE_NV("cache-control", "max-age=4461139"),
MAKE_NV("content-type", "application/x-javascript"), MAKE_NV("content-type", "application/x-javascript"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1075,8 +1067,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
}; };
nghttp2_nv nv6[] = { nghttp2_nv nv6[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=18645951"), MAKE_NV("cache-control", "max-age=18645951"),
MAKE_NV("content-type", "application/x-javascript"), MAKE_NV("content-type", "application/x-javascript"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1088,8 +1079,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
}; };
nghttp2_nv nv7[] = { nghttp2_nv nv7[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=31536000"), MAKE_NV("cache-control", "max-age=31536000"),
MAKE_NV("content-type", "application/javascript"), MAKE_NV("content-type", "application/javascript"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1101,8 +1091,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
}; };
nghttp2_nv nv8[] = { nghttp2_nv nv8[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=31536000"), MAKE_NV("cache-control", "max-age=31536000"),
MAKE_NV("content-type", "application/javascript"), MAKE_NV("content-type", "application/javascript"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1114,8 +1103,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
}; };
nghttp2_nv nv9[] = { nghttp2_nv nv9[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=31536000"), MAKE_NV("cache-control", "max-age=31536000"),
MAKE_NV("content-type", "application/javascript"), MAKE_NV("content-type", "application/javascript"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
@ -1127,8 +1115,7 @@ void test_nghttp2_hd_deflate_inflate(void) {
MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
}; };
nghttp2_nv nv10[] = { nghttp2_nv nv10[] = {
MAKE_NV(":status", "304 Not Modified"), MAKE_NV(":status", "304 Not Modified"), MAKE_NV("age", "0"),
MAKE_NV("age", "0"),
MAKE_NV("cache-control", "max-age=56682045"), MAKE_NV("cache-control", "max-age=56682045"),
MAKE_NV("content-type", "text/css"), MAKE_NV("content-type", "text/css"),
MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),

View File

@ -45,9 +45,7 @@ static string_entry *string_entry_new(const char *s) {
return ent; return ent;
} }
static void string_entry_del(string_entry *ent) { static void string_entry_del(string_entry *ent) { free(ent); }
free(ent);
}
static int pq_less(const void *lhs, const void *rhs) { static int pq_less(const void *lhs, const void *rhs) {
return strcmp(((string_entry *)lhs)->s, ((string_entry *)rhs)->s) < 0; return strcmp(((string_entry *)lhs)->s, ((string_entry *)rhs)->s) < 0;

View File

@ -3465,7 +3465,7 @@ void test_nghttp2_session_is_my_stream_id(void) {
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_session_upgrade(void) { void test_nghttp2_session_upgrade2(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
uint8_t settings_payload[128]; uint8_t settings_payload[128];
@ -3485,8 +3485,8 @@ void test_nghttp2_session_upgrade(void) {
/* Check client side */ /* Check client side */
nghttp2_session_client_new(&session, &callbacks, NULL); nghttp2_session_client_new(&session, &callbacks, NULL);
CU_ASSERT(0 == nghttp2_session_upgrade(session, settings_payload, CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload,
settings_payloadlen, &callbacks)); settings_payloadlen, 0, &callbacks));
stream = nghttp2_session_get_stream(session, 1); stream = nghttp2_session_get_stream(session, 1);
CU_ASSERT(stream != NULL); CU_ASSERT(stream != NULL);
CU_ASSERT(&callbacks == stream->stream_user_data); CU_ASSERT(&callbacks == stream->stream_user_data);
@ -3501,16 +3501,16 @@ void test_nghttp2_session_upgrade(void) {
item->frame.settings.iv[1].settings_id); item->frame.settings.iv[1].settings_id);
CU_ASSERT(4095 == item->frame.settings.iv[1].value); CU_ASSERT(4095 == item->frame.settings.iv[1].value);
/* Call nghttp2_session_upgrade() again is error */ /* Call nghttp2_session_upgrade2() again is error */
CU_ASSERT(NGHTTP2_ERR_PROTO == CU_ASSERT(NGHTTP2_ERR_PROTO ==
nghttp2_session_upgrade(session, settings_payload, nghttp2_session_upgrade2(session, settings_payload,
settings_payloadlen, &callbacks)); settings_payloadlen, 0, &callbacks));
nghttp2_session_del(session); nghttp2_session_del(session);
/* Check server side */ /* Check server side */
nghttp2_session_server_new(&session, &callbacks, NULL); nghttp2_session_server_new(&session, &callbacks, NULL);
CU_ASSERT(0 == nghttp2_session_upgrade(session, settings_payload, CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload,
settings_payloadlen, &callbacks)); settings_payloadlen, 0, &callbacks));
stream = nghttp2_session_get_stream(session, 1); stream = nghttp2_session_get_stream(session, 1);
CU_ASSERT(stream != NULL); CU_ASSERT(stream != NULL);
CU_ASSERT(NULL == stream->stream_user_data); CU_ASSERT(NULL == stream->stream_user_data);
@ -3518,10 +3518,10 @@ void test_nghttp2_session_upgrade(void) {
CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
CU_ASSERT(1 == session->remote_settings.max_concurrent_streams); CU_ASSERT(1 == session->remote_settings.max_concurrent_streams);
CU_ASSERT(4095 == session->remote_settings.initial_window_size); CU_ASSERT(4095 == session->remote_settings.initial_window_size);
/* Call nghttp2_session_upgrade() again is error */ /* Call nghttp2_session_upgrade2() again is error */
CU_ASSERT(NGHTTP2_ERR_PROTO == CU_ASSERT(NGHTTP2_ERR_PROTO ==
nghttp2_session_upgrade(session, settings_payload, nghttp2_session_upgrade2(session, settings_payload,
settings_payloadlen, &callbacks)); settings_payloadlen, 0, &callbacks));
nghttp2_session_del(session); nghttp2_session_del(session);
/* Empty SETTINGS is OK */ /* Empty SETTINGS is OK */
@ -3529,8 +3529,8 @@ void test_nghttp2_session_upgrade(void) {
settings_payload, sizeof(settings_payload), NULL, 0); settings_payload, sizeof(settings_payload), NULL, 0);
nghttp2_session_client_new(&session, &callbacks, NULL); nghttp2_session_client_new(&session, &callbacks, NULL);
CU_ASSERT(0 == nghttp2_session_upgrade(session, settings_payload, CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload,
settings_payloadlen, NULL)); settings_payloadlen, 0, NULL));
nghttp2_session_del(session); nghttp2_session_del(session);
} }
@ -8596,9 +8596,9 @@ void test_nghttp2_http_mandatory_headers(void) {
MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"),
MAKE_NV("content-length", "-1")}; MAKE_NV("content-length", "-1")};
const nghttp2_nv dupcl_reqnv[] = { const nghttp2_nv dupcl_reqnv[] = {
MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"),
MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"),
MAKE_NV("content-length", "0"), MAKE_NV("content-length", "0")}; MAKE_NV("content-length", "0"), MAKE_NV("content-length", "0")};
const nghttp2_nv badhd_reqnv[] = { const nghttp2_nv badhd_reqnv[] = {
MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"),
MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"),
@ -9191,8 +9191,8 @@ void test_nghttp2_http_ignore_regular_header(void) {
my_user_data ud; my_user_data ud;
const nghttp2_nv bad_reqnv[] = { const nghttp2_nv bad_reqnv[] = {
MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"),
MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"), MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"),
MAKE_NV("foo", "\x0zzz"), MAKE_NV("bar", "buzz"), MAKE_NV("foo", "\x0zzz"), MAKE_NV("bar", "buzz"),
}; };
const nghttp2_nv bad_ansnv[] = { const nghttp2_nv bad_ansnv[] = {
MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"),
@ -9441,3 +9441,45 @@ void test_nghttp2_http_push_promise(void) {
nghttp2_session_del(session); nghttp2_session_del(session);
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
} }
void test_nghttp2_http_head_method_upgrade_workaround(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"),
MAKE_NV("content-length", "1000000007")};
nghttp2_bufs bufs;
nghttp2_hd_deflater deflater;
nghttp2_mem *mem;
ssize_t rv;
nghttp2_stream *stream;
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
nghttp2_session_client_new(&session, &callbacks, NULL);
nghttp2_hd_deflate_init(&deflater, mem);
nghttp2_session_upgrade(session, NULL, 0, NULL);
rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv,
ARRLEN(cl_resnv), mem);
CU_ASSERT(0 == rv);
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
nghttp2_buf_len(&bufs.head->buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
stream = nghttp2_session_get_stream(session, 1);
CU_ASSERT(-1 == stream->content_length);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
nghttp2_bufs_free(&bufs);
}

View File

@ -60,7 +60,7 @@ void test_nghttp2_session_send_headers_push_reply(void);
void test_nghttp2_session_send_rst_stream(void); void test_nghttp2_session_send_rst_stream(void);
void test_nghttp2_session_send_push_promise(void); void test_nghttp2_session_send_push_promise(void);
void test_nghttp2_session_is_my_stream_id(void); void test_nghttp2_session_is_my_stream_id(void);
void test_nghttp2_session_upgrade(void); void test_nghttp2_session_upgrade2(void);
void test_nghttp2_session_reprioritize_stream(void); void test_nghttp2_session_reprioritize_stream(void);
void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void); void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void);
void test_nghttp2_submit_data(void); void test_nghttp2_submit_data(void);
@ -143,5 +143,6 @@ void test_nghttp2_http_ignore_regular_header(void);
void test_nghttp2_http_ignore_content_length(void); void test_nghttp2_http_ignore_content_length(void);
void test_nghttp2_http_record_request_method(void); void test_nghttp2_http_record_request_method(void);
void test_nghttp2_http_push_promise(void); void test_nghttp2_http_push_promise(void);
void test_nghttp2_http_head_method_upgrade_workaround(void);
#endif /* NGHTTP2_SESSION_TEST_H */ #endif /* NGHTTP2_SESSION_TEST_H */