/* readcb for bufferevent to check first 24 bytes client connection header. */ static void handshake_readcb(struct bufferevent *bev, void *ptr) { http2_session_data *session_data = (http2_session_data*)ptr; uint8_t data[24]; struct evbuffer *input = bufferevent_get_input(session_data->bev); int readlen = evbuffer_remove(input, data, session_data->handshake_leftlen); const char *conhead = NGHTTP2_CLIENT_CONNECTION_HEADER; if(memcmp(conhead + NGHTTP2_CLIENT_CONNECTION_HEADER_LEN - session_data->handshake_leftlen, data, readlen) != 0) { delete_http2_session_data(session_data); return; } session_data->handshake_leftlen -= readlen; if(session_data->handshake_leftlen == 0) { bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, ptr); /* Process pending data in buffer since they are not notified further */ initialize_nghttp2_session(session_data); if(send_server_connection_header(session_data) != 0) { delete_http2_session_data(session_data); return; } if(session_recv(session_data) != 0) { delete_http2_session_data(session_data); return; } } }
/* eventcb for bufferevent. For the purpose of simplicity and readability of the example program, we omitted the certificate and peer verification. After SSL/TLS handshake is over, initialize nghttp2 library session, and send client connection header. Then send HTTP request. */ static void eventcb(struct bufferevent *bev, short events, void *ptr) { http2_session_data *session_data = (http2_session_data*)ptr; if(events & BEV_EVENT_CONNECTED) { int fd = bufferevent_getfd(bev); int val = 1; fprintf(stderr, "Connected\n"); setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); initialize_nghttp2_session(session_data); send_client_connection_header(session_data); submit_request(session_data); if(session_send(session_data) != 0) { delete_http2_session_data(session_data); } return; } if(events & BEV_EVENT_EOF) { warnx("Disconnected from the remote host"); } else if(events & BEV_EVENT_ERROR) { warnx("Network error"); } else if(events & BEV_EVENT_TIMEOUT) { warnx("Timeout"); } delete_http2_session_data(session_data); }
/* writecb for bufferevent. To greaceful shutdown after sending or receiving GOAWAY, we check the some conditions on the nghttp2 library and output buffer of bufferevent. If it indicates we have no business to this session, tear down the connection. If the connection is not going to shutdown, we call session_send() to process pending data in the output buffer. This is necessary because we have a threshold on the buffer size to avoid too much buffering. See send_callback(). */ static void writecb(struct bufferevent *bev, void *ptr) { http2_session_data *session_data = (http2_session_data *)ptr; if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { return; } if (nghttp2_session_want_read(session_data->session) == 0 && nghttp2_session_want_write(session_data->session) == 0) { delete_http2_session_data(session_data); return; } if (session_send(session_data) != 0) { delete_http2_session_data(session_data); return; } }
/* readcb for bufferevent after client connection header was checked. */ static void readcb(struct bufferevent *bev, void *ptr) { http2_session_data *session_data = (http2_session_data*)ptr; if(session_recv(session_data) != 0) { delete_http2_session_data(session_data); return; } }
/* eventcb for bufferevent. For the purpose of simplicity and readability of the example program, we omitted the certificate and peer verification. After SSL/TLS handshake is over, initialize nghttp2 library session, and send client connection header. Then send HTTP request. */ static void eventcb(struct bufferevent *bev, short events, void *ptr) { http2_session_data *session_data = (http2_session_data *)ptr; if (events & BEV_EVENT_CONNECTED) { int fd = bufferevent_getfd(bev); int val = 1; const unsigned char *alpn = NULL; unsigned int alpnlen = 0; SSL *ssl; fprintf(stderr, "Connected\n"); ssl = bufferevent_openssl_get_ssl(session_data->bev); #ifndef OPENSSL_NO_NEXTPROTONEG SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); #endif /* !OPENSSL_NO_NEXTPROTONEG */ #if OPENSSL_VERSION_NUMBER >= 0x10002000L if (alpn == NULL) { SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); } #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { fprintf(stderr, "h2 is not negotiated\n"); delete_http2_session_data(session_data); return; } setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); initialize_nghttp2_session(session_data); send_client_connection_header(session_data); submit_request(session_data); if (session_send(session_data) != 0) { delete_http2_session_data(session_data); } return; } if (events & BEV_EVENT_EOF) { warnx("Disconnected from the remote host"); } else if (events & BEV_EVENT_ERROR) { warnx("Network error"); } else if (events & BEV_EVENT_TIMEOUT) { warnx("Timeout"); } delete_http2_session_data(session_data); }
/* eventcb for bufferevent */ static void eventcb(struct bufferevent *bev, short events, void *ptr) { http2_session_data *session_data = (http2_session_data *)ptr; if (events & BEV_EVENT_CONNECTED) { const unsigned char *alpn = NULL; unsigned int alpnlen = 0; SSL *ssl; (void)bev; fprintf(stderr, "%s connected\n", session_data->client_addr); ssl = bufferevent_openssl_get_ssl(session_data->bev); SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); #if OPENSSL_VERSION_NUMBER >= 0x10002000L if (alpn == NULL) { SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); } #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); delete_http2_session_data(session_data); return; } initialize_nghttp2_session(session_data); if (send_server_connection_header(session_data) != 0 || session_send(session_data) != 0) { delete_http2_session_data(session_data); return; } return; } if (events & BEV_EVENT_EOF) { fprintf(stderr, "%s EOF\n", session_data->client_addr); } else if (events & BEV_EVENT_ERROR) { fprintf(stderr, "%s network error\n", session_data->client_addr); } else if (events & BEV_EVENT_TIMEOUT) { fprintf(stderr, "%s timeout\n", session_data->client_addr); } delete_http2_session_data(session_data); }
/* writecb for bufferevent. To greaceful shutdown after sending or receiving GOAWAY, we check the some conditions on the nghttp2 library and output buffer of bufferevent. If it indicates we have no business to this session, tear down the connection. */ static void writecb(struct bufferevent *bev, void *ptr) { http2_session_data *session_data = (http2_session_data *)ptr; (void)bev; if (nghttp2_session_want_read(session_data->session) == 0 && nghttp2_session_want_write(session_data->session) == 0 && evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) { delete_http2_session_data(session_data); } }
/* readcb for bufferevent. Here we get the data from the input buffer of bufferevent and feed them to nghttp2 library. This may invoke nghttp2 callbacks. It may also queues the frame in nghttp2 session context. To send them, we call session_send() in the end. */ static void readcb(struct bufferevent *bev, void *ptr) { http2_session_data *session_data = (http2_session_data *)ptr; ssize_t readlen; struct evbuffer *input = bufferevent_get_input(bev); size_t datalen = evbuffer_get_length(input); unsigned char *data = evbuffer_pullup(input, -1); readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); if (readlen < 0) { warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); delete_http2_session_data(session_data); return; } if (evbuffer_drain(input, (size_t)readlen) != 0) { warnx("Fatal error: evbuffer_drain failed"); delete_http2_session_data(session_data); return; } if (session_send(session_data) != 0) { delete_http2_session_data(session_data); return; } }
/* eventcb for bufferevent */ static void eventcb(struct bufferevent *bev, short events, void *ptr) { http2_session_data *session_data = (http2_session_data*)ptr; if(events & BEV_EVENT_CONNECTED) { fprintf(stderr, "%s connected\n", session_data->client_addr); return; } if(events & BEV_EVENT_EOF) { fprintf(stderr, "%s disconnected\n", session_data->client_addr); } else if(events & BEV_EVENT_ERROR) { fprintf(stderr, "%s network error\n", session_data->client_addr); } else if(events & BEV_EVENT_TIMEOUT) { fprintf(stderr, "%s timeout\n", session_data->client_addr); } delete_http2_session_data(session_data); }