| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <apr_optional.h> | |
| #include <apr_optional_hooks.h> | |
| #include <apr_strings.h> | |
| #include <apr_time.h> | |
| #include <apr_want.h> | |
| #include <httpd.h> | |
| #include <http_protocol.h> | |
| #include <http_request.h> | |
| #include <http_log.h> | |
| #include <mpm_common.h> | |
| #include "mod_http2.h" | |
| #include <nghttp2/nghttp2.h> | |
| #include "h2_stream.h" | |
| #include "h2_c1.h" | |
| #include "h2_c2.h" | |
| #include "h2_session.h" | |
| #include "h2_config.h" | |
| #include "h2_conn_ctx.h" | |
| #include "h2_protocol.h" | |
| #include "h2_mplx.h" | |
| #include "h2_push.h" | |
| #include "h2_request.h" | |
| #include "h2_switch.h" | |
| #include "h2_version.h" | |
| #include "h2_bucket_beam.h" | |
| #include "h2_ws.h" | |
| static void h2_hooks(apr_pool_t *pool); | |
| AP_DECLARE_MODULE(http2) = { | |
| STANDARD20_MODULE_STUFF, | |
| h2_config_create_dir, /* func to create per dir config */ | |
| h2_config_merge_dir, /* func to merge per dir config */ | |
| h2_config_create_svr, /* func to create per server config */ | |
| h2_config_merge_svr, /* func to merge per server config */ | |
| h2_cmds, /* command handlers */ | |
| h2_hooks, | |
| #if defined(AP_MODULE_FLAG_NONE) | |
| AP_MODULE_FLAG_ALWAYS_MERGE | |
| #endif | |
| }; | |
| static int h2_h2_fixups(request_rec *r); | |
| typedef struct { | |
| unsigned int change_prio : 1; | |
| unsigned int sha256 : 1; | |
| unsigned int inv_headers : 1; | |
| unsigned int dyn_windows : 1; | |
| } features; | |
| static features myfeats; | |
| static int mpm_warned; | |
| /* The module initialization. Called once as apache hook, before any multi | |
| * processing (threaded or not) happens. It is typically at least called twice, | |
| * see | |
| * http://wiki.apache.org/httpd/ModuleLife | |
| * Since the first run is just a "practise" run, we want to initialize for real | |
| * only on the second try. This defeats the purpose of the first dry run a bit, | |
| * since apache wants to verify that a new configuration actually will work. | |
| * So if we have trouble with the configuration, this will only be detected | |
| * when the server has already switched. | |
| * On the other hand, when we initialize lib nghttp2, all possible crazy things | |
| * might happen and this might even eat threads. So, better init on the real | |
| * invocation, for now at least. | |
| */ | |
| static int h2_post_config(apr_pool_t *p, apr_pool_t *plog, | |
| apr_pool_t *ptemp, server_rec *s) | |
| { | |
| void *data = NULL; | |
| const char *mod_h2_init_key = "mod_http2_init_counter"; | |
| nghttp2_info *ngh2; | |
| apr_status_t status; | |
| (void)plog;(void)ptemp; | |
| #ifdef H2_NG2_CHANGE_PRIO | |
| myfeats.change_prio = 1; | |
| #endif | |
| #ifdef H2_OPENSSL | |
| myfeats.sha256 = 1; | |
| #endif | |
| #ifdef H2_NG2_INVALID_HEADER_CB | |
| myfeats.inv_headers = 1; | |
| #endif | |
| #ifdef H2_NG2_LOCAL_WIN_SIZE | |
| myfeats.dyn_windows = 1; | |
| #endif | |
| apr_pool_userdata_get(&data, mod_h2_init_key, s->process->pool); | |
| if ( data == NULL ) { | |
| ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03089) | |
| "initializing post config dry run"); | |
| apr_pool_userdata_set((const void *)1, mod_h2_init_key, | |
| apr_pool_cleanup_null, s->process->pool); | |
| return APR_SUCCESS; | |
| } | |
| ngh2 = nghttp2_version(0); | |
| ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03090) | |
| "mod_http2 (v%s, feats=%s%s%s%s, nghttp2 %s), initializing...", | |
| MOD_HTTP2_VERSION, | |
| myfeats.change_prio? "CHPRIO" : "", | |
| myfeats.sha256? "+SHA256" : "", | |
| myfeats.inv_headers? "+INVHD" : "", | |
| myfeats.dyn_windows? "+DWINS" : "", | |
| ngh2? ngh2->version_str : "unknown"); | |
| if (!h2_mpm_supported() && !mpm_warned) { | |
| mpm_warned = 1; | |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10034) | |
| "The mpm module (%s) is not supported by mod_http2. The mpm determines " | |
| "how things are processed in your server. HTTP/2 has more demands in " | |
| "this regard and the currently selected mpm will just not do. " | |
| "This is an advisory warning. Your server will continue to work, but " | |
| "the HTTP/2 protocol will be inactive.", | |
| h2_conn_mpm_name()); | |
| } | |
| status = h2_protocol_init(p, s); | |
| if (status == APR_SUCCESS) { | |
| status = h2_switch_init(p, s); | |
| } | |
| return status; | |
| } | |
| static char *http2_var_lookup(apr_pool_t *, server_rec *, | |
| conn_rec *, request_rec *, char *name); | |
| static int http2_is_h2(conn_rec *); | |
| static void http2_get_num_workers(server_rec *s, int *minw, int *maxw) | |
| { | |
| apr_time_t tdummy; | |
| h2_get_workers_config(s, minw, maxw, &tdummy); | |
| } | |
| /* Runs once per created child process. Perform any process | |
| * related initionalization here. | |
| */ | |
| static void h2_child_init(apr_pool_t *pchild, server_rec *s) | |
| { | |
| apr_status_t rv; | |
| /* Set up our connection processing */ | |
| rv = h2_c1_child_init(pchild, s); | |
| if (APR_SUCCESS == rv) { | |
| rv = h2_c2_child_init(pchild, s); | |
| } | |
| if (APR_SUCCESS != rv) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, | |
| APLOGNO(02949) "initializing connection handling"); | |
| } | |
| } | |
| /* Install this module into the apache2 infrastructure. | |
| */ | |
| static void h2_hooks(apr_pool_t *pool) | |
| { | |
| static const char *const mod_ssl[] = { "mod_ssl.c", NULL}; | |
| APR_REGISTER_OPTIONAL_FN(http2_is_h2); | |
| APR_REGISTER_OPTIONAL_FN(http2_var_lookup); | |
| APR_REGISTER_OPTIONAL_FN(http2_get_num_workers); | |
| ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks"); | |
| /* Run once after configuration is set, but before mpm children initialize. | |
| */ | |
| ap_hook_post_config(h2_post_config, mod_ssl, NULL, APR_HOOK_MIDDLE); | |
| /* Run once after a child process has been created. | |
| */ | |
| ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE); | |
| #if AP_MODULE_MAGIC_AT_LEAST(20120211, 110) | |
| ap_hook_child_stopping(h2_c1_child_stopping, NULL, NULL, APR_HOOK_MIDDLE); | |
| #endif | |
| h2_c1_register_hooks(); | |
| h2_switch_register_hooks(); | |
| h2_c2_register_hooks(); | |
| h2_ws_register_hooks(); | |
| /* Setup subprocess env for certain variables | |
| */ | |
| ap_hook_fixups(h2_h2_fixups, NULL,NULL, APR_HOOK_MIDDLE); | |
| } | |
| static const char *val_HTTP2(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx) | |
| { | |
| return ctx? "on" : "off"; | |
| } | |
| static const char *val_H2_PUSH(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, | |
| h2_conn_ctx_t *conn_ctx) | |
| { | |
| if (conn_ctx) { | |
| if (r) { | |
| if (conn_ctx->stream_id) { | |
| const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id); | |
| if (stream && stream->push_policy != H2_PUSH_NONE) { | |
| return "on"; | |
| } | |
| } | |
| } | |
| else if (c && h2_session_push_enabled(conn_ctx->session)) { | |
| return "on"; | |
| } | |
| } | |
| else if (s) { | |
| if (h2_config_geti(r, s, H2_CONF_PUSH)) { | |
| return "on"; | |
| } | |
| } | |
| return "off"; | |
| } | |
| static const char *val_H2_PUSHED(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, | |
| h2_conn_ctx_t *conn_ctx) | |
| { | |
| if (conn_ctx) { | |
| if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) { | |
| return "PUSHED"; | |
| } | |
| } | |
| return ""; | |
| } | |
| static const char *val_H2_PUSHED_ON(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, | |
| h2_conn_ctx_t *conn_ctx) | |
| { | |
| if (conn_ctx) { | |
| if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) { | |
| const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id); | |
| if (stream) { | |
| return apr_itoa(p, stream->initiated_on); | |
| } | |
| } | |
| } | |
| return ""; | |
| } | |
| static const char *val_H2_STREAM_TAG(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx) | |
| { | |
| if (c) { | |
| h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c); | |
| if (conn_ctx) { | |
| return conn_ctx->stream_id == 0? conn_ctx->id | |
| : apr_psprintf(p, "%s-%d", conn_ctx->id, conn_ctx->stream_id); | |
| } | |
| } | |
| return ""; | |
| } | |
| static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx) | |
| { | |
| const char *cp = val_H2_STREAM_TAG(p, s, c, r, ctx); | |
| if (cp && (cp = ap_strrchr_c(cp, '-'))) { | |
| return ++cp; | |
| } | |
| return NULL; | |
| } | |
| typedef const char *h2_var_lookup(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx); | |
| typedef struct h2_var_def { | |
| const char *name; | |
| h2_var_lookup *lookup; | |
| unsigned int subprocess : 1; /* should be set in r->subprocess_env */ | |
| } h2_var_def; | |
| static h2_var_def H2_VARS[] = { | |
| { "HTTP2", val_HTTP2, 1 }, | |
| { "H2PUSH", val_H2_PUSH, 1 }, | |
| { "H2_PUSH", val_H2_PUSH, 1 }, | |
| { "H2_PUSHED", val_H2_PUSHED, 1 }, | |
| { "H2_PUSHED_ON", val_H2_PUSHED_ON, 1 }, | |
| { "H2_STREAM_ID", val_H2_STREAM_ID, 1 }, | |
| { "H2_STREAM_TAG", val_H2_STREAM_TAG, 1 }, | |
| }; | |
| #ifndef H2_ALEN | |
| #define H2_ALEN(a) (sizeof(a)/sizeof((a)[0])) | |
| #endif | |
| static int http2_is_h2(conn_rec *c) | |
| { | |
| return h2_conn_ctx_get(c->master? c->master : c) != NULL; | |
| } | |
| static char *http2_var_lookup(apr_pool_t *p, server_rec *s, | |
| conn_rec *c, request_rec *r, char *name) | |
| { | |
| unsigned int i; | |
| /* If the # of vars grow, we need to put definitions in a hash */ | |
| for (i = 0; i < H2_ALEN(H2_VARS); ++i) { | |
| h2_var_def *vdef = &H2_VARS[i]; | |
| if (!strcmp(vdef->name, name)) { | |
| h2_conn_ctx_t *ctx = (r? h2_conn_ctx_get(c) : | |
| h2_conn_ctx_get(c->master? c->master : c)); | |
| return (char *)vdef->lookup(p, s, c, r, ctx); | |
| } | |
| } | |
| return (char*)""; | |
| } | |
| static int h2_h2_fixups(request_rec *r) | |
| { | |
| if (r->connection->master) { | |
| h2_conn_ctx_t *ctx = h2_conn_ctx_get(r->connection); | |
| unsigned int i; | |
| for (i = 0; ctx && i < H2_ALEN(H2_VARS); ++i) { | |
| h2_var_def *vdef = &H2_VARS[i]; | |
| if (vdef->subprocess) { | |
| apr_table_setn(r->subprocess_env, vdef->name, | |
| vdef->lookup(r->pool, r->server, r->connection, | |
| r, ctx)); | |
| } | |
| } | |
| } | |
| return DECLINED; | |
| } |