Merge branch 'master' into http2-debug-state-api

This commit is contained in:
Tatsuhiro Tsujikawa 2016-08-28 22:20:04 +09:00
commit fddb019baf
13 changed files with 202 additions and 31 deletions

View File

@ -43,6 +43,7 @@ before_script:
- git clone https://github.com/tatsuhiro-t/spdylay.git
- cd spdylay
- autoreconf -i
# Don't use ASAN for spdylay since failmalloc does not work with it.
- ./configure --disable-src --disable-examples
- make check
- export SPDYLAY_HOME=$PWD
@ -50,15 +51,13 @@ before_script:
# Now build nghttp2
- if [ "$CI_BUILD" = "autotools" ]; then autoreconf -i; fi
- git submodule update --init
- if [ "$CI_BUILD" = "autotools" ]; then ./configure --enable-werror --with-mruby --with-neverbleed LIBSPDYLAY_CFLAGS="-I$SPDYLAY_HOME/lib/includes" LIBSPDYLAY_LIBS="-L$SPDYLAY_HOME/lib/.libs -lspdylay"; fi
- if [ "$CI_BUILD" = "autotools" ]; then ./configure --enable-werror --with-mruby --with-neverbleed LIBSPDYLAY_CFLAGS="-I$SPDYLAY_HOME/lib/includes" LIBSPDYLAY_LIBS="-L$SPDYLAY_HOME/lib/.libs -lspdylay" CPPFLAGS=-fsanitize=address LDFLAGS=-fsanitize=address; fi
- if [ "$CI_BUILD" = "cmake" ]; then cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 -DSPDYLAY_INCLUDE_DIR="$SPDYLAY_HOME/lib/includes" -DSPDYLAY_LIBRARY="$SPDYLAY_HOME/lib/.libs/libspdylay.so"; fi
script:
- make
- make check
- cd integration-tests
# As of April, 23, 2016, golang http2 build fails, probably because
# the default go version is too old.
# - cd integration-tests
# - export GOPATH="$PWD/integration-tests/golang"
# - make itprep
# - make it

View File

@ -1370,7 +1370,7 @@ The extension module is called ``nghttp2``.
determined by the ``configure`` script. If the detected Python version is not
what you expect, specify a path to Python executable in a ``PYTHON``
variable as an argument to configure script (e.g., ``./configure
PYTHON=/usr/bin/python3.4``).
PYTHON=/usr/bin/python3.5``).
The following example code illustrates basic usage of the HPACK compressor
and decompressor in Python:

View File

@ -13,7 +13,7 @@ The extension module is called ``nghttp2``.
determined by configure script. If the detected Python version is not
what you expect, specify a path to Python executable in ``PYTHON``
variable as an argument to configure script (e.g., ``./configure
PYTHON=/usr/bin/python3.4``).
PYTHON=/usr/bin/python3.5``).
HPACK API
---------
@ -136,15 +136,15 @@ HTTP/2 servers
.. note::
We use :py:mod:`asyncio` for HTTP/2 server classes. Therefore,
Python 3.4 or later is required to use these objects. To
explicitly configure nghttp2 build to use Python 3.4, specify the
``PYTHON`` variable to the path to Python 3.4 executable when
We use :py:mod:`asyncio` for HTTP/2 server classes, and ALPN.
Therefore, Python 3.5 or later is required to use these objects.
To explicitly configure nghttp2 build to use Python 3.5, specify
the ``PYTHON`` variable to the path to Python 3.5 executable when
invoking configure script like this:
.. code-block:: text
$ ./configure PYTHON=/usr/bin/python3.4
$ ./configure PYTHON=/usr/bin/python3.5
.. py:class:: HTTP2Server(address, RequestHandlerClass, ssl=None)

View File

@ -860,8 +860,13 @@ typedef enum {
* achieved by returning :enum:`NGHTTP2_ERR_DEFERRED` without reading
* any data in this invocation. The library removes DATA frame from
* the outgoing queue temporarily. To move back deferred DATA frame
* to outgoing queue, call `nghttp2_session_resume_data()`. In case
* of error, there are 2 choices. Returning
* to outgoing queue, call `nghttp2_session_resume_data()`.
*
* If the application just wants to return from
* `nghttp2_session_send()` or `nghttp2_session_mem_send()` without
* sending anything, return :enum:`NGHTTP2_ERR_PAUSE`.
*
* In case of error, there are 2 choices. Returning
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will close the stream
* by issuing RST_STREAM with :enum:`NGHTTP2_INTERNAL_ERROR`. If a
* different error code is desirable, use
@ -1742,9 +1747,11 @@ typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
*
* With this callback, application inspects the incoming invalid
* field, and it also can reset stream from this callback by returning
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, or using
* `nghttp2_submit_rst_stream()` directly with the error code of
* choice.
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By default, the
* error code is :enum:`NGHTTP2_INTERNAL_ERROR`. To change the error
* code, call `nghttp2_submit_rst_stream()` with the error code of
* choice in addition to returning
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
*/
typedef int (*nghttp2_on_invalid_header_callback)(
nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name,
@ -1772,9 +1779,11 @@ typedef int (*nghttp2_on_invalid_header_callback)(
*
* With this callback, application inspects the incoming invalid
* field, and it also can reset stream from this callback by returning
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, or using
* `nghttp2_submit_rst_stream()` directly with the error code of
* choice.
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By default, the
* error code is :enum:`NGHTTP2_INTERNAL_ERROR`. To change the error
* code, call `nghttp2_submit_rst_stream()` with the error code of
* choice in addition to returning
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
*/
typedef int (*nghttp2_on_invalid_header_callback2)(
nghttp2_session *session, const nghttp2_frame *frame, nghttp2_rcbuf *name,

View File

@ -2263,6 +2263,9 @@ static int session_prep_frame(nghttp2_session *session,
rv = nghttp2_session_pack_data(session, &session->aob.framebufs,
next_readmax, frame, &item->aux_data.data,
stream);
if (rv == NGHTTP2_ERR_PAUSE) {
return rv;
}
if (rv == NGHTTP2_ERR_DEFERRED) {
rv = nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER);
@ -2918,6 +2921,9 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session,
}
rv = session_prep_frame(session, item);
if (rv == NGHTTP2_ERR_PAUSE) {
return 0;
}
if (rv == NGHTTP2_ERR_DEFERRED) {
DEBUGF(fprintf(stderr, "send: frame transmission deferred\n"));
break;
@ -7020,7 +7026,8 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
&aux_data->data_prd.source, session->user_data);
if (payloadlen == NGHTTP2_ERR_DEFERRED ||
payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE ||
payloadlen == NGHTTP2_ERR_PAUSE) {
DEBUGF(fprintf(stderr, "send: DATA postponed due to %s\n",
nghttp2_strerror((int)payloadlen)));

View File

@ -260,6 +260,7 @@ try:
import email.utils
import datetime
import time
import ssl as tls
from urllib.parse import urlparse
except ImportError:
asyncio = None
@ -294,6 +295,25 @@ def wrap_body(body):
# string and flag.
return body
def negotiated_protocol(ssl_obj):
protocol = ssl_obj.selected_alpn_protocol()
if protocol:
logging.info('alpn, protocol:%s', protocol)
return protocol
protocol = ssl_obj.selected_npn_protocol()
if protocol:
logging.info('npn, protocol:%s', protocol)
return protocol
return None
def set_application_protocol(ssl_ctx):
app_protos = [cnghttp2.NGHTTP2_PROTO_VERSION_ID.decode('utf-8')]
ssl_ctx.set_npn_protocols(app_protos)
if tls.HAS_ALPN:
ssl_ctx.set_alpn_protocols(app_protos)
cdef _get_stream_user_data(cnghttp2.nghttp2_session *session,
int32_t stream_id):
cdef void *stream_user_data
@ -902,6 +922,8 @@ cdef class _HTTP2SessionCore(_HTTP2SessionCoreBase):
return promised_handler
def connection_lost(self):
self._stop_settings_timer()
for handler in self.handlers:
handler.on_close(cnghttp2.NGHTTP2_INTERNAL_ERROR)
self.handlers = set()
@ -1284,8 +1306,8 @@ if asyncio:
logging.info('failed to set tcp-nodelay: %s', str(e))
ssl_ctx = self.transport.get_extra_info('sslcontext')
if ssl_ctx:
protocol = sock.selected_npn_protocol()
logging.info('npn, protocol:%s', protocol)
ssl_obj = self.transport.get_extra_info('ssl_object')
protocol = negotiated_protocol(ssl_obj)
if protocol is None or protocol.encode('utf-8') != \
cnghttp2.NGHTTP2_PROTO_VERSION_ID:
self.transport.abort()
@ -1346,8 +1368,7 @@ if asyncio:
self.loop = asyncio.get_event_loop()
if ssl:
ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\
.decode('utf-8')])
set_application_protocol(ssl)
coro = self.loop.create_server(session_factory,
host=address[0], port=address[1],
@ -1516,8 +1537,8 @@ if asyncio:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
ssl_ctx = self.transport.get_extra_info('sslcontext')
if ssl_ctx:
protocol = sock.selected_npn_protocol()
logging.info('npn, protocol:%s', protocol)
ssl_obj = self.transport.get_extra_info('ssl_object')
protocol = negotiated_protocol(ssl_obj)
if protocol is None or protocol.encode('utf-8') != \
cnghttp2.NGHTTP2_PROTO_VERSION_ID:
self.transport.abort()
@ -1594,8 +1615,7 @@ if asyncio:
return self.session
if ssl:
ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\
.decode('utf-8')])
set_application_protocol(ssl)
self.loop = loop
if not self.loop:

View File

@ -904,6 +904,47 @@ int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
}
} // namespace
namespace {
int on_invalid_header_callback2(nghttp2_session *session,
const nghttp2_frame *frame, nghttp2_rcbuf *name,
nghttp2_rcbuf *value, uint8_t flags,
void *user_data) {
auto http2session = static_cast<Http2Session *>(user_data);
auto sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!sd || !sd->dconn) {
return 0;
}
auto downstream = sd->dconn->get_downstream();
if (!downstream) {
return 0;
}
int32_t stream_id;
if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
stream_id = frame->push_promise.promised_stream_id;
} else {
stream_id = frame->hd.stream_id;
}
if (LOG_ENABLED(INFO)) {
auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
SSLOG(INFO, http2session)
<< "Invalid header field for stream_id=" << stream_id
<< " in frame type=" << static_cast<uint32_t>(frame->hd.type)
<< ": name=[" << StringRef{namebuf.base, namebuf.len} << "], value=["
<< StringRef{valuebuf.base, valuebuf.len} << "]";
}
http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
} // namespace
namespace {
int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
@ -1486,6 +1527,9 @@ nghttp2_session_callbacks *create_http2_downstream_callbacks() {
nghttp2_session_callbacks_set_on_header_callback2(callbacks,
on_header_callback2);
nghttp2_session_callbacks_set_on_invalid_header_callback2(
callbacks, on_invalid_header_callback2);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);

View File

@ -227,6 +227,34 @@ int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
}
} // namespace
namespace {
int on_invalid_header_callback2(nghttp2_session *session,
const nghttp2_frame *frame, nghttp2_rcbuf *name,
nghttp2_rcbuf *value, uint8_t flags,
void *user_data) {
auto upstream = static_cast<Http2Upstream *>(user_data);
auto downstream = static_cast<Downstream *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!downstream) {
return 0;
}
if (LOG_ENABLED(INFO)) {
auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
ULOG(INFO, upstream) << "Invalid header field for stream_id="
<< frame->hd.stream_id << ": name=["
<< StringRef{namebuf.base, namebuf.len} << "], value=["
<< StringRef{valuebuf.base, valuebuf.len} << "]";
}
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
} // namespace
namespace {
int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
@ -852,6 +880,9 @@ nghttp2_session_callbacks *create_http2_upstream_callbacks() {
nghttp2_session_callbacks_set_on_header_callback2(callbacks,
on_header_callback2);
nghttp2_session_callbacks_set_on_invalid_header_callback2(
callbacks, on_invalid_header_callback2);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);

View File

@ -72,6 +72,14 @@ namespace shrpx {
namespace ssl {
#if !OPENSSL_101_API
namespace {
const unsigned char *ASN1_STRING_get0_data(ASN1_STRING *x) {
return ASN1_STRING_data(x);
}
} // namespace
#endif // !OPENSSL_101_API
namespace {
int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
void *arg) {
@ -1015,7 +1023,7 @@ int verify_hostname(X509 *cert, const StringRef &hostname,
continue;
}
auto name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
auto name = ASN1_STRING_get0_data(altname->d.ia5);
if (!name) {
continue;
}
@ -1235,7 +1243,7 @@ int cert_lookup_tree_add_cert_from_x509(CertLookupTree *lt, size_t idx,
continue;
}
auto name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
auto name = ASN1_STRING_get0_data(altname->d.ia5);
if (!name) {
continue;
}

View File

@ -24,7 +24,9 @@
*/
#ifndef OPENSSL_COMPAT_H
#include <openssl/opensslv.h>
#define OPENSSL_101_API \
(!defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100005L)
(!defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x1010000fL)
#endif // OPENSSL_COMPAT_H

View File

@ -310,6 +310,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_cancel_from_before_frame_send) ||
!CU_add_test(pSuite, "session_removed_closed_stream",
test_nghttp2_session_removed_closed_stream) ||
!CU_add_test(pSuite, "session_pause_data",
test_nghttp2_session_pause_data) ||
!CU_add_test(pSuite, "http_mandatory_headers",
test_nghttp2_http_mandatory_headers) ||
!CU_add_test(pSuite, "http_content_length",

View File

@ -77,6 +77,7 @@ typedef struct {
size_t padlen;
int begin_frame_cb_called;
nghttp2_buf scratchbuf;
size_t data_source_read_cb_paused;
} my_user_data;
static const nghttp2_nv reqnv[] = {
@ -10021,6 +10022,53 @@ void test_nghttp2_session_removed_closed_stream(void) {
nghttp2_bufs_free(&bufs);
}
static ssize_t pause_once_data_source_read_callback(
nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
my_user_data *ud = user_data;
if (ud->data_source_read_cb_paused == 0) {
++ud->data_source_read_cb_paused;
return NGHTTP2_ERR_PAUSE;
}
return fixed_length_data_source_read_callback(session, stream_id, buf, len,
data_flags, source, user_data);
}
void test_nghttp2_session_pause_data(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_data_provider data_prd;
my_user_data ud;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback;
data_prd.read_callback = pause_once_data_source_read_callback;
ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN;
nghttp2_session_server_new(&session, &callbacks, &ud);
open_recv_stream(session, 1);
CU_ASSERT(
0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
ud.frame_send_cb_called = 0;
ud.data_source_read_cb_paused = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(0 == ud.frame_send_cb_called);
CU_ASSERT(NULL == session->aob.item);
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called);
CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type);
CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
nghttp2_session_del(session);
}
static void check_nghttp2_http_recv_headers_fail(
nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
int stream_state, const nghttp2_nv *nva, size_t nvlen) {

View File

@ -153,6 +153,7 @@ void test_nghttp2_session_repeated_priority_submission(void);
void test_nghttp2_session_set_local_window_size(void);
void test_nghttp2_session_cancel_from_before_frame_send(void);
void test_nghttp2_session_removed_closed_stream(void);
void test_nghttp2_session_pause_data(void);
void test_nghttp2_http_mandatory_headers(void);
void test_nghttp2_http_content_length(void);
void test_nghttp2_http_content_length_mismatch(void);