blob: 06410612933ad3d9ab5daf0b961637ba7daabc97 [file] [log] [blame]
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed 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 "mod_http2.h"
#include <nghttp2/nghttp2.h>
#include "h2_stream.h"
#include "h2_alt_svc.h"
#include "h2_conn.h"
#include "h2_filter.h"
#include "h2_task.h"
#include "h2_session.h"
#include "h2_config.h"
#include "h2_ctx.h"
#include "h2_h2.h"
#include "h2_mplx.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_switch.h"
#include "h2_version.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,
h2_config_create_svr, /* func to create per server config */
h2_config_merge, /* func to merge per server config */
h2_cmds, /* command handlers */
h2_hooks
};
static int h2_h2_fixups(request_rec *r);
typedef struct {
unsigned int change_prio : 1;
unsigned int sha256 : 1;
} features;
static features myfeats;
/* 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;
const char *sep = "";
(void)plog;(void)ptemp;
#ifdef H2_NG2_CHANGE_PRIO
myfeats.change_prio = 1;
sep = "+";
#endif
#ifdef H2_OPENSSL
myfeats.sha256 = 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, nghttp2 %s), initializing...",
MOD_HTTP2_VERSION,
myfeats.change_prio? "CHPRIO" : "", sep,
myfeats.sha256? "SHA256" : "",
ngh2? ngh2->version_str : "unknown");
switch (h2_conn_mpm_type()) {
case H2_MPM_SIMPLE:
case H2_MPM_MOTORZ:
case H2_MPM_NETWARE:
case H2_MPM_WINNT:
/* not sure we need something extra for those. */
break;
case H2_MPM_EVENT:
case H2_MPM_WORKER:
/* all fine, we know these ones */
break;
case H2_MPM_PREFORK:
/* ok, we now know how to handle that one */
break;
case H2_MPM_UNKNOWN:
/* ??? */
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03091)
"post_config: mpm type unknown");
break;
}
status = h2_h2_init(p, s);
if (status == APR_SUCCESS) {
status = h2_switch_init(p, s);
}
if (status == APR_SUCCESS) {
status = h2_task_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 apr_status_t http2_req_engine_push(const char *ngn_type,
request_rec *r,
http2_req_engine_init *einit)
{
return h2_mplx_req_engine_push(ngn_type, r, einit);
}
static apr_status_t http2_req_engine_pull(h2_req_engine *ngn,
apr_read_type_e block,
apr_uint32_t capacity,
request_rec **pr)
{
return h2_mplx_req_engine_pull(ngn, block, capacity, pr);
}
static void http2_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn)
{
h2_mplx_req_engine_done(ngn, r_conn);
}
/* Runs once per created child process. Perform any process
* related initionalization here.
*/
static void h2_child_init(apr_pool_t *pool, server_rec *s)
{
/* Set up our connection processing */
apr_status_t status = h2_conn_child_init(pool, s);
if (status != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, status, 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_req_engine_push);
APR_REGISTER_OPTIONAL_FN(http2_req_engine_pull);
APR_REGISTER_OPTIONAL_FN(http2_req_engine_done);
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);
h2_h2_register_hooks();
h2_switch_register_hooks();
h2_task_register_hooks();
h2_alt_svc_register_hooks();
/* Setup subprocess env for certain variables
*/
ap_hook_fixups(h2_h2_fixups, NULL,NULL, APR_HOOK_MIDDLE);
/* test http2 connection status handler */
ap_hook_handler(h2_filter_h2_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
static const char *val_HTTP2(apr_pool_t *p, server_rec *s,
conn_rec *c, request_rec *r, h2_ctx *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_ctx *ctx)
{
if (ctx) {
if (r) {
h2_task *task = h2_ctx_get_task(ctx);
if (task && task->request->push_policy != H2_PUSH_NONE) {
return "on";
}
}
else if (c && h2_session_push_enabled(ctx->session)) {
return "on";
}
}
else if (s) {
const h2_config *cfg = h2_config_sget(s);
if (cfg && h2_config_geti(cfg, 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_ctx *ctx)
{
if (ctx) {
h2_task *task = h2_ctx_get_task(ctx);
if (task && !H2_STREAM_CLIENT_INITIATED(task->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_ctx *ctx)
{
if (ctx) {
h2_task *task = h2_ctx_get_task(ctx);
if (task && !H2_STREAM_CLIENT_INITIATED(task->stream_id)) {
return apr_itoa(p, task->request->initiated_on);
}
}
return "";
}
static const char *val_H2_STREAM_TAG(apr_pool_t *p, server_rec *s,
conn_rec *c, request_rec *r, h2_ctx *ctx)
{
if (ctx) {
h2_task *task = h2_ctx_get_task(ctx);
if (task) {
return task->id;
}
}
return "";
}
static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s,
conn_rec *c, request_rec *r, h2_ctx *ctx)
{
const char *cp = val_H2_STREAM_TAG(p, s, c, r, ctx);
if (cp && (cp = ap_strchr_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_ctx *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_ctx_get(c->master? c->master : c, 0) != NULL;
}
static char *http2_var_lookup(apr_pool_t *p, server_rec *s,
conn_rec *c, request_rec *r, char *name)
{
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_ctx *ctx = (r? h2_ctx_rget(r) :
h2_ctx_get(c->master? c->master : c, 0));
return (char *)vdef->lookup(p, s, c, r, ctx);
}
}
return "";
}
static int h2_h2_fixups(request_rec *r)
{
if (r->connection->master) {
h2_ctx *ctx = h2_ctx_rget(r);
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;
}