]> granicus.if.org Git - apache/blob - modules/http2/h2_filter.c
log2n compilation error fix, cache digest calculation fix
[apache] / modules / http2 / h2_filter.c
1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <assert.h>
17
18 #include <httpd.h>
19 #include <http_core.h>
20 #include <http_log.h>
21 #include <http_connection.h>
22 #include <scoreboard.h>
23
24 #include "h2_private.h"
25 #include "h2_conn_io.h"
26 #include "h2_ctx.h"
27 #include "h2_mplx.h"
28 #include "h2_push.h"
29 #include "h2_task.h"
30 #include "h2_stream.h"
31 #include "h2_stream_set.h"
32 #include "h2_request.h"
33 #include "h2_response.h"
34 #include "h2_session.h"
35 #include "h2_util.h"
36 #include "h2_version.h"
37
38 #include "h2_filter.h"
39
40 #define UNSET       -1
41 #define H2MIN(x,y) ((x) < (y) ? (x) : (y))
42
43 static apr_status_t consume_brigade(h2_filter_cin *cin, 
44                                     apr_bucket_brigade *bb, 
45                                     apr_read_type_e block)
46 {
47     apr_status_t status = APR_SUCCESS;
48     apr_size_t readlen = 0;
49     
50     while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
51         
52         apr_bucket* bucket = APR_BRIGADE_FIRST(bb);
53         if (APR_BUCKET_IS_METADATA(bucket)) {
54             /* we do nothing regarding any meta here */
55         }
56         else {
57             const char *bucket_data = NULL;
58             apr_size_t bucket_length = 0;
59             status = apr_bucket_read(bucket, &bucket_data,
60                                      &bucket_length, block);
61             
62             if (status == APR_SUCCESS && bucket_length > 0) {
63                 apr_size_t consumed = 0;
64
65                 status = cin->cb(cin->cb_ctx, bucket_data, bucket_length, &consumed);
66                 if (status == APR_SUCCESS && bucket_length > consumed) {
67                     /* We have data left in the bucket. Split it. */
68                     status = apr_bucket_split(bucket, consumed);
69                 }
70                 readlen += consumed;
71                 cin->start_read = apr_time_now();
72             }
73         }
74         apr_bucket_delete(bucket);
75     }
76     
77     if (readlen == 0 && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
78         return APR_EAGAIN;
79     }
80     return status;
81 }
82
83 h2_filter_cin *h2_filter_cin_create(apr_pool_t *p, h2_filter_cin_cb *cb, void *ctx)
84 {
85     h2_filter_cin *cin;
86     
87     cin = apr_pcalloc(p, sizeof(*cin));
88     cin->pool      = p;
89     cin->cb        = cb;
90     cin->cb_ctx    = ctx;
91     cin->start_read = UNSET;
92     return cin;
93 }
94
95 void h2_filter_cin_timeout_set(h2_filter_cin *cin, int timeout_secs)
96 {
97     cin->timeout_secs = timeout_secs;
98 }
99
100 apr_status_t h2_filter_core_input(ap_filter_t* f,
101                                   apr_bucket_brigade* brigade,
102                                   ap_input_mode_t mode,
103                                   apr_read_type_e block,
104                                   apr_off_t readbytes) 
105 {
106     h2_filter_cin *cin = f->ctx;
107     apr_status_t status = APR_SUCCESS;
108     apr_time_t saved_timeout = UNSET;
109     
110     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
111                   "core_input(%ld): read, %s, mode=%d, readbytes=%ld, timeout=%d", 
112                   (long)f->c->id, (block == APR_BLOCK_READ)? "BLOCK_READ" : "NONBLOCK_READ", 
113                   mode, (long)readbytes, cin->timeout_secs);
114     
115     if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
116         return ap_get_brigade(f->next, brigade, mode, block, readbytes);
117     }
118     
119     if (mode != AP_MODE_READBYTES) {
120         return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN;
121     }
122     
123     if (!cin->bb) {
124         cin->bb = apr_brigade_create(cin->pool, f->c->bucket_alloc);
125     }
126
127     if (!cin->socket) {
128         cin->socket = ap_get_conn_socket(f->c);
129     }
130     
131     cin->start_read = apr_time_now();
132     if (APR_BRIGADE_EMPTY(cin->bb)) {
133         /* We only do a blocking read when we have no streams to process. So,
134          * in httpd scoreboard lingo, we are in a KEEPALIVE connection state.
135          * When reading non-blocking, we do have streams to process and update
136          * child with NULL request. That way, any current request information
137          * in the scoreboard is preserved.
138          */
139         if (block == APR_BLOCK_READ) {
140             if (cin->timeout_secs > 0) {
141                 apr_time_t t = apr_time_from_sec(cin->timeout_secs);
142                 apr_socket_timeout_get(cin->socket, &saved_timeout);
143                 apr_socket_timeout_set(cin->socket, t);
144             }
145         }
146         status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
147                                 block, readbytes);
148         if (saved_timeout != UNSET) {
149             apr_socket_timeout_set(cin->socket, saved_timeout);
150         }
151         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
152                       "core_input(%ld): got_brigade", (long)f->c->id);
153     }
154     
155     switch (status) {
156         case APR_SUCCESS:
157             status = consume_brigade(cin, cin->bb, block);
158             break;
159         case APR_EOF:
160         case APR_EAGAIN:
161         case APR_TIMEUP:
162             break;
163         default:
164             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c,
165                           "h2_conn_io: error reading");
166             break;
167     }
168     return status;
169 }
170
171 /*******************************************************************************
172  * http2 connection status handler + stream out source
173  ******************************************************************************/
174
175 static const char *H2_SOS_H2_STATUS = "http2-status";
176
177 int h2_filter_h2_status_handler(request_rec *r)
178 {
179     h2_ctx *ctx = h2_ctx_rget(r);
180     h2_task *task;
181     
182     if (strcmp(r->handler, "http2-status")) {
183         return DECLINED;
184     }
185     if (r->method_number != M_GET) {
186         return DECLINED;
187     }
188
189     task = ctx? h2_ctx_get_task(ctx) : NULL;
190     if (task) {
191         /* We need to handle the actual output on the main thread, as
192          * we need to access h2_session information. */
193         apr_table_setn(r->notes, H2_RESP_SOS_NOTE, H2_SOS_H2_STATUS);
194         apr_table_setn(r->headers_out, "Content-Type", "application/json");
195         r->status = 200;
196         return DONE;
197     }
198     return DECLINED;
199 }
200
201 #define bbout(...)   apr_brigade_printf(bb, NULL, NULL, __VA_ARGS__)
202 static apr_status_t h2_sos_h2_status_buffer(h2_sos *sos, apr_bucket_brigade *bb)
203 {
204     h2_stream *stream = sos->stream;
205     h2_session *session = stream->session;
206     h2_mplx *mplx = session->mplx;
207     h2_push_diary *diary;
208     apr_status_t status;
209     
210     if (!bb) {
211         bb = apr_brigade_create(stream->pool, session->c->bucket_alloc);
212     }
213     
214     bbout("{\n");
215     bbout("  \"HTTP2\": \"on\",\n");
216     bbout("  \"H2PUSH\": \"%s\",\n", h2_session_push_enabled(session)? "on" : "off");
217     bbout("  \"mod_http2_version\": \"%s\",\n", MOD_HTTP2_VERSION);
218     bbout("  \"session_id\": %ld,\n", (long)session->id);
219     bbout("  \"streams_max\": %d,\n", (int)session->max_stream_count);
220     bbout("  \"this_stream\": %d,\n", stream->id);
221     bbout("  \"streams_open\": %d,\n", (int)h2_stream_set_size(session->streams));
222     bbout("  \"max_stream_started\": %d,\n", mplx->max_stream_started);
223     bbout("  \"requests_received\": %d,\n", session->requests_received);
224     bbout("  \"responses_submitted\": %d,\n", session->responses_submitted);
225     bbout("  \"streams_reset\": %d, \n", session->streams_reset);
226     bbout("  \"pushes_promised\": %d,\n", session->pushes_promised);
227     bbout("  \"pushes_submitted\": %d,\n", session->pushes_submitted);
228     bbout("  \"pushes_reset\": %d,\n", session->pushes_reset);
229     
230     diary = session->push_diary;
231     if (diary) {
232         const char *data;
233         const char *base64_digest;
234         apr_size_t len;
235         
236         status = h2_push_diary_digest_get(diary, stream->pool, 256, 
237                                           stream->request->authority, &data, &len);
238         if (status == APR_SUCCESS) {
239             base64_digest = h2_util_base64url_encode(data, len, stream->pool);
240             bbout("  \"cache_digest\": \"%s\",\n", base64_digest);
241         }
242         
243         /* try the reverse for testing purposes */
244         status = h2_push_diary_digest_set(diary, stream->request->authority, data, len);
245         if (status == APR_SUCCESS) {
246             status = h2_push_diary_digest_get(diary, stream->pool, 256, 
247                                               stream->request->authority, &data, &len);
248             if (status == APR_SUCCESS) {
249                 base64_digest = h2_util_base64url_encode(data, len, stream->pool);
250                 bbout("  \"cache_digest^2\": \"%s\",\n", base64_digest);
251             }
252         }
253     }
254     bbout("  \"frames_received\": %ld,\n", (long)session->frames_received);
255     bbout("  \"frames_sent\": %ld,\n", (long)session->frames_sent);
256     bbout("  \"bytes_received\": %"APR_UINT64_T_FMT",\n", session->io.bytes_read);
257     bbout("  \"bytes_sent\": %"APR_UINT64_T_FMT"\n", session->io.bytes_written);
258     bbout("}\n");
259     
260     return sos->prev->buffer(sos->prev, bb);
261 }
262
263 static apr_status_t h2_sos_h2_status_read_to(h2_sos *sos, apr_bucket_brigade *bb, 
264                                              apr_off_t *plen, int *peos)
265 {
266     return sos->prev->read_to(sos->prev, bb, plen, peos);
267 }
268
269 static apr_status_t h2_sos_h2_status_prep_read(h2_sos *sos, apr_off_t *plen, int *peos)
270 {
271     return sos->prev->prep_read(sos->prev, plen, peos);
272 }
273
274 static apr_status_t h2_sos_h2_status_readx(h2_sos *sos, h2_io_data_cb *cb, void *ctx,
275                                            apr_off_t *plen, int *peos)
276 {
277     return sos->prev->readx(sos->prev, cb, ctx, plen, peos);
278 }
279
280 static apr_table_t *h2_sos_h2_status_get_trailers(h2_sos *sos)
281 {
282     return sos->prev->get_trailers(sos->prev);
283 }
284
285 static h2_sos *h2_sos_h2_status_create(h2_sos *prev) 
286 {
287     h2_sos *sos;
288     h2_response *response = prev->response;
289     
290     apr_table_unset(response->headers, "Content-Length");
291     response->content_length = -1;
292
293     sos = apr_pcalloc(prev->stream->pool, sizeof(*sos));
294     sos->prev         = prev;
295     sos->response     = response;
296     sos->stream       = prev->stream;
297     sos->buffer       = h2_sos_h2_status_buffer;
298     sos->prep_read    = h2_sos_h2_status_prep_read;
299     sos->readx        = h2_sos_h2_status_readx;
300     sos->read_to      = h2_sos_h2_status_read_to;
301     sos->get_trailers = h2_sos_h2_status_get_trailers;
302     
303     return sos;
304 }
305
306 h2_sos *h2_filter_sos_create(const char *name, struct h2_sos *prev)
307 {
308     if (!strcmp(H2_SOS_H2_STATUS, name)) {
309         return h2_sos_h2_status_create(prev);
310     }
311     return prev;
312 }
313