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