Changes with Apache 2.4.18
+ *) mod_http2: new directive 'H2PushPriority' to allow priority specifications
+ on server pushed streams according to their content-type.
+ [Stefan Eissing]
+
*) mod_http2: fixes crash on connection abort for a busy connection.
fixes crash on a request that did not produce any response.
[Stefan Eissing]
<context>server config</context>
<context>virtual host</context>
</contextlist>
+ <compatibility>Available in version 2.4.18 and later.</compatibility>
<usage>
<p>
<p>
Last but not least, pushes happen only when the client signals
its willingness to accept those. Most browsers do, some, like Safari 9,
- do not.
+ do not. Also, pushes also only happen for resources from the same
+ <em>authority</em> as the original response is for.
</p>
</usage>
</directivesynopsis>
+ <directivesynopsis>
+ <name>H2PushPriority</name>
+ <description>H2 Server Push Priority</description>
+ <syntax>H2PushPriority mime-type [after|before|interleaved] [weight]</syntax>
+ <default>H2PushPriority * After 16</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <compatibility>Available in version 2.4.18 and later. For having an
+ effect, a nghttp2 library version newer than 1.4.0 is necessary.</compatibility>
+
+ <usage>
+ <p>
+ This directive defines the priority handling of pushed responses
+ based on the content-type of the response. This is usually defined
+ per server config, but may also appear in a virtual host.
+ </p>
+ <p>
+ HTTP/2 server pushes are always related to a client request. Each
+ such request/response pairs, or <em>streams</em> have a dependency
+ and a weight, together defining the <em>priority</em> of a stream.
+ </p>
+ <p>
+ When a stream <em>depends</em> on another, say X depends on Y,
+ then Y gets all bandwidth before X gets any. Note that this
+ does not men that Y will block X. If Y has no data to send,
+ all bandwidth allocated to Y can be used by X.
+ </p>
+ <p>
+ When a stream has more than one dependant, say X1 and X2 both
+ depend on Y, the <em>weight</em> determines the bandwidth
+ allocation. If X1 and X2 have the same weight, they both get
+ half of the available bandwdith. If the weight of X1 is twice
+ as large as that for X2, X1 gets twice the bandwidth of X2.
+ </p>
+ <p>
+ Ultimately, every stream depends on the <em>root</em> stream which
+ gets all the bandwidht available, but never sends anything. So all
+ its bandwidth is distributed by weight among its children. Which
+ either have data to send or distribute the bandwidth to their
+ own children. And so on. If none of the children have data
+ to send, that bandwidth get distributed somewhere else according
+ to the same rules.
+ </p>
+ <p>
+ The purpose of this priority system is to always make use of
+ available bandwidth while allowing precedence and weight
+ to be given to specific streams. Since, normally, all streams
+ are initiated by the client, it is also the one that sets
+ these priorities.
+ </p>
+ <p>
+ Only when such a stream results in a PUSH, gets the server to
+ decide what the <em>initial</em> priority of such a pushed
+ stream is. In the examples below, X is the client stream. It
+ depends on Y and the server decides to PUSH streams P1 and P2
+ onto X.
+ </p>
+ <p>
+ The default priority rule is:
+ </p>
+ <example><title>Default Priority Rule</title>
+ <highlight language="config">
+ H2PushPriority * After 16
+ </highlight>
+ </example>
+ <p>
+ which reads as 'Send a pushed stream of any content-type
+ depending on the client stream with weight 16'. And so P1
+ and P2 will be send after X and, as they have equal weight,
+ share bandwidth equally among themselves.
+ </p>
+ <example><title>Interleaved Priority Rule</title>
+ <highlight language="config">
+ H2PushPriority text/css Interleaved 256
+ </highlight>
+ </example>
+ <p>
+ which reads as 'Send any CSS resource on the same dependency and
+ weight as the client stream'. If P1 has content-type 'text/css',
+ it will depend on Y (as does X) and its effective weight will be
+ calculated as <code>P1ew = Xw * (P1w / 256)</code>. With P1w being
+ 256, this will make the effective weight the same as the weight
+ of X. If both X and P1 have data to send, bandwidth will be allocated
+ to both equally.
+ </p>
+ <p>
+ With Pw specified as 512, a pushed, interleaved stream would
+ get double the weight of X. With 128 only half as much. Note that
+ effective weights are always capped at 256.
+ </p>
+ <example><title>Before Priority Rule</title>
+ <highlight language="config">
+ H2PushPriority application/json Before 256
+ </highlight>
+ </example>
+ <p>
+ This says that any pushed stream of content type 'application/json'
+ should be send out <em>before</em> X. This makes P1 dependant
+ on Y and X dependant on P1. So, X will be stalled as long as
+ P1 has data to send. The effective weight is calculated as
+ in the interleaved case.
+ </p>
+ <p>
+ Be aware that the effect of priority specifications is limited
+ by the available server resources. If a server does not have
+ workers available for pushed streams, the data for the stream
+ may only ever arrive when other streams have been finished.
+ </p>
+ <p>
+ Last, but not least, there are some specifics of the syntax
+ to be used in this directive.
+ <ol>
+ <li>'*' is the only special content-type that matches all oither.
+ 'image/*' will not work.</li>
+ <li>The default dependency is 'After'. </li>
+ <li>There are also default weights: for 'After' it is 16, otherwise 256.
+ </li>
+ </ol>
+ </p>
+ <example><title>Shorter Priority Rules</title>
+ <highlight language="config">
+H2PushPriority application/json 32 # an After rule
+H2PushPriority image/jpeg before # weight 256 default
+H2PushPriority text/css interleaved # weight 256 default
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
<directivesynopsis>
<name>H2Upgrade</name>
<description>H2 Upgrade Protocol Switch</description>
<context>server config</context>
<context>virtual host</context>
</contextlist>
-
+ <compatibility>Available in version 2.4.18 and later.</compatibility>
+
<usage>
<p>
This directive toggles the security checks on HTTP/2 connections
<context>server config</context>
<context>virtual host</context>
</contextlist>
+ <compatibility>Available in version 2.4.18 and later.</compatibility>
<usage>
<p>
<context>server config</context>
<context>virtual host</context>
</contextlist>
+ <compatibility>Available in version 2.4.18 and later.</compatibility>
<usage>
<p>
static int h2_alt_svc_handler(request_rec *r)
{
h2_ctx *ctx;
- h2_config *cfg;
+ const h2_config *cfg;
int i;
if (r->connection->keepalives > 0) {
#include <assert.h>
+#include <apr_hash.h>
+#include <apr_lib.h>
+
#include <httpd.h>
#include <http_core.h>
#include <http_config.h>
H2_INITIAL_WINDOW_SIZE, /* window_size */
-1, /* min workers */
-1, /* max workers */
- 10, /* max workers idle secs */
+ 10 * 60, /* max workers idle secs */
64 * 1024, /* stream max mem size */
NULL, /* no alt-svcs */
-1, /* alt-svc max age */
1024*1024, /* TLS warmup size */
1, /* TLS cooldown secs */
1, /* HTTP/2 server push enabled */
+ NULL, /* map of content-type to priorities */
};
static int files_per_session = 0;
conf->tls_warmup_size = DEF_VAL;
conf->tls_cooldown_secs = DEF_VAL;
conf->h2_push = DEF_VAL;
+ conf->priorities = NULL;
return conf;
}
n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size);
n->tls_cooldown_secs = H2_CONFIG_GET(add, base, tls_cooldown_secs);
n->h2_push = H2_CONFIG_GET(add, base, h2_push);
+ if (add->priorities && base->priorities) {
+ n->priorities = apr_hash_overlay(pool, add->priorities, base->priorities);
+ }
+ else {
+ n->priorities = add->priorities? add->priorities : base->priorities;
+ }
return n;
}
-int h2_config_geti(h2_config *conf, h2_config_var_t var)
+int h2_config_geti(const h2_config *conf, h2_config_var_t var)
{
return (int)h2_config_geti64(conf, var);
}
-apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var)
+apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var)
{
int n;
switch(var) {
}
}
-h2_config *h2_config_sget(server_rec *s)
+const h2_config *h2_config_sget(server_rec *s)
{
h2_config *cfg = (h2_config *)ap_get_module_config(s->module_config,
&http2_module);
return cfg;
}
+const struct h2_priority *h2_config_get_priority(const h2_config *conf,
+ const char *content_type)
+{
+ if (content_type && conf->priorities) {
+ size_t len = strcspn(content_type, "; \t");
+ h2_priority *prio = apr_hash_get(conf->priorities, content_type, len);
+ return prio? prio : apr_hash_get(conf->priorities, "*", 1);
+ }
+ return NULL;
+}
static const char *h2_conf_set_max_streams(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->h2_max_streams = (int)apr_atoi64(value);
(void)arg;
if (cfg->h2_max_streams < 1) {
static const char *h2_conf_set_window_size(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->h2_window_size = (int)apr_atoi64(value);
(void)arg;
if (cfg->h2_window_size < 1024) {
static const char *h2_conf_set_min_workers(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->min_workers = (int)apr_atoi64(value);
(void)arg;
if (cfg->min_workers < 1) {
static const char *h2_conf_set_max_workers(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->max_workers = (int)apr_atoi64(value);
(void)arg;
if (cfg->max_workers < 1) {
static const char *h2_conf_set_max_worker_idle_secs(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->max_worker_idle_secs = (int)apr_atoi64(value);
(void)arg;
if (cfg->max_worker_idle_secs < 1) {
static const char *h2_conf_set_stream_max_mem_size(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->stream_max_mem_size = (int)apr_atoi64(value);
void *arg, const char *value)
{
if (value && strlen(value)) {
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
h2_alt_svc *as = h2_alt_svc_parse(value, parms->pool);
if (!as) {
return "unable to parse alt-svc specifier";
static const char *h2_conf_set_alt_svc_max_age(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->alt_svc_max_age = (int)apr_atoi64(value);
(void)arg;
return NULL;
static const char *h2_conf_set_session_extra_files(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
apr_int64_t max = (int)apr_atoi64(value);
if (max < 0) {
return "value must be a non-negative number";
static const char *h2_conf_set_serialize_headers(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->serialize_headers = 1;
return NULL;
static const char *h2_conf_set_direct(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->h2_direct = 1;
return NULL;
static const char *h2_conf_set_push(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->h2_push = 1;
return NULL;
return "value must be On or Off";
}
+static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
+ const char *ctype, const char *sdependency,
+ const char *sweight)
+{
+ h2_config *cfg = (h2_config *)h2_config_sget(cmd->server);
+ const char *sdefweight = "16"; /* default AFTER weight */
+ h2_dependency dependency;
+ h2_priority *priority;
+ int weight;
+
+ if (!strlen(ctype)) {
+ return "1st argument must be a mime-type, like 'text/css' or '*'";
+ }
+
+ if (!sweight) {
+ /* 2 args only, but which one? */
+ if (apr_isdigit(sdependency[0])) {
+ sweight = sdependency;
+ sdependency = "AFTER"; /* default dependency */
+ }
+ }
+
+ if (!strcasecmp("AFTER", sdependency)) {
+ dependency = H2_DEPENDANT_AFTER;
+ }
+ else if (!strcasecmp("BEFORE", sdependency)) {
+ dependency = H2_DEPENDANT_BEFORE;
+ sdefweight = "256"; /* default BEFORE weight */
+ }
+ else if (!strcasecmp("INTERLEAVED", sdependency)) {
+ dependency = H2_DEPENDANT_INTERLEAVED;
+ sdefweight = "256"; /* default INTERLEAVED weight */
+ }
+ else {
+ return "dependency must be one of 'After', 'Before' or 'Interleaved'";
+ }
+
+ weight = (int)apr_atoi64(sweight? sweight : sdefweight);
+ if (weight < NGHTTP2_MIN_WEIGHT) {
+ return apr_psprintf(cmd->pool, "weight must be a number >= %d",
+ NGHTTP2_MIN_WEIGHT);
+ }
+
+ priority = apr_pcalloc(cmd->pool, sizeof(*priority));
+ priority->dependency = dependency;
+ priority->weight = weight;
+
+ if (!cfg->priorities) {
+ cfg->priorities = apr_hash_make(cmd->pool);
+ }
+ apr_hash_set(cfg->priorities, ctype, strlen(ctype), priority);
+ return NULL;
+}
+
static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->modern_tls_only = 1;
return NULL;
static const char *h2_conf_set_upgrade(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->h2_upgrade = 1;
return NULL;
static const char *h2_conf_set_tls_warmup_size(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->tls_warmup_size = apr_atoi64(value);
(void)arg;
return NULL;
static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = h2_config_sget(parms->server);
+ h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->tls_cooldown_secs = (int)apr_atoi64(value);
(void)arg;
return NULL;
RSRC_CONF, "seconds of idle time on TLS before shrinking writes"),
AP_INIT_TAKE1("H2Push", h2_conf_set_push, NULL,
RSRC_CONF, "off to disable HTTP/2 server push"),
+ AP_INIT_TAKE23("H2PushPriority", h2_conf_add_push_priority, NULL,
+ RSRC_CONF, "define priority of PUSHed resources per content type"),
AP_END_CMD
};
-h2_config *h2_config_rget(request_rec *r)
+const h2_config *h2_config_rget(request_rec *r)
{
h2_config *cfg = (h2_config *)ap_get_module_config(r->per_dir_config,
&http2_module);
return cfg? cfg : h2_config_sget(r->server);
}
-h2_config *h2_config_get(conn_rec *c)
+const h2_config *h2_config_get(conn_rec *c)
{
h2_ctx *ctx = h2_ctx_get(c);
H2_CONF_PUSH,
} h2_config_var_t;
+struct apr_hash_t;
+struct h2_priority;
+
/* Apache httpd module configuration for h2. */
typedef struct h2_config {
const char *name;
apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */
int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */
int h2_push; /* if HTTP/2 server push is enabled */
+ struct apr_hash_t *priorities;/* map of content-type to h2_priority records */
} h2_config;
void *h2_config_create_svr(apr_pool_t *pool, server_rec *s);
void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv);
-apr_status_t h2_config_apply_header(h2_config *config, request_rec *r);
+apr_status_t h2_config_apply_header(const h2_config *config, request_rec *r);
extern const command_rec h2_cmds[];
-h2_config *h2_config_get(conn_rec *c);
-h2_config *h2_config_sget(server_rec *s);
-h2_config *h2_config_rget(request_rec *r);
+const h2_config *h2_config_get(conn_rec *c);
+const h2_config *h2_config_sget(server_rec *s);
+const h2_config *h2_config_rget(request_rec *r);
-int h2_config_geti(h2_config *conf, h2_config_var_t var);
-apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var);
+int h2_config_geti(const h2_config *conf, h2_config_var_t var);
+apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var);
void h2_config_init(apr_pool_t *pool);
+const struct h2_priority *h2_config_get_priority(const h2_config *conf,
+ const char *content_type);
+
#endif /* __mod_h2__h2_config_h__ */
apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s)
{
- h2_config *config = h2_config_sget(s);
+ const h2_config *config = h2_config_sget(s);
apr_status_t status = APR_SUCCESS;
int minw = h2_config_geti(config, H2_CONF_MIN_WORKERS);
int maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS);
return mpm_module;
}
-apr_status_t h2_conn_process(conn_rec *c, request_rec *r)
+apr_status_t h2_conn_process(conn_rec *c, request_rec *r, server_rec *s)
{
apr_status_t status;
h2_session *session;
- h2_config *config;
+ const h2_config *config;
int rv;
if (!workers) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_process start");
+ if (!s && r) {
+ s = r->server;
+ }
+
+ config = s? h2_config_sget(s) : h2_config_get(c);
if (r) {
- config = h2_config_rget(r);
session = h2_session_rcreate(r, config, workers);
}
else {
- config = h2_config_get(c);
session = h2_session_create(c, config, workers);
}
* and the connection will close.
*
* @param c the connection HTTP/2 is starting on
- * @param r the upgrad requestion that still awaits an answer, optional
+ * @param r the upgrade request that still awaits an answer, optional
+ * @param s the server selected by request or, if NULL, connection
*/
-apr_status_t h2_conn_process(conn_rec *c, request_rec *r);
+apr_status_t h2_conn_process(conn_rec *c, request_rec *r, server_rec *s);
/* Initialize this child process for h2 connection work,
* to be called once during child init before multi processing
#define WRITE_BUFFER_SIZE (8*WRITE_SIZE_MAX)
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, apr_pool_t *pool)
+apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c,
+ const h2_config *cfg,
+ apr_pool_t *pool)
{
- h2_config *cfg = h2_config_get(c);
-
io->connection = c;
io->input = apr_brigade_create(pool, c->bucket_alloc);
io->output = apr_brigade_create(pool, c->bucket_alloc);
#ifndef __mod_h2__h2_conn_io__
#define __mod_h2__h2_conn_io__
+struct h2_config;
+
/* h2_io is the basic handler of a httpd connection. It keeps two brigades,
* one for input, one for output and works with the installed connection
* filters.
int unflushed;
} h2_conn_io;
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, apr_pool_t *pool);
+apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c,
+ const struct h2_config *cfg,
+ apr_pool_t *pool);
int h2_conn_io_is_buffered(h2_conn_io *io);
* - those created by ourself to perform work on HTTP/2 streams
*/
typedef struct h2_ctx {
- int is_h2; /* h2 engine is used */
- const char *protocol; /* the protocol negotiated */
- struct h2_task *task; /* the h2_task executing or NULL */
- const char *hostname; /* hostname negotiated via SNI, optional */
- server_rec *server; /* httpd server config selected. */
- struct h2_config *config; /* effective config in this context */
+ int is_h2; /* h2 engine is used */
+ const char *protocol; /* the protocol negotiated */
+ struct h2_task *task; /* the h2_task executing or NULL */
+ const char *hostname; /* hostname negotiated via SNI, optional */
+ server_rec *server; /* httpd server config selected. */
+ const struct h2_config *config; /* effective config in this context */
} h2_ctx;
h2_ctx *h2_ctx_get(const conn_rec *c);
int h2_is_acceptable_connection(conn_rec *c, int require_all)
{
int is_tls = h2_h2_is_tls(c);
- h2_config *cfg = h2_config_get(c);
+ const h2_config *cfg = h2_config_get(c);
if (is_tls && h2_config_geti(cfg, H2_CONF_MODERN_TLS_ONLY) > 0) {
/* Check TLS connection for modern TLS parameters, as defined in
int h2_allows_h2_direct(conn_rec *c)
{
- h2_config *cfg = h2_config_get(c);
+ const h2_config *cfg = h2_config_get(c);
int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT);
if (h2_direct < 0) {
int h2_allows_h2_upgrade(conn_rec *c)
{
- h2_config *cfg = h2_config_get(c);
+ const h2_config *cfg = h2_config_get(c);
int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE);
return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c));
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"h2_h2, connection, h2 active");
- return h2_conn_process(c, NULL);
+ return h2_conn_process(c, NULL, ctx->server);
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
* their HTTP/1 cousins, the separate allocator seems to work better
* than protecting a shared h2_session one with an own lock.
*/
-h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, h2_workers *workers)
+h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent,
+ const h2_config *conf,
+ h2_workers *workers)
{
apr_status_t status = APR_SUCCESS;
- h2_config *conf = h2_config_get(c);
apr_allocator_t *allocator = NULL;
h2_mplx *m;
AP_DEBUG_ASSERT(conf);
* Implicitly has reference count 1.
*/
h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *master,
+ const struct h2_config *conf,
struct h2_workers *workers);
/**
path = apr_uri_unparse(ctx->pool, &uri, APR_URI_UNP_OMITSITEPART);
push = apr_pcalloc(ctx->pool, sizeof(*push));
- push->initiating_id = ctx->req->id;
headers = apr_table_make(ctx->pool, 5);
apr_table_do(set_header, headers, ctx->req->headers,
"Cache-Control",
"Accept-Language",
NULL);
- /* TODO: which headers do we add here?
- */
-
- req = h2_request_createn(0, ctx->pool,
- ctx->req->method,
- ctx->req->scheme,
+ req = h2_request_createn(0, ctx->pool, ctx->req->config,
+ "GET", ctx->req->scheme,
ctx->req->authority,
path, headers);
h2_request_end_headers(req, ctx->pool, 1);
push->req = req;
- push->prio.dependency = H2_DEPENDANT_AFTER;
- push->prio.weight = NGHTTP2_DEFAULT_WEIGHT;
-
if (!ctx->pushes) {
ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*));
}
struct h2_ngheader;
typedef struct h2_push {
- int initiating_id;
const struct h2_request *req;
- h2_priority prio;
} h2_push;
#include <scoreboard.h>
#include "h2_private.h"
+#include "h2_config.h"
#include "h2_mplx.h"
#include "h2_request.h"
#include "h2_task.h"
#include "h2_util.h"
-h2_request *h2_request_create(int id, apr_pool_t *pool)
+h2_request *h2_request_create(int id, apr_pool_t *pool,
+ const struct h2_config *config)
{
- return h2_request_createn(id, pool, NULL, NULL, NULL, NULL, NULL);
+ return h2_request_createn(id, pool, config,
+ NULL, NULL, NULL, NULL, NULL);
}
h2_request *h2_request_createn(int id, apr_pool_t *pool,
+ const struct h2_config *config,
const char *method, const char *scheme,
const char *authority, const char *path,
apr_table_t *header)
h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
req->id = id;
+ req->config = config;
req->method = method;
req->scheme = scheme;
req->authority = authority;
{
apr_status_t status;
+ req->config = h2_config_rget(r);
req->method = r->method;
req->scheme = (r->parsed_uri.scheme? r->parsed_uri.scheme
: ap_http_scheme(r));
* format that will be fed to various httpd input filters to finally
* become a request_rec to be handled by soemone.
*/
+struct h2_config;
struct h2_to_h1;
struct h2_mplx;
struct h2_task;
apr_off_t content_length;
int chunked;
int eoh;
+
+ const struct h2_config *config;
};
-h2_request *h2_request_create(int id, apr_pool_t *pool);
+h2_request *h2_request_create(int id, apr_pool_t *pool,
+ const struct h2_config *config);
h2_request *h2_request_createn(int id, apr_pool_t *pool,
+ const struct h2_config *config,
const char *method, const char *scheme,
const char *authority, const char *path,
apr_table_t *headers);
return h2_session_status_from_apr_status(status);
}
+static int on_frame_send_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ void *user_data)
+{
+ h2_session *session = user_data;
+ if (APLOGctrace1(session->c)) {
+ char buffer[256];
+
+ frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_session(%ld): frame_send %s",
+ session->id, buffer);
+ }
+ return 0;
+}
+
#define NGH2_SET_CALLBACK(callbacks, name, fn)\
nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
-
+ NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
+
return APR_SUCCESS;
}
static h2_session *h2_session_create_int(conn_rec *c,
request_rec *r,
- h2_config *config,
+ const h2_config *config,
h2_workers *workers)
{
nghttp2_session_callbacks *callbacks = NULL;
session->id = c->id;
session->c = c;
session->r = r;
+ session->config = config;
session->pool = pool;
apr_pool_pre_cleanup_register(pool, session, session_pool_cleanup);
session->streams = h2_stream_set_create(session->pool, session->max_stream_count);
session->workers = workers;
- session->mplx = h2_mplx_create(c, session->pool, workers);
+ session->mplx = h2_mplx_create(c, session->pool, config, workers);
- h2_conn_io_init(&session->io, c, session->pool);
+ h2_conn_io_init(&session->io, c, config, session->pool);
session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
status = init_callbacks(c, &callbacks);
return session;
}
-h2_session *h2_session_create(conn_rec *c, h2_config *config,
+h2_session *h2_session_create(conn_rec *c, const h2_config *config,
h2_workers *workers)
{
return h2_session_create_int(c, NULL, config, workers);
}
-h2_session *h2_session_rcreate(request_rec *r, h2_config *config,
+h2_session *h2_session_rcreate(request_rec *r, const h2_config *config,
h2_workers *workers)
{
return h2_session_create_int(r->connection, r, config, workers);
apr_status_t h2_session_start(h2_session *session, int *rv)
{
apr_status_t status = APR_SUCCESS;
- h2_config *config;
nghttp2_settings_entry settings[3];
size_t slen;
int i;
AP_DEBUG_ASSERT(session);
/* Start the conversation by submitting our SETTINGS frame */
*rv = 0;
- config = h2_config_get(session->c);
if (session->r) {
const char *s, *cs;
apr_size_t dlen;
h2_stream * stream;
- /* better for vhost matching */
- config = h2_config_rget(session->r);
-
/* 'h2c' mode: we should have a 'HTTP2-Settings' header with
* base64 encoded client settings. */
s = apr_table_get(session->r->headers_in, "HTTP2-Settings");
settings[slen].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
settings[slen].value = (uint32_t)session->max_stream_count;
++slen;
- i = h2_config_geti(config, H2_CONF_WIN_SIZE);
+ i = h2_config_geti(session->config, H2_CONF_WIN_SIZE);
if (i != H2_INITIAL_WINDOW_SIZE) {
settings[slen].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
settings[slen].value = i;
nghttp2_data_provider provider;
h2_response *response = stream->response;
h2_ngheader *ngh;
- h2_priority *prio;
+ const h2_priority *prio;
memset(&provider, 0, sizeof(provider));
provider.source.fd = stream->id;
*/
if (!rv
&& !stream->initiated_on
- && h2_config_geti(h2_config_get(session->c), H2_CONF_PUSH)
+ && h2_config_geti(session->config, H2_CONF_PUSH)
&& H2_HTTP_2XX(response->http_status)
&& h2_session_push_enabled(session)) {
int nid;
ngh = h2_util_ngheader_make_req(is->pool, push->req);
- nid = nghttp2_submit_push_promise(session->ngh2, 0, push->initiating_id,
+ nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id,
ngh->nv, ngh->nvlen, NULL);
if (nid <= 0) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
"h2_stream(%ld-%d): submitting push promise fail: %s",
- session->id, push->initiating_id,
- nghttp2_strerror(nid));
+ session->id, is->id, nghttp2_strerror(nid));
return NULL;
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- "h2_stream(%ld-%d): promised new stream %d for %s %s",
- session->id, push->initiating_id, nid,
- push->req->method, push->req->path);
+ "h2_stream(%ld-%d): promised new stream %d for %s %s on %d",
+ session->id, is->id, nid,
+ push->req->method, push->req->path, is->id);
stream = h2_session_open_stream(session, nid);
if (stream) {
h2_stream_set_h2_request(stream, is->id, push->req);
- h2_stream_set_priority(stream, &push->prio);
status = stream_schedule(session, stream, 1);
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
else {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
"h2_stream(%ld-%d): failed to create stream obj %d",
- session->id, push->initiating_id, nid);
+ session->id, is->id, nid);
}
if (!stream) {
}
apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
- h2_priority *prio)
+ const h2_priority *prio)
{
apr_status_t status = APR_SUCCESS;
#ifdef H2_NG2_CHANGE_PRIO
*/
ptype = "INTERLEAVED";
w_parent = nghttp2_stream_get_weight(s_parent);
- w = valid_weight(w_parent * ((float)NGHTTP2_MAX_WEIGHT / prio->weight));
+ w = valid_weight(w_parent * ((float)prio->weight / NGHTTP2_MAX_WEIGHT));
nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
break;
* stream as child with MAX_WEIGHT.
*/
ptype = "BEFORE";
- nghttp2_priority_spec_init(&ps, stream->id, NGHTTP2_MAX_WEIGHT, 0);
+ w_parent = nghttp2_stream_get_weight(s_parent);
+ nghttp2_priority_spec_init(&ps, stream->id, w_parent, 0);
rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps);
if (rv < 0) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
return APR_EGENERAL;
}
id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
- w_parent = nghttp2_stream_get_weight(s_parent);
- nghttp2_priority_spec_init(&ps, id_grandpa, valid_weight(w_parent), 0);
+ w = valid_weight(w_parent * ((float)prio->weight / NGHTTP2_MAX_WEIGHT));
+ nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
break;
case H2_DEPENDANT_AFTER:
apr_interval_time_t wait_micros = 0;
static const int MAX_WAIT_MICROS = 200 * 1000;
int got_streams = 0;
+ h2_stream *stream;
while (!session->aborted && (nghttp2_session_want_read(session->ngh2)
|| nghttp2_session_want_write(session->ngh2))) {
int have_written = 0;
int have_read = 0;
+ got_streams = !h2_stream_set_is_empty(session->streams);
+ if (got_streams) {
+ h2_session_resume_streams_with_data(session);
+
+ if (h2_stream_set_has_unsubmitted(session->streams)) {
+ /* If we have responses ready, submit them now. */
+ while ((stream = h2_mplx_next_submit(session->mplx, session->streams))) {
+ status = submit_response(session, stream);
+ if (status == APR_SUCCESS
+ && nghttp2_session_want_write(session->ngh2)) {
+ int rv;
+
+ rv = nghttp2_session_send(session->ngh2);
+ if (rv != 0) {
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_session: send: %s", nghttp2_strerror(rv));
+ if (nghttp2_is_fatal(rv)) {
+ h2_session_abort(session, status, rv);
+ goto end_process;
+ }
+ }
+ else {
+ have_written = 1;
+ wait_micros = 0;
+ }
+ }
+ }
+ }
+ }
+
/* Send data as long as we have it and window sizes allow. We are
* a server after all.
*/
}
got_streams = !h2_stream_set_is_empty(session->streams);
- if (got_streams) {
- h2_stream *stream;
-
+ if (got_streams) {
if (session->reprioritize) {
h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
session->reprioritize = 0;
h2_conn_io_flush(&session->io);
}
}
-
- h2_session_resume_streams_with_data(session);
-
- if (h2_stream_set_has_unsubmitted(session->streams)) {
- /* If we have responses ready, submit them now. */
- while ((stream = h2_mplx_next_submit(session->mplx, session->streams))) {
- status = submit_response(session, stream);
- }
- }
}
if (have_written) {
conn_rec *c; /* the connection this session serves */
request_rec *r; /* the request that started this in case
* of 'h2c', NULL otherwise */
+ const struct h2_config *config; /* Relevant config for this session */
int aborted; /* this session is being aborted */
int reprioritize; /* scheduled streams priority needs to
* be re-evaluated */
* @param workers the worker pool to use
* @return the created session
*/
-h2_session *h2_session_create(conn_rec *c, struct h2_config *cfg,
+h2_session *h2_session_create(conn_rec *c, const struct h2_config *cfg,
struct h2_workers *workers);
/**
* @param workers the worker pool to use
* @return the created session
*/
-h2_session *h2_session_rcreate(request_rec *r, struct h2_config *cfg,
+h2_session *h2_session_rcreate(request_rec *r, const struct h2_config *cfg,
struct h2_workers *workers);
/**
apr_status_t h2_session_set_prio(h2_session *session,
struct h2_stream *stream,
- struct h2_priority *prio);
+ const struct h2_priority *prio);
#endif /* defined(__mod_h2__h2_session__) */
#include "h2_private.h"
#include "h2_conn.h"
+#include "h2_config.h"
#include "h2_h2.h"
#include "h2_mplx.h"
#include "h2_push.h"
{
h2_stream *stream = h2_stream_create(id, pool, session);
set_state(stream, H2_STREAM_ST_OPEN);
- stream->request = h2_request_create(id, pool);
+ stream->request = h2_request_create(id, pool, session->config);
stream->bbout = apr_brigade_create(stream->pool,
stream->session->c->bucket_alloc);
return stream->response? stream->response->trailers : NULL;
}
-void h2_stream_set_priority(h2_stream *stream, h2_priority *prio)
+const h2_priority *h2_stream_get_priority(h2_stream *stream)
{
- stream->prio = apr_pcalloc(stream->pool, sizeof(*prio));
- memcpy(stream->prio, prio, sizeof(*prio));
-}
-
-h2_priority *h2_stream_get_priority(h2_stream *stream)
-{
- return stream->prio;
+ if (stream->initiated_on && stream->response) {
+ const char *ctype = apr_table_get(stream->response->headers, "content-type");
+ if (ctype) {
+ /* FIXME: Not good enough, config needs to come from request->server */
+ return h2_config_get_priority(stream->session->config, ctype);
+ }
+ }
+ return NULL;
}
apr_bucket_brigade *bbout; /* output DATA */
apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */
-
- struct h2_priority *prio; /* priority information to set before submit */
};
/**
* Get priority information set for this stream.
*/
-struct h2_priority *h2_stream_get_priority(h2_stream *stream);
-
-/**
- * Set the priority information to use on the submit of the stream.
- * @param stream the stream to set priority on
- * @param prio the priority information
- */
-void h2_stream_set_priority(h2_stream *stream, struct h2_priority *prio);
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream);
#endif /* defined(__mod_h2__h2_stream__) */
ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
/* Ok, start an h2_conn on this one. */
- status = h2_conn_process(r->connection, r);
+ status = h2_conn_process(r->connection, r, r->server);
if (status != DONE) {
/* Nothing really to do about this. */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
return NULL;
}
- task->id = apr_psprintf(pool, "%ld-%d", session_id, req->id);
+ task->id = apr_psprintf(pool, "%ld-%d", session_id, req->id);
task->stream_id = req->id;
- task->pool = pool;
- task->mplx = mplx;
- task->c = h2_conn_create(mplx->c, task->pool);
+ task->pool = pool;
+ task->mplx = mplx;
+ task->c = h2_conn_create(mplx->c, task->pool);
- task->request = req;
+ task->request = req;
task->input_eos = eos;
return task;
apr_status_t h2_task_do(h2_task *task, h2_worker *worker)
{
apr_status_t status = APR_SUCCESS;
- h2_config *cfg = h2_config_get(task->mplx->c);
AP_DEBUG_ASSERT(task);
- task->serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS);
+ task->serialize_headers = h2_config_geti(task->request->config, H2_CONF_SER_HEADERS);
status = h2_worker_setup_task(worker, task);
};
h2_task *h2_task_create(long session_id, const struct h2_request *req,
- apr_pool_t *pool, struct h2_mplx *mplx, int eos);
+ apr_pool_t *pool, struct h2_mplx *mplx,
+ int eos);
apr_status_t h2_task_destroy(h2_task *task);
* @macro
* Version number of the h2 module as c string
*/
-#define MOD_HTTP2_VERSION "1.0.7"
+#define MOD_HTTP2_VERSION "1.0.8"
/**
* @macro
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_HTTP2_VERSION_NUM 0x010007
+#define MOD_HTTP2_VERSION_NUM 0x010008
#endif /* mod_h2_h2_version_h */