/** * @brief Callback function invoked after the frame |frame| is sent. * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_frame_send_callback()`. * @param[in] session: nghttp2 session. * @param[in] frame: nghttp2 frame. * @param[in] user_data: The |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()` * @return The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and `nghttp2_session_send()` * and `nghttp2_session_mem_send()` functions immediately return :enum: * `NGHTTP2_ERR_CALLBACK_FAILURE`. */ static int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { size_t i; printf("on_frame_send_callback %d\n", frame->hd.type); switch (frame->hd.type) { case NGHTTP2_HEADERS: if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) { const nghttp2_nv *nva = frame->headers.nva; printf("[INFO] C --------> S (HEADERS)\n"); for (i = 0; i < frame->headers.nvlen; ++i) { printf("%s: %s\n", nva[i].name, nva[i].value); } } break; case NGHTTP2_RST_STREAM: printf("[INFO] C ------> S (RST_STREAM)\n"); break; case NGHTTP2_GOAWAY: printf("[INFO] C -------> S (GOAWAY)\n"); break; } return 0; }
/** * @brief Callback function invoked by `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` when a frame is received. * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen``member of their data structure are always * ``NULL`` and 0 respectively. The header name/value pairs are emitted via:type:`nghttp2_on_header_callback` * To set this callback to :type:`nghttp2_session_callbacks`, use`nghttp2_session_callbacks_set_on_frame_send_callback()`. * For HEADERS, PUSH_PROMISE and DATA frames, this callback may be called after stream is closed (see:type: * `nghttp2_on_stream_close_callback`). The application should check that stream is still alive using its own stream * management or :func:`nghttp2_session_get_stream_user_data()`. * Only HEADERS and DATA frame can signal the end of incoming data. If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` * is nonzero, the|frame| is the last frame from the remote peer in this stream. * This callback won't be called for CONTINUATION frames. * HEADERS/PUSH_PROMISE + CONTINUATIONs are treated as single frame. * @param[in] session: nghttp2 session. * @param[in] frame: nghttp2 frame. * @param[in] user_data: The |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()` * @return The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and `nghttp2_session_send()` * and `nghttp2_session_mem_send()` functions immediately return :enum: * `NGHTTP2_ERR_CALLBACK_FAILURE`. */ static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { size_t i; printf("on_frame_recv_callback %d\n", frame->hd.type); switch (frame->hd.type) { case NGHTTP2_HEADERS: if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { const nghttp2_nv *nva = frame->headers.nva; struct Request *req; req = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if (req) { printf("[INFO] C <--------- S (HEADERS)\n"); for (i = 0; i < frame->headers.nvlen; ++i) { printf("%s %s\r\n", nva[i].name, nva[i].value); } } } break; case NGHTTP2_RST_STREAM: printf("[INFO] C <--------- S (RST_STREAM)\n"); break; case NGHTTP2_GOAWAY: printf("[INFO] C <-------- S (GOAWAY)\n"); break; case NGHTTP2_DATA: if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { printf("end stream flag\r\n"); } break; } return 0; }
static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *userp) { struct SessionHandle *data_s; struct HTTP *stream; (void)session; (void)stream_id; (void)userp; if(stream_id) { /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) { /* We could get stream ID not in the hash. For example, if we decided to reject stream (e.g., PUSH_PROMISE). */ return 0; } DEBUGF(infof(data_s, "on_stream_close(), error_code = %d, stream %u\n", error_code, stream_id)); stream = data_s->req.protop; if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; stream->error_code = error_code; stream->closed = TRUE; /* remove the entry from the hash as the stream is now gone */ nghttp2_session_set_stream_user_data(session, stream_id, 0); DEBUGF(infof(data_s, "Removed stream %u hash!\n", stream_id)); } 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, uint8_t flags, void *user_data) { http2_stream_data *stream_data; const char PATH[] = ":path"; (void)flags; (void)user_data; 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 || 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) { size_t i; (void)user_data; switch (frame->hd.type) { case NGHTTP2_HEADERS: if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { const nghttp2_nv *nva = frame->headers.nva; struct Request *req; req = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if (req) { printf("[INFO] C <---------------------------- S (HEADERS)\n"); for (i = 0; i < frame->headers.nvlen; ++i) { fwrite(nva[i].name, 1, nva[i].namelen, stdout); printf(": "); fwrite(nva[i].value, 1, nva[i].valuelen, stdout); printf("\n"); } } } break; case NGHTTP2_RST_STREAM: printf("[INFO] C <---------------------------- S (RST_STREAM)\n"); break; case NGHTTP2_GOAWAY: printf("[INFO] C <---------------------------- S (GOAWAY)\n"); break; } return 0; }
static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *userp) { struct HTTP *stream; struct SessionHandle *data_s; size_t nread; struct connectdata *conn = (struct connectdata *)userp; (void)session; (void)flags; (void)data; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; stream = data_s->req.protop; if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; nread = MIN(stream->len, len); memcpy(&stream->mem[stream->memlen], data, nread); stream->len -= nread; stream->memlen += nread; data_s->state.drain++; /* if we receive data for another handle, wake that up */ if(conn->data != data_s) Curl_expire(data_s, 1); /* TODO: fix so that this can be set to 0 for immediately? */ DEBUGF(infof(data_s, "%zu data received for stream %u " "(%zu left in buffer %p, total %zu)\n", nread, stream_id, stream->len, stream->mem, stream->memlen)); if(nread < len) { stream->pausedata = data + nread; stream->pauselen = len - nread; DEBUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer" ", stream %u\n", len - nread, stream_id)); data_s->easy_conn->proto.httpc.pause_stream_id = stream_id; return NGHTTP2_ERR_PAUSE; } return 0; }
static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct SessionHandle *data_s = NULL; (void)userp; data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(data_s) { DEBUGF(infof(data_s, "on_begin_headers() was called\n")); } return 0; }
static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, void *user_data) { http2_session_data *session_data = (http2_session_data*)user_data; http2_stream_data *stream_data; stream_data = nghttp2_session_get_stream_user_data(session, stream_id); remove_stream(session_data, stream_data); delete_http2_stream_data(stream_data); return 0; }
/** * @brief Callback function invoked when a chunk of data in DATA frame is received. * The implementation of nghttp2_on_data_chunk_recv_callback type. We use this function to print the received response body. * @param[in] session: nghttp2 session. * @param[in] flags: no using. * @param[in] stream_id: the stream ID this DATA frame belongs to. * @param[in] data: receive data. * @param[in] len: data length. * @param[in] user_data: The |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()` * @return The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and `nghttp2_session_send()` * and `nghttp2_session_mem_send()` functions immediately return :enum: * `NGHTTP2_ERR_CALLBACK_FAILURE`. */ static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { struct Request *req; req = nghttp2_session_get_stream_user_data(session, stream_id); if (req) { printf("[INFO] C <----------- S (DATA chunk)\n" "%lu bytes\n", (unsigned long int)len); //printf("data chunk %s\n", data); } return 0; }
static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct SessionHandle *data_s; (void)userp; data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(data_s) { DEBUGF(infof(data_s, "on_frame_send() was called, length = %zd\n", frame->hd.length)); } return 0; }
static int on_invalid_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *userp) { struct SessionHandle *data_s = NULL; (void)userp; data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(data_s) { DEBUGF(infof(data_s, "on_invalid_frame_recv() was called, error=%d:%s\n", lib_error_code, nghttp2_strerror(lib_error_code))); } return 0; }
static int on_frame_not_send(nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *userp) { struct SessionHandle *data_s; (void)userp; data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(data_s) { DEBUGF(infof(data_s, "on_frame_not_send() was called, lib_error_code = %d\n", lib_error_code)); } return 0; }
static ssize_t data_source_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *userp) { struct SessionHandle *data_s; struct HTTP *stream = NULL; size_t nread; (void)source; (void)userp; if(stream_id) { /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; stream = data_s->req.protop; if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; } else return NGHTTP2_ERR_INVALID_ARGUMENT; nread = MIN(stream->upload_len, length); if(nread > 0) { memcpy(buf, stream->upload_mem, nread); stream->upload_mem += nread; stream->upload_len -= nread; stream->upload_left -= nread; } if(stream->upload_left == 0) *data_flags = 1; else if(nread == 0) return NGHTTP2_ERR_DEFERRED; DEBUGF(infof(data_s, "data_source_read_callback: " "returns %zu bytes stream %u\n", nread, stream_id)); return nread; }
/** * @brief Callback function invoked when the stream |stream_id| is closed. * We use this function to know if the response is fully received. Since we just fetch 1 resource in this program, after * the response is received, we submit GOAWAY and close the session. * @param[in] session: nghttp2 session. * @param[in] stream_id: stream id. * @param[in] error_code: The reason of closure. * Usually one of :enum:`nghttp2_error_code`, but that is not guaranteed. The stream_user_data, which was specified in * `nghttp2_submit_request()` or `nghttp2_submit_headers()`, is still available in this function. * @param[in] user_data: The |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()` * @return The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and `nghttp2_session_send()` * and `nghttp2_session_mem_send()` functions immediately return :enum: * `NGHTTP2_ERR_CALLBACK_FAILURE`. */ static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) { struct Request *req; req = nghttp2_session_get_stream_user_data(session, stream_id); if (req) { int rv; rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); if (rv != 0) { printf("stream close nghttp2_session_terminate_session\r\n"); } } return 0; }
static int on_request_recv_callback(nghttp2_session *session, int32_t stream_id, void *user_data) { int fd; http2_session_data *session_data = (http2_session_data*)user_data; http2_stream_data *stream_data; nghttp2_nv hdrs[] = { MAKE_NV(":status", "200") }; char *rel_path; stream_data = (http2_stream_data*)nghttp2_session_get_stream_user_data (session, stream_id); if(!stream_data->request_path) { if(error_reply(session, stream_data) != 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } fprintf(stderr, "%s GET %s\n", session_data->client_addr, stream_data->request_path); if(!check_path(stream_data->request_path)) { if(error_reply(session, stream_data) != 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } for(rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path); fd = open(rel_path, O_RDONLY); if(fd == -1) { if(error_reply(session, stream_data) != 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } stream_data->fd = fd; if(send_response(session, stream_id, hdrs, ARRLEN(hdrs), fd) != 0) { close(fd); return NGHTTP2_ERR_CALLBACK_FAILURE; } 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; switch (frame->hd.type) { case NGHTTP2_DATA: case NGHTTP2_HEADERS: /* Check that the client request has finished */ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { stream_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); /* For DATA and HEADERS frame, this callback may be called after on_stream_close_callback. Check that stream still alive. */ if (!stream_data) { return 0; } return on_request_recv(session, session_data, stream_data); } break; default: break; } return 0; }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = NULL; struct http_conn *httpc = NULL; struct SessionHandle *data_s = NULL; struct HTTP *stream = NULL; static int lastStream = -1; int rv; size_t left, ncopy; int32_t stream_id = frame->hd.stream_id; (void)userp; if(!stream_id) { /* stream ID zero is for connection-oriented stuff */ return 0; } data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(lastStream != frame->hd.stream_id) { lastStream = frame->hd.stream_id; } if(!data_s) { DEBUGF(infof(conn->data, "No SessionHandle associated with stream: %x\n", stream_id)); return 0; } stream = data_s->req.protop; if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; DEBUGF(infof(data_s, "on_frame_recv() header %x stream %x\n", frame->hd.type, stream_id)); conn = data_s->easy_conn; assert(conn); assert(conn->data == data_s); httpc = &conn->proto.httpc; switch(frame->hd.type) { case NGHTTP2_DATA: /* If body started on this stream, then receiving DATA is illegal. */ if(!stream->bodystarted) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_PROTOCOL_ERROR); if(nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } } break; case NGHTTP2_HEADERS: if(frame->headers.cat == NGHTTP2_HCAT_REQUEST) break; if(stream->bodystarted) { /* Only valid HEADERS after body started is trailer HEADERS. We ignores trailer HEADERS for now. nghttp2 guarantees that it has END_STREAM flag set. */ break; } /* nghttp2 guarantees that :status is received, and we store it to stream->status_code */ DEBUGASSERT(stream->status_code != -1); /* Only final status code signals the end of header */ if(stream->status_code / 100 != 1) { stream->bodystarted = TRUE; stream->status_code = -1; } Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); left = stream->header_recvbuf->size_used - stream->nread_header_recvbuf; ncopy = MIN(stream->len, left); memcpy(&stream->mem[stream->memlen], stream->header_recvbuf->buffer + stream->nread_header_recvbuf, ncopy); stream->nread_header_recvbuf += ncopy; DEBUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p\n", ncopy, stream_id, stream->mem)); stream->len -= ncopy; stream->memlen += ncopy; data_s->state.drain++; Curl_expire(data_s, 1); break; case NGHTTP2_PUSH_PROMISE: rv = push_promise(data_s, conn, &frame->push_promise); if(rv) { /* deny! */ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL); if(nghttp2_is_fatal(rv)) { return rv; } } break; case NGHTTP2_SETTINGS: { uint32_t max_conn = httpc->settings.max_concurrent_streams; DEBUGF(infof(conn->data, "Got SETTINGS for stream %u!\n", stream_id)); httpc->settings.max_concurrent_streams = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); httpc->settings.enable_push = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_ENABLE_PUSH); DEBUGF(infof(conn->data, "MAX_CONCURRENT_STREAMS == %d\n", httpc->settings.max_concurrent_streams)); DEBUGF(infof(conn->data, "ENABLE_PUSH == %s\n", httpc->settings.enable_push?"TRUE":"false")); if(max_conn != httpc->settings.max_concurrent_streams) { /* only signal change if the value actually changed */ infof(conn->data, "Connection state changed (MAX_CONCURRENT_STREAMS updated)!\n"); Curl_multi_connchanged(conn->data->multi); } } break; default: DEBUGF(infof(conn->data, "Got frame type %x for stream %u!\n", frame->hd.type, stream_id)); break; } return 0; }
/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp) { struct HTTP *stream; struct SessionHandle *data_s; int32_t stream_id = frame->hd.stream_id; (void)flags; (void)userp; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; stream = data_s->req.protop; if(!stream) { failf(data_s, "Internal NULL stream! 5\n"); return NGHTTP2_ERR_CALLBACK_FAILURE; } if(stream->bodystarted) /* Ignore trailer or HEADERS not mapped to HTTP semantics. The consequence is handled in on_frame_recv(). */ return 0; /* Store received PUSH_PROMISE headers to be used when the subsequent PUSH_PROMISE callback comes */ if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { char *h; if(!stream->push_headers) { stream->push_headers_alloc = 10; stream->push_headers = malloc(stream->push_headers_alloc * sizeof(char *)); stream->push_headers_used = 0; } else if(stream->push_headers_used == stream->push_headers_alloc) { char **headp; stream->push_headers_alloc *= 2; headp = realloc(stream->push_headers, stream->push_headers_alloc * sizeof(char *)); if(!headp) { free(stream->push_headers); stream->push_headers = NULL; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } stream->push_headers = headp; } h = aprintf("%s:%s", name, value); if(h) stream->push_headers[stream->push_headers_used++] = h; return 0; } if(namelen == sizeof(":status") - 1 && memcmp(":status", name, namelen) == 0) { /* nghttp2 guarantees :status is received first and only once, and value is 3 digits status code, and decode_status_code always succeeds. */ stream->status_code = decode_status_code(value, valuelen); DEBUGASSERT(stream->status_code != -1); Curl_add_buffer(stream->header_recvbuf, "HTTP/2.0 ", 9); Curl_add_buffer(stream->header_recvbuf, value, valuelen); Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); data_s->state.drain++; Curl_expire(data_s, 1); DEBUGF(infof(data_s, "h2 status: HTTP/2 %03d\n", stream->status_code)); return 0; } /* nghttp2 guarantees that namelen > 0, and :status was already received, and this is not pseudo-header field . */ /* convert to a HTTP1-style header */ Curl_add_buffer(stream->header_recvbuf, name, namelen); Curl_add_buffer(stream->header_recvbuf, ":", 1); Curl_add_buffer(stream->header_recvbuf, value, valuelen); Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); data_s->state.drain++; Curl_expire(data_s, 1); DEBUGF(infof(data_s, "h2 header: %.*s: %.*s\n", namelen, name, valuelen, value)); return 0; /* 0 is successful */ }