1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <apr_strings.h>
21 #include <http_core.h>
22 #include <http_protocol.h>
24 #include <http_connection.h>
25 #include <scoreboard.h>
27 #include "h2_private.h"
29 #include "h2_config.h"
30 #include "h2_conn_io.h"
35 #include "h2_stream.h"
36 #include "h2_request.h"
37 #include "h2_headers.h"
38 #include "h2_stream.h"
39 #include "h2_session.h"
41 #include "h2_version.h"
43 #include "h2_filter.h"
46 #define H2MIN(x,y) ((x) < (y) ? (x) : (y))
48 static apr_status_t recv_RAW_DATA(conn_rec *c, h2_filter_cin *cin,
49 apr_bucket *b, apr_read_type_e block)
51 h2_session *session = cin->session;
52 apr_status_t status = APR_SUCCESS;
57 status = apr_bucket_read(b, &data, &len, block);
59 while (status == APR_SUCCESS && len > 0) {
60 n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
62 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
63 H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
66 if (nghttp2_is_fatal((int)n)) {
67 h2_session_event(session, H2_SESSION_EV_PROTO_ERROR,
68 (int)n, nghttp2_strerror((int)n));
69 status = APR_EGENERAL;
73 session->io.bytes_read += n;
85 static apr_status_t recv_RAW_brigade(conn_rec *c, h2_filter_cin *cin,
86 apr_bucket_brigade *bb,
87 apr_read_type_e block)
89 apr_status_t status = APR_SUCCESS;
93 h2_util_bb_log(c, c->id, APLOG_TRACE2, "RAW_in", bb);
94 while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
95 b = APR_BRIGADE_FIRST(bb);
97 if (APR_BUCKET_IS_METADATA(b)) {
101 status = recv_RAW_DATA(c, cin, b, block);
104 apr_bucket_delete(b);
107 if (!consumed && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
113 h2_filter_cin *h2_filter_cin_create(h2_session *session)
117 cin = apr_pcalloc(session->pool, sizeof(*cin));
121 cin->session = session;
125 void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout)
127 cin->timeout = timeout;
130 apr_status_t h2_filter_core_input(ap_filter_t* f,
131 apr_bucket_brigade* brigade,
132 ap_input_mode_t mode,
133 apr_read_type_e block,
136 h2_filter_cin *cin = f->ctx;
137 apr_status_t status = APR_SUCCESS;
138 apr_interval_time_t saved_timeout = UNSET;
139 const int trace1 = APLOGctrace1(f->c);
142 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
143 "h2_session(%ld): read, %s, mode=%d, readbytes=%ld",
144 (long)f->c->id, (block == APR_BLOCK_READ)?
145 "BLOCK_READ" : "NONBLOCK_READ", mode, (long)readbytes);
148 if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
149 return ap_get_brigade(f->next, brigade, mode, block, readbytes);
152 if (mode != AP_MODE_READBYTES) {
153 return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN;
157 cin->bb = apr_brigade_create(cin->session->pool, f->c->bucket_alloc);
161 cin->socket = ap_get_conn_socket(f->c);
164 if (APR_BRIGADE_EMPTY(cin->bb)) {
165 /* We only do a blocking read when we have no streams to process. So,
166 * in httpd scoreboard lingo, we are in a KEEPALIVE connection state.
168 if (block == APR_BLOCK_READ) {
169 if (cin->timeout > 0) {
170 apr_socket_timeout_get(cin->socket, &saved_timeout);
171 apr_socket_timeout_set(cin->socket, cin->timeout);
174 status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
176 if (saved_timeout != UNSET) {
177 apr_socket_timeout_set(cin->socket, saved_timeout);
183 status = recv_RAW_brigade(f->c, cin, cin->bb, block);
189 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
190 "h2_session(%ld): read", f->c->id);
194 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, APLOGNO(03046)
195 "h2_session(%ld): error reading", f->c->id);
201 /*******************************************************************************
202 * http2 connection status handler + stream out source
203 ******************************************************************************/
206 apr_bucket_refcount refcount;
207 h2_bucket_event_cb *cb;
209 } h2_bucket_observer;
211 static apr_status_t bucket_read(apr_bucket *b, const char **str,
212 apr_size_t *len, apr_read_type_e block)
221 static void bucket_destroy(void *data)
223 h2_bucket_observer *h = data;
224 if (apr_bucket_shared_destroy(h)) {
226 h->cb(h->ctx, H2_BUCKET_EV_BEFORE_DESTROY, NULL);
232 apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
235 h2_bucket_observer *br;
237 br = apr_bucket_alloc(sizeof(*br), b->list);
241 b = apr_bucket_shared_make(b, br, 0, 0);
242 b->type = &h2_bucket_type_observer;
246 apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list,
247 h2_bucket_event_cb *cb, void *ctx)
249 apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
252 b->free = apr_bucket_free;
254 b = h2_bucket_observer_make(b, cb, ctx);
258 apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event)
260 if (H2_BUCKET_IS_OBSERVER(b)) {
261 h2_bucket_observer *l = (h2_bucket_observer *)b->data;
262 return l->cb(l->ctx, event, b);
267 const apr_bucket_type_t h2_bucket_type_observer = {
268 "H2OBS", 5, APR_BUCKET_METADATA,
271 apr_bucket_setaside_noop,
272 apr_bucket_split_notimpl,
273 apr_bucket_shared_copy
276 apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
277 apr_bucket_brigade *dest,
278 const apr_bucket *src)
280 if (H2_BUCKET_IS_OBSERVER(src)) {
281 h2_bucket_observer *l = (h2_bucket_observer *)src->data;
282 apr_bucket *b = h2_bucket_observer_create(dest->bucket_alloc,
284 APR_BRIGADE_INSERT_TAIL(dest, b);
287 h2_bucket_observer_fire(b, H2_BUCKET_EV_BEFORE_MASTER_SEND);
293 static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
299 rv = apr_brigade_vprintf(bb, NULL, NULL, fmt, args);
305 static void add_settings(apr_bucket_brigade *bb, h2_session *s, int last)
307 h2_mplx *m = s->mplx;
309 bbout(bb, " \"settings\": {\n");
310 bbout(bb, " \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", m->max_streams);
311 bbout(bb, " \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 16*1024);
312 bbout(bb, " \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n",
313 h2_config_geti(s->config, H2_CONF_WIN_SIZE));
314 bbout(bb, " \"SETTINGS_ENABLE_PUSH\": %d\n", h2_session_push_enabled(s));
315 bbout(bb, " }%s\n", last? "" : ",");
318 static void add_peer_settings(apr_bucket_brigade *bb, h2_session *s, int last)
320 bbout(bb, " \"peerSettings\": {\n");
321 bbout(bb, " \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n",
322 nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS));
323 bbout(bb, " \"SETTINGS_MAX_FRAME_SIZE\": %d,\n",
324 nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_FRAME_SIZE));
325 bbout(bb, " \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n",
326 nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE));
327 bbout(bb, " \"SETTINGS_ENABLE_PUSH\": %d,\n",
328 nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_ENABLE_PUSH));
329 bbout(bb, " \"SETTINGS_HEADER_TABLE_SIZE\": %d,\n",
330 nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE));
331 bbout(bb, " \"SETTINGS_MAX_HEADER_LIST_SIZE\": %d\n",
332 nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE));
333 bbout(bb, " }%s\n", last? "" : ",");
337 apr_bucket_brigade *bb;
342 static int add_stream(h2_stream *stream, void *ctx)
344 stream_ctx_t *x = ctx;
345 int32_t flowIn, flowOut;
347 flowIn = nghttp2_session_get_stream_effective_local_window_size(x->s->ngh2, stream->id);
348 flowOut = nghttp2_session_get_stream_remote_window_size(x->s->ngh2, stream->id);
349 bbout(x->bb, "%s\n \"%d\": {\n", (x->idx? "," : ""), stream->id);
350 bbout(x->bb, " \"state\": \"%s\",\n", h2_stream_state_str(stream));
351 bbout(x->bb, " \"created\": %f,\n", ((double)stream->created)/APR_USEC_PER_SEC);
352 bbout(x->bb, " \"flowIn\": %d,\n", flowIn);
353 bbout(x->bb, " \"flowOut\": %d,\n", flowOut);
354 bbout(x->bb, " \"dataIn\": %"APR_UINT64_T_FMT",\n", stream->in_data_octets);
355 bbout(x->bb, " \"dataOut\": %"APR_UINT64_T_FMT"\n", stream->out_data_octets);
362 static void add_streams(apr_bucket_brigade *bb, h2_session *s, int last)
369 bbout(bb, " \"streams\": {");
370 h2_mplx_stream_do(s->mplx, add_stream, &x);
371 bbout(bb, "\n }%s\n", last? "" : ",");
374 static void add_push(apr_bucket_brigade *bb, h2_session *s,
375 h2_stream *stream, int last)
377 h2_push_diary *diary;
380 bbout(bb, " \"push\": {\n");
381 diary = s->push_diary;
384 const char *base64_digest;
387 status = h2_push_diary_digest_get(diary, bb->p, 256,
388 stream->request->authority,
390 if (status == APR_SUCCESS) {
391 base64_digest = h2_util_base64url_encode(data, len, bb->p);
392 bbout(bb, " \"cacheDigest\": \"%s\",\n", base64_digest);
395 bbout(bb, " \"promises\": %d,\n", s->pushes_promised);
396 bbout(bb, " \"submits\": %d,\n", s->pushes_submitted);
397 bbout(bb, " \"resets\": %d\n", s->pushes_reset);
398 bbout(bb, " }%s\n", last? "" : ",");
401 static void add_in(apr_bucket_brigade *bb, h2_session *s, int last)
403 bbout(bb, " \"in\": {\n");
404 bbout(bb, " \"requests\": %d,\n", s->remote.emitted_count);
405 bbout(bb, " \"resets\": %d, \n", s->streams_reset);
406 bbout(bb, " \"frames\": %ld,\n", (long)s->frames_received);
407 bbout(bb, " \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_read);
408 bbout(bb, " }%s\n", last? "" : ",");
411 static void add_out(apr_bucket_brigade *bb, h2_session *s, int last)
413 bbout(bb, " \"out\": {\n");
414 bbout(bb, " \"responses\": %d,\n", s->responses_submitted);
415 bbout(bb, " \"frames\": %ld,\n", (long)s->frames_sent);
416 bbout(bb, " \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_written);
417 bbout(bb, " }%s\n", last? "" : ",");
420 static void add_stats(apr_bucket_brigade *bb, h2_session *s,
421 h2_stream *stream, int last)
423 bbout(bb, " \"stats\": {\n");
426 add_push(bb, s, stream, 1);
427 bbout(bb, " }%s\n", last? "" : ",");
430 static apr_status_t h2_status_insert(h2_task *task, apr_bucket *b)
432 conn_rec *c = task->c->master;
433 h2_ctx *h2ctx = h2_ctx_get(c, 0);
436 apr_bucket_brigade *bb;
438 int32_t connFlowIn, connFlowOut;
441 if (!h2ctx || (session = h2_ctx_session_get(h2ctx)) == NULL) {
445 stream = h2_session_stream_get(session, task->stream_id);
447 /* stream already done */
451 bb = apr_brigade_create(stream->pool, c->bucket_alloc);
453 connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2);
454 connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
457 bbout(bb, " \"version\": \"draft-01\",\n");
458 add_settings(bb, session, 0);
459 add_peer_settings(bb, session, 0);
460 bbout(bb, " \"connFlowIn\": %d,\n", connFlowIn);
461 bbout(bb, " \"connFlowOut\": %d,\n", connFlowOut);
462 bbout(bb, " \"sentGoAway\": %d,\n", session->local.shutdown);
464 add_streams(bb, session, 0);
466 add_stats(bb, session, stream, 1);
469 while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
470 APR_BUCKET_REMOVE(e);
471 APR_BUCKET_INSERT_AFTER(b, e);
474 apr_brigade_destroy(bb);
479 static apr_status_t status_event(void *ctx, h2_bucket_event event,
484 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, task->c->master,
485 "status_event(%s): %d", task->id, event);
487 case H2_BUCKET_EV_BEFORE_MASTER_SEND:
488 h2_status_insert(task, b);
496 int h2_filter_h2_status_handler(request_rec *r)
498 h2_ctx *ctx = h2_ctx_rget(r);
499 conn_rec *c = r->connection;
501 apr_bucket_brigade *bb;
505 if (strcmp(r->handler, "http2-status")) {
508 if (r->method_number != M_GET && r->method_number != M_POST) {
512 task = ctx? h2_ctx_get_task(ctx) : NULL;
515 if ((status = ap_discard_request_body(r)) != OK) {
519 /* We need to handle the actual output on the main thread, as
520 * we need to access h2_session information. */
524 apr_table_unset(r->headers_out, "Content-Length");
525 /* Discourage content-encodings */
526 apr_table_unset(r->headers_out, "Content-Encoding");
527 apr_table_setn(r->subprocess_env, "no-brotli", "1");
528 apr_table_setn(r->subprocess_env, "no-gzip", "1");
530 ap_set_content_type(r, "application/json");
531 apr_table_setn(r->notes, H2_FILTER_DEBUG_NOTE, "on");
533 bb = apr_brigade_create(r->pool, c->bucket_alloc);
534 b = h2_bucket_observer_create(c->bucket_alloc, status_event, task);
535 APR_BRIGADE_INSERT_TAIL(bb, b);
536 b = apr_bucket_eos_create(c->bucket_alloc);
537 APR_BRIGADE_INSERT_TAIL(bb, b);
539 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
540 "status_handler(%s): checking for incoming trailers",
542 if (r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
543 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
544 "status_handler(%s): seeing incoming trailers",
546 apr_table_setn(r->trailers_out, "h2-trailers-in",
547 apr_itoa(r->pool, 1));
550 status = ap_pass_brigade(r->output_filters, bb);
551 if (status == APR_SUCCESS
552 || r->status != HTTP_OK
557 /* no way to know what type of error occurred */
558 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r,
559 "status_handler(%s): ap_pass_brigade failed",
561 return AP_FILTER_ERROR;