]> granicus.if.org Git - apache/blob - modules/http2/h2_filter.c
On the 2.4.x branch:
[apache] / modules / http2 / h2_filter.c
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16  
17 #include <assert.h>
18
19 #include <apr_strings.h>
20 #include <httpd.h>
21 #include <http_core.h>
22 #include <http_protocol.h>
23 #include <http_log.h>
24 #include <http_connection.h>
25 #include <scoreboard.h>
26
27 #include "h2_private.h"
28 #include "h2.h"
29 #include "h2_config.h"
30 #include "h2_conn_io.h"
31 #include "h2_ctx.h"
32 #include "h2_mplx.h"
33 #include "h2_push.h"
34 #include "h2_task.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"
40 #include "h2_util.h"
41 #include "h2_version.h"
42
43 #include "h2_filter.h"
44
45 #define UNSET       -1
46 #define H2MIN(x,y) ((x) < (y) ? (x) : (y))
47
48 static apr_status_t recv_RAW_DATA(conn_rec *c, h2_filter_cin *cin, 
49                                   apr_bucket *b, apr_read_type_e block)
50 {
51     h2_session *session = cin->session;
52     apr_status_t status = APR_SUCCESS;
53     apr_size_t len;
54     const char *data;
55     ssize_t n;
56     
57     status = apr_bucket_read(b, &data, &len, block);
58     
59     while (status == APR_SUCCESS && len > 0) {
60         n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
61         
62         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
63                       H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
64                       (long)len, (long)n);
65         if (n < 0) {
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;
70             }
71         }
72         else {
73             session->io.bytes_read += n;
74             if (len <= n) {
75                 break;
76             }
77             len -= n;
78             data += n;
79         }
80     }
81     
82     return status;
83 }
84
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)
88 {
89     apr_status_t status = APR_SUCCESS;
90     apr_bucket* b;
91     int consumed = 0;
92     
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);
96
97         if (APR_BUCKET_IS_METADATA(b)) {
98             /* nop */
99         }
100         else {
101             status = recv_RAW_DATA(c, cin, b, block);
102         }
103         consumed = 1;
104         apr_bucket_delete(b);
105     }
106     
107     if (!consumed && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
108         return APR_EAGAIN;
109     }
110     return status;
111 }
112
113 h2_filter_cin *h2_filter_cin_create(h2_session *session)
114 {
115     h2_filter_cin *cin;
116     
117     cin = apr_pcalloc(session->pool, sizeof(*cin));
118     if (!cin) {
119         return NULL;
120     }
121     cin->session = session;
122     return cin;
123 }
124
125 void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout)
126 {
127     cin->timeout = timeout;
128 }
129
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,
134                                   apr_off_t readbytes) 
135 {
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);
140     
141     if (trace1) {
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);
146     }
147     
148     if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
149         return ap_get_brigade(f->next, brigade, mode, block, readbytes);
150     }
151     
152     if (mode != AP_MODE_READBYTES) {
153         return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN;
154     }
155     
156     if (!cin->bb) {
157         cin->bb = apr_brigade_create(cin->session->pool, f->c->bucket_alloc);
158     }
159
160     if (!cin->socket) {
161         cin->socket = ap_get_conn_socket(f->c);
162     }
163     
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.
167          */
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);
172             }
173         }
174         status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
175                                 block, readbytes);
176         if (saved_timeout != UNSET) {
177             apr_socket_timeout_set(cin->socket, saved_timeout);
178         }
179     }
180     
181     switch (status) {
182         case APR_SUCCESS:
183             status = recv_RAW_brigade(f->c, cin, cin->bb, block);
184             break;
185         case APR_EOF:
186         case APR_EAGAIN:
187         case APR_TIMEUP:
188             if (trace1) {
189                 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
190                               "h2_session(%ld): read", f->c->id);
191             }
192             break;
193         default:
194             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, APLOGNO(03046)
195                           "h2_session(%ld): error reading", f->c->id);
196             break;
197     }
198     return status;
199 }
200
201 /*******************************************************************************
202  * http2 connection status handler + stream out source
203  ******************************************************************************/
204
205 typedef struct {
206     apr_bucket_refcount refcount;
207     h2_bucket_event_cb *cb;
208     void *ctx;
209 } h2_bucket_observer;
210  
211 static apr_status_t bucket_read(apr_bucket *b, const char **str,
212                                 apr_size_t *len, apr_read_type_e block)
213 {
214     (void)b;
215     (void)block;
216     *str = NULL;
217     *len = 0;
218     return APR_SUCCESS;
219 }
220
221 static void bucket_destroy(void *data)
222 {
223     h2_bucket_observer *h = data;
224     if (apr_bucket_shared_destroy(h)) {
225         if (h->cb) {
226             h->cb(h->ctx, H2_BUCKET_EV_BEFORE_DESTROY, NULL);
227         }
228         apr_bucket_free(h);
229     }
230 }
231
232 apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
233                                  void *ctx)
234 {
235     h2_bucket_observer *br;
236
237     br = apr_bucket_alloc(sizeof(*br), b->list);
238     br->cb = cb;
239     br->ctx = ctx;
240
241     b = apr_bucket_shared_make(b, br, 0, 0);
242     b->type = &h2_bucket_type_observer;
243     return b;
244
245
246 apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list, 
247                                        h2_bucket_event_cb *cb, void *ctx)
248 {
249     apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
250
251     APR_BUCKET_INIT(b);
252     b->free = apr_bucket_free;
253     b->list = list;
254     b = h2_bucket_observer_make(b, cb, ctx);
255     return b;
256 }
257                                        
258 apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event)
259 {
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);
263     }
264     return APR_EINVAL;
265 }
266
267 const apr_bucket_type_t h2_bucket_type_observer = {
268     "H2OBS", 5, APR_BUCKET_METADATA,
269     bucket_destroy,
270     bucket_read,
271     apr_bucket_setaside_noop,
272     apr_bucket_split_notimpl,
273     apr_bucket_shared_copy
274 };
275
276 apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
277                                     apr_bucket_brigade *dest,
278                                     const apr_bucket *src)
279 {
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, 
283                                                   l->cb, l->ctx);
284         APR_BRIGADE_INSERT_TAIL(dest, b);
285         l->cb = NULL;
286         l->ctx = NULL;
287         h2_bucket_observer_fire(b, H2_BUCKET_EV_BEFORE_MASTER_SEND);
288         return b;
289     }
290     return NULL;
291 }
292
293 static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
294 {
295     va_list args;
296     apr_status_t rv;
297
298     va_start(args, fmt);
299     rv = apr_brigade_vprintf(bb, NULL, NULL, fmt, args);
300     va_end(args);
301
302     return rv;
303 }
304
305 static void add_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
306 {
307     h2_mplx *m = s->mplx;
308     
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? "" : ",");
316 }
317
318 static void add_peer_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
319 {
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? "" : ",");
334 }
335
336 typedef struct {
337     apr_bucket_brigade *bb;
338     h2_session *s;
339     int idx;
340 } stream_ctx_t;
341
342 static int add_stream(h2_stream *stream, void *ctx)
343 {
344     stream_ctx_t *x = ctx;
345     int32_t flowIn, flowOut;
346     
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);  
356     bbout(x->bb, "    }");
357     
358     ++x->idx;
359     return 1;
360
361
362 static void add_streams(apr_bucket_brigade *bb, h2_session *s, int last) 
363 {
364     stream_ctx_t x;
365     
366     x.bb = bb;
367     x.s = s;
368     x.idx = 0;
369     bbout(bb, "  \"streams\": {");
370     h2_mplx_stream_do(s->mplx, add_stream, &x);
371     bbout(bb, "\n  }%s\n", last? "" : ",");
372 }
373
374 static void add_push(apr_bucket_brigade *bb, h2_session *s, 
375                      h2_stream *stream, int last) 
376 {
377     h2_push_diary *diary;
378     apr_status_t status;
379     
380     bbout(bb, "    \"push\": {\n");
381     diary = s->push_diary;
382     if (diary) {
383         const char *data;
384         const char *base64_digest;
385         apr_size_t len;
386         
387         status = h2_push_diary_digest_get(diary, bb->p, 256, 
388                                           stream->request->authority, 
389                                           &data, &len);
390         if (status == APR_SUCCESS) {
391             base64_digest = h2_util_base64url_encode(data, len, bb->p);
392             bbout(bb, "      \"cacheDigest\": \"%s\",\n", base64_digest);
393         }
394     }
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? "" : ",");
399 }
400
401 static void add_in(apr_bucket_brigade *bb, h2_session *s, int last) 
402 {
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? "" : ",");
409 }
410
411 static void add_out(apr_bucket_brigade *bb, h2_session *s, int last) 
412 {
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? "" : ",");
418 }
419
420 static void add_stats(apr_bucket_brigade *bb, h2_session *s, 
421                      h2_stream *stream, int last) 
422 {
423     bbout(bb, "  \"stats\": {\n");
424     add_in(bb, s, 0);
425     add_out(bb, s, 0);
426     add_push(bb, s, stream, 1);
427     bbout(bb, "  }%s\n", last? "" : ",");
428 }
429
430 static apr_status_t h2_status_insert(h2_task *task, apr_bucket *b)
431 {
432     conn_rec *c = task->c->master;
433     h2_ctx *h2ctx = h2_ctx_get(c, 0);
434     h2_session *session;
435     h2_stream *stream;
436     apr_bucket_brigade *bb;
437     apr_bucket *e;
438     int32_t connFlowIn, connFlowOut;
439     
440     
441     if (!h2ctx || (session = h2_ctx_session_get(h2ctx)) == NULL) {
442         return APR_SUCCESS;
443     }
444     
445     stream = h2_session_stream_get(session, task->stream_id);
446     if (!stream) {
447         /* stream already done */
448         return APR_SUCCESS;
449     }
450     
451     bb = apr_brigade_create(stream->pool, c->bucket_alloc);
452     
453     connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); 
454     connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
455      
456     bbout(bb, "{\n");
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);
463
464     add_streams(bb, session, 0);
465     
466     add_stats(bb, session, stream, 1);
467     bbout(bb, "}\n");
468     
469     while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
470         APR_BUCKET_REMOVE(e);
471         APR_BUCKET_INSERT_AFTER(b, e);
472         b = e;
473     }
474     apr_brigade_destroy(bb);
475     
476     return APR_SUCCESS;
477 }
478
479 static apr_status_t status_event(void *ctx, h2_bucket_event event, 
480                                  apr_bucket *b)
481 {
482     h2_task *task = ctx;
483     
484     ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, task->c->master, 
485                   "status_event(%s): %d", task->id, event);
486     switch (event) {
487         case H2_BUCKET_EV_BEFORE_MASTER_SEND:
488             h2_status_insert(task, b);
489             break;
490         default:
491             break;
492     }
493     return APR_SUCCESS;
494 }
495
496 int h2_filter_h2_status_handler(request_rec *r)
497 {
498     h2_ctx *ctx = h2_ctx_rget(r);
499     conn_rec *c = r->connection;
500     h2_task *task;
501     apr_bucket_brigade *bb;
502     apr_bucket *b;
503     apr_status_t status;
504     
505     if (strcmp(r->handler, "http2-status")) {
506         return DECLINED;
507     }
508     if (r->method_number != M_GET && r->method_number != M_POST) {
509         return DECLINED;
510     }
511
512     task = ctx? h2_ctx_get_task(ctx) : NULL;
513     if (task) {
514
515         if ((status = ap_discard_request_body(r)) != OK) {
516             return status;
517         }
518         
519         /* We need to handle the actual output on the main thread, as
520          * we need to access h2_session information. */
521         r->status = 200;
522         r->clength = -1;
523         r->chunked = 1;
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");
529
530         ap_set_content_type(r, "application/json");
531         apr_table_setn(r->notes, H2_FILTER_DEBUG_NOTE, "on");
532
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);
538
539         ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
540                       "status_handler(%s): checking for incoming trailers", 
541                       task->id);
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", 
545                           task->id);
546             apr_table_setn(r->trailers_out, "h2-trailers-in", 
547                            apr_itoa(r->pool, 1));
548         }
549         
550         status = ap_pass_brigade(r->output_filters, bb);
551         if (status == APR_SUCCESS
552             || r->status != HTTP_OK
553             || c->aborted) {
554             return OK;
555         }
556         else {
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", 
560                           task->id);
561             return AP_FILTER_ERROR;
562         }
563     }
564     return DECLINED;
565 }
566