1Tutorial: HTTP/2 server 2========================= 3 4In this tutorial, we are going to write a single-threaded, event-based 5HTTP/2 web server, which supports HTTPS only. It can handle concurrent 6multiple requests, but only the GET method is supported. The complete 7source code, `libevent-server.c`_, is attached at the end of this 8page. The source also resides in the examples directory in the 9archive or repository. 10 11This simple server takes 3 arguments: The port number to listen on, 12the path to your SSL/TLS private key file, and the path to your 13certificate file. The synopsis is: 14 15.. code-block:: text 16 17 $ libevent-server PORT /path/to/server.key /path/to/server.crt 18 19We use libevent in this tutorial to handle networking I/O. Please 20note that nghttp2 itself does not depend on libevent. 21 22The server starts with some libevent and OpenSSL setup in the 23``main()`` and ``run()`` functions. This setup isn't specific to 24nghttp2, but one thing you should look at is setup of the NPN 25callback. The NPN callback is used by the server to advertise which 26application protocols the server supports to a client. In this 27example program, when creating the ``SSL_CTX`` object, we store the 28application protocol name in the wire format of NPN in a statically 29allocated buffer. This is safe because we only create one ``SSL_CTX`` 30object in the program's entire lifetime. 31 32If you are following TLS related RFC, you know that NPN is not the 33standardized way to negotiate HTTP/2. NPN itself is not even 34published as RFC. The standard way to negotiate HTTP/2 is ALPN, 35Application-Layer Protocol Negotiation Extension, defined in `RFC 7301 36<https://tools.ietf.org/html/rfc7301>`_. The one caveat of ALPN is 37that OpenSSL >= 1.0.2 is required. We use macro to enable/disable 38ALPN support depending on OpenSSL version. In ALPN, client sends the 39list of supported application protocols, and server selects one of 40them. We provide the callback for it:: 41 42 static unsigned char next_proto_list[256]; 43 static size_t next_proto_list_len; 44 45 static int next_proto_cb(SSL *s _U_, const unsigned char **data, 46 unsigned int *len, void *arg _U_) { 47 *data = next_proto_list; 48 *len = (unsigned int)next_proto_list_len; 49 return SSL_TLSEXT_ERR_OK; 50 } 51 52 #if OPENSSL_VERSION_NUMBER >= 0x10002000L 53 static int alpn_select_proto_cb(SSL *ssl _U_, const unsigned char **out, 54 unsigned char *outlen, const unsigned char *in, 55 unsigned int inlen, void *arg _U_) { 56 int rv; 57 58 rv = nghttp2_select_next_protocol((unsigned char **)out, outlen, in, inlen); 59 60 if (rv != 1) { 61 return SSL_TLSEXT_ERR_NOACK; 62 } 63 64 return SSL_TLSEXT_ERR_OK; 65 } 66 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L 67 68 static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { 69 SSL_CTX *ssl_ctx; 70 EC_KEY *ecdh; 71 72 ssl_ctx = SSL_CTX_new(SSLv23_server_method()); 73 74 ... 75 76 next_proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN; 77 memcpy(&next_proto_list[1], NGHTTP2_PROTO_VERSION_ID, 78 NGHTTP2_PROTO_VERSION_ID_LEN); 79 next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN; 80 81 SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, NULL); 82 83 #if OPENSSL_VERSION_NUMBER >= 0x10002000L 84 SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); 85 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L 86 87 return ssl_ctx; 88 } 89 90The wire format of NPN is a sequence of length prefixed strings, with 91exactly one byte used to specify the length of each protocol 92identifier. In this tutorial, we advertise the specific HTTP/2 93protocol version the current nghttp2 library supports, which is 94exported in the identifier :macro:`NGHTTP2_PROTO_VERSION_ID`. The 95``next_proto_cb()`` function is the server-side NPN callback. In the 96OpenSSL implementation, we just assign the pointer to the NPN buffers 97we filled in earlier. The NPN callback function is set to the 98``SSL_CTX`` object using ``SSL_CTX_set_next_protos_advertised_cb()``. 99 100In ``alpn_select_proto_cb()``, we use `nghttp2_select_next_protocol()` 101to select application protocol. The `nghttp2_select_next_protocol()` 102returns 1 only if it selected h2 (ALPN identifier for HTTP/2), and out 103parameters were assigned accordingly. 104 105Next, let's take a look at the main structures used by the example 106application: 107 108We use the ``app_context`` structure to store application-wide data:: 109 110 struct app_context { 111 SSL_CTX *ssl_ctx; 112 struct event_base *evbase; 113 }; 114 115We use the ``http2_session_data`` structure to store session-level 116(which corresponds to one HTTP/2 connection) data:: 117 118 typedef struct http2_session_data { 119 struct http2_stream_data root; 120 struct bufferevent *bev; 121 app_context *app_ctx; 122 nghttp2_session *session; 123 char *client_addr; 124 } http2_session_data; 125 126We use the ``http2_stream_data`` structure to store stream-level data:: 127 128 typedef struct http2_stream_data { 129 struct http2_stream_data *prev, *next; 130 char *request_path; 131 int32_t stream_id; 132 int fd; 133 } http2_stream_data; 134 135A single HTTP/2 session can have multiple streams. To manage them, we 136use a doubly linked list: The first element of this list is pointed 137to by the ``root->next`` in ``http2_session_data``. Initially, 138``root->next`` is ``NULL``. 139 140libevent's bufferevent structure is used to perform network I/O, with 141the pointer to the bufferevent stored in the ``http2_session_data`` 142structure. Note that the bufferevent object is kept in 143``http2_session_data`` and not in ``http2_stream_data``. This is 144because ``http2_stream_data`` is just a logical stream multiplexed 145over the single connection managed by the bufferevent in 146``http2_session_data``. 147 148We first create a listener object to accept incoming connections. 149libevent's ``struct evconnlistener`` is used for this purpose:: 150 151 static void start_listen(struct event_base *evbase, const char *service, 152 app_context *app_ctx) { 153 int rv; 154 struct addrinfo hints; 155 struct addrinfo *res, *rp; 156 157 memset(&hints, 0, sizeof(hints)); 158 hints.ai_family = AF_UNSPEC; 159 hints.ai_socktype = SOCK_STREAM; 160 hints.ai_flags = AI_PASSIVE; 161 #ifdef AI_ADDRCONFIG 162 hints.ai_flags |= AI_ADDRCONFIG; 163 #endif /* AI_ADDRCONFIG */ 164 165 rv = getaddrinfo(NULL, service, &hints, &res); 166 if (rv != 0) { 167 errx(1, NULL); 168 } 169 for (rp = res; rp; rp = rp->ai_next) { 170 struct evconnlistener *listener; 171 listener = evconnlistener_new_bind( 172 evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 173 16, rp->ai_addr, (int)rp->ai_addrlen); 174 if (listener) { 175 freeaddrinfo(res); 176 177 return; 178 } 179 } 180 errx(1, "Could not start listener"); 181 } 182 183We specify the ``acceptcb`` callback, which is called when a new connection is 184accepted:: 185 186 static void acceptcb(struct evconnlistener *listener _U_, int fd, 187 struct sockaddr *addr, int addrlen, void *arg) { 188 app_context *app_ctx = (app_context *)arg; 189 http2_session_data *session_data; 190 191 session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); 192 193 bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); 194 } 195 196Here we create the ``http2_session_data`` object. The connection's 197bufferevent is initialized at the same time. We specify three 198callbacks for the bufferevent: ``readcb``, ``writecb``, and 199``eventcb``. 200 201The ``eventcb()`` callback is invoked by the libevent event loop when an event 202(e.g. connection has been established, timeout, etc.) occurs on the 203underlying network socket:: 204 205 static void eventcb(struct bufferevent *bev _U_, short events, void *ptr) { 206 http2_session_data *session_data = (http2_session_data *)ptr; 207 if (events & BEV_EVENT_CONNECTED) { 208 const unsigned char *alpn = NULL; 209 unsigned int alpnlen = 0; 210 SSL *ssl; 211 212 fprintf(stderr, "%s connected\n", session_data->client_addr); 213 214 ssl = bufferevent_openssl_get_ssl(session_data->bev); 215 216 SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); 217 #if OPENSSL_VERSION_NUMBER >= 0x10002000L 218 if (alpn == NULL) { 219 SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); 220 } 221 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L 222 223 if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { 224 fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); 225 delete_http2_session_data(session_data); 226 return; 227 } 228 229 initialize_nghttp2_session(session_data); 230 231 if (send_server_connection_header(session_data) != 0 || 232 session_send(session_data) != 0) { 233 delete_http2_session_data(session_data); 234 return; 235 } 236 237 return; 238 } 239 if (events & BEV_EVENT_EOF) { 240 fprintf(stderr, "%s EOF\n", session_data->client_addr); 241 } else if (events & BEV_EVENT_ERROR) { 242 fprintf(stderr, "%s network error\n", session_data->client_addr); 243 } else if (events & BEV_EVENT_TIMEOUT) { 244 fprintf(stderr, "%s timeout\n", session_data->client_addr); 245 } 246 delete_http2_session_data(session_data); 247 } 248 249Here we validate that HTTP/2 is negotiated, and if not, drop 250connection. 251 252For the ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR``, and 253``BEV_EVENT_TIMEOUT`` events, we just simply tear down the connection. 254The ``delete_http2_session_data()`` function destroys the 255``http2_session_data`` object and its associated bufferevent member. 256As a result, the underlying connection is closed. 257 258The 259``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake has 260completed successfully. After this we are ready to begin communicating 261via HTTP/2. 262 263The ``initialize_nghttp2_session()`` function initializes the nghttp2 264session object and several callbacks:: 265 266 static void initialize_nghttp2_session(http2_session_data *session_data) { 267 nghttp2_session_callbacks *callbacks; 268 269 nghttp2_session_callbacks_new(&callbacks); 270 271 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); 272 273 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, 274 on_frame_recv_callback); 275 276 nghttp2_session_callbacks_set_on_stream_close_callback( 277 callbacks, on_stream_close_callback); 278 279 nghttp2_session_callbacks_set_on_header_callback(callbacks, 280 on_header_callback); 281 282 nghttp2_session_callbacks_set_on_begin_headers_callback( 283 callbacks, on_begin_headers_callback); 284 285 nghttp2_session_server_new(&session_data->session, callbacks, session_data); 286 287 nghttp2_session_callbacks_del(callbacks); 288 } 289 290Since we are creating a server, we use `nghttp2_session_server_new()` 291to initialize the nghttp2 session object. We also setup 5 callbacks 292for the nghttp2 session, these are explained later. 293 294The server now begins by sending the server connection preface, which 295always consists of a SETTINGS frame. 296``send_server_connection_header()`` configures and submits it:: 297 298 static int send_server_connection_header(http2_session_data *session_data) { 299 nghttp2_settings_entry iv[1] = { 300 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; 301 int rv; 302 303 rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, 304 ARRLEN(iv)); 305 if (rv != 0) { 306 warnx("Fatal error: %s", nghttp2_strerror(rv)); 307 return -1; 308 } 309 return 0; 310 } 311 312In the example SETTINGS frame we've set 313SETTINGS_MAX_CONCURRENT_STREAMS to 100. `nghttp2_submit_settings()` 314is used to queue the frame for transmission, but note it only queues 315the frame for transmission, and doesn't actually send it. All 316functions in the ``nghttp2_submit_*()`` family have this property. To 317actually send the frame, `nghttp2_session_send()` should be used, as 318described later. 319 320Since bufferevent may buffer more than the first 24 bytes from the client, we 321have to process them here since libevent won't invoke callback functions for 322this pending data. To process the received data, we call the 323``session_recv()`` function:: 324 325 static int session_recv(http2_session_data *session_data) { 326 ssize_t readlen; 327 struct evbuffer *input = bufferevent_get_input(session_data->bev); 328 size_t datalen = evbuffer_get_length(input); 329 unsigned char *data = evbuffer_pullup(input, -1); 330 331 readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); 332 if (readlen < 0) { 333 warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); 334 return -1; 335 } 336 if (evbuffer_drain(input, (size_t)readlen) != 0) { 337 warnx("Fatal error: evbuffer_drain failed"); 338 return -1; 339 } 340 if (session_send(session_data) != 0) { 341 return -1; 342 } 343 return 0; 344 } 345 346In this function, we feed all unprocessed but already received data to 347the nghttp2 session object using the `nghttp2_session_mem_recv()` 348function. The `nghttp2_session_mem_recv()` function processes the data 349and may both invoke the previously setup callbacks and also queue 350outgoing frames. To send any pending outgoing frames, we immediately 351call ``session_send()``. 352 353The ``session_send()`` function is defined as follows:: 354 355 static int session_send(http2_session_data *session_data) { 356 int rv; 357 rv = nghttp2_session_send(session_data->session); 358 if (rv != 0) { 359 warnx("Fatal error: %s", nghttp2_strerror(rv)); 360 return -1; 361 } 362 return 0; 363 } 364 365The `nghttp2_session_send()` function serializes the frame into wire 366format and calls the ``send_callback()``, which is of type 367:type:`nghttp2_send_callback`. The ``send_callback()`` is defined as 368follows:: 369 370 static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data, 371 size_t length, int flags _U_, void *user_data) { 372 http2_session_data *session_data = (http2_session_data *)user_data; 373 struct bufferevent *bev = session_data->bev; 374 /* Avoid excessive buffering in server side. */ 375 if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= 376 OUTPUT_WOULDBLOCK_THRESHOLD) { 377 return NGHTTP2_ERR_WOULDBLOCK; 378 } 379 bufferevent_write(bev, data, length); 380 return (ssize_t)length; 381 } 382 383Since we use bufferevent to abstract network I/O, we just write the 384data to the bufferevent object. Note that `nghttp2_session_send()` 385continues to write all frames queued so far. If we were writing the 386data to a non-blocking socket directly using the ``write()`` system 387call in the ``send_callback()``, we'd soon receive an ``EAGAIN`` or 388``EWOULDBLOCK`` error since sockets have a limited send buffer. If 389that happens, it's possible to return :macro:`NGHTTP2_ERR_WOULDBLOCK` 390to signal the nghttp2 library to stop sending further data. But here, 391when writing to the bufferevent, we have to regulate the amount data 392to buffered ourselves to avoid using huge amounts of memory. To 393achieve this, we check the size of the output buffer and if it reaches 394more than or equal to ``OUTPUT_WOULDBLOCK_THRESHOLD`` bytes, we stop 395writing data and return :macro:`NGHTTP2_ERR_WOULDBLOCK`. 396 397The next bufferevent callback is ``readcb()``, which is invoked when 398data is available to read in the bufferevent input buffer:: 399 400 static void readcb(struct bufferevent *bev _U_, void *ptr) { 401 http2_session_data *session_data = (http2_session_data *)ptr; 402 if (session_recv(session_data) != 0) { 403 delete_http2_session_data(session_data); 404 return; 405 } 406 } 407 408In this function, we just call ``session_recv()`` to process incoming 409data. 410 411The third bufferevent callback is ``writecb()``, which is invoked when all 412data in the bufferevent output buffer has been sent:: 413 414 static void writecb(struct bufferevent *bev, void *ptr) { 415 http2_session_data *session_data = (http2_session_data *)ptr; 416 if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { 417 return; 418 } 419 if (nghttp2_session_want_read(session_data->session) == 0 && 420 nghttp2_session_want_write(session_data->session) == 0) { 421 delete_http2_session_data(session_data); 422 return; 423 } 424 if (session_send(session_data) != 0) { 425 delete_http2_session_data(session_data); 426 return; 427 } 428 } 429 430First we check whether we should drop the connection or not. The 431nghttp2 session object keeps track of reception and transmission of 432GOAWAY frames and other error conditions as well. Using this 433information, the nghttp2 session object can state whether the 434connection should be dropped or not. More specifically, if both 435`nghttp2_session_want_read()` and `nghttp2_session_want_write()` 436return 0, the connection is no-longer required and can be closed. 437Since we are using bufferevent and its deferred callback option, the 438bufferevent output buffer may still contain pending data when the 439``writecb()`` is called. To handle this, we check whether the output 440buffer is empty or not. If all of these conditions are met, we drop 441connection. 442 443Otherwise, we call ``session_send()`` to process the pending output 444data. Remember that in ``send_callback()``, we must not write all data to 445bufferevent to avoid excessive buffering. We continue processing pending data 446when the output buffer becomes empty. 447 448We have already described the nghttp2 callback ``send_callback()``. Let's 449learn about the remaining nghttp2 callbacks setup in 450``initialize_nghttp2_setup()`` function. 451 452The ``on_begin_headers_callback()`` function is invoked when the reception of 453a header block in HEADERS or PUSH_PROMISE frame is started:: 454 455 static int on_begin_headers_callback(nghttp2_session *session, 456 const nghttp2_frame *frame, 457 void *user_data) { 458 http2_session_data *session_data = (http2_session_data *)user_data; 459 http2_stream_data *stream_data; 460 461 if (frame->hd.type != NGHTTP2_HEADERS || 462 frame->headers.cat != NGHTTP2_HCAT_REQUEST) { 463 return 0; 464 } 465 stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); 466 nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, 467 stream_data); 468 return 0; 469 } 470 471We are only interested in the HEADERS frame in this function. Since 472the HEADERS frame has several roles in the HTTP/2 protocol, we check 473that it is a request HEADERS, which opens new stream. If the frame is 474a request HEADERS, we create a ``http2_stream_data`` object to store 475the stream related data. We associate the created 476``http2_stream_data`` object with the stream in the nghttp2 session 477object using `nghttp2_set_stream_user_data()`. The 478``http2_stream_data`` object can later be easily retrieved from the 479stream, without searching through the doubly linked list. 480 481In this example server, we want to serve files relative to the current working 482directory in which the program was invoked. Each header name/value pair is 483emitted via ``on_header_callback`` function, which is called after 484``on_begin_headers_callback()``:: 485 486 static int on_header_callback(nghttp2_session *session, 487 const nghttp2_frame *frame, const uint8_t *name, 488 size_t namelen, const uint8_t *value, 489 size_t valuelen, uint8_t flags _U_, 490 void *user_data _U_) { 491 http2_stream_data *stream_data; 492 const char PATH[] = ":path"; 493 switch (frame->hd.type) { 494 case NGHTTP2_HEADERS: 495 if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { 496 break; 497 } 498 stream_data = 499 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); 500 if (!stream_data || stream_data->request_path) { 501 break; 502 } 503 if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { 504 size_t j; 505 for (j = 0; j < valuelen && value[j] != '?'; ++j) 506 ; 507 stream_data->request_path = percent_decode(value, j); 508 } 509 break; 510 } 511 return 0; 512 } 513 514We search for the ``:path`` header field among the request headers and 515store the requested path in the ``http2_stream_data`` object. In this 516example program, we ignore the ``:method`` header field and always 517treat the request as a GET request. 518 519The ``on_frame_recv_callback()`` function is invoked when a frame is 520fully received:: 521 522 static int on_frame_recv_callback(nghttp2_session *session, 523 const nghttp2_frame *frame, void *user_data) { 524 http2_session_data *session_data = (http2_session_data *)user_data; 525 http2_stream_data *stream_data; 526 switch (frame->hd.type) { 527 case NGHTTP2_DATA: 528 case NGHTTP2_HEADERS: 529 /* Check that the client request has finished */ 530 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { 531 stream_data = 532 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); 533 /* For DATA and HEADERS frame, this callback may be called after 534 on_stream_close_callback. Check that stream still alive. */ 535 if (!stream_data) { 536 return 0; 537 } 538 return on_request_recv(session, session_data, stream_data); 539 } 540 break; 541 default: 542 break; 543 } 544 return 0; 545 } 546 547First we retrieve the ``http2_stream_data`` object associated with the 548stream in ``on_begin_headers_callback()`` using 549`nghttp2_session_get_stream_user_data()`. If the requested path 550cannot be served for some reason (e.g. file is not found), we send a 551404 response using ``error_reply()``. Otherwise, we open 552the requested file and send its content. We send the header field 553``:status`` as a single response header. 554 555Sending the file content is performed by the ``send_response()`` function:: 556 557 static int send_response(nghttp2_session *session, int32_t stream_id, 558 nghttp2_nv *nva, size_t nvlen, int fd) { 559 int rv; 560 nghttp2_data_provider data_prd; 561 data_prd.source.fd = fd; 562 data_prd.read_callback = file_read_callback; 563 564 rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd); 565 if (rv != 0) { 566 warnx("Fatal error: %s", nghttp2_strerror(rv)); 567 return -1; 568 } 569 return 0; 570 } 571 572nghttp2 uses the :type:`nghttp2_data_provider` structure to send the 573entity body to the remote peer. The ``source`` member of this 574structure is a union, which can be either a void pointer or an int 575(which is intended to be used as file descriptor). In this example 576server, we use it as a file descriptor. We also set the 577``file_read_callback()`` callback function to read the contents of the 578file:: 579 580 static ssize_t file_read_callback(nghttp2_session *session _U_, 581 int32_t stream_id _U_, uint8_t *buf, 582 size_t length, uint32_t *data_flags, 583 nghttp2_data_source *source, 584 void *user_data _U_) { 585 int fd = source->fd; 586 ssize_t r; 587 while ((r = read(fd, buf, length)) == -1 && errno == EINTR) 588 ; 589 if (r == -1) { 590 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 591 } 592 if (r == 0) { 593 *data_flags |= NGHTTP2_DATA_FLAG_EOF; 594 } 595 return r; 596 } 597 598If an error occurs while reading the file, we return 599:macro:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. This tells the 600library to send RST_STREAM to the stream. When all data has been 601read, the :macro:`NGHTTP2_DATA_FLAG_EOF` flag is set to signal nghttp2 602that we have finished reading the file. 603 604The `nghttp2_submit_response()` function is used to send the response to the 605remote peer. 606 607The ``on_stream_close_callback()`` function is invoked when the stream 608is about to close:: 609 610 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, 611 uint32_t error_code _U_, void *user_data) { 612 http2_session_data *session_data = (http2_session_data *)user_data; 613 http2_stream_data *stream_data; 614 615 stream_data = nghttp2_session_get_stream_user_data(session, stream_id); 616 if (!stream_data) { 617 return 0; 618 } 619 remove_stream(session_data, stream_data); 620 delete_http2_stream_data(stream_data); 621 return 0; 622 } 623 624Lastly, we destroy the ``http2_stream_data`` object in this function, 625since the stream is about to close and we no longer need the object. 626