1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <http_core.h>
21 #include <http_connection.h>
22 #include <scoreboard.h>
24 #include "h2_private.h"
25 #include "h2_conn_io.h"
30 #include "h2_stream.h"
31 #include "h2_request.h"
32 #include "h2_response.h"
33 #include "h2_session.h"
35 #include "h2_version.h"
37 #include "h2_filter.h"
40 #define H2MIN(x,y) ((x) < (y) ? (x) : (y))
42 static apr_status_t consume_brigade(h2_filter_cin *cin,
43 apr_bucket_brigade *bb,
44 apr_read_type_e block)
46 apr_status_t status = APR_SUCCESS;
47 apr_size_t readlen = 0;
49 while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
51 apr_bucket* bucket = APR_BRIGADE_FIRST(bb);
52 if (APR_BUCKET_IS_METADATA(bucket)) {
53 /* we do nothing regarding any meta here */
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);
61 if (status == APR_SUCCESS && bucket_length > 0) {
62 apr_size_t consumed = 0;
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);
70 cin->start_read = apr_time_now();
73 apr_bucket_delete(bucket);
76 if (readlen == 0 && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
82 h2_filter_cin *h2_filter_cin_create(apr_pool_t *p, h2_filter_cin_cb *cb, void *ctx)
86 cin = apr_pcalloc(p, sizeof(*cin));
90 cin->start_read = UNSET;
94 void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout)
96 cin->timeout = timeout;
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,
105 h2_filter_cin *cin = f->ctx;
106 apr_status_t status = APR_SUCCESS;
107 apr_interval_time_t saved_timeout = UNSET;
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);
114 if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
115 return ap_get_brigade(f->next, brigade, mode, block, readbytes);
118 if (mode != AP_MODE_READBYTES) {
119 return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN;
123 cin->bb = apr_brigade_create(cin->pool, f->c->bucket_alloc);
127 cin->socket = ap_get_conn_socket(f->c);
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.
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);
144 status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
146 if (saved_timeout != UNSET) {
147 apr_socket_timeout_set(cin->socket, saved_timeout);
153 status = consume_brigade(cin, cin->bb, block);
158 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
159 "core_input(%ld): read", (long)f->c->id);
162 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, APLOGNO(03046)
163 "h2_conn_io: error reading");
169 /*******************************************************************************
170 * http2 connection status handler + stream out source
171 ******************************************************************************/
173 static const char *H2_SOS_H2_STATUS = "http2-status";
175 int h2_filter_h2_status_handler(request_rec *r)
177 h2_ctx *ctx = h2_ctx_rget(r);
180 if (strcmp(r->handler, "http2-status")) {
183 if (r->method_number != M_GET) {
187 task = ctx? h2_ctx_get_task(ctx) : NULL;
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");
199 static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
205 rv = apr_brigade_vprintf(bb, NULL, NULL, fmt, args);
211 static apr_status_t h2_sos_h2_status_buffer(h2_sos *sos, apr_bucket_brigade *bb)
213 h2_stream *stream = sos->stream;
214 h2_session *session = stream->session;
215 h2_mplx *mplx = session->mplx;
216 h2_push_diary *diary;
220 bb = apr_brigade_create(stream->pool, session->c->bucket_alloc);
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);
239 diary = session->push_diary;
242 const char *base64_digest;
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);
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);
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);
269 return sos->prev->buffer(sos->prev, bb);
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)
275 return sos->prev->read_to(sos->prev, bb, plen, peos);
278 static apr_status_t h2_sos_h2_status_prep_read(h2_sos *sos, apr_off_t *plen, int *peos)
280 return sos->prev->prep_read(sos->prev, plen, peos);
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)
286 return sos->prev->readx(sos->prev, cb, ctx, plen, peos);
289 static apr_table_t *h2_sos_h2_status_get_trailers(h2_sos *sos)
291 return sos->prev->get_trailers(sos->prev);
294 static h2_sos *h2_sos_h2_status_create(h2_sos *prev)
297 h2_response *response = prev->response;
299 apr_table_unset(response->headers, "Content-Length");
300 response->content_length = -1;
302 sos = apr_pcalloc(prev->stream->pool, sizeof(*sos));
304 sos->response = response;
305 sos->stream = prev->stream;
306 sos->buffer = h2_sos_h2_status_buffer;
307 sos->prep_read = h2_sos_h2_status_prep_read;
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;
315 h2_sos *h2_filter_sos_create(const char *name, struct h2_sos *prev)
317 if (!strcmp(H2_SOS_H2_STATUS, name)) {
318 return h2_sos_h2_status_create(prev);