mirror of
https://github.com/apache/httpd.git
synced 2025-08-07 04:02:58 +03:00
new directive H2Push on/off to en-/disable HTTP/2 server pushes. Server pushes are recognized by Link: headers in responses that carry the rel=preload parameter
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1714219 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@@ -89,6 +89,74 @@
|
|||||||
</usage>
|
</usage>
|
||||||
</directivesynopsis>
|
</directivesynopsis>
|
||||||
|
|
||||||
|
<directivesynopsis>
|
||||||
|
<name>H2Push</name>
|
||||||
|
<description>H2 Server Push Switch</description>
|
||||||
|
<syntax>H2Push on|off</syntax>
|
||||||
|
<default>H2Push on</default>
|
||||||
|
<contextlist>
|
||||||
|
<context>server config</context>
|
||||||
|
<context>virtual host</context>
|
||||||
|
</contextlist>
|
||||||
|
|
||||||
|
<usage>
|
||||||
|
<p>
|
||||||
|
This directive toggles the usage of the HTTP/2 server push
|
||||||
|
protocol feature. This should be used inside a
|
||||||
|
<directive module="core" type="section">VirtualHost</directive>
|
||||||
|
section to enable direct HTTP/2 communication for that virtual host.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The HTTP/2 protocol allows the server to push other resources to
|
||||||
|
a client when it asked for a particular one. This is helpful
|
||||||
|
if those resources are connected in some way and the client can
|
||||||
|
be expected to ask for it anyway. The pushing then saves the
|
||||||
|
time it takes the client to ask for the resources itself. On the
|
||||||
|
other hand, pushing resources the client never needs or already
|
||||||
|
has is a waste of bandwidth.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Server pushes are detected by inspecting the <code>Link</code> headers of
|
||||||
|
responses (see https://tools.ietf.org/html/rfc5988 for the
|
||||||
|
specification). When a link thus specified has the <code>rel=preload</code>
|
||||||
|
attribute, it is treated as a resource to be pushed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Link headers in responses are either set by the application or
|
||||||
|
can be configured via <module>mod_headers</module> as:
|
||||||
|
</p>
|
||||||
|
<example><title>mod_headers example</title>
|
||||||
|
<highlight language="config">
|
||||||
|
<Location /index.html>
|
||||||
|
Header add Link "</css/site.css>;rel=preload"
|
||||||
|
Header add Link "</images/logo.jpg>;rel=preload"
|
||||||
|
</Location>
|
||||||
|
</highlight>
|
||||||
|
</example>
|
||||||
|
<p>
|
||||||
|
As the example shows, there can be several link headers added
|
||||||
|
to a response, resulting in several pushes being triggered. There
|
||||||
|
are no checks in the module to avoid pushing the same resource
|
||||||
|
twice or more to one client. Use with care.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
HTTP/2 server pushes are enabled by default. This directive
|
||||||
|
allows it to be switch off on all resources of this server/virtual
|
||||||
|
host.
|
||||||
|
</p>
|
||||||
|
<example><title>Example</title>
|
||||||
|
<highlight language="config">
|
||||||
|
H2Push off
|
||||||
|
</highlight>
|
||||||
|
</example>
|
||||||
|
<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.
|
||||||
|
</p>
|
||||||
|
</usage>
|
||||||
|
</directivesynopsis>
|
||||||
|
|
||||||
<directivesynopsis>
|
<directivesynopsis>
|
||||||
<name>H2Upgrade</name>
|
<name>H2Upgrade</name>
|
||||||
<description>H2 Upgrade Protocol Switch</description>
|
<description>H2 Upgrade Protocol Switch</description>
|
||||||
@@ -285,7 +353,7 @@
|
|||||||
<p>
|
<p>
|
||||||
This directive sets maximum number of <em>extra</em> file handles
|
This directive sets maximum number of <em>extra</em> file handles
|
||||||
a HTTP/2 session is allowed to use. A file handle is counted as
|
a HTTP/2 session is allowed to use. A file handle is counted as
|
||||||
<em>extra</em> when it is transfered from a h2 worker thread to
|
<em>extra</em> when it is transferred from a h2 worker thread to
|
||||||
the main HTTP/2 connection handling. This commonly happens when
|
the main HTTP/2 connection handling. This commonly happens when
|
||||||
serving static files.
|
serving static files.
|
||||||
</p><p>
|
</p><p>
|
||||||
@@ -362,8 +430,8 @@
|
|||||||
<p>
|
<p>
|
||||||
The name stems from the
|
The name stems from the
|
||||||
<a href="https://wiki.mozilla.org/Security/Server_Side_TLS">Security/Server Side TLS</a>
|
<a href="https://wiki.mozilla.org/Security/Server_Side_TLS">Security/Server Side TLS</a>
|
||||||
definitions at mozilla where "modern compatiblity" is defined. Mozilla Firefox and
|
definitions at mozilla where "modern compatibility" is defined. Mozilla Firefox and
|
||||||
other browsers require modern compatiblity for HTTP/2 connections. As everything
|
other browsers require modern compatibility for HTTP/2 connections. As everything
|
||||||
in OpSec, this is a moving target and can be expected to evolve in the future.
|
in OpSec, this is a moving target and can be expected to evolve in the future.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -423,7 +491,7 @@
|
|||||||
<p>
|
<p>
|
||||||
In deployments where servers are reached locally or over reliable
|
In deployments where servers are reached locally or over reliable
|
||||||
connections only, the value might be decreased with 0 disabling
|
connections only, the value might be decreased with 0 disabling
|
||||||
any warmup phase alltogether.
|
any warmup phase altogether.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The following example sets the size to zero, effectively disabling
|
The following example sets the size to zero, effectively disabling
|
||||||
@@ -458,7 +526,7 @@
|
|||||||
<p>
|
<p>
|
||||||
See <directive type="section">H2TLSWarmUpSize</directive> for a
|
See <directive type="section">H2TLSWarmUpSize</directive> for a
|
||||||
description of TLS warmup. H2TLSCoolDownSecs reflects the fact
|
description of TLS warmup. H2TLSCoolDownSecs reflects the fact
|
||||||
that connections may detoriate over time (and TCP flow adjusts)
|
that connections may deteriorate over time (and TCP flow adjusts)
|
||||||
for idle connections as well. It is beneficial to overall performance
|
for idle connections as well. It is beneficial to overall performance
|
||||||
to fall back to the pre-warmup phase after a number of seconds that
|
to fall back to the pre-warmup phase after a number of seconds that
|
||||||
no data has been sent.
|
no data has been sent.
|
||||||
@@ -469,7 +537,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The following example sets the seconds to zero, effectively disabling
|
The following example sets the seconds to zero, effectively disabling
|
||||||
any cooldown. Warmed up TLS connections stay on maximum record
|
any cool down. Warmed up TLS connections stay on maximum record
|
||||||
size.
|
size.
|
||||||
</p>
|
</p>
|
||||||
<example><title>Example</title>
|
<example><title>Example</title>
|
||||||
|
@@ -53,6 +53,7 @@ static h2_config defconf = {
|
|||||||
-1, /* HTTP/1 Upgrade support */
|
-1, /* HTTP/1 Upgrade support */
|
||||||
1024*1024, /* TLS warmup size */
|
1024*1024, /* TLS warmup size */
|
||||||
1, /* TLS cooldown secs */
|
1, /* TLS cooldown secs */
|
||||||
|
1, /* HTTP/2 server push enabled */
|
||||||
};
|
};
|
||||||
|
|
||||||
static int files_per_session = 0;
|
static int files_per_session = 0;
|
||||||
@@ -108,6 +109,7 @@ static void *h2_config_create(apr_pool_t *pool,
|
|||||||
conf->h2_upgrade = DEF_VAL;
|
conf->h2_upgrade = DEF_VAL;
|
||||||
conf->tls_warmup_size = DEF_VAL;
|
conf->tls_warmup_size = DEF_VAL;
|
||||||
conf->tls_cooldown_secs = DEF_VAL;
|
conf->tls_cooldown_secs = DEF_VAL;
|
||||||
|
conf->h2_push = DEF_VAL;
|
||||||
|
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
@@ -151,6 +153,7 @@ void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
|
|||||||
n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
|
n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
|
||||||
n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size);
|
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->tls_cooldown_secs = H2_CONFIG_GET(add, base, tls_cooldown_secs);
|
||||||
|
n->h2_push = H2_CONFIG_GET(add, base, h2_push);
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -196,6 +199,8 @@ apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var)
|
|||||||
return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
|
return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
|
||||||
case H2_CONF_TLS_COOLDOWN_SECS:
|
case H2_CONF_TLS_COOLDOWN_SECS:
|
||||||
return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs);
|
return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs);
|
||||||
|
case H2_CONF_PUSH:
|
||||||
|
return H2_CONFIG_GET(conf, &defconf, h2_push);
|
||||||
default:
|
default:
|
||||||
return DEF_VAL;
|
return DEF_VAL;
|
||||||
}
|
}
|
||||||
@@ -358,6 +363,23 @@ static const char *h2_conf_set_direct(cmd_parms *parms,
|
|||||||
return "value must be On or Off";
|
return "value must be On or Off";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *h2_conf_set_push(cmd_parms *parms,
|
||||||
|
void *arg, const char *value)
|
||||||
|
{
|
||||||
|
h2_config *cfg = h2_config_sget(parms->server);
|
||||||
|
if (!strcasecmp(value, "On")) {
|
||||||
|
cfg->h2_push = 1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else if (!strcasecmp(value, "Off")) {
|
||||||
|
cfg->h2_push = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)arg;
|
||||||
|
return "value must be On or Off";
|
||||||
|
}
|
||||||
|
|
||||||
static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
|
static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
|
||||||
void *arg, const char *value)
|
void *arg, const char *value)
|
||||||
{
|
{
|
||||||
@@ -444,6 +466,8 @@ const command_rec h2_cmds[] = {
|
|||||||
RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
|
RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
|
||||||
AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
|
AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
|
||||||
RSRC_CONF, "seconds of idle time on TLS before shrinking writes"),
|
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_END_CMD
|
AP_END_CMD
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -38,6 +38,7 @@ typedef enum {
|
|||||||
H2_CONF_UPGRADE,
|
H2_CONF_UPGRADE,
|
||||||
H2_CONF_TLS_WARMUP_SIZE,
|
H2_CONF_TLS_WARMUP_SIZE,
|
||||||
H2_CONF_TLS_COOLDOWN_SECS,
|
H2_CONF_TLS_COOLDOWN_SECS,
|
||||||
|
H2_CONF_PUSH,
|
||||||
} h2_config_var_t;
|
} h2_config_var_t;
|
||||||
|
|
||||||
/* Apache httpd module configuration for h2. */
|
/* Apache httpd module configuration for h2. */
|
||||||
@@ -59,6 +60,7 @@ typedef struct h2_config {
|
|||||||
int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
|
int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
|
||||||
apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */
|
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 tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */
|
||||||
|
int h2_push; /* if HTTP/2 server push is enabled */
|
||||||
} h2_config;
|
} h2_config;
|
||||||
|
|
||||||
|
|
||||||
|
@@ -664,7 +664,7 @@ static int h2_h2_post_read_req(request_rec *r)
|
|||||||
struct h2_task *task = h2_ctx_get_task(ctx);
|
struct h2_task *task = h2_ctx_get_task(ctx);
|
||||||
if (task) {
|
if (task) {
|
||||||
/* h2_task connection for a stream, not for h2c */
|
/* h2_task connection for a stream, not for h2c */
|
||||||
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
|
||||||
"adding h1_to_h2_resp output filter");
|
"adding h1_to_h2_resp output filter");
|
||||||
if (task->serialize_headers) {
|
if (task->serialize_headers) {
|
||||||
ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP");
|
ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP");
|
||||||
|
@@ -583,7 +583,7 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response,
|
|||||||
h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
|
h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
|
||||||
if (io) {
|
if (io) {
|
||||||
if (f) {
|
if (f) {
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||||
"h2_mplx(%ld-%d): open response: %d, rst=%d",
|
"h2_mplx(%ld-%d): open response: %d, rst=%d",
|
||||||
m->id, stream_id, response->http_status,
|
m->id, stream_id, response->http_status,
|
||||||
response->rst_error);
|
response->rst_error);
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <apr_strings.h>
|
#include <apr_strings.h>
|
||||||
|
#include <apr_lib.h>
|
||||||
|
|
||||||
#include <httpd.h>
|
#include <httpd.h>
|
||||||
#include <http_core.h>
|
#include <http_core.h>
|
||||||
@@ -31,18 +32,283 @@
|
|||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
apr_array_header_t *pushes;
|
|
||||||
apr_pool_t *pool;
|
|
||||||
const h2_request *req;
|
const h2_request *req;
|
||||||
|
apr_pool_t *pool;
|
||||||
|
apr_array_header_t *pushes;
|
||||||
|
const char *s;
|
||||||
|
size_t slen;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
const char *link;
|
||||||
|
apr_table_t *params;
|
||||||
|
char b[4096];
|
||||||
} link_ctx;
|
} link_ctx;
|
||||||
|
|
||||||
static size_t skip_ws(const char *s, size_t i, size_t max)
|
static int attr_char(char c)
|
||||||
|
{
|
||||||
|
switch (c) {
|
||||||
|
case '!':
|
||||||
|
case '#':
|
||||||
|
case '$':
|
||||||
|
case '&':
|
||||||
|
case '+':
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '^':
|
||||||
|
case '_':
|
||||||
|
case '`':
|
||||||
|
case '|':
|
||||||
|
case '~':
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return apr_isalnum(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ptoken_char(char c)
|
||||||
|
{
|
||||||
|
switch (c) {
|
||||||
|
case '!':
|
||||||
|
case '#':
|
||||||
|
case '$':
|
||||||
|
case '&':
|
||||||
|
case '\'':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '*':
|
||||||
|
case '+':
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '/':
|
||||||
|
case ':':
|
||||||
|
case '<':
|
||||||
|
case '=':
|
||||||
|
case '>':
|
||||||
|
case '?':
|
||||||
|
case '@':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '^':
|
||||||
|
case '_':
|
||||||
|
case '`':
|
||||||
|
case '{':
|
||||||
|
case '|':
|
||||||
|
case '}':
|
||||||
|
case '~':
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return apr_isalnum(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int skip_ws(link_ctx *ctx)
|
||||||
{
|
{
|
||||||
char c;
|
char c;
|
||||||
while (i < max && (((c = s[i]) == ' ') || (c == '\t'))) {
|
while (ctx->i < ctx->slen
|
||||||
++i;
|
&& (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
|
||||||
|
++ctx->i;
|
||||||
}
|
}
|
||||||
return i;
|
return (ctx->i < ctx->slen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_chr(link_ctx *ctx, char c, size_t *pidx)
|
||||||
|
{
|
||||||
|
size_t j;
|
||||||
|
for (j = ctx->i; j < ctx->slen; ++j) {
|
||||||
|
if (ctx->s[j] == c) {
|
||||||
|
*pidx = j;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_chr(link_ctx *ctx, char c)
|
||||||
|
{
|
||||||
|
if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
|
||||||
|
++ctx->i;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *mk_str(link_ctx *ctx, size_t end)
|
||||||
|
{
|
||||||
|
if (ctx->i < end) {
|
||||||
|
return apr_pstrndup(ctx->pool, ctx->s + ctx->i, end - ctx->i);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_qstring(link_ctx *ctx, char **ps)
|
||||||
|
{
|
||||||
|
if (skip_ws(ctx) && read_chr(ctx, '\"')) {
|
||||||
|
size_t end;
|
||||||
|
if (find_chr(ctx, '\"', &end)) {
|
||||||
|
*ps = mk_str(ctx, end);
|
||||||
|
ctx->i = end + 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_ptoken(link_ctx *ctx, char **ps)
|
||||||
|
{
|
||||||
|
if (skip_ws(ctx)) {
|
||||||
|
size_t i;
|
||||||
|
for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
|
||||||
|
/* nop */
|
||||||
|
}
|
||||||
|
if (i > ctx->i) {
|
||||||
|
*ps = mk_str(ctx, i);
|
||||||
|
ctx->i = i;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int read_link(link_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (skip_ws(ctx) && read_chr(ctx, '<')) {
|
||||||
|
size_t end;
|
||||||
|
if (find_chr(ctx, '>', &end)) {
|
||||||
|
ctx->link = mk_str(ctx, end);
|
||||||
|
ctx->i = end + 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_pname(link_ctx *ctx, char **pname)
|
||||||
|
{
|
||||||
|
if (skip_ws(ctx)) {
|
||||||
|
size_t i;
|
||||||
|
for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
|
||||||
|
/* nop */
|
||||||
|
}
|
||||||
|
if (i > ctx->i) {
|
||||||
|
*pname = mk_str(ctx, i);
|
||||||
|
ctx->i = i;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_pvalue(link_ctx *ctx, char **pvalue)
|
||||||
|
{
|
||||||
|
if (skip_ws(ctx) && read_chr(ctx, '=')) {
|
||||||
|
if (read_qstring(ctx, pvalue) || read_ptoken(ctx, pvalue)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_param(link_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (skip_ws(ctx) && read_chr(ctx, ';')) {
|
||||||
|
char *name, *value = "";
|
||||||
|
if (read_pname(ctx, &name)) {
|
||||||
|
read_pvalue(ctx, &value); /* value is optional */
|
||||||
|
apr_table_setn(ctx->params, name, value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_sep(link_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (skip_ws(ctx) && read_chr(ctx, ',')) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_params(link_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx->params) {
|
||||||
|
ctx->params = apr_table_make(ctx->pool, 5);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
apr_table_clear(ctx->params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int same_authority(const h2_request *req, const apr_uri_t *uri)
|
||||||
|
{
|
||||||
|
if (uri->scheme != NULL && strcmp(uri->scheme, req->scheme)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (uri->hostinfo != NULL && strcmp(uri->hostinfo, req->authority)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int set_header(void *ctx, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
apr_table_setn(ctx, key, value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int add_push(link_ctx *ctx)
|
||||||
|
{
|
||||||
|
/* so, we have read a Link header and need to decide
|
||||||
|
* if we transform it into a push.
|
||||||
|
*/
|
||||||
|
const char *rel = apr_table_get(ctx->params, "rel");
|
||||||
|
if (rel && !strcmp("preload", rel)) {
|
||||||
|
apr_uri_t uri;
|
||||||
|
if (apr_uri_parse(ctx->pool, ctx->link, &uri) == APR_SUCCESS) {
|
||||||
|
if (uri.path && same_authority(ctx->req, &uri)) {
|
||||||
|
char *path;
|
||||||
|
apr_table_t *headers;
|
||||||
|
h2_request *req;
|
||||||
|
h2_push *push;
|
||||||
|
|
||||||
|
/* We only want to generate pushes for resources in the
|
||||||
|
* same authority than the original request.
|
||||||
|
* icing: i think that is wise, otherwise we really need to
|
||||||
|
* check that the vhost/server is available and uses the same
|
||||||
|
* TLS (if any) parameters.
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
"User-Agent",
|
||||||
|
"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,
|
||||||
|
ctx->req->authority,
|
||||||
|
path, headers);
|
||||||
|
h2_request_end_headers(req, ctx->pool, 1);
|
||||||
|
push->req = req;
|
||||||
|
|
||||||
|
if (!ctx->pushes) {
|
||||||
|
ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*));
|
||||||
|
}
|
||||||
|
APR_ARRAY_PUSH(ctx->pushes, h2_push*) = push;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
|
static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
|
||||||
@@ -77,20 +343,41 @@ static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
|
|||||||
relation-type = reg-rel-type | ext-rel-type
|
relation-type = reg-rel-type | ext-rel-type
|
||||||
reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
|
reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
|
||||||
ext-rel-type = URI
|
ext-rel-type = URI
|
||||||
|
|
||||||
|
and from <https://tools.ietf.org/html/rfc5987>
|
||||||
|
parmname = 1*attr-char
|
||||||
|
attr-char = ALPHA / DIGIT
|
||||||
|
/ "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||||||
|
/ "^" / "_" / "`" / "|" / "~"
|
||||||
*/
|
*/
|
||||||
/* TODO */
|
|
||||||
(void)skip_ws;
|
ctx->s = s;
|
||||||
|
ctx->slen = slen;
|
||||||
|
ctx->i = 0;
|
||||||
|
|
||||||
|
while (read_link(ctx)) {
|
||||||
|
init_params(ctx);
|
||||||
|
while (read_param(ctx)) {
|
||||||
|
/* nop */
|
||||||
|
}
|
||||||
|
add_push(ctx);
|
||||||
|
if (!read_sep(ctx)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int head_iter(void *ctx, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
if (!apr_strnatcasecmp("link", key)) {
|
||||||
|
inspect_link(ctx, value, strlen(value));
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
|
apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
|
||||||
const h2_response *res)
|
const h2_response *res)
|
||||||
{
|
{
|
||||||
link_ctx ctx;
|
|
||||||
|
|
||||||
ctx.pushes = NULL;
|
|
||||||
ctx.pool = p;
|
|
||||||
ctx.req = req;
|
|
||||||
|
|
||||||
/* Collect push candidates from the request/response pair.
|
/* Collect push candidates from the request/response pair.
|
||||||
*
|
*
|
||||||
* One source for pushes are "rel=preload" link headers
|
* One source for pushes are "rel=preload" link headers
|
||||||
@@ -99,16 +386,15 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
|
|||||||
* TODO: This may be extended in the future by hooks or callbacks
|
* TODO: This may be extended in the future by hooks or callbacks
|
||||||
* where other modules can provide push information directly.
|
* where other modules can provide push information directly.
|
||||||
*/
|
*/
|
||||||
if (res->ngheader) {
|
if (res->header) {
|
||||||
int i;
|
link_ctx ctx;
|
||||||
for (i = 0; i < res->ngheader->nvlen; ++i) {
|
|
||||||
nghttp2_nv *nv = &res->ngheader->nv[i];
|
memset(&ctx, 0, sizeof(ctx));
|
||||||
if (nv->namelen == 4
|
ctx.req = req;
|
||||||
&& apr_strnatcasecmp("link", (const char *)nv->name)) {
|
ctx.pool = p;
|
||||||
inspect_link(&ctx, (const char *)nv->value, nv->valuelen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.pushes;
|
apr_table_do(head_iter, &ctx, res->header, NULL);
|
||||||
|
return ctx.pushes;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,6 @@ struct h2_ngheader;
|
|||||||
typedef struct h2_push {
|
typedef struct h2_push {
|
||||||
int initiating_id;
|
int initiating_id;
|
||||||
const struct h2_request *req;
|
const struct h2_request *req;
|
||||||
const struct h2_ngheader *promise;
|
|
||||||
const char *as;
|
const char *as;
|
||||||
} h2_push;
|
} h2_push;
|
||||||
|
|
||||||
|
@@ -30,12 +30,23 @@
|
|||||||
|
|
||||||
|
|
||||||
h2_request *h2_request_create(int id, apr_pool_t *pool)
|
h2_request *h2_request_create(int id, apr_pool_t *pool)
|
||||||
|
{
|
||||||
|
return h2_request_createn(id, pool, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2_request *h2_request_createn(int id, apr_pool_t *pool,
|
||||||
|
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));
|
h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
|
||||||
|
|
||||||
req->id = id;
|
req->id = id;
|
||||||
req->headers = apr_table_make(pool, 10);
|
req->method = method;
|
||||||
req->content_length = -1;
|
req->scheme = scheme;
|
||||||
|
req->authority = authority;
|
||||||
|
req->path = path;
|
||||||
|
req->headers = header? header : apr_table_make(pool, 10);
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -44,45 +55,26 @@ void h2_request_destroy(h2_request *req)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static apr_status_t inspect_clen(h2_request *req, const char *s)
|
||||||
|
{
|
||||||
|
char *end;
|
||||||
|
req->content_length = apr_strtoi64(s, &end, 10);
|
||||||
|
return (s == end)? APR_EINVAL : APR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool,
|
static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool,
|
||||||
const char *name, size_t nlen,
|
const char *name, size_t nlen,
|
||||||
const char *value, size_t vlen)
|
const char *value, size_t vlen)
|
||||||
{
|
{
|
||||||
char *hname, *hvalue;
|
char *hname, *hvalue;
|
||||||
|
|
||||||
if (H2_HD_MATCH_LIT("transfer-encoding", name, nlen)) {
|
if (H2_HD_MATCH_LIT("expect", name, nlen)
|
||||||
if (!apr_strnatcasecmp("chunked", value)) {
|
|| H2_HD_MATCH_LIT("upgrade", name, nlen)
|
||||||
/* This should never arrive here in a HTTP/2 request */
|
|| H2_HD_MATCH_LIT("connection", name, nlen)
|
||||||
ap_log_perror(APLOG_MARK, APLOG_ERR, APR_BADARG, pool,
|
|| H2_HD_MATCH_LIT("proxy-connection", name, nlen)
|
||||||
APLOGNO(02945)
|
|| H2_HD_MATCH_LIT("transfer-encoding", name, nlen)
|
||||||
"h2_request: 'transfer-encoding: chunked' received");
|
|| H2_HD_MATCH_LIT("keep-alive", name, nlen)
|
||||||
return APR_BADARG;
|
|| H2_HD_MATCH_LIT("http2-settings", name, nlen)) {
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
|
|
||||||
char *end;
|
|
||||||
req->content_length = apr_strtoi64(value, &end, 10);
|
|
||||||
if (value == end) {
|
|
||||||
ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
|
|
||||||
APLOGNO(02959)
|
|
||||||
"h2_request(%d): content-length value not parsed: %s",
|
|
||||||
req->id, value);
|
|
||||||
return APR_EINVAL;
|
|
||||||
}
|
|
||||||
req->chunked = 0;
|
|
||||||
}
|
|
||||||
else if (H2_HD_MATCH_LIT("content-type", name, nlen)) {
|
|
||||||
/* If we see a content-type and have no length (yet),
|
|
||||||
* we need to chunk. */
|
|
||||||
req->chunked = (req->content_length == -1);
|
|
||||||
}
|
|
||||||
else if ((req->seen_host && H2_HD_MATCH_LIT("host", name, nlen))
|
|
||||||
|| H2_HD_MATCH_LIT("expect", name, nlen)
|
|
||||||
|| H2_HD_MATCH_LIT("upgrade", name, nlen)
|
|
||||||
|| H2_HD_MATCH_LIT("connection", name, nlen)
|
|
||||||
|| H2_HD_MATCH_LIT("proxy-connection", name, nlen)
|
|
||||||
|| H2_HD_MATCH_LIT("keep-alive", name, nlen)
|
|
||||||
|| H2_HD_MATCH_LIT("http2-settings", name, nlen)) {
|
|
||||||
/* ignore these. */
|
/* ignore these. */
|
||||||
return APR_SUCCESS;
|
return APR_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -91,7 +83,7 @@ static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool,
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
char *nval;
|
char *nval;
|
||||||
|
|
||||||
/* Cookie headers come separately in HTTP/2, but need
|
/* Cookie header come separately in HTTP/2, but need
|
||||||
* to be merged by "; " (instead of default ", ")
|
* to be merged by "; " (instead of default ", ")
|
||||||
*/
|
*/
|
||||||
hvalue = apr_pstrndup(pool, value, vlen);
|
hvalue = apr_pstrndup(pool, value, vlen);
|
||||||
@@ -101,7 +93,9 @@ static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (H2_HD_MATCH_LIT("host", name, nlen)) {
|
else if (H2_HD_MATCH_LIT("host", name, nlen)) {
|
||||||
req->seen_host = 1;
|
if (apr_table_get(req->headers, "Host")) {
|
||||||
|
return APR_SUCCESS; /* ignore duplicate */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hname = apr_pstrndup(pool, name, nlen);
|
hname = apr_pstrndup(pool, name, nlen);
|
||||||
@@ -124,13 +118,13 @@ static int set_h1_header(void *ctx, const char *key, const char *value)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static apr_status_t add_h1_headers(h2_request *req, apr_pool_t *pool,
|
static apr_status_t add_all_h1_header(h2_request *req, apr_pool_t *pool,
|
||||||
apr_table_t *headers)
|
apr_table_t *header)
|
||||||
{
|
{
|
||||||
h1_ctx x;
|
h1_ctx x;
|
||||||
x.req = req;
|
x.req = req;
|
||||||
x.pool = pool;
|
x.pool = pool;
|
||||||
apr_table_do(set_h1_header, &x, headers, NULL);
|
apr_table_do(set_h1_header, &x, header, NULL);
|
||||||
return APR_SUCCESS;
|
return APR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +144,7 @@ apr_status_t h2_request_rwrite(h2_request *req, request_rec *r)
|
|||||||
r->parsed_uri.port_str);
|
r->parsed_uri.port_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
status = add_h1_headers(req, r->pool, r->headers_in);
|
status = add_all_h1_header(req, r->pool, r->headers_in);
|
||||||
|
|
||||||
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
|
||||||
"h2_request(%d): rwrite %s host=%s://%s%s",
|
"h2_request(%d): rwrite %s host=%s://%s%s",
|
||||||
@@ -215,11 +209,22 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
|
|||||||
|
|
||||||
apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos)
|
apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos)
|
||||||
{
|
{
|
||||||
|
const char *s;
|
||||||
|
|
||||||
if (req->eoh) {
|
if (req->eoh) {
|
||||||
return APR_EINVAL;
|
return APR_EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req->seen_host) {
|
/* be safe, some header we do not accept on h2(c) */
|
||||||
|
apr_table_unset(req->headers, "expect");
|
||||||
|
apr_table_unset(req->headers, "upgrade");
|
||||||
|
apr_table_unset(req->headers, "connection");
|
||||||
|
apr_table_unset(req->headers, "proxy-connection");
|
||||||
|
apr_table_unset(req->headers, "transfer-encoding");
|
||||||
|
apr_table_unset(req->headers, "keep-alive");
|
||||||
|
apr_table_unset(req->headers, "http2-settings");
|
||||||
|
|
||||||
|
if (!apr_table_get(req->headers, "Host")) {
|
||||||
/* Need to add a "Host" header if not already there to
|
/* Need to add a "Host" header if not already there to
|
||||||
* make virtual hosts work correctly. */
|
* make virtual hosts work correctly. */
|
||||||
if (!req->authority) {
|
if (!req->authority) {
|
||||||
@@ -228,20 +233,34 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos)
|
|||||||
apr_table_set(req->headers, "Host", req->authority);
|
apr_table_set(req->headers, "Host", req->authority);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eos && req->chunked) {
|
s = apr_table_get(req->headers, "Content-Length");
|
||||||
/* We had chunking figured out, but the EOS is already there.
|
if (s) {
|
||||||
* unmark chunking and set a definitive content-length.
|
if (inspect_clen(req, s) != APR_SUCCESS) {
|
||||||
*/
|
ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
|
||||||
|
APLOGNO(02959)
|
||||||
|
"h2_request(%d): content-length value not parsed: %s",
|
||||||
|
req->id, s);
|
||||||
|
return APR_EINVAL;
|
||||||
|
}
|
||||||
req->chunked = 0;
|
req->chunked = 0;
|
||||||
apr_table_setn(req->headers, "Content-Length", "0");
|
|
||||||
}
|
}
|
||||||
else if (req->chunked) {
|
else {
|
||||||
/* We have not seen a content-length. We therefore must
|
/* no content-length given */
|
||||||
* pass any request content in chunked form.
|
req->content_length = -1;
|
||||||
*/
|
s = apr_table_get(req->headers, "Content-Type");
|
||||||
apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
|
if (eos && s) {
|
||||||
|
req->chunked = 0;
|
||||||
|
apr_table_setn(req->headers, "Content-Length", "0");
|
||||||
|
}
|
||||||
|
else if (s) {
|
||||||
|
/* We have not seen a content-length, but a content-type.
|
||||||
|
* must pass any request content in chunked form.
|
||||||
|
*/
|
||||||
|
req->chunked = 1;
|
||||||
|
apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req->eoh = 1;
|
req->eoh = 1;
|
||||||
|
|
||||||
return APR_SUCCESS;
|
return APR_SUCCESS;
|
||||||
@@ -253,13 +272,12 @@ void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src)
|
|||||||
{
|
{
|
||||||
/* keep the dst id */
|
/* keep the dst id */
|
||||||
dst->method = OPT_COPY(p, src->method);
|
dst->method = OPT_COPY(p, src->method);
|
||||||
dst->scheme = OPT_COPY(p, src->method);
|
dst->scheme = OPT_COPY(p, src->scheme);
|
||||||
dst->authority = OPT_COPY(p, src->method);
|
dst->authority = OPT_COPY(p, src->authority);
|
||||||
dst->path = OPT_COPY(p, src->method);
|
dst->path = OPT_COPY(p, src->path);
|
||||||
dst->headers = apr_table_clone(p, src->headers);
|
dst->headers = apr_table_clone(p, src->headers);
|
||||||
dst->content_length = src->content_length;
|
dst->content_length = src->content_length;
|
||||||
dst->chunked = src->chunked;
|
dst->chunked = src->chunked;
|
||||||
dst->eoh = src->eoh;
|
dst->eoh = src->eoh;
|
||||||
dst->seen_host = src->seen_host;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,12 +40,15 @@ struct h2_request {
|
|||||||
apr_off_t content_length;
|
apr_off_t content_length;
|
||||||
int chunked;
|
int chunked;
|
||||||
int eoh;
|
int eoh;
|
||||||
|
|
||||||
int seen_host;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
h2_request *h2_request_create(int id, apr_pool_t *pool);
|
h2_request *h2_request_create(int id, apr_pool_t *pool);
|
||||||
|
|
||||||
|
h2_request *h2_request_createn(int id, apr_pool_t *pool,
|
||||||
|
const char *method, const char *scheme,
|
||||||
|
const char *authority, const char *path,
|
||||||
|
apr_table_t *headers);
|
||||||
|
|
||||||
void h2_request_destroy(h2_request *req);
|
void h2_request_destroy(h2_request *req);
|
||||||
|
|
||||||
apr_status_t h2_request_rwrite(h2_request *req, request_rec *r);
|
apr_status_t h2_request_rwrite(h2_request *req, request_rec *r);
|
||||||
|
@@ -27,20 +27,8 @@
|
|||||||
#include "h2_private.h"
|
#include "h2_private.h"
|
||||||
#include "h2_h2.h"
|
#include "h2_h2.h"
|
||||||
#include "h2_util.h"
|
#include "h2_util.h"
|
||||||
#include "h2_push.h"
|
|
||||||
#include "h2_response.h"
|
#include "h2_response.h"
|
||||||
|
|
||||||
static void make_ngheader(apr_pool_t *pool, h2_response *to,
|
|
||||||
apr_table_t *header);
|
|
||||||
|
|
||||||
static int ignore_header(const char *name)
|
|
||||||
{
|
|
||||||
return (H2_HD_MATCH_LIT_CS("connection", name)
|
|
||||||
|| H2_HD_MATCH_LIT_CS("proxy-connection", name)
|
|
||||||
|| H2_HD_MATCH_LIT_CS("upgrade", name)
|
|
||||||
|| H2_HD_MATCH_LIT_CS("keep-alive", name)
|
|
||||||
|| H2_HD_MATCH_LIT_CS("transfer-encoding", name));
|
|
||||||
}
|
|
||||||
|
|
||||||
h2_response *h2_response_create(int stream_id,
|
h2_response *h2_response_create(int stream_id,
|
||||||
int rst_error,
|
int rst_error,
|
||||||
@@ -76,10 +64,8 @@ h2_response *h2_response_create(int stream_id,
|
|||||||
while (*sep == ' ' || *sep == '\t') {
|
while (*sep == ' ' || *sep == '\t') {
|
||||||
++sep;
|
++sep;
|
||||||
}
|
}
|
||||||
if (ignore_header(hline)) {
|
|
||||||
/* never forward, ch. 8.1.2.2 */
|
if (!h2_util_ignore_header(hline)) {
|
||||||
}
|
|
||||||
else {
|
|
||||||
apr_table_merge(header, hline, sep);
|
apr_table_merge(header, hline, sep);
|
||||||
if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) {
|
if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) {
|
||||||
char *end;
|
char *end;
|
||||||
@@ -100,7 +86,7 @@ h2_response *h2_response_create(int stream_id,
|
|||||||
header = apr_table_make(pool, 0);
|
header = apr_table_make(pool, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
response->rheader = header;
|
response->header = header;
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +101,7 @@ h2_response *h2_response_rcreate(int stream_id, request_rec *r,
|
|||||||
response->stream_id = stream_id;
|
response->stream_id = stream_id;
|
||||||
response->http_status = r->status;
|
response->http_status = r->status;
|
||||||
response->content_length = -1;
|
response->content_length = -1;
|
||||||
response->rheader = header;
|
response->header = header;
|
||||||
|
|
||||||
if (response->http_status == HTTP_FORBIDDEN) {
|
if (response->http_status == HTTP_FORBIDDEN) {
|
||||||
const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
|
const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
|
||||||
@@ -144,149 +130,10 @@ h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from)
|
|||||||
to->stream_id = from->stream_id;
|
to->stream_id = from->stream_id;
|
||||||
to->http_status = from->http_status;
|
to->http_status = from->http_status;
|
||||||
to->content_length = from->content_length;
|
to->content_length = from->content_length;
|
||||||
if (from->rheader) {
|
if (from->header) {
|
||||||
make_ngheader(pool, to, from->rheader);
|
to->header = apr_table_clone(pool, from->header);
|
||||||
}
|
}
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
nghttp2_nv *nv;
|
|
||||||
size_t nvlen;
|
|
||||||
size_t nvstrlen;
|
|
||||||
size_t offset;
|
|
||||||
char *strbuf;
|
|
||||||
apr_pool_t *pool;
|
|
||||||
apr_array_header_t *pushes;
|
|
||||||
} nvctx_t;
|
|
||||||
|
|
||||||
static int count_header(void *ctx, const char *key, const char *value)
|
|
||||||
{
|
|
||||||
if (!ignore_header(key)) {
|
|
||||||
nvctx_t *nvctx = (nvctx_t*)ctx;
|
|
||||||
nvctx->nvlen++;
|
|
||||||
nvctx->nvstrlen += strlen(key) + strlen(value) + 2;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define NV_ADD_LIT_CS(nv, k, v) addnv_lit_cs(nv, k, sizeof(k) - 1, v, strlen(v))
|
|
||||||
#define NV_ADD_CS_CS(nv, k, v) addnv_cs_cs(nv, k, strlen(k), v, strlen(v))
|
|
||||||
#define NV_BUF_ADD(nv, s, len) memcpy(nv->strbuf, s, len); \
|
|
||||||
s = nv->strbuf; \
|
|
||||||
nv->strbuf += len + 1
|
|
||||||
|
|
||||||
static void addnv_cs_cs(nvctx_t *ctx, const char *key, size_t key_len,
|
|
||||||
const char *value, size_t val_len)
|
|
||||||
{
|
|
||||||
nghttp2_nv *nv = &ctx->nv[ctx->offset];
|
|
||||||
|
|
||||||
NV_BUF_ADD(ctx, key, key_len);
|
|
||||||
NV_BUF_ADD(ctx, value, val_len);
|
|
||||||
|
|
||||||
nv->name = (uint8_t*)key;
|
|
||||||
nv->namelen = key_len;
|
|
||||||
nv->value = (uint8_t*)value;
|
|
||||||
nv->valuelen = val_len;
|
|
||||||
|
|
||||||
ctx->offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addnv_lit_cs(nvctx_t *ctx, const char *key, size_t key_len,
|
|
||||||
const char *value, size_t val_len)
|
|
||||||
{
|
|
||||||
nghttp2_nv *nv = &ctx->nv[ctx->offset];
|
|
||||||
|
|
||||||
NV_BUF_ADD(ctx, value, val_len);
|
|
||||||
|
|
||||||
nv->name = (uint8_t*)key;
|
|
||||||
nv->namelen = key_len;
|
|
||||||
nv->value = (uint8_t*)value;
|
|
||||||
nv->valuelen = val_len;
|
|
||||||
|
|
||||||
ctx->offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
static h2_push *h2_parse_preload(apr_pool_t *pool, const char *str)
|
|
||||||
{
|
|
||||||
/* TODO: implement this */
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int add_header(void *ctx, const char *key, const char *value)
|
|
||||||
{
|
|
||||||
if (!ignore_header(key)) {
|
|
||||||
nvctx_t *nvctx = (nvctx_t*)ctx;
|
|
||||||
NV_ADD_CS_CS(nvctx, key, value);
|
|
||||||
|
|
||||||
if (apr_strnatcasecmp("link", key)
|
|
||||||
&& ap_strstr_c("preload", value)) {
|
|
||||||
/* Detect link headers with rel=preload to use as possible
|
|
||||||
* server push candidates. */
|
|
||||||
h2_push *push;
|
|
||||||
if ((push = h2_parse_preload(nvctx->pool, value))) {
|
|
||||||
if (!nvctx->pushes) {
|
|
||||||
nvctx->pushes = apr_array_make(nvctx->pool, 5,
|
|
||||||
sizeof(h2_push*));
|
|
||||||
}
|
|
||||||
APR_ARRAY_PUSH(nvctx->pushes, h2_push*) = push;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void make_ngheader(apr_pool_t *pool, h2_response *to,
|
|
||||||
apr_table_t *header)
|
|
||||||
{
|
|
||||||
size_t n;
|
|
||||||
h2_ngheader *h;
|
|
||||||
nvctx_t ctx;
|
|
||||||
char *status;
|
|
||||||
|
|
||||||
to->ngheader = NULL;
|
|
||||||
to->pushes = NULL;
|
|
||||||
|
|
||||||
status = apr_psprintf(pool, "%d", to->http_status);
|
|
||||||
ctx.nv = NULL;
|
|
||||||
ctx.nvlen = 1;
|
|
||||||
ctx.nvstrlen = strlen(status) + 1;
|
|
||||||
ctx.offset = 0;
|
|
||||||
ctx.strbuf = NULL;
|
|
||||||
ctx.pool = pool;
|
|
||||||
ctx.pushes = NULL;
|
|
||||||
|
|
||||||
apr_table_do(count_header, &ctx, header, NULL);
|
|
||||||
|
|
||||||
n = (sizeof(h2_ngheader)
|
|
||||||
+ (ctx.nvlen * sizeof(nghttp2_nv)) + ctx.nvstrlen);
|
|
||||||
h = apr_pcalloc(pool, n);
|
|
||||||
if (h) {
|
|
||||||
ctx.nv = (nghttp2_nv*)(h + 1);
|
|
||||||
ctx.strbuf = (char*)&ctx.nv[ctx.nvlen];
|
|
||||||
|
|
||||||
NV_ADD_LIT_CS(&ctx, ":status", status);
|
|
||||||
apr_table_do(add_header, &ctx, header, NULL);
|
|
||||||
|
|
||||||
h->nv = ctx.nv;
|
|
||||||
h->nvlen = ctx.nvlen;
|
|
||||||
|
|
||||||
to->ngheader = h;
|
|
||||||
to->pushes = ctx.pushes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int h2_response_push_count(h2_response *response)
|
|
||||||
{
|
|
||||||
return response->pushes? response->pushes->nelts : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2_push *h2_response_get_push(h2_response *response, size_t i)
|
|
||||||
{
|
|
||||||
if (response->pushes && i < response->pushes->nelts) {
|
|
||||||
return APR_ARRAY_IDX(response->pushes, i, h2_push*);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -18,24 +18,12 @@
|
|||||||
|
|
||||||
struct h2_push;
|
struct h2_push;
|
||||||
|
|
||||||
/* h2_response is just the data belonging the the head of a HTTP response,
|
|
||||||
* suitable prepared to be fed to nghttp2 for response submit.
|
|
||||||
*/
|
|
||||||
typedef struct h2_ngheader {
|
|
||||||
nghttp2_nv *nv;
|
|
||||||
apr_size_t nvlen;
|
|
||||||
} h2_ngheader;
|
|
||||||
|
|
||||||
struct h2_push;
|
|
||||||
|
|
||||||
typedef struct h2_response {
|
typedef struct h2_response {
|
||||||
int stream_id;
|
int stream_id;
|
||||||
int rst_error;
|
int rst_error;
|
||||||
int http_status;
|
int http_status;
|
||||||
apr_off_t content_length;
|
apr_off_t content_length;
|
||||||
apr_table_t *rheader;
|
apr_table_t *header;
|
||||||
h2_ngheader *ngheader;
|
|
||||||
apr_array_header_t *pushes;
|
|
||||||
} h2_response;
|
} h2_response;
|
||||||
|
|
||||||
h2_response *h2_response_create(int stream_id,
|
h2_response *h2_response_create(int stream_id,
|
||||||
@@ -51,20 +39,4 @@ void h2_response_destroy(h2_response *response);
|
|||||||
|
|
||||||
h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from);
|
h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from);
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of push proposals included with the response.
|
|
||||||
* @return number of push proposals in this response
|
|
||||||
*/
|
|
||||||
int h2_response_push_count(h2_response *response);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ith h2_push contained in this response.
|
|
||||||
*
|
|
||||||
* @param response the response
|
|
||||||
* @param i the index of the push to get
|
|
||||||
* @return the ith h2_push or NULL if out of bounds
|
|
||||||
*/
|
|
||||||
struct h2_push *h2_response_get_push(h2_response *response, size_t i);
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(__mod_h2__h2_response__) */
|
#endif /* defined(__mod_h2__h2_response__) */
|
||||||
|
@@ -30,6 +30,7 @@
|
|||||||
#include "h2_h2.h"
|
#include "h2_h2.h"
|
||||||
#include "h2_mplx.h"
|
#include "h2_mplx.h"
|
||||||
#include "h2_push.h"
|
#include "h2_push.h"
|
||||||
|
#include "h2_request.h"
|
||||||
#include "h2_response.h"
|
#include "h2_response.h"
|
||||||
#include "h2_stream.h"
|
#include "h2_stream.h"
|
||||||
#include "h2_stream_set.h"
|
#include "h2_stream_set.h"
|
||||||
@@ -80,10 +81,6 @@ h2_stream *h2_session_open_stream(h2_session *session, int stream_id)
|
|||||||
session->max_stream_received = stream->id;
|
session->max_stream_received = stream->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
|
||||||
"h2_session: stream(%ld-%d): opened",
|
|
||||||
session->id, stream_id);
|
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +302,7 @@ static int on_frame_not_send_cb(nghttp2_session *ngh2,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static apr_status_t stream_destroy(h2_session *session,
|
static apr_status_t stream_release(h2_session *session,
|
||||||
h2_stream *stream,
|
h2_stream *stream,
|
||||||
uint32_t error_code)
|
uint32_t error_code)
|
||||||
{
|
{
|
||||||
@@ -342,12 +339,12 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
|
|||||||
}
|
}
|
||||||
stream = h2_session_get_stream(session, stream_id);
|
stream = h2_session_get_stream(session, stream_id);
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream_destroy(session, stream, error_code);
|
stream_release(session, stream, error_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error_code) {
|
if (error_code) {
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
||||||
"h2_stream(%ld-%d): close error %d",
|
"h2_stream(%ld-%d): closed, error=%d",
|
||||||
session->id, (int)stream_id, error_code);
|
session->id, (int)stream_id, error_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,24 +1118,40 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream)
|
|||||||
if (stream->submitted) {
|
if (stream->submitted) {
|
||||||
rv = NGHTTP2_PROTOCOL_ERROR;
|
rv = NGHTTP2_PROTOCOL_ERROR;
|
||||||
}
|
}
|
||||||
else if (stream->response && stream->response->ngheader) {
|
else if (stream->response && stream->response->header) {
|
||||||
nghttp2_data_provider provider;
|
nghttp2_data_provider provider;
|
||||||
h2_response *response = stream->response;
|
h2_response *response = stream->response;
|
||||||
|
h2_ngheader *ngh;
|
||||||
|
|
||||||
memset(&provider, 0, sizeof(provider));
|
memset(&provider, 0, sizeof(provider));
|
||||||
provider.source.fd = stream->id;
|
provider.source.fd = stream->id;
|
||||||
provider.read_callback = stream_data_cb;
|
provider.read_callback = stream_data_cb;
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
||||||
"h2_stream(%ld-%d): submitting response %d",
|
"h2_stream(%ld-%d): submit response %d",
|
||||||
session->id, stream->id, response->http_status);
|
session->id, stream->id, response->http_status);
|
||||||
|
|
||||||
|
ngh = h2_util_ngheader_make_res(stream->pool, response->http_status,
|
||||||
|
response->header);
|
||||||
rv = nghttp2_submit_response(session->ngh2, response->stream_id,
|
rv = nghttp2_submit_response(session->ngh2, response->stream_id,
|
||||||
response->ngheader->nv,
|
ngh->nv, ngh->nvlen, &provider);
|
||||||
response->ngheader->nvlen, &provider);
|
|
||||||
|
/* If the submit worked,
|
||||||
|
* and this stream is not a pushed one itself,
|
||||||
|
* and HTTP/2 server push is enabled here,
|
||||||
|
* and the response is in the range 200-299 *),
|
||||||
|
* and the remote side has pushing enabled,
|
||||||
|
* -> find and perform any pushes on this stream
|
||||||
|
*
|
||||||
|
* *) the response code is relevant, as we do not want to
|
||||||
|
* make pushes on 401 or 403 codes, neiterh on 301/302
|
||||||
|
* and friends. And if we see a 304, we do not push either
|
||||||
|
* as the client, having this resource in its cache, might
|
||||||
|
* also have the pushed ones as well.
|
||||||
|
*/
|
||||||
if (!rv
|
if (!rv
|
||||||
&& !stream->promised_on
|
&& !stream->initiated_on
|
||||||
|
&& h2_config_geti(h2_config_get(session->c), H2_CONF_PUSH)
|
||||||
&& H2_HTTP_2XX(response->http_status)
|
&& H2_HTTP_2XX(response->http_status)
|
||||||
&& h2_session_push_enabled(session)) {
|
&& h2_session_push_enabled(session)) {
|
||||||
|
|
||||||
@@ -1148,7 +1161,7 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream)
|
|||||||
else {
|
else {
|
||||||
int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
|
int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
||||||
"h2_stream(%ld-%d): RST_STREAM, err=%d",
|
"h2_stream(%ld-%d): RST_STREAM, err=%d",
|
||||||
session->id, stream->id, err);
|
session->id, stream->id, err);
|
||||||
|
|
||||||
@@ -1169,15 +1182,18 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct h2_stream *h2_session_push(h2_session *session, h2_push *push)
|
struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
|
||||||
|
h2_push *push)
|
||||||
{
|
{
|
||||||
apr_status_t status;
|
apr_status_t status;
|
||||||
h2_stream *stream;
|
h2_stream *stream;
|
||||||
|
h2_ngheader *ngh;
|
||||||
int nid;
|
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, push->initiating_id,
|
||||||
push->promise->nv, push->promise->nvlen,
|
ngh->nv, ngh->nvlen, NULL);
|
||||||
NULL);
|
|
||||||
if (nid <= 0) {
|
if (nid <= 0) {
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
||||||
"h2_stream(%ld-%d): submitting push promise fail: %s",
|
"h2_stream(%ld-%d): submitting push promise fail: %s",
|
||||||
@@ -1186,16 +1202,17 @@ struct h2_stream *h2_session_push(h2_session *session, h2_push *push)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
||||||
"h2_stream(%ld-%d): promised new stream %d",
|
"h2_stream(%ld-%d): promised new stream %d for %s %s",
|
||||||
session->id, push->initiating_id, nid);
|
session->id, push->initiating_id, nid,
|
||||||
|
push->req->method, push->req->path);
|
||||||
|
|
||||||
stream = h2_session_open_stream(session, nid);
|
stream = h2_session_open_stream(session, nid);
|
||||||
if (stream) {
|
if (stream) {
|
||||||
h2_stream_set_h2_request(stream, push->req);
|
h2_stream_set_h2_request(stream, is->id, push->req);
|
||||||
status = stream_end_headers(session, stream, 1);
|
status = stream_end_headers(session, stream, 1);
|
||||||
if (status != APR_SUCCESS) {
|
if (status != APR_SUCCESS) {
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
|
||||||
"h2_stream(%ld-%d): scheduling push stream",
|
"h2_stream(%ld-%d): scheduling push stream",
|
||||||
session->id, stream->id);
|
session->id, stream->id);
|
||||||
h2_stream_cleanup(stream);
|
h2_stream_cleanup(stream);
|
||||||
@@ -1203,7 +1220,7 @@ struct h2_stream *h2_session_push(h2_session *session, h2_push *push)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
|
||||||
"h2_stream(%ld-%d): failed to create stream obj %d",
|
"h2_stream(%ld-%d): failed to create stream obj %d",
|
||||||
session->id, push->initiating_id, nid);
|
session->id, push->initiating_id, nid);
|
||||||
}
|
}
|
||||||
|
@@ -189,10 +189,11 @@ apr_status_t h2_session_stream_destroy(h2_session *session,
|
|||||||
* processing..
|
* processing..
|
||||||
*
|
*
|
||||||
* @param session the session to work in
|
* @param session the session to work in
|
||||||
* @param stream the stream on which the push depends
|
* @param is the stream initiating the push
|
||||||
* @param push the push to promise
|
* @param push the push to promise
|
||||||
* @return the new promised stream or NULL
|
* @return the new promised stream or NULL
|
||||||
*/
|
*/
|
||||||
struct h2_stream *h2_session_push(h2_session *session, struct h2_push *push);
|
struct h2_stream *h2_session_push(h2_session *session,
|
||||||
|
struct h2_stream *is, struct h2_push *push);
|
||||||
|
|
||||||
#endif /* defined(__mod_h2__h2_session__) */
|
#endif /* defined(__mod_h2__h2_session__) */
|
||||||
|
@@ -159,9 +159,6 @@ h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session)
|
|||||||
apr_status_t h2_stream_destroy(h2_stream *stream)
|
apr_status_t h2_stream_destroy(h2_stream *stream)
|
||||||
{
|
{
|
||||||
AP_DEBUG_ASSERT(stream);
|
AP_DEBUG_ASSERT(stream);
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
|
|
||||||
"h2_stream(%ld-%d): destroy", stream->session->id, stream->id);
|
|
||||||
if (stream->request) {
|
if (stream->request) {
|
||||||
h2_request_destroy(stream->request);
|
h2_request_destroy(stream->request);
|
||||||
stream->request = NULL;
|
stream->request = NULL;
|
||||||
@@ -237,9 +234,12 @@ apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void h2_stream_set_h2_request(h2_stream *stream, const h2_request *req)
|
void h2_stream_set_h2_request(h2_stream *stream, int initiated_on,
|
||||||
|
const h2_request *req)
|
||||||
{
|
{
|
||||||
h2_request_copy(stream->pool, stream->request, req);
|
h2_request_copy(stream->pool, stream->request, req);
|
||||||
|
stream->initiated_on = initiated_on;
|
||||||
|
stream->request->eoh = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
apr_status_t h2_stream_add_header(h2_stream *stream,
|
apr_status_t h2_stream_add_header(h2_stream *stream,
|
||||||
@@ -286,10 +286,10 @@ apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
|
||||||
"h2_mplx(%ld-%d): start stream, task %s %s (%s)",
|
"h2_stream(%ld-%d): scheduled %s %s://%s%s",
|
||||||
stream->session->id, stream->id,
|
stream->session->id, stream->id,
|
||||||
stream->request->method, stream->request->path,
|
stream->request->method, stream->request->scheme,
|
||||||
stream->request->authority);
|
stream->request->authority, stream->request->path);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -545,9 +545,12 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream)
|
|||||||
|
|
||||||
pushes = h2_push_collect(stream->pool, stream->request, stream->response);
|
pushes = h2_push_collect(stream->pool, stream->request, stream->response);
|
||||||
if (pushes && !apr_is_empty_array(pushes)) {
|
if (pushes && !apr_is_empty_array(pushes)) {
|
||||||
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
|
||||||
|
"h2_stream(%ld-%d): found %d push candidates",
|
||||||
|
stream->session->id, stream->id, pushes->nelts);
|
||||||
for (i = 0; i < pushes->nelts; ++i) {
|
for (i = 0; i < pushes->nelts; ++i) {
|
||||||
h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*);
|
h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*);
|
||||||
h2_stream *s = h2_session_push(stream->session, push);
|
h2_stream *s = h2_session_push(stream->session, stream, push);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
status = APR_ECONNRESET;
|
status = APR_ECONNRESET;
|
||||||
break;
|
break;
|
||||||
|
@@ -50,7 +50,7 @@ typedef struct h2_stream h2_stream;
|
|||||||
|
|
||||||
struct h2_stream {
|
struct h2_stream {
|
||||||
int id; /* http2 stream id */
|
int id; /* http2 stream id */
|
||||||
int promised_on; /* http2 stream this was a promise on, or 0 */
|
int initiated_on; /* http2 stream id this was initiated on or 0 */
|
||||||
h2_stream_state_t state; /* http/2 state of this stream */
|
h2_stream_state_t state; /* http/2 state of this stream */
|
||||||
struct h2_session *session; /* the session this stream belongs to */
|
struct h2_session *session; /* the session this stream belongs to */
|
||||||
|
|
||||||
@@ -131,7 +131,8 @@ apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r);
|
|||||||
* @param stream the stream to init the request for
|
* @param stream the stream to init the request for
|
||||||
* @param req the request for initializing, will be copied
|
* @param req the request for initializing, will be copied
|
||||||
*/
|
*/
|
||||||
void h2_stream_set_h2_request(h2_stream *stream, const struct h2_request *req);
|
void h2_stream_set_h2_request(h2_stream *stream, int initiated_on,
|
||||||
|
const struct h2_request *req);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a HTTP/2 header (including pseudo headers) to the given stream.
|
* Add a HTTP/2 header (including pseudo headers) to the given stream.
|
||||||
|
@@ -71,21 +71,6 @@ h2_task_input *h2_task_input_create(h2_task *task, apr_pool_t *pool,
|
|||||||
/* We do not serialize and have eos already, no need to
|
/* We do not serialize and have eos already, no need to
|
||||||
* create a bucket brigade. */
|
* create a bucket brigade. */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (APLOGcdebug(task->c)) {
|
|
||||||
char buffer[1024];
|
|
||||||
apr_size_t len = sizeof(buffer)-1;
|
|
||||||
if (input->bb) {
|
|
||||||
apr_brigade_flatten(input->bb, buffer, &len);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
len = 0;
|
|
||||||
}
|
|
||||||
buffer[len] = 0;
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
|
|
||||||
"h2_task_input(%s): request is: %s",
|
|
||||||
task->id, buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
@@ -105,21 +90,20 @@ apr_status_t h2_task_input_read(h2_task_input *input,
|
|||||||
apr_status_t status = APR_SUCCESS;
|
apr_status_t status = APR_SUCCESS;
|
||||||
apr_off_t bblen = 0;
|
apr_off_t bblen = 0;
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||||
"h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld",
|
"h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld",
|
||||||
input->task->id, block, mode, (long)readbytes);
|
input->task->id, block, mode, (long)readbytes);
|
||||||
|
|
||||||
if (is_aborted(f)) {
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
|
||||||
"h2_task_input(%s): is aborted",
|
|
||||||
input->task->id);
|
|
||||||
return APR_ECONNABORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == AP_MODE_INIT) {
|
if (mode == AP_MODE_INIT) {
|
||||||
return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
|
return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_aborted(f)) {
|
||||||
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||||
|
"h2_task_input(%s): is aborted", input->task->id);
|
||||||
|
return APR_ECONNABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
if (input->bb) {
|
if (input->bb) {
|
||||||
status = apr_brigade_length(input->bb, 1, &bblen);
|
status = apr_brigade_length(input->bb, 1, &bblen);
|
||||||
if (status != APR_SUCCESS) {
|
if (status != APR_SUCCESS) {
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
#include "h2_private.h"
|
#include "h2_private.h"
|
||||||
|
#include "h2_request.h"
|
||||||
#include "h2_util.h"
|
#include "h2_util.h"
|
||||||
|
|
||||||
size_t h2_util_hex_dump(char *buffer, size_t maxlen,
|
size_t h2_util_hex_dump(char *buffer, size_t maxlen,
|
||||||
@@ -205,6 +206,10 @@ const char *h2_util_first_token_match(apr_pool_t *pool, const char *s,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* h2_util for bucket brigades
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
/* DEEP_COPY==0 crashes under load. I think the setaside is fine,
|
/* DEEP_COPY==0 crashes under load. I think the setaside is fine,
|
||||||
* however buckets moved to another thread will still be
|
* however buckets moved to another thread will still be
|
||||||
* free'd against the old bucket_alloc. *And* if the old
|
* free'd against the old bucket_alloc. *And* if the old
|
||||||
@@ -789,3 +794,91 @@ apr_status_t h2_transfer_brigade(apr_bucket_brigade *to,
|
|||||||
return APR_SUCCESS;
|
return APR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* h2_ngheader
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
int h2_util_ignore_header(const char *name)
|
||||||
|
{
|
||||||
|
/* never forward, ch. 8.1.2.2 */
|
||||||
|
return (H2_HD_MATCH_LIT_CS("connection", name)
|
||||||
|
|| H2_HD_MATCH_LIT_CS("proxy-connection", name)
|
||||||
|
|| H2_HD_MATCH_LIT_CS("upgrade", name)
|
||||||
|
|| H2_HD_MATCH_LIT_CS("keep-alive", name)
|
||||||
|
|| H2_HD_MATCH_LIT_CS("transfer-encoding", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int count_header(void *ctx, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
if (!h2_util_ignore_header(key)) {
|
||||||
|
(*((size_t*)ctx))++;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NV_ADD_LIT_CS(nv, k, v) add_header(nv, k, sizeof(k) - 1, v, strlen(v))
|
||||||
|
#define NV_ADD_CS_CS(nv, k, v) add_header(nv, k, strlen(k), v, strlen(v))
|
||||||
|
|
||||||
|
static int add_header(h2_ngheader *ngh,
|
||||||
|
const char *key, size_t key_len,
|
||||||
|
const char *value, size_t val_len)
|
||||||
|
{
|
||||||
|
nghttp2_nv *nv = &ngh->nv[ngh->nvlen++];
|
||||||
|
|
||||||
|
nv->name = (uint8_t*)key;
|
||||||
|
nv->namelen = key_len;
|
||||||
|
nv->value = (uint8_t*)value;
|
||||||
|
nv->valuelen = val_len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_table_header(void *ctx, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
if (!h2_util_ignore_header(key)) {
|
||||||
|
add_header(ctx, key, strlen(key), value, strlen(value));
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p,
|
||||||
|
int http_status,
|
||||||
|
apr_table_t *header)
|
||||||
|
{
|
||||||
|
h2_ngheader *ngh;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
n = 1;
|
||||||
|
apr_table_do(count_header, &n, header, NULL);
|
||||||
|
|
||||||
|
ngh = apr_pcalloc(p, sizeof(h2_ngheader));
|
||||||
|
ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
|
||||||
|
NV_ADD_LIT_CS(ngh, ":status", apr_psprintf(p, "%d", http_status));
|
||||||
|
apr_table_do(add_table_header, ngh, header, NULL);
|
||||||
|
|
||||||
|
return ngh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p,
|
||||||
|
const struct h2_request *req)
|
||||||
|
{
|
||||||
|
|
||||||
|
h2_ngheader *ngh;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
AP_DEBUG_ASSERT(req);
|
||||||
|
|
||||||
|
n = 4;
|
||||||
|
apr_table_do(count_header, &n, req->headers, NULL);
|
||||||
|
|
||||||
|
ngh = apr_pcalloc(p, sizeof(h2_ngheader));
|
||||||
|
ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
|
||||||
|
NV_ADD_LIT_CS(ngh, ":scheme", req->scheme);
|
||||||
|
NV_ADD_LIT_CS(ngh, ":authority", req->authority);
|
||||||
|
NV_ADD_LIT_CS(ngh, ":path", req->path);
|
||||||
|
NV_ADD_LIT_CS(ngh, ":method", req->method);
|
||||||
|
apr_table_do(add_table_header, ngh, req->headers, NULL);
|
||||||
|
|
||||||
|
return ngh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
#ifndef __mod_h2__h2_util__
|
#ifndef __mod_h2__h2_util__
|
||||||
#define __mod_h2__h2_util__
|
#define __mod_h2__h2_util__
|
||||||
|
|
||||||
|
struct h2_request;
|
||||||
struct nghttp2_frame;
|
struct nghttp2_frame;
|
||||||
|
|
||||||
size_t h2_util_hex_dump(char *buffer, size_t maxlen,
|
size_t h2_util_hex_dump(char *buffer, size_t maxlen,
|
||||||
@@ -67,6 +68,19 @@ apr_size_t h2_util_base64url_decode(const char **decoded,
|
|||||||
nv->value = (uint8_t *)VALUE; \
|
nv->value = (uint8_t *)VALUE; \
|
||||||
nv->valuelen = strlen(VALUE)
|
nv->valuelen = strlen(VALUE)
|
||||||
|
|
||||||
|
int h2_util_ignore_header(const char *name);
|
||||||
|
|
||||||
|
typedef struct h2_ngheader {
|
||||||
|
nghttp2_nv *nv;
|
||||||
|
apr_size_t nvlen;
|
||||||
|
} h2_ngheader;
|
||||||
|
|
||||||
|
h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p,
|
||||||
|
int http_status,
|
||||||
|
apr_table_t *header);
|
||||||
|
h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p,
|
||||||
|
const struct h2_request *req);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves data from one brigade into another. If maxlen > 0, it only
|
* Moves data from one brigade into another. If maxlen > 0, it only
|
||||||
* moves up to maxlen bytes into the target brigade, making bucket splits
|
* moves up to maxlen bytes into the target brigade, making bucket splits
|
||||||
|
@@ -174,7 +174,7 @@ static apr_status_t get_mplx_next(h2_worker *worker, h2_mplx **pm,
|
|||||||
* needed to give up with more than enough workers.
|
* needed to give up with more than enough workers.
|
||||||
*/
|
*/
|
||||||
if (task) {
|
if (task) {
|
||||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
|
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
|
||||||
"h2_worker(%d): start task(%s)",
|
"h2_worker(%d): start task(%s)",
|
||||||
h2_worker_get_id(worker), task->id);
|
h2_worker_get_id(worker), task->id);
|
||||||
/* Since we hand out a reference to the worker, we increase
|
/* Since we hand out a reference to the worker, we increase
|
||||||
@@ -326,7 +326,7 @@ apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
|
|||||||
{
|
{
|
||||||
apr_status_t status = apr_thread_mutex_lock(workers->lock);
|
apr_status_t status = apr_thread_mutex_lock(workers->lock);
|
||||||
if (status == APR_SUCCESS) {
|
if (status == APR_SUCCESS) {
|
||||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, status, workers->s,
|
ap_log_error(APLOG_MARK, APLOG_TRACE2, status, workers->s,
|
||||||
"h2_workers: register mplx(%ld)", m->id);
|
"h2_workers: register mplx(%ld)", m->id);
|
||||||
if (in_list(workers, m)) {
|
if (in_list(workers, m)) {
|
||||||
status = APR_EAGAIN;
|
status = APR_EAGAIN;
|
||||||
|
Reference in New Issue
Block a user