static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *c = &conn->proto.httpc; int rv; (void)session; (void)frame; infof(conn->data, "on_frame_recv() was called with header %x\n", frame->hd.type); switch(frame->hd.type) { case NGHTTP2_HEADERS: if(frame->headers.cat != NGHTTP2_HCAT_RESPONSE) break; c->bodystarted = TRUE; Curl_add_buffer(c->header_recvbuf, "\r\n", 2); c->nread_header_recvbuf = c->len < c->header_recvbuf->size_used ? c->len : c->header_recvbuf->size_used; memcpy(c->mem, c->header_recvbuf->buffer, c->nread_header_recvbuf); c->mem += c->nread_header_recvbuf; c->len -= c->nread_header_recvbuf; break; case NGHTTP2_PUSH_PROMISE: rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_CANCEL); if(nghttp2_is_fatal(rv)) { return rv; } break; } return 0; }
/* Start submitting the response to a stream request. This is possible * once we have all the response headers. The response body will be * read by the session using the callback we supply. */ apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream) { apr_status_t status = APR_SUCCESS; int rv = 0; AP_DEBUG_ASSERT(session); AP_DEBUG_ASSERT(stream); AP_DEBUG_ASSERT(stream->response); if (stream->response->ngheader) { rv = submit_response(session, stream->response); } else { rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, stream->id, NGHTTP2_PROTOCOL_ERROR); } if (nghttp2_is_fatal(rv)) { status = APR_EGENERAL; h2_session_abort_int(session, rv); ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02940) "submit_response: %s", nghttp2_strerror(rv)); } return status; }
static int error_reply(nghttp2_session *session, http2_stream_data *stream_data) { int rv; int pipefd[2]; nghttp2_nv hdrs[] = { MAKE_NV(":status", "404") }; rv = pipe(pipefd); if(rv != 0) { warn("Could not create pipe"); rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_data->stream_id, NGHTTP2_INTERNAL_ERROR); if(rv != 0) { warnx("Fatal error: %s", nghttp2_strerror(rv)); return -1; } return 0; } write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1); close(pipefd[1]); stream_data->fd = pipefd[0]; if(send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), pipefd[0]) != 0) { close(pipefd[0]); return -1; } return 0; }
static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *userp) { int rv; h2_session *session = (h2_session *)userp; h2_stream * stream; apr_status_t status; (void)flags; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02919) "h2_session: stream(%ld-%d): on_data_chunk for unknown stream", session->id, (int)stream_id); rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } status = h2_stream_write_data(stream, (const char *)data, len); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, "h2_stream(%ld-%d): written DATA, length %d", session->id, stream_id, (int)len); if (status != APR_SUCCESS) { rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } } return 0; }
/* Start submitting the response to a stream request. This is possible * once we have all the response headers. The response body will be * read by the session using the callback we supply. */ apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream, h2_response *head) { assert(session); apr_status_t status = APR_SUCCESS; int rv = 0; if (head->http_status) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_stream(%ld-%d): submitting response %s with %d headers", session->id, stream->id, head->http_status, (int)head->nvlen); assert(head->nvlen); nghttp2_data_provider provider = { stream->id, stream_data_cb }; rv = nghttp2_submit_response(session->ngh2, stream->id, &head->nv, head->nvlen, &provider); if (rv != 0) { ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "h2_stream(%ld-%d): submit_response: %s", session->id, stream->id, nghttp2_strerror(rv)); } else { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): submitted response %s with %d " "headers, rv=%d", session->id, stream->id, head->http_status, (int)head->nvlen, rv); } } else { rv = nghttp2_submit_rst_stream(session->ngh2, 0, stream->id, NGHTTP2_ERR_INVALID_STATE); } if (nghttp2_is_fatal(rv)) { status = APR_EGENERAL; h2_session_abort_int(session, rv); ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "submit_response: %s", nghttp2_strerror(rv)); } return status; }
/* 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 connectdata *conn = (struct connectdata *)userp; struct http_conn *c = &conn->proto.httpc; int rv; (void)session; (void)frame; (void)flags; if(frame->hd.stream_id != c->stream_id) { return 0; } if(c->bodystarted) { /* Ignore trailer or HEADERS not mapped to HTTP semantics. The consequence is handled in on_frame_recv(). */ return 0; } if(!nghttp2_check_header_name(name, namelen) || !nghttp2_check_header_value(value, valuelen)) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); if(nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } if(namelen == sizeof(":status") - 1 && memcmp(STATUS, name, namelen) == 0) { /* :status must appear exactly once. */ if(c->status_code != -1 || (c->status_code = decode_status_code(value, valuelen)) == -1) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); if(nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } Curl_add_buffer(c->header_recvbuf, "HTTP/2.0 ", 9); Curl_add_buffer(c->header_recvbuf, value, valuelen); Curl_add_buffer(c->header_recvbuf, "\r\n", 2); return 0; } else { /* Here we are sure that namelen > 0 because of nghttp2_check_header_name(). Pseudo header other than :status is illegal. */ if(c->status_code == -1 || name[0] == ':') { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); if(nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } /* convert to a HTTP1-style header */ infof(conn->data, "got header\n"); Curl_add_buffer(c->header_recvbuf, name, namelen); Curl_add_buffer(c->header_recvbuf, ":", 1); Curl_add_buffer(c->header_recvbuf, value, valuelen); Curl_add_buffer(c->header_recvbuf, "\r\n", 2); } return 0; /* 0 is successful */ }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *c = &conn->proto.httpc; int rv; size_t left, ncopy; (void)session; (void)frame; infof(conn->data, "on_frame_recv() was called with header %x\n", frame->hd.type); switch(frame->hd.type) { case NGHTTP2_DATA: /* If body started, then receiving DATA is illegal. */ if(!c->bodystarted) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.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(c->bodystarted) { /* Only valid HEADERS after body started is trailer header, which is not fully supported in this code. If HEADERS is not trailer, then it is a PROTOCOL_ERROR. */ if((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); if(nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } } break; } if(c->status_code == -1) { /* No :status header field means PROTOCOL_ERROR. */ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); if(nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } break; } /* Only final status code signals the end of header */ if(c->status_code / 100 != 1) { c->bodystarted = TRUE; } c->status_code = -1; Curl_add_buffer(c->header_recvbuf, "\r\n", 2); left = c->header_recvbuf->size_used - c->nread_header_recvbuf; ncopy = c->len < left ? c->len : left; memcpy(c->mem, c->header_recvbuf->buffer + c->nread_header_recvbuf, ncopy); c->nread_header_recvbuf += ncopy; c->mem += ncopy; c->len -= ncopy; break; case NGHTTP2_PUSH_PROMISE: rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_CANCEL); if(nghttp2_is_fatal(rv)) { return rv; } break; } return 0; }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *httpc = &conn->proto.httpc; struct SessionHandle *data_s = NULL; struct HTTP *stream = NULL; int rv; size_t left, ncopy; int32_t stream_id = frame->hd.stream_id; (void)session; (void)frame; DEBUGF(infof(conn->data, "on_frame_recv() header %x stream %x\n", frame->hd.type, stream_id)); if(stream_id) { /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ data_s = Curl_hash_pick(&httpc->streamsh, &stream_id, sizeof(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! */ failf(conn->data, "Received frame on Stream ID: %x not in stream hash!", stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = data_s->req.protop; } 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: DEBUGF(infof(data_s, "Got PUSH_PROMISE, RST_STREAM it!\n")); 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; }
/** * nghttp2 session has received a complete frame. Most, it uses * for processing of internal state. HEADER and DATA frames however * we need to handle ourself. */ static int on_frame_recv_cb(nghttp2_session *ng2s, const nghttp2_frame *frame, void *userp) { int rv; h2_session *session = (h2_session *)userp; apr_status_t status = APR_SUCCESS; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } ++session->frames_received; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id, (long)session->frames_received, frame->hd.type); switch (frame->hd.type) { case NGHTTP2_HEADERS: { int eos; h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream == NULL) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02921) "h2_session: stream(%ld-%d): HEADERS frame " "for unknown stream", session->id, (int)frame->hd.stream_id); rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM); status = stream_end_headers(session, stream, eos); break; } case NGHTTP2_DATA: { h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream == NULL) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02922) "h2_session: stream(%ld-%d): DATA frame " "for unknown stream", session->id, (int)frame->hd.stream_id); rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } break; } case NGHTTP2_PRIORITY: { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_session: stream(%ld-%d): PRIORITY frame " " weight=%d, dependsOn=%d, exclusive=%d", session->id, (int)frame->hd.stream_id, frame->priority.pri_spec.weight, frame->priority.pri_spec.stream_id, frame->priority.pri_spec.exclusive); break; } default: if (APLOGctrace2(session->c)) { char buffer[256]; frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, "h2_session: on_frame_rcv %s", buffer); } break; } /* only DATA and HEADERS frame can bear END_STREAM flag. Other frame types may have other flag which has the same value, so we have to check the frame type first. */ if ((frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream != NULL) { status = h2_stream_write_eos(stream); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_stream(%ld-%d): input closed", session->id, (int)frame->hd.stream_id); } } if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02923) "h2_session: stream(%ld-%d): error handling frame", session->id, (int)frame->hd.stream_id); rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } return 0; }
static ssize_t stream_data_cb(nghttp2_session *ng2s, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *puser) { h2_session *session = (h2_session *)puser; apr_size_t nread = length; int eos = 0; apr_status_t status; h2_stream *stream; AP_DEBUG_ASSERT(session); /* The session wants to send more DATA for the stream. We need * to find out how much of the requested length we can send without * blocking. * Indicate EOS when we encounter it or DEFERRED if the stream * should be suspended. * TODO: for handling of TRAILERS, the EOF indication needs * to be aware of that. */ (void)ng2s; (void)buf; (void)source; stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02937) "h2_stream(%ld-%d): data requested but stream not found", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream)); status = h2_stream_prep_read(stream, &nread, &eos); if (nread) { *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; } switch (status) { case APR_SUCCESS: break; case APR_ECONNRESET: return nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, stream->id, stream->rst_error); case APR_EAGAIN: /* If there is no data available, our session will automatically * suspend this stream and not ask for more data until we resume * it. Remember at our h2_stream that we need to do this. */ nread = 0; h2_stream_set_suspended(stream, 1); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): suspending stream", session->id, (int)stream_id); return NGHTTP2_ERR_DEFERRED; case APR_EOF: nread = 0; eos = 1; break; default: nread = 0; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02938) "h2_stream(%ld-%d): reading data", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } if (eos) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; } return (ssize_t)nread; }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = (struct connectdata *)userp; 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; 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++; { /* get the pointer from userp again since it was re-assigned above */ struct connectdata *conn_s = (struct connectdata *)userp; /* if we receive data for another handle, wake that up */ if(conn_s->data != data_s) 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; }
static void run_nghttp2_session_send(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; nghttp2_nv nv[] = {MAKE_NV(":host", "example.org"), MAKE_NV(":scheme", "https")}; nghttp2_data_provider data_prd; nghttp2_settings_entry iv[2]; my_user_data ud; int rv; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = null_send_callback; data_prd.read_callback = fixed_length_data_source_read_callback; ud.data_source_length = 64 * 1024; iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; iv[0].value = 4096; iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[1].value = 100; rv = nghttp2_session_client_new3(&session, &callbacks, &ud, NULL, nghttp2_mem_fm()); if (rv != 0) { goto client_new_fail; } rv = nghttp2_submit_request(session, NULL, nv, ARRLEN(nv), &data_prd, NULL); if (rv < 0) { goto fail; } rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, nv, ARRLEN(nv), NULL); if (rv < 0) { goto fail; } rv = nghttp2_session_send(session); if (rv != 0) { goto fail; } /* The HEADERS submitted by the previous nghttp2_submit_headers will have stream ID 3. Send HEADERS to that stream. */ rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, NULL, nv, ARRLEN(nv), NULL); if (rv != 0) { goto fail; } rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 3, &data_prd); if (rv != 0) { goto fail; } rv = nghttp2_session_send(session); if (rv != 0) { goto fail; } rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 3, NGHTTP2_CANCEL); if (rv != 0) { goto fail; } rv = nghttp2_session_send(session); if (rv != 0) { goto fail; } /* Sending against half-closed stream */ rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, NULL, nv, ARRLEN(nv), NULL); if (rv != 0) { goto fail; } rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 3, &data_prd); if (rv != 0) { goto fail; } rv = nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); if (rv != 0) { goto fail; } rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2); if (rv != 0) { goto fail; } rv = nghttp2_session_send(session); if (rv != 0) { goto fail; } rv = nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 100, NGHTTP2_NO_ERROR, NULL, 0); if (rv != 0) { goto fail; } rv = nghttp2_session_send(session); if (rv != 0) { goto fail; } fail: nghttp2_session_del(session); client_new_fail:; }