diff --git a/doc/tutorial-client.rst b/doc/tutorial-client.rst index 79756499..bd7aae85 100644 --- a/doc/tutorial-client.rst +++ b/doc/tutorial-client.rst @@ -163,11 +163,13 @@ finished successfully. We first initialize nghttp2 session object in callbacks.on_frame_recv_callback = on_frame_recv_callback; callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; + callbacks.on_header_callback = on_header_callback; + callbacks.on_end_headers_callback = on_end_headers_callback; nghttp2_session_client_new(&session_data->session, &callbacks, session_data); } Since we are creating client, we use `nghttp2_session_client_new()` to -initialize nghttp2 session object. We setup 5 callbacks for the +initialize nghttp2 session object. We setup 7 callbacks for the nghttp2 session. We'll explain these callbacks later. The `delete_http2_session_data()` destroys ``session_data`` and frees @@ -395,9 +397,8 @@ received from the remote peer:: case NGHTTP2_HEADERS: if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && session_data->stream_data->stream_id == frame->hd.stream_id) { - /* Print response headers for the initiated request. */ - fprintf(stderr, "Response headers:\n"); - print_headers(stderr, frame->headers.nva, frame->headers.nvlen); + fprintf(stderr, "Response headers for stream ID=%d:\n", + frame->hd.stream_id); } break; } @@ -409,6 +410,53 @@ HEADERS. We check te frame type and its category (it should be :macro:`NGHTTP2_HCAT_RESPONSE` for HTTP response HEADERS). Also check its stream ID. +Each request header name/value pair is emitted via +``on_header_callback`` function:: + + static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + void *user_data) + { + http2_session_data *session_data = (http2_session_data*)user_data; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + /* Print response headers for the initiated request. */ + print_header(stderr, name, namelen, value, valuelen); + break; + } + } + return 0; + } + +In this turotial, we just print the name/value pair. + +After all name/value pairs are emitted for a frame, +``on_end_headers_callback`` function is called:: + + static int on_end_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + nghttp2_error_code error_code, + void *user_data) + { + http2_session_data *session_data = (http2_session_data*)user_data; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + fprintf(stderr, "All headers received with error_code=%d\n", error_code); + } + break; + } + return 0; + } + +This callback may be called prematurely because of errors (e.g., +header decompression failure) which is indicated by ``error_code``. + The ``on_data_chunk_recv_callback()`` function is invoked when a chunk of data is received from the remote peer:: diff --git a/doc/tutorial-server.rst b/doc/tutorial-server.rst index 98199365..723c9b6e 100644 --- a/doc/tutorial-server.rst +++ b/doc/tutorial-server.rst @@ -243,11 +243,12 @@ We initialize nghttp2 session object which is done in callbacks.on_frame_recv_callback = on_frame_recv_callback; callbacks.on_request_recv_callback = on_request_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; + callbacks.on_header_callback = on_header_callback; nghttp2_session_server_new(&session_data->session, &callbacks, session_data); } Since we are creating server, nghttp2 session object is created using -`nghttp2_session_server_new()` function. We registers 4 callbacks to +`nghttp2_session_server_new()` function. We registers 5 callbacks to nghttp2 session object. We'll talk about these callbacks later. After initialization of nghttp2 session object, we are going to send @@ -421,8 +422,6 @@ received from the remote peer:: { http2_session_data *session_data = (http2_session_data*)user_data; http2_stream_data *stream_data; - size_t i; - const char PATH[] = ":path"; switch(frame->hd.type) { case NGHTTP2_HEADERS: if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { @@ -431,16 +430,6 @@ received from the remote peer:: stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); - for(i = 0; i < frame->headers.nvlen; ++i) { - nghttp2_nv *nv = &frame->headers.nva[i]; - if(nv->namelen == sizeof(PATH) - 1 && - memcmp(PATH, nv->name, nv->namelen) == 0) { - size_t j; - for(j = 0; j < nv->valuelen && nv->value[j] != '?'; ++j); - stream_data->request_path = percent_decode(nv->value, j); - break; - } - } break; default: break; @@ -457,13 +446,47 @@ in nghttp2 session object using `nghttp2_set_stream_user_data()` in order to get the object without searching through doubly linked list. In this example server, we want to serve files relative to the current -working directory the program was invoked. We search ``:path`` header -field in request headers and keep the requested path in -``http2_stream_data`` object. In this example program, we ignore -``:method`` header field and always treat the request as GET request. +working directory the program was invoked. Each header name/value pair +is emitted via ``on_header_callback`` function, which is called after +``on_frame_recv_callback()``:: -It is ok for the server to start sending response in this callback. In -this example, we defer it to ``on_request_recv_callback()`` function. + static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + void *user_data) + { + http2_stream_data *stream_data; + const char PATH[] = ":path"; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + stream_data = nghttp2_session_get_stream_user_data(session, + frame->hd.stream_id); + if(stream_data->request_path) { + break; + } + if(namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for(j = 0; j < valuelen && value[j] != '?'; ++j); + stream_data->request_path = percent_decode(value, j); + } + break; + } + return 0; + } + +We search ``:path`` header field in request headers and keep the +requested path in ``http2_stream_data`` object. In this example +program, we ignore ``:method`` header field and always treat the +request as GET request. + +It is ok for the server to start sending response in this callback (or +in `nghttp2_on_end_headers_callback()`, which is not used in this +tutorial). In this example, we defer it to +``on_request_recv_callback()`` function. The ``on_request_recv_callback()`` function is invoked when all HTTP request, including entity body, was received:: diff --git a/examples/libevent-client.c b/examples/libevent-client.c index fa455f61..753c3d05 100644 --- a/examples/libevent-client.c +++ b/examples/libevent-client.c @@ -151,6 +151,16 @@ static void delete_http2_session_data(http2_session_data *session_data) free(session_data); } +static void print_header(FILE *f, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen) +{ + fwrite(name, namelen, 1, f); + fprintf(f, ": "); + fwrite(value, valuelen, 1, f); + fprintf(f, "\n"); +} + /* Print HTTP headers to |f|. Please note that this function does not take into account that header name and value are sequence of octets, therefore they may contain non-printable characters. */ @@ -158,12 +168,11 @@ static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) { size_t i; for(i = 0; i < nvlen; ++i) { - fwrite(nva[i].name, nva[i].namelen, 1, stderr); - fprintf(stderr, ": "); - fwrite(nva[i].value, nva[i].valuelen, 1, stderr); - fprintf(stderr, "\n"); + print_header(f, + nva[i].name, nva[i].namelen, + nva[i].value, nva[i].valuelen); } - fprintf(stderr, "\n"); + fprintf(f, "\n"); } /* nghttp2_send_callback. Here we transmit the |data|, |length| bytes, @@ -201,6 +210,47 @@ static int before_frame_send_callback return 0; } +/* nghttp2_on_header_callback: Called when nghttp2 library emits + single header name/value pair. */ +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + void *user_data) +{ + http2_session_data *session_data = (http2_session_data*)user_data; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + /* Print response headers for the initiated request. */ + print_header(stderr, name, namelen, value, valuelen); + break; + } + } + return 0; +} + +/* nghttp2_on_end_headers_callback: Called when nghttp2 library emits + all header name/value pairs, or may be called prematurely because + of errors which is indicated by |error_code|. */ +static int on_end_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + nghttp2_error_code error_code, + void *user_data) +{ + http2_session_data *session_data = (http2_session_data*)user_data; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + fprintf(stderr, "All headers received with error_code=%d\n", error_code); + } + break; + } + return 0; +} + /* nghttp2_on_frame_recv_callback: Called when nghttp2 library received a frame from the remote peer. */ static int on_frame_recv_callback(nghttp2_session *session, @@ -211,9 +261,8 @@ static int on_frame_recv_callback(nghttp2_session *session, case NGHTTP2_HEADERS: if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && session_data->stream_data->stream_id == frame->hd.stream_id) { - /* Print response headers for the initiated request. */ - fprintf(stderr, "Response headers:\n"); - print_headers(stderr, frame->headers.nva, frame->headers.nvlen); + fprintf(stderr, "Response headers for stream ID=%d:\n", + frame->hd.stream_id); } break; } @@ -311,6 +360,8 @@ static void initialize_nghttp2_session(http2_session_data *session_data) callbacks.on_frame_recv_callback = on_frame_recv_callback; callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; + callbacks.on_header_callback = on_header_callback; + callbacks.on_end_headers_callback = on_end_headers_callback; nghttp2_session_client_new(&session_data->session, &callbacks, session_data); } diff --git a/examples/libevent-server.c b/examples/libevent-server.c index 6b3933dc..7af5e7e8 100644 --- a/examples/libevent-server.c +++ b/examples/libevent-server.c @@ -325,13 +325,41 @@ static char* percent_decode(const uint8_t *value, size_t valuelen) return res; } +/* nghttp2_on_header_callback: Called when nghttp2 library emits + single header name/value pair. */ +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + void *user_data) +{ + http2_stream_data *stream_data; + const char PATH[] = ":path"; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + stream_data = nghttp2_session_get_stream_user_data(session, + frame->hd.stream_id); + if(stream_data->request_path) { + break; + } + if(namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for(j = 0; j < valuelen && value[j] != '?'; ++j); + stream_data->request_path = percent_decode(value, j); + } + break; + } + return 0; +} + static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { http2_session_data *session_data = (http2_session_data*)user_data; http2_stream_data *stream_data; - size_t i; - const char PATH[] = ":path"; switch(frame->hd.type) { case NGHTTP2_HEADERS: if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { @@ -340,16 +368,6 @@ static int on_frame_recv_callback(nghttp2_session *session, stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); - for(i = 0; i < frame->headers.nvlen; ++i) { - nghttp2_nv *nv = &frame->headers.nva[i]; - if(nv->namelen == sizeof(PATH) - 1 && - memcmp(PATH, nv->name, nv->namelen) == 0) { - size_t j; - for(j = 0; j < nv->valuelen && nv->value[j] != '?'; ++j); - stream_data->request_path = percent_decode(nv->value, j); - break; - } - } break; default: break; @@ -503,6 +521,7 @@ static void initialize_nghttp2_session(http2_session_data *session_data) callbacks.on_frame_recv_callback = on_frame_recv_callback; callbacks.on_request_recv_callback = on_request_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; + callbacks.on_header_callback = on_header_callback; nghttp2_session_server_new(&session_data->session, &callbacks, session_data); }