mirror of
https://github.com/apache/httpd.git
synced 2026-10-23 22:49:01 +03:00
*) mod_http2:
- Fixed an issue since 1.15.24 that "Server" headers in proxied requests
were overwritten instead of preserved. [PR by @daum3ns]
- Added directove 'H2StreamTimeout' to configure a separate value for HTTP/2
streams, overriding server's 'Timeout' configuration. [rpluem]
- HTTP/2 connections now use pollsets to monitor the status of the
ongoing streams and their main connection when host OS allows this.
- Removed work-arounds for older versions of libnghttp2 and checking
during configure that at least version 1.15.0 is present.
- The HTTP/2 connection state handler, based on an experiment and draft
at the IETF http working group (abandoned for some time), has been removed.
- H2SerializeHeaders no longer has an effect. A warning is logged when it is
set to "on". The switch enabled the internal writing of requests to be parsed
by the internal HTTP/1.1 protocol handler and was introduced to avoid
potential incompatibilities during the introduction of HTTP/2.
- Removed the abort/redo of tasks when mood swings lower the active limit.
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1894163 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@@ -469,18 +469,15 @@ SET(mod_http2_extra_defines ssize_t=long)
|
||||
SET(mod_http2_extra_includes ${NGHTTP2_INCLUDE_DIR})
|
||||
SET(mod_http2_extra_libs ${NGHTTP2_LIBRARIES})
|
||||
SET(mod_http2_extra_sources
|
||||
modules/http2/h2_alt_svc.c
|
||||
modules/http2/h2_bucket_eos.c modules/http2/h2_config.c
|
||||
modules/http2/h2_conn.c modules/http2/h2_conn_io.c
|
||||
modules/http2/h2_ctx.c modules/http2/h2_filter.c
|
||||
modules/http2/h2_from_h1.c modules/http2/h2_h2.c
|
||||
modules/http2/h2_bucket_beam.c
|
||||
modules/http2/h2_mplx.c modules/http2/h2_push.c
|
||||
modules/http2/h2_request.c modules/http2/h2_headers.c
|
||||
modules/http2/h2_session.c modules/http2/h2_stream.c
|
||||
modules/http2/h2_switch.c
|
||||
modules/http2/h2_task.c modules/http2/h2_util.c
|
||||
modules/http2/h2_workers.c
|
||||
modules/http2/h2_bucket_beam.c modules/http2/h2_bucket_eos.c
|
||||
modules/http2/h2_c1.c modules/http2/h2_c1_io.c
|
||||
modules/http2/h2_c2.c modules/http2/h2_c2_filter.c
|
||||
modules/http2/h2_config.c modules/http2/h2_conn_ctx.c
|
||||
modules/http2/h2_headers.c modules/http2/h2_mplx.c
|
||||
modules/http2/h2_protocol.c modules/http2/h2_push.c
|
||||
modules/http2/h2_request.c modules/http2/h2_session.c
|
||||
modules/http2/h2_stream.c modules/http2/h2_switch.c
|
||||
modules/http2/h2_util.c modules/http2/h2_workers.c
|
||||
)
|
||||
SET(mod_ldap_extra_defines LDAP_DECLARE_EXPORT)
|
||||
SET(mod_ldap_extra_libs wldap32)
|
||||
|
||||
17
changes-entries/http2_additions.txt
Normal file
17
changes-entries/http2_additions.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
*) mod_http2:
|
||||
- Fixed an issue since 1.15.24 that "Server" headers in proxied requests
|
||||
were overwritten instead of preserved. [PR by @daum3ns]
|
||||
- Added directove 'H2StreamTimeout' to configure a separate value for HTTP/2
|
||||
streams, overriding server's 'Timeout' configuration. [rpluem]
|
||||
- HTTP/2 connections now use pollsets to monitor the status of the
|
||||
ongoing streams and their main connection when host OS allows this.
|
||||
- Removed work-arounds for older versions of libnghttp2 and checking
|
||||
during configure that at least version 1.15.0 is present.
|
||||
- The HTTP/2 connection state handler, based on an experiment and draft
|
||||
at the IETF http working group (abandoned for some time), has been removed.
|
||||
- H2SerializeHeaders no longer has an effect. A warning is logged when it is
|
||||
set to "on". The switch enabled the internal writing of requests to be parsed
|
||||
by the internal HTTP/1.1 protocol handler and was introduced to avoid
|
||||
potential incompatibilities during the introduction of HTTP/2.
|
||||
- Removed the abort/redo of tasks when mood swings lower the active limit.
|
||||
[Ruediger Pluem, daum3ns, Stefan Eissing]
|
||||
@@ -184,27 +184,25 @@ TARGET_lib = \
|
||||
# Paths must all use the '/' character
|
||||
#
|
||||
FILES_nlm_objs = \
|
||||
$(OBJDIR)/h2_alt_svc.o \
|
||||
$(OBJDIR)/h2_bucket_beam.o \
|
||||
$(OBJDIR)/h2_bucket_eos.o \
|
||||
$(OBJDIR)/h2_config.o \
|
||||
$(OBJDIR)/h2_conn.o \
|
||||
$(OBJDIR)/h2_conn_io.o \
|
||||
$(OBJDIR)/h2_ctx.o \
|
||||
$(OBJDIR)/h2_filter.o \
|
||||
$(OBJDIR)/h2_from_h1.o \
|
||||
$(OBJDIR)/h2_h2.o \
|
||||
$(OBJDIR)/h2_mplx.o \
|
||||
$(OBJDIR)/h2_push.o \
|
||||
$(OBJDIR)/h2_request.o \
|
||||
$(OBJDIR)/h2_headers.o \
|
||||
$(OBJDIR)/h2_session.o \
|
||||
$(OBJDIR)/h2_stream.o \
|
||||
$(OBJDIR)/h2_switch.o \
|
||||
$(OBJDIR)/h2_task.o \
|
||||
$(OBJDIR)/h2_util.o \
|
||||
$(OBJDIR)/h2_workers.o \
|
||||
$(OBJDIR)/mod_http2.o \
|
||||
$(OBJDIR)/h2_bucket_beam.lo \
|
||||
$(OBJDIR)/h2_bucket_eos.lo \
|
||||
$(OBJDIR)/h2_c1.lo \
|
||||
$(OBJDIR)/h2_c1_io.lo \
|
||||
$(OBJDIR)/h2_c2.lo \
|
||||
$(OBJDIR)/h2_c2_filter.lo \
|
||||
$(OBJDIR)/h2_config.lo \
|
||||
$(OBJDIR)/h2_conn_ctx.lo \
|
||||
$(OBJDIR)/h2_headers.lo \
|
||||
$(OBJDIR)/h2_mplx.lo \
|
||||
$(OBJDIR)/h2_protocol.lo \
|
||||
$(OBJDIR)/h2_push.lo \
|
||||
$(OBJDIR)/h2_request.lo \
|
||||
$(OBJDIR)/h2_session.lo \
|
||||
$(OBJDIR)/h2_stream.lo \
|
||||
$(OBJDIR)/h2_switch.lo \
|
||||
$(OBJDIR)/h2_util.lo \
|
||||
$(OBJDIR)/h2_workers.lo \
|
||||
$(OBJDIR)/mod_http2.lo \
|
||||
$(EOLIST)
|
||||
|
||||
#
|
||||
|
||||
@@ -19,24 +19,22 @@ APACHE_MODPATH_INIT(http2)
|
||||
dnl # list of module object files
|
||||
http2_objs="dnl
|
||||
mod_http2.lo dnl
|
||||
h2_alt_svc.lo dnl
|
||||
h2_bucket_beam.lo dnl
|
||||
h2_bucket_eos.lo dnl
|
||||
h2_c1.lo dnl
|
||||
h2_c1_io.lo dnl
|
||||
h2_c2.lo dnl
|
||||
h2_c2_filter.lo dnl
|
||||
h2_config.lo dnl
|
||||
h2_conn.lo dnl
|
||||
h2_conn_io.lo dnl
|
||||
h2_ctx.lo dnl
|
||||
h2_filter.lo dnl
|
||||
h2_from_h1.lo dnl
|
||||
h2_h2.lo dnl
|
||||
h2_conn_ctx.lo dnl
|
||||
h2_headers.lo dnl
|
||||
h2_mplx.lo dnl
|
||||
h2_protocol.lo dnl
|
||||
h2_push.lo dnl
|
||||
h2_request.lo dnl
|
||||
h2_session.lo dnl
|
||||
h2_stream.lo dnl
|
||||
h2_switch.lo dnl
|
||||
h2_task.lo dnl
|
||||
h2_util.lo dnl
|
||||
h2_workers.lo dnl
|
||||
"
|
||||
|
||||
@@ -17,6 +17,19 @@
|
||||
#ifndef __mod_h2__h2__
|
||||
#define __mod_h2__h2__
|
||||
|
||||
struct h2_session;
|
||||
struct h2_stream;
|
||||
|
||||
/*
|
||||
* When apr pollsets can poll file descriptors (e.g. pipes),
|
||||
* we use it for polling stream input/output.
|
||||
*/
|
||||
#ifdef H2_NO_POLL_STREAMS
|
||||
#define H2_POLL_STREAMS 0
|
||||
#else
|
||||
#define H2_POLL_STREAMS APR_FILES_AS_SOCKETS
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The magic PRIamble of RFC 7540 that is always sent when starting
|
||||
* a h2 communication.
|
||||
@@ -89,7 +102,7 @@ typedef enum {
|
||||
H2_SESSION_ST_DONE, /* finished, connection close */
|
||||
H2_SESSION_ST_IDLE, /* nothing to write, expecting data inc */
|
||||
H2_SESSION_ST_BUSY, /* read/write without stop */
|
||||
H2_SESSION_ST_WAIT, /* waiting for tasks reporting back */
|
||||
H2_SESSION_ST_WAIT, /* waiting for c1 incoming + c2s output */
|
||||
H2_SESSION_ST_CLEANUP, /* pool is being cleaned up */
|
||||
} h2_session_state;
|
||||
|
||||
@@ -120,7 +133,9 @@ typedef enum {
|
||||
H2_SEV_CLOSED_R,
|
||||
H2_SEV_CANCELLED,
|
||||
H2_SEV_EOS_SENT,
|
||||
H2_SEV_IN_ERROR,
|
||||
H2_SEV_IN_DATA_PENDING,
|
||||
H2_SEV_OUT_C1_BLOCK,
|
||||
} h2_stream_event_t;
|
||||
|
||||
|
||||
@@ -129,7 +144,6 @@ typedef enum {
|
||||
* become a request_rec to be handled by soemone.
|
||||
*/
|
||||
typedef struct h2_request h2_request;
|
||||
|
||||
struct h2_request {
|
||||
const char *method; /* pseudo header values, see ch. 8.1.2.3 */
|
||||
const char *scheme;
|
||||
@@ -138,8 +152,7 @@ struct h2_request {
|
||||
apr_table_t *headers;
|
||||
|
||||
apr_time_t request_time;
|
||||
unsigned int chunked : 1; /* iff request body needs to be forwarded as chunked */
|
||||
unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
|
||||
int chunked; /* iff request body needs to be forwarded as chunked */
|
||||
apr_off_t raw_bytes; /* RAW network bytes that generated this request - if known. */
|
||||
int http_status; /* Store a possible HTTP status code that gets
|
||||
* defined before creating the dummy HTTP/1.1
|
||||
@@ -155,7 +168,6 @@ struct h2_request {
|
||||
#define H2_HTTP_STATUS_UNSET (0)
|
||||
|
||||
typedef struct h2_headers h2_headers;
|
||||
|
||||
struct h2_headers {
|
||||
int status;
|
||||
apr_table_t *headers;
|
||||
@@ -165,12 +177,10 @@ struct h2_headers {
|
||||
|
||||
typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_off_t len);
|
||||
|
||||
typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx);
|
||||
typedef int h2_stream_pri_cmp_fn(int stream_id1, int stream_id2, void *session);
|
||||
typedef struct h2_stream *h2_stream_get_fn(struct h2_session *session, int stream_id);
|
||||
|
||||
/* Note key to attach connection task id to conn_rec/request_rec instances */
|
||||
|
||||
#define H2_TASK_ID_NOTE "http2-task-id"
|
||||
#define H2_FILTER_DEBUG_NOTE "http2-debug"
|
||||
/* Note key to attach stream id to conn_rec/request_rec instances */
|
||||
#define H2_HDR_CONFORMANCE "http2-hdr-conformance"
|
||||
#define H2_HDR_CONFORMANCE_UNSAFE "unsafe"
|
||||
#define H2_PUSH_MODE_NOTE "http2-push-mode"
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <apr_strings.h>
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_connection.h>
|
||||
#include <http_protocol.h>
|
||||
#include <http_ssl.h>
|
||||
#include <http_log.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2_alt_svc.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_util.h"
|
||||
|
||||
static int h2_alt_svc_handler(request_rec *r);
|
||||
|
||||
void h2_alt_svc_register_hooks(void)
|
||||
{
|
||||
ap_hook_post_read_request(h2_alt_svc_handler, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an Alt-Svc specifier as described in "HTTP Alternative Services"
|
||||
* (https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04)
|
||||
* with the following changes:
|
||||
* - do not percent encode token values
|
||||
* - do not use quotation marks
|
||||
*/
|
||||
h2_alt_svc *h2_alt_svc_parse(const char *s, apr_pool_t *pool)
|
||||
{
|
||||
const char *sep = ap_strchr_c(s, '=');
|
||||
if (sep) {
|
||||
const char *alpn = apr_pstrmemdup(pool, s, (apr_size_t)(sep - s));
|
||||
const char *host = NULL;
|
||||
int port = 0;
|
||||
s = sep + 1;
|
||||
sep = ap_strchr_c(s, ':'); /* mandatory : */
|
||||
if (sep) {
|
||||
if (sep != s) { /* optional host */
|
||||
host = apr_pstrmemdup(pool, s, (apr_size_t)(sep - s));
|
||||
}
|
||||
s = sep + 1;
|
||||
if (*s) { /* must be a port number */
|
||||
port = (int)apr_atoi64(s);
|
||||
if (port > 0 && port < (0x1 << 16)) {
|
||||
h2_alt_svc *as = apr_pcalloc(pool, sizeof(*as));
|
||||
as->alpn = alpn;
|
||||
as->host = host;
|
||||
as->port = port;
|
||||
return as;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define h2_alt_svc_IDX(list, i) ((h2_alt_svc**)(list)->elts)[i]
|
||||
|
||||
static int h2_alt_svc_handler(request_rec *r)
|
||||
{
|
||||
apr_array_header_t *alt_svcs;
|
||||
int i;
|
||||
|
||||
if (r->connection->keepalives > 0) {
|
||||
/* Only announce Alt-Svc on the first response */
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
if (h2_ctx_rget(r)) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
alt_svcs = h2_config_alt_svcs(r);
|
||||
if (r->hostname && alt_svcs && alt_svcs->nelts > 0) {
|
||||
const char *alt_svc_used = apr_table_get(r->headers_in, "Alt-Svc-Used");
|
||||
if (!alt_svc_used) {
|
||||
/* We have alt-svcs defined and client is not already using
|
||||
* one, announce the services that were configured and match.
|
||||
* The security of this connection determines if we allow
|
||||
* other host names or ports only.
|
||||
*/
|
||||
const char *alt_svc = "";
|
||||
const char *svc_ma = "";
|
||||
int secure = ap_ssl_conn_is_ssl(r->connection);
|
||||
int ma = h2_config_rgeti(r, H2_CONF_ALT_SVC_MAX_AGE);
|
||||
if (ma >= 0) {
|
||||
svc_ma = apr_psprintf(r->pool, "; ma=%d", ma);
|
||||
}
|
||||
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03043)
|
||||
"h2_alt_svc: announce %s for %s:%d",
|
||||
(secure? "secure" : "insecure"),
|
||||
r->hostname, (int)r->server->port);
|
||||
for (i = 0; i < alt_svcs->nelts; ++i) {
|
||||
h2_alt_svc *as = h2_alt_svc_IDX(alt_svcs, i);
|
||||
const char *ahost = as->host;
|
||||
if (ahost && !apr_strnatcasecmp(ahost, r->hostname)) {
|
||||
ahost = NULL;
|
||||
}
|
||||
if (secure || !ahost) {
|
||||
alt_svc = apr_psprintf(r->pool, "%s%s%s=\"%s:%d\"%s",
|
||||
alt_svc,
|
||||
(*alt_svc? ", " : ""), as->alpn,
|
||||
ahost? ahost : "", as->port,
|
||||
svc_ma);
|
||||
}
|
||||
}
|
||||
if (*alt_svc) {
|
||||
apr_table_setn(r->headers_out, "Alt-Svc", alt_svc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DECLINED;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_alt_svc__
|
||||
#define __mod_h2__h2_alt_svc__
|
||||
|
||||
typedef struct h2_alt_svc h2_alt_svc;
|
||||
|
||||
struct h2_alt_svc {
|
||||
const char *alpn;
|
||||
const char *host;
|
||||
int port;
|
||||
};
|
||||
|
||||
void h2_alt_svc_register_hooks(void);
|
||||
|
||||
/**
|
||||
* Parse an Alt-Svc specifier as described in "HTTP Alternative Services"
|
||||
* (https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04)
|
||||
* with the following changes:
|
||||
* - do not percent encode token values
|
||||
* - do not use quotation marks
|
||||
*/
|
||||
h2_alt_svc *h2_alt_svc_parse(const char *s, apr_pool_t *pool);
|
||||
|
||||
|
||||
#endif /* defined(__mod_h2__h2_alt_svc__) */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,113 +17,14 @@
|
||||
#ifndef h2_bucket_beam_h
|
||||
#define h2_bucket_beam_h
|
||||
|
||||
#include "h2_conn_ctx.h"
|
||||
|
||||
struct apr_thread_mutex_t;
|
||||
struct apr_thread_cond_t;
|
||||
|
||||
/*******************************************************************************
|
||||
* apr_bucket list without bells and whistles
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* h2_blist can hold a list of buckets just like apr_bucket_brigade, but
|
||||
* does not to any allocations or related features.
|
||||
*/
|
||||
typedef struct {
|
||||
APR_RING_HEAD(h2_bucket_list, apr_bucket) list;
|
||||
} h2_blist;
|
||||
|
||||
#define H2_BLIST_INIT(b) APR_RING_INIT(&(b)->list, apr_bucket, link);
|
||||
#define H2_BLIST_SENTINEL(b) APR_RING_SENTINEL(&(b)->list, apr_bucket, link)
|
||||
#define H2_BLIST_EMPTY(b) APR_RING_EMPTY(&(b)->list, apr_bucket, link)
|
||||
#define H2_BLIST_FIRST(b) APR_RING_FIRST(&(b)->list)
|
||||
#define H2_BLIST_LAST(b) APR_RING_LAST(&(b)->list)
|
||||
#define H2_BLIST_INSERT_HEAD(b, e) do { \
|
||||
apr_bucket *ap__b = (e); \
|
||||
APR_RING_INSERT_HEAD(&(b)->list, ap__b, apr_bucket, link); \
|
||||
} while (0)
|
||||
#define H2_BLIST_INSERT_TAIL(b, e) do { \
|
||||
apr_bucket *ap__b = (e); \
|
||||
APR_RING_INSERT_TAIL(&(b)->list, ap__b, apr_bucket, link); \
|
||||
} while (0)
|
||||
#define H2_BLIST_CONCAT(a, b) do { \
|
||||
APR_RING_CONCAT(&(a)->list, &(b)->list, apr_bucket, link); \
|
||||
} while (0)
|
||||
#define H2_BLIST_PREPEND(a, b) do { \
|
||||
APR_RING_PREPEND(&(a)->list, &(b)->list, apr_bucket, link); \
|
||||
} while (0)
|
||||
|
||||
/*******************************************************************************
|
||||
* h2_bucket_beam
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* A h2_bucket_beam solves the task of transferring buckets, esp. their data,
|
||||
* across threads with zero buffer copies.
|
||||
*
|
||||
* When a thread, let's call it the sender thread, wants to send buckets to
|
||||
* another, the green thread, it creates a h2_bucket_beam and adds buckets
|
||||
* via the h2_beam_send(). It gives the beam to the green thread which then
|
||||
* can receive buckets into its own brigade via h2_beam_receive().
|
||||
*
|
||||
* Sending and receiving can happen concurrently.
|
||||
*
|
||||
* The beam can limit the amount of data it accepts via the buffer_size. This
|
||||
* can also be adjusted during its lifetime. Sends and receives can be done blocking.
|
||||
* A timeout can be set for such blocks.
|
||||
*
|
||||
* Care needs to be taken when terminating the beam. The beam registers at
|
||||
* the pool it was created with and will cleanup after itself. However, if
|
||||
* received buckets do still exist, already freed memory might be accessed.
|
||||
* The beam does a assertion on this condition.
|
||||
*
|
||||
* The proper way of shutting down a beam is to first make sure there are no
|
||||
* more green buckets out there, then cleanup the beam to purge eventually
|
||||
* still existing sender buckets and then, possibly, terminate the beam itself
|
||||
* (or the pool it was created with).
|
||||
*
|
||||
* The following restrictions apply to bucket transport:
|
||||
* - only EOS and FLUSH meta buckets are copied through. All other meta buckets
|
||||
* are kept in the beams hold.
|
||||
* - all kind of data buckets are transported through:
|
||||
* - transient buckets are converted to heap ones on send
|
||||
* - heap and pool buckets require no extra handling
|
||||
* - buckets with indeterminate length are read on send
|
||||
* - file buckets will transfer the file itself into a new bucket, if allowed
|
||||
* - all other buckets are read on send to make sure data is present
|
||||
*
|
||||
* This assures that when the sender thread sends its sender buckets, the data
|
||||
* is made accessible while still on the sender side. The sender bucket then enters
|
||||
* the beams hold storage.
|
||||
* When the green thread calls receive, sender buckets in the hold are wrapped
|
||||
* into special beam buckets. Beam buckets on read present the data directly
|
||||
* from the internal sender one, but otherwise live on the green side. When a
|
||||
* beam bucket gets destroyed, it notifies its beam that the corresponding
|
||||
* sender bucket from the hold may be destroyed.
|
||||
* Since the destruction of green buckets happens in the green thread, any
|
||||
* corresponding sender bucket can not immediately be destroyed, as that would
|
||||
* result in race conditions.
|
||||
* Instead, the beam transfers such sender buckets from the hold to the purge
|
||||
* storage. Next time there is a call from the sender side, the buckets in
|
||||
* purge will be deleted.
|
||||
*
|
||||
* There are callbacks that can be registesender with a beam:
|
||||
* - a "consumed" callback that gets called on the sender side with the
|
||||
* amount of data that has been received by the green side. The amount
|
||||
* is a delta from the last callback invocation. The sender side can trigger
|
||||
* these callbacks by calling h2_beam_send() with a NULL brigade.
|
||||
* - a "can_beam_file" callback that can prohibit the transfer of file handles
|
||||
* through the beam. This will cause file buckets to be read on send and
|
||||
* its data buffer will then be transports just like a heap bucket would.
|
||||
* When no callback is registered, no restrictions apply and all files are
|
||||
* passed through.
|
||||
* File handles transfersender to the green side will stay there until the
|
||||
* receiving brigade's pool is destroyed/cleared. If the pool lives very
|
||||
* long or if many different files are beamed, the process might run out
|
||||
* of available file handles.
|
||||
*
|
||||
* The name "beam" of course is inspired by good old transporter
|
||||
* technology where humans are kept inside the transporter's memory
|
||||
* buffers until the transmission is complete. Star gates use a similar trick.
|
||||
* across threads with as little copying as possible.
|
||||
*/
|
||||
|
||||
typedef void h2_beam_mutex_leave(struct apr_thread_mutex_t *lock);
|
||||
@@ -141,66 +42,43 @@ typedef void h2_beam_io_callback(void *ctx, h2_bucket_beam *beam,
|
||||
apr_off_t bytes);
|
||||
typedef void h2_beam_ev_callback(void *ctx, h2_bucket_beam *beam);
|
||||
|
||||
typedef struct h2_beam_proxy h2_beam_proxy;
|
||||
typedef struct {
|
||||
APR_RING_HEAD(h2_beam_proxy_list, h2_beam_proxy) list;
|
||||
} h2_bproxy_list;
|
||||
|
||||
typedef int h2_beam_can_beam_callback(void *ctx, h2_bucket_beam *beam,
|
||||
apr_file_t *file);
|
||||
|
||||
typedef enum {
|
||||
H2_BEAM_OWNER_SEND,
|
||||
H2_BEAM_OWNER_RECV
|
||||
} h2_beam_owner_t;
|
||||
|
||||
/**
|
||||
* Will deny all transfer of apr_file_t across the beam and force
|
||||
* a data copy instead.
|
||||
* h2_blist can hold a list of buckets just like apr_bucket_brigade, but
|
||||
* does not to any allocations or related features.
|
||||
*/
|
||||
int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file);
|
||||
typedef struct {
|
||||
APR_RING_HEAD(h2_bucket_list, apr_bucket) list;
|
||||
} h2_blist;
|
||||
|
||||
struct h2_bucket_beam {
|
||||
int id;
|
||||
const char *tag;
|
||||
const char *name;
|
||||
conn_rec *from;
|
||||
apr_pool_t *pool;
|
||||
h2_beam_owner_t owner;
|
||||
h2_blist send_list;
|
||||
h2_blist hold_list;
|
||||
h2_blist purge_list;
|
||||
h2_blist buckets_to_send;
|
||||
h2_blist buckets_consumed;
|
||||
apr_bucket_brigade *recv_buffer;
|
||||
h2_bproxy_list proxies;
|
||||
apr_pool_t *send_pool;
|
||||
apr_pool_t *recv_pool;
|
||||
|
||||
apr_size_t max_buf_size;
|
||||
apr_interval_time_t timeout;
|
||||
|
||||
apr_off_t sent_bytes; /* amount of bytes send */
|
||||
apr_off_t received_bytes; /* amount of bytes received */
|
||||
|
||||
apr_size_t buckets_sent; /* # of beam buckets sent */
|
||||
apr_size_t files_beamed; /* how many file handles have been set aside */
|
||||
|
||||
unsigned int aborted : 1;
|
||||
unsigned int closed : 1;
|
||||
unsigned int close_sent : 1;
|
||||
unsigned int tx_mem_limits : 1; /* only memory size counts on transfers */
|
||||
int aborted;
|
||||
int tx_mem_limits; /* only memory size counts on transfers */
|
||||
int copy_files;
|
||||
|
||||
struct apr_thread_mutex_t *lock;
|
||||
struct apr_thread_cond_t *change;
|
||||
|
||||
apr_off_t cons_bytes_reported; /* amount of bytes reported as consumed */
|
||||
h2_beam_ev_callback *cons_ev_cb;
|
||||
h2_beam_io_callback *cons_io_cb;
|
||||
h2_beam_ev_callback *was_empty_cb; /* event: beam changed to non-empty in h2_beam_send() */
|
||||
void *was_empty_ctx;
|
||||
h2_beam_ev_callback *recv_cb; /* event: buckets were transfered in h2_beam_receive() */
|
||||
void *recv_ctx;
|
||||
|
||||
apr_off_t recv_bytes; /* amount of bytes transferred in h2_beam_receive() */
|
||||
apr_off_t recv_bytes_reported; /* amount of bytes reported as received via callback */
|
||||
h2_beam_io_callback *cons_io_cb; /* report: recv_bytes deltas for sender */
|
||||
void *cons_ctx;
|
||||
|
||||
apr_off_t prod_bytes_reported; /* amount of bytes reported as produced */
|
||||
h2_beam_io_callback *prod_io_cb;
|
||||
void *prod_ctx;
|
||||
|
||||
h2_beam_can_beam_callback *can_beam_fn;
|
||||
void *can_beam_ctx;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -211,62 +89,66 @@ struct h2_bucket_beam {
|
||||
* that is only used inside that same mutex.
|
||||
*
|
||||
* @param pbeam will hold the created beam on return
|
||||
* @param c_from connection from which buchets are sent
|
||||
* @param pool pool owning the beam, beam will cleanup when pool released
|
||||
* @param id identifier of the beam
|
||||
* @param tag tag identifying beam for logging
|
||||
* @param owner if the beam is owned by the sender or receiver, e.g. if
|
||||
* the pool owner is using this beam for sending or receiving
|
||||
* @param buffer_size maximum memory footprint of buckets buffered in beam, or
|
||||
* 0 for no limitation
|
||||
* @param timeout timeout for blocking operations
|
||||
*/
|
||||
apr_status_t h2_beam_create(h2_bucket_beam **pbeam,
|
||||
conn_rec *from,
|
||||
apr_pool_t *pool,
|
||||
int id, const char *tag,
|
||||
h2_beam_owner_t owner,
|
||||
apr_size_t buffer_size,
|
||||
apr_interval_time_t timeout);
|
||||
|
||||
/**
|
||||
* Destroys the beam immediately without cleanup.
|
||||
*/
|
||||
apr_status_t h2_beam_destroy(h2_bucket_beam *beam);
|
||||
apr_status_t h2_beam_destroy(h2_bucket_beam *beam, conn_rec *c);
|
||||
|
||||
/**
|
||||
* Send buckets from the given brigade through the beam. Will hold buckets
|
||||
* internally as long as they have not been processed by the receiving side.
|
||||
* All accepted buckets are removed from the given brigade. Will return with
|
||||
* APR_EAGAIN on non-blocking sends when not all buckets could be accepted.
|
||||
*
|
||||
* Call from the sender side only.
|
||||
* Switch copying of file buckets on/off.
|
||||
*/
|
||||
apr_status_t h2_beam_send(h2_bucket_beam *beam,
|
||||
void h2_beam_set_copy_files(h2_bucket_beam * beam, int enabled);
|
||||
|
||||
/**
|
||||
* Send buckets from the given brigade through the beam.
|
||||
* This can block of the amount of bucket data is above the buffer limit.
|
||||
* @param beam the beam to add buckets to
|
||||
* @param from the connection the sender operates on, must be the same as
|
||||
* used to create the beam
|
||||
* @param bb the brigade to take buckets from
|
||||
* @param block if the sending should block when the buffer is full
|
||||
* @return APR_SUCCESS when buckets were added to the beam. This can be
|
||||
* a partial transfer and other buckets may still remain in bb
|
||||
* APR_EAGAIN on non-blocking send when the buffer is full
|
||||
* APR_TIMEUP on blocking semd that time out
|
||||
* APR_ECONNABORTED when beam has been aborted
|
||||
*/
|
||||
apr_status_t h2_beam_send(h2_bucket_beam *beam, conn_rec *from,
|
||||
apr_bucket_brigade *bb,
|
||||
apr_read_type_e block);
|
||||
|
||||
/**
|
||||
* Register the pool from which future buckets are send. This defines
|
||||
* the lifetime of the buckets, e.g. the pool should not be cleared/destroyed
|
||||
* until the data is no longer needed (or has been received).
|
||||
* Receive buckets from the beam into the given brigade. The caller is
|
||||
* operating on connection `to`.
|
||||
* @param beam the beam to receive buckets from
|
||||
* @param to the connection the receiver is working with
|
||||
* @param bb the bucket brigade to append to
|
||||
* @param block if the read should block when buckets are unavailable
|
||||
* @param readbytes the amount of data the receiver wants
|
||||
* @return APR_SUCCESS when buckets were appended
|
||||
* APR_EAGAIN on non-blocking read when no buckets are available
|
||||
* APR_TIMEUP on blocking reads that time out
|
||||
* APR_ECONNABORTED when beam has been aborted
|
||||
*/
|
||||
void h2_beam_send_from(h2_bucket_beam *beam, apr_pool_t *p);
|
||||
|
||||
/**
|
||||
* Receive buckets from the beam into the given brigade. Will return APR_EOF
|
||||
* when reading past an EOS bucket. Reads can be blocking until data is
|
||||
* available or the beam has been closed. Non-blocking calls return APR_EAGAIN
|
||||
* if no data is available.
|
||||
*
|
||||
* Call from the receiver side only.
|
||||
* @param pclosed on return != 0 iff the beam has been closed by the sender. It
|
||||
* may still hold untransfered data. Maybe NULL if the caller is
|
||||
* not interested in this.
|
||||
*/
|
||||
apr_status_t h2_beam_receive(h2_bucket_beam *beam,
|
||||
apr_bucket_brigade *green_buckets,
|
||||
apr_status_t h2_beam_receive(h2_bucket_beam *beam, conn_rec *to,
|
||||
apr_bucket_brigade *bb,
|
||||
apr_read_type_e block,
|
||||
apr_off_t readbytes,
|
||||
int *pclosed);
|
||||
apr_off_t readbytes);
|
||||
|
||||
/**
|
||||
* Determine if beam is empty.
|
||||
@@ -274,53 +156,19 @@ apr_status_t h2_beam_receive(h2_bucket_beam *beam,
|
||||
int h2_beam_empty(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
* Determine if beam has handed out proxy buckets that are not destroyed.
|
||||
*/
|
||||
int h2_beam_holds_proxies(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
* Abort the beam. Will cleanup any buffered buckets and answer all send
|
||||
* and receives with APR_ECONNABORTED.
|
||||
*
|
||||
* Call from the sender side only.
|
||||
*/
|
||||
void h2_beam_abort(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
* Close the beam. Sending an EOS bucket serves the same purpose.
|
||||
*
|
||||
* Call from the sender side only.
|
||||
*/
|
||||
apr_status_t h2_beam_close(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
* Receives leaves the beam, e.g. will no longer read. This will
|
||||
* interrupt any sender blocked writing and fail future send.
|
||||
*
|
||||
* Call from the receiver side only.
|
||||
*/
|
||||
apr_status_t h2_beam_leave(h2_bucket_beam *beam);
|
||||
|
||||
int h2_beam_is_closed(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
* Return APR_SUCCESS when all buckets in transit have been handled.
|
||||
* When called with APR_BLOCK_READ and a mutex set, will wait until the green
|
||||
* side has consumed all data. Otherwise APR_EAGAIN is returned.
|
||||
* With clear_buffers set, any queued data is discarded.
|
||||
* If a timeout is set on the beam, waiting might also time out and
|
||||
* return APR_ETIMEUP.
|
||||
* Abort the beam, either from receiving or sending side.
|
||||
*
|
||||
* Call from the sender side only.
|
||||
* @param beam the beam to abort
|
||||
* @param c the connection the caller is working with
|
||||
*/
|
||||
apr_status_t h2_beam_wait_empty(h2_bucket_beam *beam, apr_read_type_e block);
|
||||
void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c);
|
||||
|
||||
/**
|
||||
* Set/get the timeout for blocking read/write operations. Only works
|
||||
* if a mutex has been set for the beam.
|
||||
/**
|
||||
* Set/get the timeout for blocking sebd/receive operations.
|
||||
*/
|
||||
void h2_beam_timeout_set(h2_bucket_beam *beam,
|
||||
apr_interval_time_t timeout);
|
||||
|
||||
apr_interval_time_t h2_beam_timeout_get(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
@@ -335,7 +183,6 @@ apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam);
|
||||
* amount of bytes that have been consumed by the receiver, since the
|
||||
* last callback invocation or reset.
|
||||
* @param beam the beam to set the callback on
|
||||
* @param ev_cb the callback or NULL, called when bytes are consumed
|
||||
* @param io_cb the callback or NULL, called on sender with bytes consumed
|
||||
* @param ctx the context to use in callback invocation
|
||||
*
|
||||
@@ -343,9 +190,29 @@ apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam);
|
||||
* from any side.
|
||||
*/
|
||||
void h2_beam_on_consumed(h2_bucket_beam *beam,
|
||||
h2_beam_ev_callback *ev_cb,
|
||||
h2_beam_io_callback *io_cb, void *ctx);
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked on the receiver side whenever
|
||||
* buckets have been transfered in a h2_beam_receive() call.
|
||||
* @param beam the beam to set the callback on
|
||||
* @param recv_cb the callback or NULL, called when buckets are received
|
||||
* @param ctx the context to use in callback invocation
|
||||
*/
|
||||
void h2_beam_on_received(h2_bucket_beam *beam,
|
||||
h2_beam_ev_callback *recv_cb, void *ctx);
|
||||
|
||||
/**
|
||||
* Register a call back from the sender side to be invoked when send
|
||||
* has added to a previously empty beam.
|
||||
* Unregister by passing a NULL was_empty_cb.
|
||||
* @param beam the beam to set the callback on
|
||||
* @param was_empty_cb the callback to invoke on blocked send
|
||||
* @param ctx the context to use in callback invocation
|
||||
*/
|
||||
void h2_beam_on_was_empty(h2_bucket_beam *beam,
|
||||
h2_beam_ev_callback *was_empty_cb, void *ctx);
|
||||
|
||||
/**
|
||||
* Call any registered consumed handler, if any changes have happened
|
||||
* since the last invocation.
|
||||
@@ -355,32 +222,6 @@ void h2_beam_on_consumed(h2_bucket_beam *beam,
|
||||
*/
|
||||
int h2_beam_report_consumption(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked on the receiver side with the
|
||||
* amount of bytes that have been produces by the sender, since the
|
||||
* last callback invocation or reset.
|
||||
* @param beam the beam to set the callback on
|
||||
* @param io_cb the callback or NULL, called on receiver with bytes produced
|
||||
* @param ctx the context to use in callback invocation
|
||||
*
|
||||
* Call from the receiver side, callbacks invoked on either side.
|
||||
*/
|
||||
void h2_beam_on_produced(h2_bucket_beam *beam,
|
||||
h2_beam_io_callback *io_cb, void *ctx);
|
||||
|
||||
/**
|
||||
* Register a callback that may prevent a file from being beam as
|
||||
* file handle, forcing the file content to be copied. Then no callback
|
||||
* is set (NULL), file handles are transferred directly.
|
||||
* @param beam the beam to set the callback on
|
||||
* @param io_cb the callback or NULL, called on receiver with bytes produced
|
||||
* @param ctx the context to use in callback invocation
|
||||
*
|
||||
* Call from the receiver side, callbacks invoked on either side.
|
||||
*/
|
||||
void h2_beam_on_file_beam(h2_bucket_beam *beam,
|
||||
h2_beam_can_beam_callback *cb, void *ctx);
|
||||
|
||||
/**
|
||||
* Get the amount of bytes currently buffered in the beam (unread).
|
||||
*/
|
||||
@@ -391,19 +232,10 @@ apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam);
|
||||
*/
|
||||
apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam);
|
||||
|
||||
/**
|
||||
* Return != 0 iff (some) data from the beam has been received.
|
||||
*/
|
||||
int h2_beam_was_received(h2_bucket_beam *beam);
|
||||
|
||||
apr_size_t h2_beam_get_files_beamed(h2_bucket_beam *beam);
|
||||
|
||||
typedef apr_bucket *h2_bucket_beamer(h2_bucket_beam *beam,
|
||||
typedef apr_bucket *h2_bucket_beamer(h2_bucket_beam *beam,
|
||||
apr_bucket_brigade *dest,
|
||||
const apr_bucket *src);
|
||||
|
||||
void h2_register_bucket_beamer(h2_bucket_beamer *beamer);
|
||||
|
||||
void h2_beam_log(h2_bucket_beam *beam, conn_rec *c, int level, const char *msg);
|
||||
|
||||
#endif /* h2_bucket_beam_h */
|
||||
|
||||
328
modules/http2/h2_c1.c
Normal file
328
modules/http2/h2_c1.c
Normal file
@@ -0,0 +1,328 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <apr_strings.h>
|
||||
|
||||
#include <ap_mpm.h>
|
||||
#include <ap_mmn.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_config.h>
|
||||
#include <http_log.h>
|
||||
#include <http_connection.h>
|
||||
#include <http_protocol.h>
|
||||
#include <http_request.h>
|
||||
#include <http_ssl.h>
|
||||
|
||||
#include <mpm_common.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2.h"
|
||||
#include "h2_bucket_beam.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
#include "h2_headers.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_workers.h"
|
||||
#include "h2_c1.h"
|
||||
#include "h2_version.h"
|
||||
#include "h2_util.h"
|
||||
|
||||
static struct h2_workers *workers;
|
||||
|
||||
static int async_mpm;
|
||||
|
||||
apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s)
|
||||
{
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
int minw, maxw;
|
||||
int max_threads_per_child = 0;
|
||||
int idle_secs = 0;
|
||||
|
||||
ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
|
||||
|
||||
status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
|
||||
if (status != APR_SUCCESS) {
|
||||
/* some MPMs do not implemnent this */
|
||||
async_mpm = 0;
|
||||
status = APR_SUCCESS;
|
||||
}
|
||||
|
||||
h2_config_init(pool);
|
||||
|
||||
h2_get_num_workers(s, &minw, &maxw);
|
||||
idle_secs = h2_config_sgeti(s, H2_CONF_MAX_WORKER_IDLE_SECS);
|
||||
ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
|
||||
"h2_workers: min=%d max=%d, mthrpchild=%d, idle_secs=%d",
|
||||
minw, maxw, max_threads_per_child, idle_secs);
|
||||
workers = h2_workers_create(s, pool, minw, maxw, idle_secs);
|
||||
|
||||
return h2_mplx_c1_child_init(pool, s);
|
||||
}
|
||||
|
||||
void h2_c1_child_stopping(apr_pool_t *pool, int graceful)
|
||||
{
|
||||
if (workers && graceful) {
|
||||
h2_workers_graceful_shutdown(workers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s)
|
||||
{
|
||||
h2_session *session;
|
||||
h2_conn_ctx_t *ctx;
|
||||
apr_status_t rv;
|
||||
|
||||
if (!workers) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911)
|
||||
"workers not initialized");
|
||||
rv = APR_EGENERAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rv = h2_session_create(&session, c, r, s, workers);
|
||||
if (APR_SUCCESS != rv) goto cleanup;
|
||||
|
||||
ctx = h2_conn_ctx_get(c);
|
||||
ap_assert(ctx);
|
||||
ctx->session = session;
|
||||
/* remove the input filter of mod_reqtimeout, now that the connection
|
||||
* is established and we have switched to h2. reqtimeout has supervised
|
||||
* possibly configured handshake timeouts and needs to get out of the way
|
||||
* now since the rest of its state handling assumes http/1.x to take place. */
|
||||
ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
|
||||
|
||||
cleanup:
|
||||
return rv;
|
||||
}
|
||||
|
||||
apr_status_t h2_c1_run(conn_rec *c)
|
||||
{
|
||||
apr_status_t status;
|
||||
int mpm_state = 0;
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
|
||||
|
||||
ap_assert(conn_ctx);
|
||||
ap_assert(conn_ctx->session);
|
||||
do {
|
||||
if (c->cs) {
|
||||
c->cs->sense = CONN_SENSE_DEFAULT;
|
||||
c->cs->state = CONN_STATE_HANDLER;
|
||||
}
|
||||
|
||||
status = h2_session_process(conn_ctx->session, async_mpm);
|
||||
|
||||
if (APR_STATUS_IS_EOF(status)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
|
||||
H2_SSSN_LOG(APLOGNO(03045), conn_ctx->session,
|
||||
"process, closing conn"));
|
||||
c->keepalive = AP_CONN_CLOSE;
|
||||
}
|
||||
else {
|
||||
c->keepalive = AP_CONN_KEEPALIVE;
|
||||
}
|
||||
|
||||
if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
|
||||
break;
|
||||
}
|
||||
} while (!async_mpm
|
||||
&& c->keepalive == AP_CONN_KEEPALIVE
|
||||
&& mpm_state != AP_MPMQ_STOPPING);
|
||||
|
||||
if (c->cs) {
|
||||
switch (conn_ctx->session->state) {
|
||||
case H2_SESSION_ST_INIT:
|
||||
case H2_SESSION_ST_IDLE:
|
||||
case H2_SESSION_ST_BUSY:
|
||||
case H2_SESSION_ST_WAIT:
|
||||
c->cs->state = CONN_STATE_WRITE_COMPLETION;
|
||||
if (c->cs && !conn_ctx->session->remote.emitted_count) {
|
||||
/* let the MPM know that we are not done and want
|
||||
* the Timeout behaviour instead of a KeepAliveTimeout
|
||||
* See PR 63534.
|
||||
*/
|
||||
c->cs->sense = CONN_SENSE_WANT_READ;
|
||||
}
|
||||
break;
|
||||
case H2_SESSION_ST_CLEANUP:
|
||||
case H2_SESSION_ST_DONE:
|
||||
default:
|
||||
c->cs->state = CONN_STATE_LINGER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
|
||||
|
||||
if (conn_ctx && conn_ctx->session) {
|
||||
apr_status_t status = h2_session_pre_close(conn_ctx->session, async_mpm);
|
||||
return (status == APR_SUCCESS)? DONE : status;
|
||||
}
|
||||
return DONE;
|
||||
}
|
||||
|
||||
int h2_c1_allows_direct(conn_rec *c)
|
||||
{
|
||||
if (!c->master) {
|
||||
int is_tls = ap_ssl_conn_is_ssl(c);
|
||||
const char *needed_protocol = is_tls? "h2" : "h2c";
|
||||
int h2_direct = h2_config_cgeti(c, H2_CONF_DIRECT);
|
||||
|
||||
if (h2_direct < 0) {
|
||||
h2_direct = is_tls? 0 : 1;
|
||||
}
|
||||
return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int h2_c1_can_upgrade(request_rec *r)
|
||||
{
|
||||
if (!r->connection->master) {
|
||||
int h2_upgrade = h2_config_rgeti(r, H2_CONF_UPGRADE);
|
||||
return h2_upgrade > 0 || (h2_upgrade < 0 && !ap_ssl_conn_is_ssl(r->connection));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h2_c1_hook_process_connection(conn_rec* c)
|
||||
{
|
||||
apr_status_t status;
|
||||
h2_conn_ctx_t *ctx;
|
||||
|
||||
if (c->master) goto declined;
|
||||
ctx = h2_conn_ctx_get(c);
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
|
||||
if (!ctx && c->keepalives == 0) {
|
||||
const char *proto = ap_get_protocol(c);
|
||||
|
||||
if (APLOGctrace1(c)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, "
|
||||
"new connection using protocol '%s', direct=%d, "
|
||||
"tls acceptable=%d", proto, h2_c1_allows_direct(c),
|
||||
h2_protocol_is_acceptable_c1(c, NULL, 1));
|
||||
}
|
||||
|
||||
if (!strcmp(AP_PROTOCOL_HTTP1, proto)
|
||||
&& h2_c1_allows_direct(c)
|
||||
&& h2_protocol_is_acceptable_c1(c, NULL, 1)) {
|
||||
/* Fresh connection still is on http/1.1 and H2Direct is enabled.
|
||||
* Otherwise connection is in a fully acceptable state.
|
||||
* -> peek at the first 24 incoming bytes
|
||||
*/
|
||||
apr_bucket_brigade *temp;
|
||||
char *peek = NULL;
|
||||
apr_size_t peeklen;
|
||||
|
||||
temp = apr_brigade_create(c->pool, c->bucket_alloc);
|
||||
status = ap_get_brigade(c->input_filters, temp,
|
||||
AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
|
||||
|
||||
if (status != APR_SUCCESS) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054)
|
||||
"h2_h2, error reading 24 bytes speculative");
|
||||
apr_brigade_destroy(temp);
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
apr_brigade_pflatten(temp, &peek, &peeklen, c->pool);
|
||||
if ((peeklen >= 24) && !memcmp(H2_MAGIC_TOKEN, peek, 24)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_h2, direct mode detected");
|
||||
ctx = h2_conn_ctx_create_for_c1(c, c->base_server,
|
||||
ap_ssl_conn_is_ssl(c)? "h2" : "h2c");
|
||||
}
|
||||
else if (APLOGctrace2(c)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
|
||||
"h2_h2, not detected in %d bytes(base64): %s",
|
||||
(int)peeklen, h2_util_base64url_encode(peek, peeklen, c->pool));
|
||||
}
|
||||
apr_brigade_destroy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx) goto declined;
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn");
|
||||
if (!ctx->session) {
|
||||
status = h2_c1_setup(c, NULL, ctx->server? ctx->server : c->base_server);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
|
||||
if (status != APR_SUCCESS) {
|
||||
h2_conn_ctx_detach(c);
|
||||
return !OK;
|
||||
}
|
||||
}
|
||||
h2_c1_run(c);
|
||||
return OK;
|
||||
|
||||
declined:
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static int h2_c1_hook_pre_close(conn_rec *c)
|
||||
{
|
||||
h2_conn_ctx_t *ctx;
|
||||
|
||||
/* secondary connection? */
|
||||
if (c->master) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
ctx = h2_conn_ctx_get(c);
|
||||
if (ctx) {
|
||||
/* If the session has been closed correctly already, we will not
|
||||
* find a h2_conn_ctx_there. The presence indicates that the session
|
||||
* is still ongoing. */
|
||||
return h2_c1_pre_close(ctx, c);
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static const char* const mod_ssl[] = { "mod_ssl.c", NULL};
|
||||
static const char* const mod_reqtimeout[] = { "mod_ssl.c", "mod_reqtimeout.c", NULL};
|
||||
|
||||
void h2_c1_register_hooks(void)
|
||||
{
|
||||
/* Our main processing needs to run quite late. Definitely after mod_ssl,
|
||||
* as we need its connection filters, but also before reqtimeout as its
|
||||
* method of timeouts is specific to HTTP/1.1 (as of now).
|
||||
* The core HTTP/1 processing run as REALLY_LAST, so we will have
|
||||
* a chance to take over before it.
|
||||
*/
|
||||
ap_hook_process_connection(h2_c1_hook_process_connection,
|
||||
mod_reqtimeout, NULL, APR_HOOK_LAST);
|
||||
|
||||
/* One last chance to properly say goodbye if we have not done so
|
||||
* already. */
|
||||
ap_hook_pre_close_connection(h2_c1_hook_pre_close, NULL, mod_ssl, APR_HOOK_LAST);
|
||||
|
||||
/* special bucket type transfer through a h2_bucket_beam */
|
||||
h2_register_bucket_beamer(h2_bucket_headers_beam);
|
||||
}
|
||||
|
||||
@@ -14,69 +14,65 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_conn__
|
||||
#define __mod_h2__h2_conn__
|
||||
#ifndef __mod_h2__h2_c1__
|
||||
#define __mod_h2__h2_c1__
|
||||
|
||||
struct h2_ctx;
|
||||
struct h2_task;
|
||||
struct h2_conn_ctx_t;
|
||||
|
||||
/* Initialize this child process for h2 primary connection work,
|
||||
* to be called once during child init before multi processing
|
||||
* starts.
|
||||
*/
|
||||
apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s);
|
||||
|
||||
/**
|
||||
* Setup the connection and our context for HTTP/2 processing
|
||||
* Setup the primary connection and our context for HTTP/2 processing
|
||||
*
|
||||
* @param c the connection HTTP/2 is starting on
|
||||
* @param r the upgrade request that still awaits an answer, optional
|
||||
* @param s the server selected for this connection (can be != c->base_server)
|
||||
*/
|
||||
apr_status_t h2_conn_setup(conn_rec *c, request_rec *r, server_rec *s);
|
||||
apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s);
|
||||
|
||||
/**
|
||||
* Run the HTTP/2 connection in synchronous fashion.
|
||||
* Run the HTTP/2 primary connection in synchronous fashion.
|
||||
* Return when the HTTP/2 session is done
|
||||
* and the connection will close or a fatal error occurred.
|
||||
*
|
||||
* @param c the http2 connection to run
|
||||
* @return APR_SUCCESS when session is done.
|
||||
*/
|
||||
apr_status_t h2_conn_run(conn_rec *c);
|
||||
apr_status_t h2_c1_run(conn_rec *c);
|
||||
|
||||
/**
|
||||
* The connection is about to close. If we have not send a GOAWAY
|
||||
* The primary connection is about to close. If we have not send a GOAWAY
|
||||
* yet, this is the last chance.
|
||||
*/
|
||||
apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c);
|
||||
apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c);
|
||||
|
||||
/**
|
||||
* Initialize this child process for h2 connection work,
|
||||
* to be called once during child init before multi processing
|
||||
* starts.
|
||||
* Check if the connection allows a direct detection of HTTPP/2,
|
||||
* as configurable by the H2Direct directive.
|
||||
* @param c the connection to check on
|
||||
* @return != 0 if direct detection is enabled
|
||||
*/
|
||||
apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s);
|
||||
int h2_c1_allows_direct(conn_rec *c);
|
||||
|
||||
/**
|
||||
* Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
|
||||
* for the given request.
|
||||
* @param r the request to check
|
||||
* @return != 0 iff Upgrade switching is enabled
|
||||
*/
|
||||
int h2_c1_can_upgrade(request_rec *r);
|
||||
|
||||
/* Register hooks for h2 handling on primary connections.
|
||||
*/
|
||||
void h2_c1_register_hooks(void);
|
||||
|
||||
/**
|
||||
* Child is about to be stopped, release unused resources
|
||||
*/
|
||||
void h2_conn_child_stopping(apr_pool_t *pool, int graceful);
|
||||
void h2_c1_child_stopping(apr_pool_t *pool, int graceful);
|
||||
|
||||
typedef enum {
|
||||
H2_MPM_UNKNOWN,
|
||||
H2_MPM_WORKER,
|
||||
H2_MPM_EVENT,
|
||||
H2_MPM_PREFORK,
|
||||
H2_MPM_MOTORZ,
|
||||
H2_MPM_SIMPLE,
|
||||
H2_MPM_NETWARE,
|
||||
H2_MPM_WINNT,
|
||||
} h2_mpm_type_t;
|
||||
|
||||
/* Returns the type of MPM module detected */
|
||||
h2_mpm_type_t h2_conn_mpm_type(void);
|
||||
const char *h2_conn_mpm_name(void);
|
||||
int h2_mpm_supported(void);
|
||||
|
||||
conn_rec *h2_secondary_create(conn_rec *master, int sec_id, apr_pool_t *parent);
|
||||
void h2_secondary_destroy(conn_rec *secondary);
|
||||
|
||||
apr_status_t h2_secondary_run_pre_connection(conn_rec *secondary, apr_socket_t *csd);
|
||||
void h2_secondary_run_connection(conn_rec *secondary);
|
||||
|
||||
#endif /* defined(__mod_h2__h2_conn__) */
|
||||
#endif /* defined(__mod_h2__h2_c1__) */
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <assert.h>
|
||||
#include <apr_strings.h>
|
||||
#include <ap_mpm.h>
|
||||
#include <mpm_common.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
@@ -29,8 +30,8 @@
|
||||
#include "h2_private.h"
|
||||
#include "h2_bucket_eos.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_conn_io.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_c1_io.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_util.h"
|
||||
|
||||
@@ -56,8 +57,8 @@
|
||||
|
||||
#define BUF_REMAIN ((apr_size_t)(bmax-off))
|
||||
|
||||
static void h2_conn_io_bb_log(conn_rec *c, int stream_id, int level,
|
||||
const char *tag, apr_bucket_brigade *bb)
|
||||
static void h2_c1_io_bb_log(conn_rec *c, int stream_id, int level,
|
||||
const char *tag, apr_bucket_brigade *bb)
|
||||
{
|
||||
char buffer[16 * 1024];
|
||||
const char *line = "(null)";
|
||||
@@ -130,23 +131,29 @@ static void h2_conn_io_bb_log(conn_rec *c, int stream_id, int level,
|
||||
c->id, tag, line);
|
||||
|
||||
}
|
||||
#define C1_IO_BB_LOG(c, stream_id, level, tag, bb) \
|
||||
if (APLOG_C_IS_LEVEL(c, level)) { \
|
||||
h2_c1_io_bb_log((c), (stream_id), (level), (tag), (bb)); \
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, server_rec *s)
|
||||
|
||||
apr_status_t h2_c1_io_init(h2_c1_io *io, conn_rec *c, server_rec *s)
|
||||
{
|
||||
io->c = c;
|
||||
io->output = apr_brigade_create(c->pool, c->bucket_alloc);
|
||||
io->is_tls = ap_ssl_conn_is_ssl(c);
|
||||
io->buffer_output = io->is_tls;
|
||||
io->flush_threshold = (apr_size_t)h2_config_sgeti64(s, H2_CONF_STREAM_MAX_MEM);
|
||||
io->flush_threshold = 4 * (apr_size_t)h2_config_sgeti64(s, H2_CONF_STREAM_MAX_MEM);
|
||||
|
||||
if (io->is_tls) {
|
||||
if (io->buffer_output) {
|
||||
/* This is what we start with,
|
||||
* see https://issues.apache.org/jira/browse/TS-2503
|
||||
*/
|
||||
io->warmup_size = h2_config_sgeti64(s, H2_CONF_TLS_WARMUP_SIZE);
|
||||
io->cooldown_usecs = (h2_config_sgeti(s, H2_CONF_TLS_COOLDOWN_SECS)
|
||||
io->cooldown_usecs = (h2_config_sgeti(s, H2_CONF_TLS_COOLDOWN_SECS)
|
||||
* APR_USEC_PER_SEC);
|
||||
io->write_size = (io->cooldown_usecs > 0?
|
||||
io->cooldown_usecs = 0;
|
||||
io->write_size = (io->cooldown_usecs > 0?
|
||||
WRITE_SIZE_INITIAL : WRITE_SIZE_MAX);
|
||||
}
|
||||
else {
|
||||
@@ -157,7 +164,7 @@ apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, server_rec *s)
|
||||
|
||||
if (APLOGctrace1(c)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->c,
|
||||
"h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, "
|
||||
"h2_c1_io(%ld): init, buffering=%d, warmup_size=%ld, "
|
||||
"cd_secs=%f", io->c->id, io->buffer_output,
|
||||
(long)io->warmup_size,
|
||||
((double)io->cooldown_usecs/APR_USEC_PER_SEC));
|
||||
@@ -166,19 +173,20 @@ apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, server_rec *s)
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static void append_scratch(h2_conn_io *io)
|
||||
static void append_scratch(h2_c1_io *io)
|
||||
{
|
||||
if (io->scratch && io->slen > 0) {
|
||||
apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
|
||||
apr_bucket_free,
|
||||
io->c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(io->output, b);
|
||||
io->buffered_len += io->slen;
|
||||
io->scratch = NULL;
|
||||
io->slen = io->ssize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static apr_size_t assure_scratch_space(h2_conn_io *io) {
|
||||
static apr_size_t assure_scratch_space(h2_c1_io *io) {
|
||||
apr_size_t remain = io->ssize - io->slen;
|
||||
if (io->scratch && remain == 0) {
|
||||
append_scratch(io);
|
||||
@@ -194,7 +202,7 @@ static apr_size_t assure_scratch_space(h2_conn_io *io) {
|
||||
return remain;
|
||||
}
|
||||
|
||||
static apr_status_t read_to_scratch(h2_conn_io *io, apr_bucket *b)
|
||||
static apr_status_t read_to_scratch(h2_c1_io *io, apr_bucket *b)
|
||||
{
|
||||
apr_status_t status;
|
||||
const char *data;
|
||||
@@ -211,9 +219,10 @@ static apr_status_t read_to_scratch(h2_conn_io *io, apr_bucket *b)
|
||||
apr_off_t offset = b->start;
|
||||
|
||||
len = b->length;
|
||||
/* file buckets will either mmap (which we do not want) or
|
||||
* read 8000 byte chunks and split themself. However, we do
|
||||
* know *exactly* how many bytes we need where.
|
||||
/* file buckets will read 8000 byte chunks and split
|
||||
* themselves. However, we do know *exactly* how many
|
||||
* bytes we need where. So we read the file directly to
|
||||
* where we need it.
|
||||
*/
|
||||
status = apr_file_seek(fd, APR_SET, &offset);
|
||||
if (status != APR_SUCCESS) {
|
||||
@@ -225,6 +234,16 @@ static apr_status_t read_to_scratch(h2_conn_io *io, apr_bucket *b)
|
||||
}
|
||||
io->slen += len;
|
||||
}
|
||||
else if (APR_BUCKET_IS_MMAP(b)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, io->c,
|
||||
"h2_c1_io(%ld): seeing mmap bucket of size %ld, scratch remain=%ld",
|
||||
io->c->id, (long)b->length, (long)(io->ssize - io->slen));
|
||||
status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
|
||||
if (status == APR_SUCCESS) {
|
||||
memcpy(io->scratch+io->slen, data, len);
|
||||
io->slen += len;
|
||||
}
|
||||
}
|
||||
else {
|
||||
status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
|
||||
if (status == APR_SUCCESS) {
|
||||
@@ -235,94 +254,101 @@ static apr_status_t read_to_scratch(h2_conn_io *io, apr_bucket *b)
|
||||
return status;
|
||||
}
|
||||
|
||||
static void check_write_size(h2_conn_io *io)
|
||||
{
|
||||
if (io->write_size > WRITE_SIZE_INITIAL
|
||||
&& (io->cooldown_usecs > 0)
|
||||
&& (apr_time_now() - io->last_write) >= io->cooldown_usecs) {
|
||||
/* long time not written, reset write size */
|
||||
io->write_size = WRITE_SIZE_INITIAL;
|
||||
io->bytes_written = 0;
|
||||
}
|
||||
else if (io->write_size < WRITE_SIZE_MAX
|
||||
&& io->bytes_written >= io->warmup_size) {
|
||||
/* connection is hot, use max size */
|
||||
io->write_size = WRITE_SIZE_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
static apr_status_t pass_output(h2_conn_io *io, int flush)
|
||||
static apr_status_t pass_output(h2_c1_io *io, int flush)
|
||||
{
|
||||
conn_rec *c = io->c;
|
||||
apr_bucket_brigade *bb = io->output;
|
||||
apr_bucket *b;
|
||||
apr_off_t bblen;
|
||||
apr_status_t status;
|
||||
apr_status_t rv;
|
||||
|
||||
append_scratch(io);
|
||||
if (flush && !io->is_flushed) {
|
||||
b = apr_bucket_flush_create(c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
if (flush) {
|
||||
if (!APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output))) {
|
||||
apr_bucket *b = apr_bucket_flush_create(io->c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(io->output, b);
|
||||
}
|
||||
}
|
||||
|
||||
if (APR_BRIGADE_EMPTY(bb)) {
|
||||
if (APR_BRIGADE_EMPTY(io->output)) {
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
|
||||
apr_brigade_length(bb, 0, &bblen);
|
||||
h2_conn_io_bb_log(c, 0, APLOG_TRACE2, "out", bb);
|
||||
io->unflushed = !APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output));
|
||||
apr_brigade_length(io->output, 0, &bblen);
|
||||
C1_IO_BB_LOG(c, 0, APLOG_TRACE2, "out", io->output);
|
||||
|
||||
status = ap_pass_brigade(c->output_filters, bb);
|
||||
if (status == APR_SUCCESS) {
|
||||
io->bytes_written += (apr_size_t)bblen;
|
||||
io->last_write = apr_time_now();
|
||||
if (flush) {
|
||||
io->is_flushed = 1;
|
||||
rv = ap_pass_brigade(c->output_filters, io->output);
|
||||
if (APR_SUCCESS != rv) goto cleanup;
|
||||
|
||||
io->buffered_len = 0;
|
||||
io->bytes_written += (apr_size_t)bblen;
|
||||
if (io->write_size < WRITE_SIZE_MAX
|
||||
&& io->bytes_written >= io->warmup_size) {
|
||||
/* connection is hot, use max size */
|
||||
io->write_size = WRITE_SIZE_MAX;
|
||||
}
|
||||
else if (io->cooldown_usecs > 0
|
||||
&& io->write_size > WRITE_SIZE_INITIAL) {
|
||||
apr_time_t now = apr_time_now();
|
||||
if ((now - io->last_write) >= io->cooldown_usecs) {
|
||||
/* long time not written, reset write size */
|
||||
io->write_size = WRITE_SIZE_INITIAL;
|
||||
io->bytes_written = 0;
|
||||
}
|
||||
else {
|
||||
io->last_write = now;
|
||||
}
|
||||
}
|
||||
apr_brigade_cleanup(bb);
|
||||
|
||||
if (status != APR_SUCCESS) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03044)
|
||||
"h2_conn_io(%ld): pass_out brigade %ld bytes",
|
||||
cleanup:
|
||||
if (APR_SUCCESS != rv) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(03044)
|
||||
"h2_c1_io(%ld): pass_out brigade %ld bytes",
|
||||
c->id, (long)bblen);
|
||||
}
|
||||
return status;
|
||||
apr_brigade_cleanup(io->output);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int h2_conn_io_needs_flush(h2_conn_io *io)
|
||||
int h2_c1_io_needs_flush(h2_c1_io *io)
|
||||
{
|
||||
if (!io->is_flushed) {
|
||||
apr_off_t len = h2_brigade_mem_size(io->output);
|
||||
if (len > (apr_off_t)io->flush_threshold) {
|
||||
return 1;
|
||||
}
|
||||
/* if we do not exceed flush length due to memory limits,
|
||||
* we want at least flush when we have that amount of data. */
|
||||
apr_brigade_length(io->output, 0, &len);
|
||||
return len > (apr_off_t)(4 * io->flush_threshold);
|
||||
return io->buffered_len >= io->flush_threshold;
|
||||
}
|
||||
|
||||
int h2_c1_io_pending(h2_c1_io *io)
|
||||
{
|
||||
return !APR_BRIGADE_EMPTY(io->output) || (io->scratch && io->slen > 0);
|
||||
}
|
||||
|
||||
apr_status_t h2_c1_io_pass(h2_c1_io *io)
|
||||
{
|
||||
apr_status_t rv = APR_SUCCESS;
|
||||
|
||||
if (h2_c1_io_pending(io)) {
|
||||
rv = pass_output(io, 0);
|
||||
}
|
||||
return 0;
|
||||
return rv;
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_io_flush(h2_conn_io *io)
|
||||
apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io)
|
||||
{
|
||||
apr_status_t status;
|
||||
status = pass_output(io, 1);
|
||||
check_write_size(io);
|
||||
return status;
|
||||
apr_status_t rv = APR_SUCCESS;
|
||||
|
||||
if (h2_c1_io_pending(io) || io->unflushed) {
|
||||
rv = pass_output(io, 1);
|
||||
if (APR_SUCCESS != rv) goto cleanup;
|
||||
}
|
||||
cleanup:
|
||||
return rv;
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_io_write(h2_conn_io *io, const char *data, size_t length)
|
||||
apr_status_t h2_c1_io_add_data(h2_c1_io *io, const char *data, size_t length)
|
||||
{
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_size_t remain;
|
||||
|
||||
if (length > 0) {
|
||||
io->is_flushed = 0;
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->c,
|
||||
"h2_c1_io(%ld): adding %ld data bytes",
|
||||
io->c->id, (long)length);
|
||||
if (io->buffer_output) {
|
||||
while (length > 0) {
|
||||
remain = assure_scratch_space(io);
|
||||
@@ -341,24 +367,20 @@ apr_status_t h2_conn_io_write(h2_conn_io *io, const char *data, size_t length)
|
||||
}
|
||||
else {
|
||||
status = apr_brigade_write(io->output, NULL, NULL, data, length);
|
||||
io->buffered_len += length;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb)
|
||||
apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb)
|
||||
{
|
||||
apr_bucket *b;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
|
||||
if (!APR_BRIGADE_EMPTY(bb)) {
|
||||
io->is_flushed = 0;
|
||||
}
|
||||
apr_status_t rv = APR_SUCCESS;
|
||||
|
||||
while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
|
||||
while (!APR_BRIGADE_EMPTY(bb)) {
|
||||
b = APR_BRIGADE_FIRST(bb);
|
||||
|
||||
if (APR_BUCKET_IS_METADATA(b)) {
|
||||
/* need to finish any open scratch bucket, as meta data
|
||||
if (APR_BUCKET_IS_METADATA(b) || APR_BUCKET_IS_MMAP(b)) {
|
||||
/* need to finish any open scratch bucket, as meta data
|
||||
* needs to be forward "in order". */
|
||||
append_scratch(io);
|
||||
APR_BUCKET_REMOVE(b);
|
||||
@@ -372,25 +394,143 @@ apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb)
|
||||
/* complete write_size bucket, append unchanged */
|
||||
APR_BUCKET_REMOVE(b);
|
||||
APR_BRIGADE_INSERT_TAIL(io->output, b);
|
||||
io->buffered_len += b->length;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* bucket fits in remain, copy to scratch */
|
||||
status = read_to_scratch(io, b);
|
||||
rv = read_to_scratch(io, b);
|
||||
apr_bucket_delete(b);
|
||||
if (APR_SUCCESS != rv) goto cleanup;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* no buffering, forward buckets setaside on flush */
|
||||
if (APR_BUCKET_IS_TRANSIENT(b)) {
|
||||
apr_bucket_setaside(b, io->c->pool);
|
||||
}
|
||||
apr_bucket_setaside(b, io->c->pool);
|
||||
APR_BUCKET_REMOVE(b);
|
||||
APR_BRIGADE_INSERT_TAIL(io->output, b);
|
||||
io->buffered_len += b->length;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
cleanup:
|
||||
return rv;
|
||||
}
|
||||
|
||||
static apr_status_t c1_in_feed_bucket(h2_session *session,
|
||||
apr_bucket *b, apr_ssize_t *inout_len)
|
||||
{
|
||||
apr_status_t rv = APR_SUCCESS;
|
||||
apr_size_t len;
|
||||
const char *data;
|
||||
ssize_t n;
|
||||
|
||||
rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
|
||||
while (APR_SUCCESS == rv && len > 0) {
|
||||
n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, session->c1,
|
||||
H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
|
||||
(long)len, (long)n);
|
||||
if (n < 0) {
|
||||
if (nghttp2_is_fatal((int)n)) {
|
||||
h2_session_event(session, H2_SESSION_EV_PROTO_ERROR,
|
||||
(int)n, nghttp2_strerror((int)n));
|
||||
rv = APR_EGENERAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
*inout_len += n;
|
||||
if ((apr_ssize_t)len <= n) {
|
||||
break;
|
||||
}
|
||||
len -= (apr_size_t)n;
|
||||
data += n;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static apr_status_t c1_in_feed_brigade(h2_session *session,
|
||||
apr_bucket_brigade *bb,
|
||||
apr_ssize_t *inout_len)
|
||||
{
|
||||
apr_status_t rv = APR_SUCCESS;
|
||||
apr_bucket* b;
|
||||
|
||||
*inout_len = 0;
|
||||
while (!APR_BRIGADE_EMPTY(bb)) {
|
||||
b = APR_BRIGADE_FIRST(bb);
|
||||
if (!APR_BUCKET_IS_METADATA(b)) {
|
||||
rv = c1_in_feed_bucket(session, b, inout_len);
|
||||
if (APR_SUCCESS != rv) goto cleanup;
|
||||
}
|
||||
apr_bucket_delete(b);
|
||||
}
|
||||
cleanup:
|
||||
apr_brigade_cleanup(bb);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static apr_status_t read_and_feed(h2_session *session)
|
||||
{
|
||||
apr_ssize_t bytes_fed, bytes_requested;
|
||||
apr_status_t rv;
|
||||
|
||||
bytes_requested = H2MAX(APR_BUCKET_BUFF_SIZE, session->max_stream_mem * 4);
|
||||
rv = ap_get_brigade(session->c1->input_filters,
|
||||
session->bbtmp, AP_MODE_READBYTES,
|
||||
APR_NONBLOCK_READ, bytes_requested);
|
||||
|
||||
if (APR_SUCCESS == rv) {
|
||||
h2_util_bb_log(session->c1, session->id, APLOG_TRACE2, "c1 in", session->bbtmp);
|
||||
rv = c1_in_feed_brigade(session, session->bbtmp, &bytes_fed);
|
||||
session->io.bytes_read += bytes_fed;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
apr_status_t h2_c1_read(h2_session *session)
|
||||
{
|
||||
apr_status_t rv;
|
||||
|
||||
/* H2_IN filter handles all incoming data against the session.
|
||||
* We just pull at the filter chain to make it happen */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
|
||||
H2_SSSN_MSG(session, "session_read start"));
|
||||
rv = read_and_feed(session);
|
||||
|
||||
if (APR_SUCCESS == rv) {
|
||||
h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_PENDING, 0, NULL);
|
||||
}
|
||||
else if (APR_STATUS_IS_EAGAIN(rv)) {
|
||||
/* Signal that we have exhausted the input momentarily.
|
||||
* This might switch to polling the socket */
|
||||
h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_EXHAUSTED, 0, NULL);
|
||||
}
|
||||
else if (APR_SUCCESS != rv) {
|
||||
if (APR_STATUS_IS_ETIMEDOUT(rv)
|
||||
|| APR_STATUS_IS_ECONNABORTED(rv)
|
||||
|| APR_STATUS_IS_ECONNRESET(rv)
|
||||
|| APR_STATUS_IS_EOF(rv)
|
||||
|| APR_STATUS_IS_EBADF(rv)) {
|
||||
/* common status for a client that has left */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
|
||||
H2_SSSN_MSG(session, "input gone"));
|
||||
}
|
||||
else {
|
||||
/* uncommon status, log on INFO so that we see this */
|
||||
ap_log_cerror( APLOG_MARK, APLOG_DEBUG, rv, session->c1,
|
||||
H2_SSSN_LOG(APLOGNO(02950), session,
|
||||
"error reading, terminating"));
|
||||
}
|
||||
h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
|
||||
}
|
||||
|
||||
apr_brigade_cleanup(session->bbtmp);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
|
||||
H2_SSSN_MSG(session, "session_read done"));
|
||||
return rv;
|
||||
}
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_conn_io__
|
||||
#define __mod_h2__h2_conn_io__
|
||||
#ifndef __mod_h2__h2_c1_io__
|
||||
#define __mod_h2__h2_c1_io__
|
||||
|
||||
struct h2_config;
|
||||
struct h2_session;
|
||||
@@ -31,6 +31,7 @@ typedef struct {
|
||||
apr_bucket_brigade *output;
|
||||
|
||||
int is_tls;
|
||||
int unflushed;
|
||||
apr_time_t cooldown_usecs;
|
||||
apr_int64_t warmup_size;
|
||||
|
||||
@@ -40,37 +41,60 @@ typedef struct {
|
||||
apr_int64_t bytes_written;
|
||||
|
||||
int buffer_output;
|
||||
apr_size_t flush_threshold;
|
||||
apr_off_t buffered_len;
|
||||
apr_off_t flush_threshold;
|
||||
unsigned int is_flushed : 1;
|
||||
|
||||
char *scratch;
|
||||
apr_size_t ssize;
|
||||
apr_size_t slen;
|
||||
} h2_conn_io;
|
||||
} h2_c1_io;
|
||||
|
||||
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, server_rec *s);
|
||||
apr_status_t h2_c1_io_init(h2_c1_io *io, conn_rec *c, server_rec *s);
|
||||
|
||||
/**
|
||||
* Append data to the buffered output.
|
||||
* @param buf the data to append
|
||||
* @param length the length of the data to append
|
||||
*/
|
||||
apr_status_t h2_conn_io_write(h2_conn_io *io,
|
||||
apr_status_t h2_c1_io_add_data(h2_c1_io *io,
|
||||
const char *buf,
|
||||
size_t length);
|
||||
|
||||
apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb);
|
||||
apr_status_t h2_c1_io_add(h2_c1_io *io, apr_bucket *b);
|
||||
|
||||
apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb);
|
||||
|
||||
/**
|
||||
* Pass any buffered data on to the connection output filters.
|
||||
* @param io the connection io
|
||||
* @param flush if a flush bucket should be appended to any output
|
||||
*/
|
||||
apr_status_t h2_conn_io_flush(h2_conn_io *io);
|
||||
apr_status_t h2_c1_io_pass(h2_c1_io *io);
|
||||
|
||||
/**
|
||||
* if there is any data pendiong or was any data send
|
||||
* since the last FLUSH, send out a FLUSH now.
|
||||
*/
|
||||
apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io);
|
||||
|
||||
/**
|
||||
* Check if the buffered amount of data needs flushing.
|
||||
*/
|
||||
int h2_conn_io_needs_flush(h2_conn_io *io);
|
||||
int h2_c1_io_needs_flush(h2_c1_io *io);
|
||||
|
||||
#endif /* defined(__mod_h2__h2_conn_io__) */
|
||||
/**
|
||||
* Check if we have output pending.
|
||||
*/
|
||||
int h2_c1_io_pending(h2_c1_io *io);
|
||||
|
||||
struct h2_session;
|
||||
|
||||
/**
|
||||
* Read c1 input and pass it on to nghttp2.
|
||||
* @param session the session
|
||||
* @param when_pending != 0 if only pending input (sitting in filters)
|
||||
* needs to be read
|
||||
*/
|
||||
apr_status_t h2_c1_read(struct h2_session *session);
|
||||
|
||||
#endif /* defined(__mod_h2__h2_c1_io__) */
|
||||
729
modules/http2/h2_c2.c
Normal file
729
modules/http2/h2_c2.c
Normal file
@@ -0,0 +1,729 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <apr_atomic.h>
|
||||
#include <apr_strings.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_config.h>
|
||||
#include <http_connection.h>
|
||||
#include <http_protocol.h>
|
||||
#include <http_request.h>
|
||||
#include <http_log.h>
|
||||
#include <http_vhost.h>
|
||||
#include <util_filter.h>
|
||||
#include <ap_mmn.h>
|
||||
#include <ap_mpm.h>
|
||||
#include <mpm_common.h>
|
||||
#include <mod_core.h>
|
||||
#include <scoreboard.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2.h"
|
||||
#include "h2_bucket_beam.h"
|
||||
#include "h2_c1.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
#include "h2_c2_filter.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_request.h"
|
||||
#include "h2_headers.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_c2.h"
|
||||
#include "h2_util.h"
|
||||
|
||||
|
||||
static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
|
||||
static module *mpm_module;
|
||||
static int mpm_supported = 1;
|
||||
static apr_socket_t *dummy_socket;
|
||||
|
||||
static void check_modules(int force)
|
||||
{
|
||||
static int checked = 0;
|
||||
int i;
|
||||
|
||||
if (force || !checked) {
|
||||
for (i = 0; ap_loaded_modules[i]; ++i) {
|
||||
module *m = ap_loaded_modules[i];
|
||||
|
||||
if (!strcmp("event.c", m->name)) {
|
||||
mpm_type = H2_MPM_EVENT;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("motorz.c", m->name)) {
|
||||
mpm_type = H2_MPM_MOTORZ;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("mpm_netware.c", m->name)) {
|
||||
mpm_type = H2_MPM_NETWARE;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("prefork.c", m->name)) {
|
||||
mpm_type = H2_MPM_PREFORK;
|
||||
mpm_module = m;
|
||||
/* While http2 can work really well on prefork, it collides
|
||||
* today's use case for prefork: running single-thread app engines
|
||||
* like php. If we restrict h2_workers to 1 per process, php will
|
||||
* work fine, but browser will be limited to 1 active request at a
|
||||
* time. */
|
||||
mpm_supported = 0;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("simple_api.c", m->name)) {
|
||||
mpm_type = H2_MPM_SIMPLE;
|
||||
mpm_module = m;
|
||||
mpm_supported = 0;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("mpm_winnt.c", m->name)) {
|
||||
mpm_type = H2_MPM_WINNT;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("worker.c", m->name)) {
|
||||
mpm_type = H2_MPM_WORKER;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
checked = 1;
|
||||
}
|
||||
}
|
||||
|
||||
h2_mpm_type_t h2_conn_mpm_type(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_type;
|
||||
}
|
||||
|
||||
const char *h2_conn_mpm_name(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_module? mpm_module->name : "unknown";
|
||||
}
|
||||
|
||||
int h2_mpm_supported(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_supported;
|
||||
}
|
||||
|
||||
static module *h2_conn_mpm_module(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_module;
|
||||
}
|
||||
|
||||
apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s)
|
||||
{
|
||||
check_modules(1);
|
||||
return apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM,
|
||||
APR_PROTO_TCP, pool);
|
||||
}
|
||||
|
||||
/* APR callback invoked if allocation fails. */
|
||||
static int abort_on_oom(int retcode)
|
||||
{
|
||||
ap_abort_on_oom();
|
||||
return retcode; /* unreachable, hopefully. */
|
||||
}
|
||||
|
||||
conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent)
|
||||
{
|
||||
apr_allocator_t *allocator;
|
||||
apr_status_t status;
|
||||
apr_pool_t *pool;
|
||||
conn_rec *c2;
|
||||
void *cfg;
|
||||
module *mpm;
|
||||
|
||||
ap_assert(c1);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c1,
|
||||
"h2_c2: create for c1(%ld)", c1->id);
|
||||
|
||||
/* We create a pool with its own allocator to be used for
|
||||
* processing a request. This is the only way to have the processing
|
||||
* independent of its parent pool in the sense that it can work in
|
||||
* another thread.
|
||||
*/
|
||||
apr_allocator_create(&allocator);
|
||||
apr_allocator_max_free_set(allocator, ap_max_mem_free);
|
||||
status = apr_pool_create_ex(&pool, parent, NULL, allocator);
|
||||
if (status != APR_SUCCESS) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c1,
|
||||
APLOGNO(10004) "h2_c2: create pool");
|
||||
return NULL;
|
||||
}
|
||||
apr_allocator_owner_set(allocator, pool);
|
||||
apr_pool_abort_set(abort_on_oom, pool);
|
||||
apr_pool_tag(pool, "h2_c2_conn");
|
||||
|
||||
c2 = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
|
||||
memcpy(c2, c1, sizeof(conn_rec));
|
||||
|
||||
c2->master = c1;
|
||||
c2->pool = pool;
|
||||
c2->conn_config = ap_create_conn_config(pool);
|
||||
c2->notes = apr_table_make(pool, 5);
|
||||
c2->input_filters = NULL;
|
||||
c2->output_filters = NULL;
|
||||
c2->keepalives = 0;
|
||||
#if AP_MODULE_MAGIC_AT_LEAST(20180903, 1)
|
||||
c2->filter_conn_ctx = NULL;
|
||||
#endif
|
||||
c2->bucket_alloc = apr_bucket_alloc_create(pool);
|
||||
#if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1)
|
||||
c2->data_in_input_filters = 0;
|
||||
c2->data_in_output_filters = 0;
|
||||
#endif
|
||||
/* prevent mpm_event from making wrong assumptions about this connection,
|
||||
* like e.g. using its socket for an async read check. */
|
||||
c2->clogging_input_filters = 1;
|
||||
c2->log = NULL;
|
||||
c2->aborted = 0;
|
||||
/* We cannot install the master connection socket on the secondary, as
|
||||
* modules mess with timeouts/blocking of the socket, with
|
||||
* unwanted side effects to the master connection processing.
|
||||
* Fortunately, since we never use the secondary socket, we can just install
|
||||
* a single, process-wide dummy and everyone is happy.
|
||||
*/
|
||||
ap_set_module_config(c2->conn_config, &core_module, dummy_socket);
|
||||
/* TODO: these should be unique to this thread */
|
||||
c2->sbh = NULL; /*c1->sbh;*/
|
||||
/* TODO: not all mpm modules have learned about secondary connections yet.
|
||||
* copy their config from master to secondary.
|
||||
*/
|
||||
if ((mpm = h2_conn_mpm_module()) != NULL) {
|
||||
cfg = ap_get_module_config(c1->conn_config, mpm);
|
||||
ap_set_module_config(c2->conn_config, mpm, cfg);
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
|
||||
"h2_c2(%s): created", c2->log_id);
|
||||
return c2;
|
||||
}
|
||||
|
||||
void h2_c2_destroy(conn_rec *c2)
|
||||
{
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
|
||||
"h2_c2(%s): destroy", c2->log_id);
|
||||
apr_pool_destroy(c2->pool);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
apr_bucket_brigade *bb; /* c2: data in holding area */
|
||||
} h2_c2_fctx_in_t;
|
||||
|
||||
static apr_status_t h2_c2_filter_in(ap_filter_t* f,
|
||||
apr_bucket_brigade* bb,
|
||||
ap_input_mode_t mode,
|
||||
apr_read_type_e block,
|
||||
apr_off_t readbytes)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx;
|
||||
h2_c2_fctx_in_t *fctx = f->ctx;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_bucket *b, *next;
|
||||
apr_off_t bblen;
|
||||
const int trace1 = APLOGctrace1(f->c);
|
||||
apr_size_t rmax = ((readbytes <= APR_SIZE_MAX)?
|
||||
(apr_size_t)readbytes : APR_SIZE_MAX);
|
||||
|
||||
conn_ctx = h2_conn_ctx_get(f->c);
|
||||
ap_assert(conn_ctx);
|
||||
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_c2_in(%s-%d): read, mode=%d, block=%d, readbytes=%ld",
|
||||
conn_ctx->id, conn_ctx->stream_id, mode, block, (long)readbytes);
|
||||
}
|
||||
|
||||
if (mode == AP_MODE_INIT) {
|
||||
return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
|
||||
}
|
||||
|
||||
if (f->c->aborted) {
|
||||
return APR_ECONNABORTED;
|
||||
}
|
||||
|
||||
if (!conn_ctx->beam_in) {
|
||||
return APR_EOF;
|
||||
}
|
||||
|
||||
if (!fctx) {
|
||||
fctx = apr_pcalloc(f->c->pool, sizeof(*fctx));
|
||||
f->ctx = fctx;
|
||||
fctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
|
||||
}
|
||||
|
||||
/* Cleanup brigades from those nasty 0 length non-meta buckets
|
||||
* that apr_brigade_split_line() sometimes produces. */
|
||||
for (b = APR_BRIGADE_FIRST(fctx->bb);
|
||||
b != APR_BRIGADE_SENTINEL(fctx->bb); b = next) {
|
||||
next = APR_BUCKET_NEXT(b);
|
||||
if (b->length == 0 && !APR_BUCKET_IS_METADATA(b)) {
|
||||
apr_bucket_delete(b);
|
||||
}
|
||||
}
|
||||
|
||||
while (APR_BRIGADE_EMPTY(fctx->bb)) {
|
||||
/* Get more input data for our request. */
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_c2_in(%s-%d): get more data from mplx, block=%d, "
|
||||
"readbytes=%ld",
|
||||
conn_ctx->id, conn_ctx->stream_id, block, (long)readbytes);
|
||||
}
|
||||
if (conn_ctx->beam_in) {
|
||||
if (conn_ctx->pipe_in_prod[H2_PIPE_OUT]) {
|
||||
receive:
|
||||
status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, APR_NONBLOCK_READ,
|
||||
conn_ctx->mplx->stream_max_mem);
|
||||
if (APR_STATUS_IS_EAGAIN(status) && APR_BLOCK_READ == block) {
|
||||
status = h2_util_wait_on_pipe(conn_ctx->pipe_in_prod[H2_PIPE_OUT]);
|
||||
if (APR_SUCCESS == status) {
|
||||
goto receive;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, block,
|
||||
conn_ctx->mplx->stream_max_mem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
status = APR_EOF;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
|
||||
"h2_c2_in(%s-%d): read returned",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
}
|
||||
if (APR_STATUS_IS_EAGAIN(status)
|
||||
&& (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
|
||||
/* chunked input handling does not seem to like it if we
|
||||
* return with APR_EAGAIN from a GETLINE read...
|
||||
* upload 100k test on test-ser.example.org hangs */
|
||||
status = APR_SUCCESS;
|
||||
}
|
||||
else if (APR_STATUS_IS_EOF(status)) {
|
||||
break;
|
||||
}
|
||||
else if (status != APR_SUCCESS) {
|
||||
conn_ctx->last_err = status;
|
||||
return status;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE2,
|
||||
"c2 input recv raw", fctx->bb);
|
||||
}
|
||||
if (h2_c2_logio_add_bytes_in) {
|
||||
apr_brigade_length(bb, 0, &bblen);
|
||||
h2_c2_logio_add_bytes_in(f->c, bblen);
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing there, no more data to get. Return. */
|
||||
if (status == APR_EOF && APR_BRIGADE_EMPTY(fctx->bb)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE2,
|
||||
"c2 input.bb", fctx->bb);
|
||||
}
|
||||
|
||||
if (APR_BRIGADE_EMPTY(fctx->bb)) {
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_c2_in(%s-%d): no data",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
}
|
||||
return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
|
||||
}
|
||||
|
||||
if (mode == AP_MODE_EXHAUSTIVE) {
|
||||
/* return all we have */
|
||||
APR_BRIGADE_CONCAT(bb, fctx->bb);
|
||||
}
|
||||
else if (mode == AP_MODE_READBYTES) {
|
||||
status = h2_brigade_concat_length(bb, fctx->bb, rmax);
|
||||
}
|
||||
else if (mode == AP_MODE_SPECULATIVE) {
|
||||
status = h2_brigade_copy_length(bb, fctx->bb, rmax);
|
||||
}
|
||||
else if (mode == AP_MODE_GETLINE) {
|
||||
/* we are reading a single LF line, e.g. the HTTP headers.
|
||||
* this has the nasty side effect to split the bucket, even
|
||||
* though it ends with CRLF and creates a 0 length bucket */
|
||||
status = apr_brigade_split_line(bb, fctx->bb, block,
|
||||
HUGE_STRING_LEN);
|
||||
if (APLOGctrace1(f->c)) {
|
||||
char buffer[1024];
|
||||
apr_size_t len = sizeof(buffer)-1;
|
||||
apr_brigade_flatten(bb, buffer, &len);
|
||||
buffer[len] = 0;
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_c2_in(%s-%d): getline: %s",
|
||||
conn_ctx->id, conn_ctx->stream_id, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
|
||||
* to support it. Seems to work. */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
|
||||
APLOGNO(03472)
|
||||
"h2_c2_in(%s-%d), unsupported READ mode %d",
|
||||
conn_ctx->id, conn_ctx->stream_id, mode);
|
||||
status = APR_ENOTIMPL;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
apr_brigade_length(bb, 0, &bblen);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_c2_in(%s-%d): %ld data bytes",
|
||||
conn_ctx->id, conn_ctx->stream_id, (long)bblen);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static apr_status_t beam_out(conn_rec *c2, h2_conn_ctx_t *conn_ctx, apr_bucket_brigade* bb)
|
||||
{
|
||||
apr_off_t written, left;
|
||||
apr_status_t rv;
|
||||
|
||||
apr_brigade_length(bb, 0, &written);
|
||||
rv = h2_beam_send(conn_ctx->beam_out, c2, bb, APR_BLOCK_READ);
|
||||
|
||||
if (APR_STATUS_IS_EAGAIN(rv)) {
|
||||
apr_brigade_length(bb, 0, &left);
|
||||
written -= left;
|
||||
rv = APR_SUCCESS;
|
||||
}
|
||||
if (written && h2_c2_logio_add_bytes_out) {
|
||||
h2_c2_logio_add_bytes_out(c2, written);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static apr_status_t h2_c2_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
|
||||
apr_status_t rv;
|
||||
|
||||
ap_assert(conn_ctx);
|
||||
rv = beam_out(f->c, conn_ctx, bb);
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
|
||||
"h2_c2(%s-%d): output leave",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
if (APR_SUCCESS != rv) {
|
||||
if (!conn_ctx->done) {
|
||||
h2_beam_abort(conn_ctx->beam_out, f->c);
|
||||
}
|
||||
f->c->aborted = 1;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* post config init */
|
||||
apr_status_t h2_c2_init(apr_pool_t *pool, server_rec *s)
|
||||
{
|
||||
h2_c2_logio_add_bytes_in = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_in);
|
||||
h2_c2_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static apr_status_t c2_run_pre_connection(conn_rec *c2, apr_socket_t *csd)
|
||||
{
|
||||
if (c2->keepalives == 0) {
|
||||
/* Simulate that we had already a request on this connection. Some
|
||||
* hooks trigger special behaviour when keepalives is 0.
|
||||
* (Not necessarily in pre_connection, but later. Set it here, so it
|
||||
* is in place.) */
|
||||
c2->keepalives = 1;
|
||||
/* We signal that this connection will be closed after the request.
|
||||
* Which is true in that sense that we throw away all traffic data
|
||||
* on this c2 connection after each requests. Although we might
|
||||
* reuse internal structures like memory pools.
|
||||
* The wanted effect of this is that httpd does not try to clean up
|
||||
* any dangling data on this connection when a request is done. Which
|
||||
* is unnecessary on a h2 stream.
|
||||
*/
|
||||
c2->keepalive = AP_CONN_CLOSE;
|
||||
return ap_run_pre_connection(c2, csd);
|
||||
}
|
||||
ap_assert(c2->output_filters);
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
apr_status_t h2_c2_process(conn_rec *c2, apr_thread_t *thread, int worker_id)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
|
||||
|
||||
ap_assert(conn_ctx);
|
||||
ap_assert(conn_ctx->mplx);
|
||||
|
||||
/* See the discussion at <https://github.com/icing/mod_h2/issues/195>
|
||||
*
|
||||
* Each conn_rec->id is supposed to be unique at a point in time. Since
|
||||
* some modules (and maybe external code) uses this id as an identifier
|
||||
* for the request_rec they handle, it needs to be unique for secondary
|
||||
* connections also.
|
||||
*
|
||||
* The MPM module assigns the connection ids and mod_unique_id is using
|
||||
* that one to generate identifier for requests. While the implementation
|
||||
* works for HTTP/1.x, the parallel execution of several requests per
|
||||
* connection will generate duplicate identifiers on load.
|
||||
*
|
||||
* The original implementation for secondary connection identifiers used
|
||||
* to shift the master connection id up and assign the stream id to the
|
||||
* lower bits. This was cramped on 32 bit systems, but on 64bit there was
|
||||
* enough space.
|
||||
*
|
||||
* As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
|
||||
* connection id, even on 64bit systems. Therefore collisions in request ids.
|
||||
*
|
||||
* The way master connection ids are generated, there is some space "at the
|
||||
* top" of the lower 32 bits on allmost all systems. If you have a setup
|
||||
* with 64k threads per child and 255 child processes, you live on the edge.
|
||||
*
|
||||
* The new implementation shifts 8 bits and XORs in the worker
|
||||
* id. This will experience collisions with > 256 h2 workers and heavy
|
||||
* load still. There seems to be no way to solve this in all possible
|
||||
* configurations by mod_h2 alone.
|
||||
*/
|
||||
c2->id = (c2->master->id << 8)^worker_id;
|
||||
|
||||
if (!conn_ctx->pre_conn_done) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
|
||||
"h2_c2(%s-%d), adding filters",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
ap_add_input_filter("H2_C2_NET_IN", NULL, NULL, c2);
|
||||
ap_add_output_filter("H2_C2_NET_CATCH_H1", NULL, NULL, c2);
|
||||
ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
|
||||
|
||||
c2_run_pre_connection(c2, ap_get_conn_socket(c2));
|
||||
conn_ctx->pre_conn_done = 1;
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
|
||||
"h2_c2(%s-%d): process connection",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
|
||||
c2->current_thread = thread;
|
||||
ap_run_process_connection(c2);
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
|
||||
"h2_c2(%s-%d): processing done",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c)
|
||||
{
|
||||
const h2_request *req = conn_ctx->request;
|
||||
conn_state_t *cs = c->cs;
|
||||
request_rec *r;
|
||||
|
||||
r = h2_create_request_rec(conn_ctx->request, c);
|
||||
if (!r) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_c2(%s-%d): create request_rec failed, r=NULL",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
goto cleanup;
|
||||
}
|
||||
if (r->status != HTTP_OK) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_c2(%s-%d): create request_rec failed, r->status=%d",
|
||||
conn_ctx->id, conn_ctx->stream_id, r->status);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_c2(%s-%d): created request_rec",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
conn_ctx->server = r->server;
|
||||
|
||||
/* the request_rec->server carries the timeout value that applies */
|
||||
h2_conn_ctx_set_timeout(conn_ctx, r->server->timeout);
|
||||
|
||||
if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_mplx(%s-%d): copy_files in output",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
h2_beam_set_copy_files(conn_ctx->beam_out, 1);
|
||||
}
|
||||
|
||||
ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
|
||||
if (cs) {
|
||||
cs->state = CONN_STATE_HANDLER;
|
||||
}
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_c2(%s-%d): start process_request",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
|
||||
/* Add the raw bytes of the request (e.g. header frame lengths to
|
||||
* the logio for this request. */
|
||||
if (req->raw_bytes && h2_c2_logio_add_bytes_in) {
|
||||
h2_c2_logio_add_bytes_in(c, req->raw_bytes);
|
||||
}
|
||||
|
||||
ap_process_request(r);
|
||||
/* After the call to ap_process_request, the
|
||||
* request pool may have been deleted. */
|
||||
r = NULL;
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_c2(%s-%d): process_request done",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
if (cs)
|
||||
cs->state = CONN_STATE_WRITE_COMPLETION;
|
||||
|
||||
cleanup:
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static int h2_c2_hook_process(conn_rec* c)
|
||||
{
|
||||
h2_conn_ctx_t *ctx;
|
||||
|
||||
if (!c->master) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
ctx = h2_conn_ctx_get(c);
|
||||
if (ctx->stream_id) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_h2, processing request directly");
|
||||
c2_process(ctx, c);
|
||||
return DONE;
|
||||
}
|
||||
else {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"secondary_conn(%ld): no h2 stream assing?", c->id);
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static void check_push(request_rec *r, const char *tag)
|
||||
{
|
||||
apr_array_header_t *push_list = h2_config_push_list(r);
|
||||
|
||||
if (!r->expecting_100 && push_list && push_list->nelts > 0) {
|
||||
int i, old_status;
|
||||
const char *old_line;
|
||||
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
|
||||
"%s, early announcing %d resources for push",
|
||||
tag, push_list->nelts);
|
||||
for (i = 0; i < push_list->nelts; ++i) {
|
||||
h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
|
||||
apr_table_add(r->headers_out, "Link",
|
||||
apr_psprintf(r->pool, "<%s>; rel=preload%s",
|
||||
push->uri_ref, push->critical? "; critical" : ""));
|
||||
}
|
||||
old_status = r->status;
|
||||
old_line = r->status_line;
|
||||
r->status = 103;
|
||||
r->status_line = "103 Early Hints";
|
||||
ap_send_interim_response(r, 1);
|
||||
r->status = old_status;
|
||||
r->status_line = old_line;
|
||||
}
|
||||
}
|
||||
|
||||
static int h2_c2_hook_post_read_request(request_rec *r)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
|
||||
|
||||
if (conn_ctx && conn_ctx->stream_id) {
|
||||
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
|
||||
"h2_c2(%s-%d): adding request filters",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
|
||||
/* setup the correct filters to process the request for h2 */
|
||||
ap_add_input_filter("H2_C2_REQUEST_IN", NULL, r, r->connection);
|
||||
|
||||
/* replace the core http filter that formats response headers
|
||||
* in HTTP/1 with our own that collects status and headers */
|
||||
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
|
||||
|
||||
ap_add_output_filter("H2_C2_RESPONSE_OUT", NULL, r, r->connection);
|
||||
ap_add_output_filter("H2_C2_TRAILERS_OUT", NULL, r, r->connection);
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static int h2_c2_hook_fixups(request_rec *r)
|
||||
{
|
||||
/* secondary connection? */
|
||||
if (r->connection->master) {
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
|
||||
if (conn_ctx) {
|
||||
check_push(r, "late_fixup");
|
||||
}
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c2_logio_add_bytes_in;
|
||||
APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c2_logio_add_bytes_out;
|
||||
|
||||
void h2_c2_register_hooks(void)
|
||||
{
|
||||
/* When the connection processing actually starts, we might
|
||||
* take over, if the connection is for a h2 stream.
|
||||
*/
|
||||
ap_hook_process_connection(h2_c2_hook_process,
|
||||
NULL, NULL, APR_HOOK_FIRST);
|
||||
/* We need to manipulate the standard HTTP/1.1 protocol filters and
|
||||
* install our own. This needs to be done very early. */
|
||||
ap_hook_post_read_request(h2_c2_hook_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
|
||||
ap_hook_fixups(h2_c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
|
||||
|
||||
ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
|
||||
NULL, AP_FTYPE_NETWORK);
|
||||
ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out,
|
||||
NULL, AP_FTYPE_NETWORK);
|
||||
ap_register_output_filter("H2_C2_NET_CATCH_H1", h2_c2_filter_catch_h1_out,
|
||||
NULL, AP_FTYPE_NETWORK);
|
||||
|
||||
ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in,
|
||||
NULL, AP_FTYPE_PROTOCOL);
|
||||
ap_register_output_filter("H2_C2_RESPONSE_OUT", h2_c2_filter_response_out,
|
||||
NULL, AP_FTYPE_PROTOCOL);
|
||||
ap_register_output_filter("H2_C2_TRAILERS_OUT", h2_c2_filter_trailers_out,
|
||||
NULL, AP_FTYPE_PROTOCOL);
|
||||
}
|
||||
|
||||
61
modules/http2/h2_c2.h
Normal file
61
modules/http2/h2_c2.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_c2__
|
||||
#define __mod_h2__h2_c2__
|
||||
|
||||
#include <http_core.h>
|
||||
|
||||
typedef enum {
|
||||
H2_MPM_UNKNOWN,
|
||||
H2_MPM_WORKER,
|
||||
H2_MPM_EVENT,
|
||||
H2_MPM_PREFORK,
|
||||
H2_MPM_MOTORZ,
|
||||
H2_MPM_SIMPLE,
|
||||
H2_MPM_NETWARE,
|
||||
H2_MPM_WINNT,
|
||||
} h2_mpm_type_t;
|
||||
|
||||
/* Returns the type of MPM module detected */
|
||||
h2_mpm_type_t h2_conn_mpm_type(void);
|
||||
const char *h2_conn_mpm_name(void);
|
||||
int h2_mpm_supported(void);
|
||||
|
||||
/* Initialize this child process for h2 secondary connection work,
|
||||
* to be called once during child init before multi processing
|
||||
* starts.
|
||||
*/
|
||||
apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s);
|
||||
|
||||
conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent);
|
||||
void h2_c2_destroy(conn_rec *c2);
|
||||
|
||||
/**
|
||||
* Process a secondary connection for a HTTP/2 stream request.
|
||||
*/
|
||||
apr_status_t h2_c2_process(conn_rec *c, apr_thread_t *thread, int worker_id);
|
||||
|
||||
void h2_c2_register_hooks(void);
|
||||
/*
|
||||
* One time, post config initialization.
|
||||
*/
|
||||
apr_status_t h2_c2_init(apr_pool_t *pool, server_rec *s);
|
||||
|
||||
extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c2_logio_add_bytes_in;
|
||||
extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c2_logio_add_bytes_out;
|
||||
|
||||
#endif /* defined(__mod_h2__h2_c2__) */
|
||||
@@ -30,12 +30,28 @@
|
||||
#include <util_time.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
#include "h2_headers.h"
|
||||
#include "h2_from_h1.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_c2_filter.h"
|
||||
#include "h2_c2.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_request.h"
|
||||
#include "h2_util.h"
|
||||
|
||||
|
||||
#define H2_FILTER_LOG(name, c, level, rv, msg, bb) \
|
||||
do { \
|
||||
if (APLOG_C_IS_LEVEL((c),(level))) { \
|
||||
char buffer[4 * 1024]; \
|
||||
apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
|
||||
len = h2_util_bb_print(buffer, bmax, "", "", (bb)); \
|
||||
ap_log_cerror(APLOG_MARK, (level), rv, (c), \
|
||||
"FILTER[%s]: %s %s", \
|
||||
(name), (msg), len? buffer : ""); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
/* This routine is called by apr_table_do and merges all instances of
|
||||
* the passed field values into a single array that will be further
|
||||
* processed by some later routine. Originally intended to help split
|
||||
@@ -114,7 +130,7 @@ static void fix_vary(request_rec *r)
|
||||
}
|
||||
}
|
||||
|
||||
static h2_headers *create_response(h2_task *task, request_rec *r)
|
||||
static h2_headers *create_response(request_rec *r)
|
||||
{
|
||||
const char *clheader;
|
||||
const char *ctype;
|
||||
@@ -229,13 +245,14 @@ static h2_headers *create_response(h2_task *task, request_rec *r)
|
||||
* keep the set-by-proxy server and date headers, otherwise
|
||||
* generate a new server header / date header
|
||||
*/
|
||||
if (r->proxyreq != PROXYREQ_RESPONSE
|
||||
|| !apr_table_get(r->headers_out, "Date")) {
|
||||
if (r->proxyreq != PROXYREQ_NONE
|
||||
&& !apr_table_get(r->headers_out, "Date")) {
|
||||
char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
|
||||
ap_recent_rfc822_date(date, r->request_time);
|
||||
apr_table_setn(r->headers_out, "Date", date );
|
||||
}
|
||||
if (r->proxyreq != PROXYREQ_RESPONSE) {
|
||||
if (r->proxyreq != PROXYREQ_NONE
|
||||
&& !apr_table_get(r->headers_out, "Server")) {
|
||||
const char *us = ap_get_server_banner();
|
||||
if (us) {
|
||||
apr_table_setn(r->headers_out, "Server", us);
|
||||
@@ -251,14 +268,17 @@ typedef enum {
|
||||
H2_RP_DONE
|
||||
} h2_rp_state_t;
|
||||
|
||||
typedef struct h2_response_parser {
|
||||
typedef struct h2_response_parser h2_response_parser;
|
||||
struct h2_response_parser {
|
||||
const char *id;
|
||||
h2_rp_state_t state;
|
||||
h2_task *task;
|
||||
conn_rec *c;
|
||||
apr_pool_t *pool;
|
||||
int http_status;
|
||||
apr_array_header_t *hlines;
|
||||
apr_bucket_brigade *tmp;
|
||||
apr_bucket_brigade *saveto;
|
||||
} h2_response_parser;
|
||||
};
|
||||
|
||||
static apr_status_t parse_header(h2_response_parser *parser, char *line) {
|
||||
const char *hline;
|
||||
@@ -274,11 +294,11 @@ static apr_status_t parse_header(h2_response_parser *parser, char *line) {
|
||||
/* not well formed */
|
||||
return APR_EINVAL;
|
||||
}
|
||||
hline = apr_psprintf(parser->task->pool, "%s %s", *plast, line);
|
||||
hline = apr_psprintf(parser->pool, "%s %s", *plast, line);
|
||||
}
|
||||
else {
|
||||
/* new header line */
|
||||
hline = apr_pstrdup(parser->task->pool, line);
|
||||
hline = apr_pstrdup(parser->pool, line);
|
||||
}
|
||||
APR_ARRAY_PUSH(parser->hlines, const char*) = hline;
|
||||
return APR_SUCCESS;
|
||||
@@ -287,11 +307,10 @@ static apr_status_t parse_header(h2_response_parser *parser, char *line) {
|
||||
static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
|
||||
char *line, apr_size_t len)
|
||||
{
|
||||
h2_task *task = parser->task;
|
||||
apr_status_t status;
|
||||
|
||||
if (!parser->tmp) {
|
||||
parser->tmp = apr_brigade_create(task->pool, task->c->bucket_alloc);
|
||||
parser->tmp = apr_brigade_create(parser->pool, parser->c->bucket_alloc);
|
||||
}
|
||||
status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ,
|
||||
len);
|
||||
@@ -309,9 +328,9 @@ static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
|
||||
len -= 2;
|
||||
line[len] = '\0';
|
||||
apr_brigade_cleanup(parser->tmp);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
|
||||
"h2_task(%s): read response line: %s",
|
||||
task->id, line);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
|
||||
"h2_c2(%s): read response line: %s",
|
||||
parser->id, line);
|
||||
}
|
||||
else {
|
||||
apr_off_t brigade_length;
|
||||
@@ -330,18 +349,18 @@ static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
|
||||
*/
|
||||
status = apr_brigade_length(parser->tmp, 0, &brigade_length);
|
||||
if ((status != APR_SUCCESS) || (brigade_length > len)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, task->c, APLOGNO(10257)
|
||||
"h2_task(%s): read response, line too long",
|
||||
task->id);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, parser->c, APLOGNO(10257)
|
||||
"h2_c2(%s): read response, line too long",
|
||||
parser->id);
|
||||
return APR_ENOSPC;
|
||||
}
|
||||
/* this does not look like a complete line yet */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
|
||||
"h2_task(%s): read response, incomplete line: %s",
|
||||
task->id, line);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
|
||||
"h2_c2(%s): read response, incomplete line: %s",
|
||||
parser->id, line);
|
||||
if (!parser->saveto) {
|
||||
parser->saveto = apr_brigade_create(task->pool,
|
||||
task->c->bucket_alloc);
|
||||
parser->saveto = apr_brigade_create(parser->pool,
|
||||
parser->c->bucket_alloc);
|
||||
}
|
||||
/*
|
||||
* Be on the save side and save the parser->tmp brigade
|
||||
@@ -365,19 +384,18 @@ static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
|
||||
|
||||
static apr_table_t *make_table(h2_response_parser *parser)
|
||||
{
|
||||
h2_task *task = parser->task;
|
||||
apr_array_header_t *hlines = parser->hlines;
|
||||
if (hlines) {
|
||||
apr_table_t *headers = apr_table_make(task->pool, hlines->nelts);
|
||||
apr_table_t *headers = apr_table_make(parser->pool, hlines->nelts);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < hlines->nelts; ++i) {
|
||||
char *hline = ((char **)hlines->elts)[i];
|
||||
char *sep = ap_strchr(hline, ':');
|
||||
if (!sep) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, task->c,
|
||||
APLOGNO(02955) "h2_task(%s): invalid header[%d] '%s'",
|
||||
task->id, i, (char*)hline);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, parser->c,
|
||||
APLOGNO(02955) "h2_c2(%s): invalid header[%d] '%s'",
|
||||
parser->id, i, (char*)hline);
|
||||
/* not valid format, abort */
|
||||
return NULL;
|
||||
}
|
||||
@@ -393,23 +411,23 @@ static apr_table_t *make_table(h2_response_parser *parser)
|
||||
return headers;
|
||||
}
|
||||
else {
|
||||
return apr_table_make(task->pool, 0);
|
||||
return apr_table_make(parser->pool, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static apr_status_t pass_response(h2_task *task, ap_filter_t *f,
|
||||
h2_response_parser *parser)
|
||||
static apr_status_t pass_response(h2_conn_ctx_t *conn_ctx, ap_filter_t *f,
|
||||
h2_response_parser *parser)
|
||||
{
|
||||
apr_bucket *b;
|
||||
apr_status_t status;
|
||||
|
||||
h2_headers *response = h2_headers_create(parser->http_status,
|
||||
make_table(parser),
|
||||
NULL, 0, task->pool);
|
||||
NULL, 0, parser->pool);
|
||||
apr_brigade_cleanup(parser->tmp);
|
||||
b = h2_bucket_headers_create(task->c->bucket_alloc, response);
|
||||
b = h2_bucket_headers_create(parser->c->bucket_alloc, response);
|
||||
APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
|
||||
b = apr_bucket_flush_create(task->c->bucket_alloc);
|
||||
b = apr_bucket_flush_create(parser->c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
|
||||
status = ap_pass_brigade(f->next, parser->tmp);
|
||||
apr_brigade_cleanup(parser->tmp);
|
||||
@@ -419,18 +437,17 @@ static apr_status_t pass_response(h2_task *task, ap_filter_t *f,
|
||||
apr_array_clear(parser->hlines);
|
||||
|
||||
if (response->status >= 200) {
|
||||
task->output.sent_response = 1;
|
||||
conn_ctx->has_final_response = 1;
|
||||
}
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
|
||||
APLOGNO(03197) "h2_task(%s): passed response %d",
|
||||
task->id, response->status);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
|
||||
APLOGNO(03197) "h2_c2(%s): passed response %d",
|
||||
parser->id, response->status);
|
||||
return status;
|
||||
}
|
||||
|
||||
static apr_status_t parse_status(h2_task *task, char *line)
|
||||
static apr_status_t parse_status(h2_response_parser *parser, char *line)
|
||||
{
|
||||
h2_response_parser *parser = task->output.rparser;
|
||||
int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 :
|
||||
int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 :
|
||||
(apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
|
||||
if (sindex > 0) {
|
||||
int k = sindex + 3;
|
||||
@@ -449,27 +466,19 @@ static apr_status_t parse_status(h2_task *task, char *line)
|
||||
* to write something. Probably just the interim response we are
|
||||
* waiting for. But if there is other data hanging around before
|
||||
* that, this needs to fail. */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03467)
|
||||
"h2_task(%s): unable to parse status line: %s",
|
||||
task->id, line);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c, APLOGNO(03467)
|
||||
"h2_c2(%s): unable to parse status line: %s",
|
||||
parser->id, line);
|
||||
return APR_EINVAL;
|
||||
}
|
||||
|
||||
apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f,
|
||||
apr_bucket_brigade *bb)
|
||||
static apr_status_t parse_response(h2_response_parser *parser,
|
||||
h2_conn_ctx_t *conn_ctx,
|
||||
ap_filter_t* f, apr_bucket_brigade *bb)
|
||||
{
|
||||
h2_response_parser *parser = task->output.rparser;
|
||||
char line[HUGE_STRING_LEN];
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
|
||||
if (!parser) {
|
||||
parser = apr_pcalloc(task->pool, sizeof(*parser));
|
||||
parser->task = task;
|
||||
parser->state = H2_RP_STATUS_LINE;
|
||||
parser->hlines = apr_array_make(task->pool, 10, sizeof(char *));
|
||||
task->output.rparser = parser;
|
||||
}
|
||||
|
||||
while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
|
||||
switch (parser->state) {
|
||||
case H2_RP_STATUS_LINE:
|
||||
@@ -484,17 +493,17 @@ apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f,
|
||||
}
|
||||
if (parser->state == H2_RP_STATUS_LINE) {
|
||||
/* instead of parsing, just take it directly */
|
||||
status = parse_status(task, line);
|
||||
status = parse_status(parser, line);
|
||||
}
|
||||
else if (line[0] == '\0') {
|
||||
/* end of headers, pass response onward */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_task(%s): end of response", task->id);
|
||||
return pass_response(task, f, parser);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
|
||||
"h2_c2(%s): end of response", parser->id);
|
||||
return pass_response(conn_ctx, f, parser);
|
||||
}
|
||||
else {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_task(%s): response header %s", task->id, line);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
|
||||
"h2_c2(%s): response header %s", parser->id, line);
|
||||
status = parse_header(parser, line);
|
||||
}
|
||||
break;
|
||||
@@ -506,18 +515,72 @@ apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f,
|
||||
return status;
|
||||
}
|
||||
|
||||
apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb)
|
||||
{
|
||||
h2_task *task = f->ctx;
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
|
||||
h2_response_parser *parser = f->ctx;
|
||||
apr_status_t rv;
|
||||
|
||||
ap_assert(conn_ctx);
|
||||
H2_FILTER_LOG("c2_catch_h1_out", f->c, APLOG_TRACE2, 0, "check", bb);
|
||||
|
||||
if (!conn_ctx->has_final_response) {
|
||||
if (!parser) {
|
||||
parser = apr_pcalloc(f->c->pool, sizeof(*parser));
|
||||
parser->id = apr_psprintf(f->c->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
|
||||
parser->pool = f->c->pool;
|
||||
parser->c = f->c;
|
||||
parser->state = H2_RP_STATUS_LINE;
|
||||
parser->hlines = apr_array_make(parser->pool, 10, sizeof(char *));
|
||||
f->ctx = parser;
|
||||
}
|
||||
|
||||
if (!APR_BRIGADE_EMPTY(bb)) {
|
||||
apr_bucket *b = APR_BRIGADE_FIRST(bb);
|
||||
if (AP_BUCKET_IS_EOR(b)) {
|
||||
/* TODO: Yikes, this happens when errors are encountered on input
|
||||
* before anything from the repsonse has been processed. The
|
||||
* ap_die_r() call will do nothing in certain conditions.
|
||||
*/
|
||||
int result = ap_map_http_request_error(conn_ctx->last_err,
|
||||
HTTP_INTERNAL_SERVER_ERROR);
|
||||
request_rec *r = h2_create_request_rec(conn_ctx->request, f->c);
|
||||
ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
|
||||
b = ap_bucket_eor_create(f->c->bucket_alloc, r);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
}
|
||||
}
|
||||
/* There are cases where we need to parse a serialized http/1.1 response.
|
||||
* One example is a 100-continue answer via a mod_proxy setup. */
|
||||
while (bb && !f->c->aborted && !conn_ctx->has_final_response) {
|
||||
rv = parse_response(parser, conn_ctx, f, bb);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
|
||||
"h2_c2(%s): parsed response", parser->id);
|
||||
if (APR_BRIGADE_EMPTY(bb) || APR_SUCCESS != rv) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ap_pass_brigade(f->next, bb);
|
||||
}
|
||||
|
||||
apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
|
||||
request_rec *r = f->r;
|
||||
apr_bucket *b, *bresp, *body_bucket = NULL, *next;
|
||||
ap_bucket_error *eb = NULL;
|
||||
h2_headers *response = NULL;
|
||||
int headers_passing = 0;
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_task(%s): output_filter called", task->id);
|
||||
|
||||
if (!task->output.sent_response && !f->c->aborted) {
|
||||
|
||||
H2_FILTER_LOG("c2_response_out", f->c, APLOG_TRACE1, 0, "called with", bb);
|
||||
|
||||
if (f->c->aborted || !conn_ctx || conn_ctx->has_final_response) {
|
||||
return ap_pass_brigade(f->next, bb);
|
||||
}
|
||||
|
||||
if (!conn_ctx->has_final_response) {
|
||||
/* check, if we need to send the response now. Until we actually
|
||||
* see a DATA bucket or some EOS/EOR, we do not do so. */
|
||||
for (b = APR_BRIGADE_FIRST(bb);
|
||||
@@ -533,39 +596,40 @@ apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
*/
|
||||
ap_remove_output_filter(f);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
|
||||
"h2_task(%s): eoc bucket passed", task->id);
|
||||
"h2_c2(%s): eoc bucket passed", conn_ctx->id);
|
||||
return ap_pass_brigade(f->next, bb);
|
||||
}
|
||||
else if (H2_BUCKET_IS_HEADERS(b)) {
|
||||
headers_passing = 1;
|
||||
}
|
||||
else if (!APR_BUCKET_IS_FLUSH(b)) {
|
||||
else if (!APR_BUCKET_IS_FLUSH(b)) {
|
||||
body_bucket = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (eb) {
|
||||
int st = eb->status;
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
|
||||
"h2_task(%s): err bucket status=%d", task->id, st);
|
||||
"h2_c2(%s): err bucket status=%d",
|
||||
conn_ctx->id, st);
|
||||
/* throw everything away and replace it with the error response
|
||||
* generated by ap_die() */
|
||||
apr_brigade_cleanup(bb);
|
||||
ap_die(st, r);
|
||||
return AP_FILTER_ERROR;
|
||||
}
|
||||
|
||||
|
||||
if (body_bucket || !headers_passing) {
|
||||
/* time to insert the response bucket before the body or if
|
||||
* no h2_headers is passed, e.g. the response is empty */
|
||||
response = create_response(task, r);
|
||||
response = create_response(r);
|
||||
if (response == NULL) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
|
||||
"h2_task(%s): unable to create response", task->id);
|
||||
"h2_c2(%s): unable to create response", conn_ctx->id);
|
||||
return APR_ENOMEM;
|
||||
}
|
||||
|
||||
|
||||
bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
|
||||
if (body_bucket) {
|
||||
APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
|
||||
@@ -573,15 +637,15 @@ apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
else {
|
||||
APR_BRIGADE_INSERT_HEAD(bb, bresp);
|
||||
}
|
||||
task->output.sent_response = 1;
|
||||
conn_ctx->has_final_response = 1;
|
||||
r->sent_bodyct = 1;
|
||||
ap_remove_output_filter_byhandle(f->r->output_filters, "H2_C2_NET_CATCH_H1");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_task(%s): headers only, cleanup output brigade",
|
||||
task->id);
|
||||
"h2_c2(%s): headers only, cleanup output brigade", conn_ctx->id);
|
||||
b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
|
||||
while (b != APR_BRIGADE_SENTINEL(bb)) {
|
||||
next = APR_BUCKET_NEXT(b);
|
||||
@@ -595,14 +659,24 @@ apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
b = next;
|
||||
}
|
||||
}
|
||||
else if (task->output.sent_response) {
|
||||
if (conn_ctx->has_final_response) {
|
||||
/* lets get out of the way, our task is done */
|
||||
ap_remove_output_filter(f);
|
||||
}
|
||||
return ap_pass_brigade(f->next, bb);
|
||||
}
|
||||
|
||||
static void make_chunk(h2_task *task, apr_bucket_brigade *bb,
|
||||
|
||||
struct h2_chunk_filter_t {
|
||||
const char *id;
|
||||
int eos_chunk_added;
|
||||
apr_bucket_brigade *bbchunk;
|
||||
apr_off_t chunked_total;
|
||||
};
|
||||
typedef struct h2_chunk_filter_t h2_chunk_filter_t;
|
||||
|
||||
|
||||
static void make_chunk(conn_rec *c, h2_chunk_filter_t *fctx, apr_bucket_brigade *bb,
|
||||
apr_bucket *first, apr_off_t chunk_len,
|
||||
apr_bucket *tail)
|
||||
{
|
||||
@@ -610,24 +684,24 @@ static void make_chunk(h2_task *task, apr_bucket_brigade *bb,
|
||||
* HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends
|
||||
* to the end of the brigade. */
|
||||
char buffer[128];
|
||||
apr_bucket *c;
|
||||
apr_bucket *b;
|
||||
apr_size_t len;
|
||||
|
||||
len = (apr_size_t)apr_snprintf(buffer, H2_ALEN(buffer),
|
||||
"%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
|
||||
c = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
|
||||
APR_BUCKET_INSERT_BEFORE(first, c);
|
||||
c = apr_bucket_heap_create("\r\n", 2, NULL, bb->bucket_alloc);
|
||||
b = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
|
||||
APR_BUCKET_INSERT_BEFORE(first, b);
|
||||
b = apr_bucket_immortal_create("\r\n", 2, bb->bucket_alloc);
|
||||
if (tail) {
|
||||
APR_BUCKET_INSERT_BEFORE(tail, c);
|
||||
APR_BUCKET_INSERT_BEFORE(tail, b);
|
||||
}
|
||||
else {
|
||||
APR_BRIGADE_INSERT_TAIL(bb, c);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
}
|
||||
task->input.chunked_total += chunk_len;
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
|
||||
"h2_task(%s): added chunk %ld, total %ld",
|
||||
task->id, (long)chunk_len, (long)task->input.chunked_total);
|
||||
fctx->chunked_total += chunk_len;
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
|
||||
"h2_c2(%s): added chunk %ld, total %ld",
|
||||
fctx->id, (long)chunk_len, (long)fctx->chunked_total);
|
||||
}
|
||||
|
||||
static int ser_header(void *ctx, const char *name, const char *value)
|
||||
@@ -637,47 +711,36 @@ static int ser_header(void *ctx, const char *name, const char *value)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static apr_status_t read_and_chunk(ap_filter_t *f, h2_task *task,
|
||||
static apr_status_t read_and_chunk(ap_filter_t *f, h2_conn_ctx_t *conn_ctx,
|
||||
apr_read_type_e block) {
|
||||
h2_chunk_filter_t *fctx = f->ctx;
|
||||
request_rec *r = f->r;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_bucket_brigade *bb = task->input.bbchunk;
|
||||
|
||||
if (!bb) {
|
||||
bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
|
||||
task->input.bbchunk = bb;
|
||||
|
||||
if (!fctx->bbchunk) {
|
||||
fctx->bbchunk = apr_brigade_create(r->pool, f->c->bucket_alloc);
|
||||
}
|
||||
|
||||
if (APR_BRIGADE_EMPTY(bb)) {
|
||||
if (APR_BRIGADE_EMPTY(fctx->bbchunk)) {
|
||||
apr_bucket *b, *next, *first_data = NULL;
|
||||
apr_bucket_brigade *tmp;
|
||||
apr_off_t bblen = 0;
|
||||
|
||||
/* get more data from the lower layer filters. Always do this
|
||||
* in larger pieces, since we handle the read modes ourself. */
|
||||
status = ap_get_brigade(f->next, bb,
|
||||
AP_MODE_READBYTES, block, 32*1024);
|
||||
if (status == APR_EOF) {
|
||||
if (!task->input.eos) {
|
||||
status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
|
||||
task->input.eos = 1;
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
ap_remove_input_filter(f);
|
||||
return status;
|
||||
|
||||
}
|
||||
else if (status != APR_SUCCESS) {
|
||||
status = ap_get_brigade(f->next, fctx->bbchunk,
|
||||
AP_MODE_READBYTES, block, conn_ctx->mplx->stream_max_mem);
|
||||
if (status != APR_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
for (b = APR_BRIGADE_FIRST(bb);
|
||||
b != APR_BRIGADE_SENTINEL(bb) && !task->input.eos;
|
||||
for (b = APR_BRIGADE_FIRST(fctx->bbchunk);
|
||||
b != APR_BRIGADE_SENTINEL(fctx->bbchunk);
|
||||
b = next) {
|
||||
next = APR_BUCKET_NEXT(b);
|
||||
if (APR_BUCKET_IS_METADATA(b)) {
|
||||
if (first_data) {
|
||||
make_chunk(task, bb, first_data, bblen, b);
|
||||
make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, b);
|
||||
first_data = NULL;
|
||||
}
|
||||
|
||||
@@ -686,29 +749,35 @@ static apr_status_t read_and_chunk(ap_filter_t *f, h2_task *task,
|
||||
|
||||
ap_assert(headers);
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
||||
"h2_task(%s): receiving trailers", task->id);
|
||||
tmp = apr_brigade_split_ex(bb, b, NULL);
|
||||
"h2_c2(%s-%d): receiving trailers",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
|
||||
if (!apr_is_empty_table(headers->headers)) {
|
||||
status = apr_brigade_puts(bb, NULL, NULL, "0\r\n");
|
||||
apr_table_do(ser_header, bb, headers->headers, NULL);
|
||||
status = apr_brigade_puts(bb, NULL, NULL, "\r\n");
|
||||
status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n");
|
||||
apr_table_do(ser_header, fctx->bbchunk, headers->headers, NULL);
|
||||
status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "\r\n");
|
||||
}
|
||||
else {
|
||||
status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
|
||||
status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
|
||||
}
|
||||
r->trailers_in = apr_table_clone(r->pool, headers->headers);
|
||||
APR_BUCKET_REMOVE(b);
|
||||
apr_bucket_destroy(b);
|
||||
APR_BRIGADE_CONCAT(bb, tmp);
|
||||
APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
|
||||
apr_brigade_destroy(tmp);
|
||||
task->input.eos = 1;
|
||||
fctx->eos_chunk_added = 1;
|
||||
}
|
||||
else if (APR_BUCKET_IS_EOS(b)) {
|
||||
tmp = apr_brigade_split_ex(bb, b, NULL);
|
||||
status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
|
||||
APR_BRIGADE_CONCAT(bb, tmp);
|
||||
apr_brigade_destroy(tmp);
|
||||
task->input.eos = 1;
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
||||
"h2_c2(%s-%d): receiving eos",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
if (!fctx->eos_chunk_added) {
|
||||
tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
|
||||
status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
|
||||
APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
|
||||
apr_brigade_destroy(tmp);
|
||||
}
|
||||
fctx->eos_chunk_added = 0;
|
||||
}
|
||||
}
|
||||
else if (b->length == 0) {
|
||||
@@ -725,29 +794,38 @@ static apr_status_t read_and_chunk(ap_filter_t *f, h2_task *task,
|
||||
}
|
||||
|
||||
if (first_data) {
|
||||
make_chunk(task, bb, first_data, bblen, NULL);
|
||||
make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, NULL);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
apr_status_t h2_filter_request_in(ap_filter_t* f,
|
||||
apr_status_t h2_c2_filter_request_in(ap_filter_t* f,
|
||||
apr_bucket_brigade* bb,
|
||||
ap_input_mode_t mode,
|
||||
apr_read_type_e block,
|
||||
apr_off_t readbytes)
|
||||
{
|
||||
h2_task *task = f->ctx;
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
|
||||
h2_chunk_filter_t *fctx = f->ctx;
|
||||
request_rec *r = f->r;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_bucket *b, *next;
|
||||
core_server_config *conf =
|
||||
(core_server_config *) ap_get_module_config(r->server->module_config,
|
||||
&core_module);
|
||||
ap_assert(conn_ctx);
|
||||
|
||||
if (!fctx) {
|
||||
fctx = apr_pcalloc(r->pool, sizeof(*fctx));
|
||||
fctx->id = apr_psprintf(r->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
|
||||
f->ctx = fctx;
|
||||
}
|
||||
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
|
||||
"h2_task(%s): request filter, exp=%d", task->id, r->expecting_100);
|
||||
if (!task->request->chunked) {
|
||||
"h2_c2(%s-%d): request input, exp=%d",
|
||||
conn_ctx->id, conn_ctx->stream_id, r->expecting_100);
|
||||
if (!conn_ctx->request->chunked) {
|
||||
status = ap_get_brigade(f->next, bb, mode, block, readbytes);
|
||||
/* pipe data through, just take care of trailers */
|
||||
for (b = APR_BRIGADE_FIRST(bb);
|
||||
@@ -757,7 +835,8 @@ apr_status_t h2_filter_request_in(ap_filter_t* f,
|
||||
h2_headers *headers = h2_bucket_headers_get(b);
|
||||
ap_assert(headers);
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
||||
"h2_task(%s): receiving trailers", task->id);
|
||||
"h2_c2(%s-%d): receiving trailers",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
r->trailers_in = headers->headers;
|
||||
if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) {
|
||||
r->headers_in = apr_table_overlay(r->pool, r->headers_in,
|
||||
@@ -767,8 +846,8 @@ apr_status_t h2_filter_request_in(ap_filter_t* f,
|
||||
apr_bucket_destroy(b);
|
||||
ap_remove_input_filter(f);
|
||||
|
||||
if (headers->raw_bytes && h2_task_logio_add_bytes_in) {
|
||||
h2_task_logio_add_bytes_in(task->c, headers->raw_bytes);
|
||||
if (headers->raw_bytes && h2_c2_logio_add_bytes_in) {
|
||||
h2_c2_logio_add_bytes_in(f->c, headers->raw_bytes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -781,34 +860,33 @@ apr_status_t h2_filter_request_in(ap_filter_t* f,
|
||||
* transfer encoding and trailers.
|
||||
* We need to simulate chunked encoding for it to be happy.
|
||||
*/
|
||||
if ((status = read_and_chunk(f, task, block)) != APR_SUCCESS) {
|
||||
if ((status = read_and_chunk(f, conn_ctx, block)) != APR_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (mode == AP_MODE_EXHAUSTIVE) {
|
||||
/* return all we have */
|
||||
APR_BRIGADE_CONCAT(bb, task->input.bbchunk);
|
||||
APR_BRIGADE_CONCAT(bb, fctx->bbchunk);
|
||||
}
|
||||
else if (mode == AP_MODE_READBYTES) {
|
||||
status = h2_brigade_concat_length(bb, task->input.bbchunk, readbytes);
|
||||
status = h2_brigade_concat_length(bb, fctx->bbchunk, readbytes);
|
||||
}
|
||||
else if (mode == AP_MODE_SPECULATIVE) {
|
||||
status = h2_brigade_copy_length(bb, task->input.bbchunk, readbytes);
|
||||
status = h2_brigade_copy_length(bb, fctx->bbchunk, readbytes);
|
||||
}
|
||||
else if (mode == AP_MODE_GETLINE) {
|
||||
/* we are reading a single LF line, e.g. the HTTP headers.
|
||||
* this has the nasty side effect to split the bucket, even
|
||||
* though it ends with CRLF and creates a 0 length bucket */
|
||||
status = apr_brigade_split_line(bb, task->input.bbchunk, block,
|
||||
HUGE_STRING_LEN);
|
||||
status = apr_brigade_split_line(bb, fctx->bbchunk, block, HUGE_STRING_LEN);
|
||||
if (APLOGctrace1(f->c)) {
|
||||
char buffer[1024];
|
||||
apr_size_t len = sizeof(buffer)-1;
|
||||
apr_brigade_flatten(bb, buffer, &len);
|
||||
buffer[len] = 0;
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_task(%s): getline: %s",
|
||||
task->id, buffer);
|
||||
"h2_c2(%s-%d): getline: %s",
|
||||
conn_ctx->id, conn_ctx->stream_id, buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -816,21 +894,21 @@ apr_status_t h2_filter_request_in(ap_filter_t* f,
|
||||
* to support it. Seems to work. */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
|
||||
APLOGNO(02942)
|
||||
"h2_task, unsupported READ mode %d", mode);
|
||||
"h2_c2, unsupported READ mode %d", mode);
|
||||
status = APR_ENOTIMPL;
|
||||
}
|
||||
|
||||
h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, "forwarding input", bb);
|
||||
h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE2, "returning input", bb);
|
||||
return status;
|
||||
}
|
||||
|
||||
apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
{
|
||||
h2_task *task = f->ctx;
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
|
||||
request_rec *r = f->r;
|
||||
apr_bucket *b, *e;
|
||||
|
||||
if (task && r) {
|
||||
if (conn_ctx && r) {
|
||||
/* Detect the EOS/EOR bucket and forward any trailers that may have
|
||||
* been set to our h2_headers.
|
||||
*/
|
||||
@@ -844,7 +922,8 @@ apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
|
||||
apr_table_t *trailers;
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
|
||||
"h2_task(%s): sending trailers", task->id);
|
||||
"h2_c2(%s-%d): sending trailers",
|
||||
conn_ctx->id, conn_ctx->stream_id);
|
||||
trailers = apr_table_clone(r->pool, r->trailers_out);
|
||||
headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
|
||||
e = h2_bucket_headers_create(bb->bucket_alloc, headers);
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_from_h1__
|
||||
#define __mod_h2__h2_from_h1__
|
||||
#ifndef __mod_h2__h2_c2_filter__
|
||||
#define __mod_h2__h2_c2_filter__
|
||||
|
||||
/**
|
||||
* h2_from_h1 parses a HTTP/1.1 response into
|
||||
@@ -32,19 +32,18 @@
|
||||
* processing, so this seems to be the way for now.
|
||||
*/
|
||||
struct h2_headers;
|
||||
struct h2_task;
|
||||
struct h2_response_parser;
|
||||
|
||||
apr_status_t h2_from_h1_parse_response(struct h2_task *task, ap_filter_t *f,
|
||||
apr_bucket_brigade *bb);
|
||||
apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb);
|
||||
|
||||
apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb);
|
||||
apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb);
|
||||
|
||||
apr_status_t h2_filter_request_in(ap_filter_t* f,
|
||||
apr_status_t h2_c2_filter_request_in(ap_filter_t* f,
|
||||
apr_bucket_brigade* brigade,
|
||||
ap_input_mode_t mode,
|
||||
apr_read_type_e block,
|
||||
apr_off_t readbytes);
|
||||
|
||||
apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
|
||||
apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
|
||||
|
||||
#endif /* defined(__mod_h2__h2_from_h1__) */
|
||||
#endif /* defined(__mod_h2__h2_c2_filter__) */
|
||||
@@ -30,11 +30,10 @@
|
||||
#include <apr_strings.h>
|
||||
|
||||
#include "h2.h"
|
||||
#include "h2_alt_svc.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_conn.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
#include "h2_c1.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_private.h"
|
||||
|
||||
#define DEF_VAL (-1)
|
||||
@@ -54,41 +53,37 @@
|
||||
/* Apache httpd module configuration for h2. */
|
||||
typedef struct h2_config {
|
||||
const char *name;
|
||||
int h2_max_streams; /* max concurrent # streams (http2) */
|
||||
int h2_window_size; /* stream window size (http2) */
|
||||
int min_workers; /* min # of worker threads/child */
|
||||
int max_workers; /* max # of worker threads/child */
|
||||
int max_worker_idle_secs; /* max # of idle seconds for worker */
|
||||
int stream_max_mem_size; /* max # bytes held in memory/stream */
|
||||
apr_array_header_t *alt_svcs; /* h2_alt_svc specs for this server */
|
||||
int alt_svc_max_age; /* seconds clients can rely on alt-svc info*/
|
||||
int serialize_headers; /* Use serialized HTTP/1.1 headers for
|
||||
processing, better compatibility */
|
||||
int h2_direct; /* if mod_h2 is active directly */
|
||||
int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */
|
||||
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 */
|
||||
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 */
|
||||
int h2_max_streams; /* max concurrent # streams (http2) */
|
||||
int h2_window_size; /* stream window size (http2) */
|
||||
int min_workers; /* min # of worker threads/child */
|
||||
int max_workers; /* max # of worker threads/child */
|
||||
int max_worker_idle_secs; /* max # of idle seconds for worker */
|
||||
int stream_max_mem_size; /* max # bytes held in memory/stream */
|
||||
int h2_direct; /* if mod_h2 is active directly */
|
||||
int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */
|
||||
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 */
|
||||
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 */
|
||||
|
||||
int push_diary_size; /* # of entries in push diary */
|
||||
int copy_files; /* if files shall be copied vs setaside on output */
|
||||
apr_array_header_t *push_list;/* list of h2_push_res configurations */
|
||||
int early_hints; /* support status code 103 */
|
||||
int push_diary_size; /* # of entries in push diary */
|
||||
int copy_files; /* if files shall be copied vs setaside on output */
|
||||
apr_array_header_t *push_list; /* list of h2_push_res configurations */
|
||||
int early_hints; /* support status code 103 */
|
||||
int padding_bits;
|
||||
int padding_always;
|
||||
int output_buffered;
|
||||
apr_interval_time_t stream_timeout;/* beam timeout */
|
||||
} h2_config;
|
||||
|
||||
typedef struct h2_dir_config {
|
||||
const char *name;
|
||||
apr_array_header_t *alt_svcs; /* h2_alt_svc specs for this server */
|
||||
int alt_svc_max_age; /* seconds clients can rely on alt-svc info*/
|
||||
int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
|
||||
int h2_push; /* if HTTP/2 server push is enabled */
|
||||
apr_array_header_t *push_list;/* list of h2_push_res configurations */
|
||||
int early_hints; /* support status code 103 */
|
||||
int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
|
||||
int h2_push; /* if HTTP/2 server push is enabled */
|
||||
apr_array_header_t *push_list; /* list of h2_push_res configurations */
|
||||
int early_hints; /* support status code 103 */
|
||||
apr_interval_time_t stream_timeout;/* beam timeout */
|
||||
} h2_dir_config;
|
||||
|
||||
|
||||
@@ -100,9 +95,6 @@ static h2_config defconf = {
|
||||
-1, /* max workers */
|
||||
10 * 60, /* max workers idle secs */
|
||||
32 * 1024, /* stream max mem size */
|
||||
NULL, /* no alt-svcs */
|
||||
-1, /* alt-svc max age */
|
||||
0, /* serialize headers */
|
||||
-1, /* h2 direct mode */
|
||||
1, /* modern TLS only */
|
||||
-1, /* HTTP/1 Upgrade support */
|
||||
@@ -116,17 +108,17 @@ static h2_config defconf = {
|
||||
0, /* early hints, http status 103 */
|
||||
0, /* padding bits */
|
||||
1, /* padding always */
|
||||
1, /* strean output buffered */
|
||||
1, /* stream output buffered */
|
||||
-1, /* beam timeout */
|
||||
};
|
||||
|
||||
static h2_dir_config defdconf = {
|
||||
"default",
|
||||
NULL, /* no alt-svcs */
|
||||
-1, /* alt-svc max age */
|
||||
-1, /* HTTP/1 Upgrade support */
|
||||
-1, /* HTTP/2 server push enabled */
|
||||
NULL, /* push list */
|
||||
-1, /* early hints, http status 103 */
|
||||
-1, /* beam timeout */
|
||||
};
|
||||
|
||||
void h2_config_init(apr_pool_t *pool)
|
||||
@@ -146,8 +138,6 @@ void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
|
||||
conf->max_workers = DEF_VAL;
|
||||
conf->max_worker_idle_secs = DEF_VAL;
|
||||
conf->stream_max_mem_size = DEF_VAL;
|
||||
conf->alt_svc_max_age = DEF_VAL;
|
||||
conf->serialize_headers = DEF_VAL;
|
||||
conf->h2_direct = DEF_VAL;
|
||||
conf->modern_tls_only = DEF_VAL;
|
||||
conf->h2_upgrade = DEF_VAL;
|
||||
@@ -162,6 +152,7 @@ void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
|
||||
conf->padding_bits = DEF_VAL;
|
||||
conf->padding_always = DEF_VAL;
|
||||
conf->output_buffered = DEF_VAL;
|
||||
conf->stream_timeout = DEF_VAL;
|
||||
return conf;
|
||||
}
|
||||
|
||||
@@ -179,9 +170,6 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
|
||||
n->max_workers = H2_CONFIG_GET(add, base, max_workers);
|
||||
n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs);
|
||||
n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
|
||||
n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
|
||||
n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
|
||||
n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
|
||||
n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
|
||||
n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only);
|
||||
n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
|
||||
@@ -206,6 +194,7 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
|
||||
n->early_hints = H2_CONFIG_GET(add, base, early_hints);
|
||||
n->padding_bits = H2_CONFIG_GET(add, base, padding_bits);
|
||||
n->padding_always = H2_CONFIG_GET(add, base, padding_always);
|
||||
n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -221,10 +210,10 @@ void *h2_config_create_dir(apr_pool_t *pool, char *x)
|
||||
char *name = apr_pstrcat(pool, "dir[", s, "]", NULL);
|
||||
|
||||
conf->name = name;
|
||||
conf->alt_svc_max_age = DEF_VAL;
|
||||
conf->h2_upgrade = DEF_VAL;
|
||||
conf->h2_push = DEF_VAL;
|
||||
conf->early_hints = DEF_VAL;
|
||||
conf->stream_timeout = DEF_VAL;
|
||||
return conf;
|
||||
}
|
||||
|
||||
@@ -235,8 +224,6 @@ void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
|
||||
h2_dir_config *n = (h2_dir_config *)apr_pcalloc(pool, sizeof(h2_dir_config));
|
||||
|
||||
n->name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL);
|
||||
n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
|
||||
n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
|
||||
n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
|
||||
n->h2_push = H2_CONFIG_GET(add, base, h2_push);
|
||||
if (add->push_list && base->push_list) {
|
||||
@@ -246,6 +233,7 @@ void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
|
||||
n->push_list = add->push_list? add->push_list : base->push_list;
|
||||
}
|
||||
n->early_hints = H2_CONFIG_GET(add, base, early_hints);
|
||||
n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -264,10 +252,6 @@ static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t v
|
||||
return H2_CONFIG_GET(conf, &defconf, max_worker_idle_secs);
|
||||
case H2_CONF_STREAM_MAX_MEM:
|
||||
return H2_CONFIG_GET(conf, &defconf, stream_max_mem_size);
|
||||
case H2_CONF_ALT_SVC_MAX_AGE:
|
||||
return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age);
|
||||
case H2_CONF_SER_HEADERS:
|
||||
return H2_CONFIG_GET(conf, &defconf, serialize_headers);
|
||||
case H2_CONF_MODERN_TLS_ONLY:
|
||||
return H2_CONFIG_GET(conf, &defconf, modern_tls_only);
|
||||
case H2_CONF_UPGRADE:
|
||||
@@ -292,6 +276,8 @@ static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t v
|
||||
return H2_CONFIG_GET(conf, &defconf, padding_always);
|
||||
case H2_CONF_OUTPUT_BUFFER:
|
||||
return H2_CONFIG_GET(conf, &defconf, output_buffered);
|
||||
case H2_CONF_STREAM_TIMEOUT:
|
||||
return H2_CONFIG_GET(conf, &defconf, stream_timeout);
|
||||
default:
|
||||
return DEF_VAL;
|
||||
}
|
||||
@@ -318,12 +304,6 @@ static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
|
||||
case H2_CONF_STREAM_MAX_MEM:
|
||||
H2_CONFIG_SET(conf, stream_max_mem_size, val);
|
||||
break;
|
||||
case H2_CONF_ALT_SVC_MAX_AGE:
|
||||
H2_CONFIG_SET(conf, alt_svc_max_age, val);
|
||||
break;
|
||||
case H2_CONF_SER_HEADERS:
|
||||
H2_CONFIG_SET(conf, serialize_headers, val);
|
||||
break;
|
||||
case H2_CONF_MODERN_TLS_ONLY:
|
||||
H2_CONFIG_SET(conf, modern_tls_only, val);
|
||||
break;
|
||||
@@ -371,6 +351,9 @@ static void h2_srv_config_seti64(h2_config *conf, h2_config_var_t var, apr_int64
|
||||
case H2_CONF_TLS_WARMUP_SIZE:
|
||||
H2_CONFIG_SET(conf, tls_warmup_size, val);
|
||||
break;
|
||||
case H2_CONF_STREAM_TIMEOUT:
|
||||
H2_CONFIG_SET(conf, stream_timeout, val);
|
||||
break;
|
||||
default:
|
||||
h2_srv_config_seti(conf, var, (int)val);
|
||||
break;
|
||||
@@ -396,14 +379,14 @@ static const h2_dir_config *h2_config_rget(request_rec *r)
|
||||
static apr_int64_t h2_dir_config_geti64(const h2_dir_config *conf, h2_config_var_t var)
|
||||
{
|
||||
switch(var) {
|
||||
case H2_CONF_ALT_SVC_MAX_AGE:
|
||||
return H2_CONFIG_GET(conf, &defdconf, alt_svc_max_age);
|
||||
case H2_CONF_UPGRADE:
|
||||
return H2_CONFIG_GET(conf, &defdconf, h2_upgrade);
|
||||
case H2_CONF_PUSH:
|
||||
return H2_CONFIG_GET(conf, &defdconf, h2_push);
|
||||
case H2_CONF_EARLY_HINTS:
|
||||
return H2_CONFIG_GET(conf, &defdconf, early_hints);
|
||||
case H2_CONF_STREAM_TIMEOUT:
|
||||
return H2_CONFIG_GET(conf, &defdconf, stream_timeout);
|
||||
|
||||
default:
|
||||
return DEF_VAL;
|
||||
@@ -415,9 +398,6 @@ static void h2_config_seti(h2_dir_config *dconf, h2_config *conf, h2_config_var_
|
||||
int set_srv = !dconf;
|
||||
if (dconf) {
|
||||
switch(var) {
|
||||
case H2_CONF_ALT_SVC_MAX_AGE:
|
||||
H2_CONFIG_SET(dconf, alt_svc_max_age, val);
|
||||
break;
|
||||
case H2_CONF_UPGRADE:
|
||||
H2_CONFIG_SET(dconf, h2_upgrade, val);
|
||||
break;
|
||||
@@ -444,6 +424,9 @@ static void h2_config_seti64(h2_dir_config *dconf, h2_config *conf, h2_config_va
|
||||
int set_srv = !dconf;
|
||||
if (dconf) {
|
||||
switch(var) {
|
||||
case H2_CONF_STREAM_TIMEOUT:
|
||||
H2_CONFIG_SET(dconf, stream_timeout, val);
|
||||
break;
|
||||
default:
|
||||
/* not handled in dir_conf */
|
||||
set_srv = 1;
|
||||
@@ -458,18 +441,11 @@ static void h2_config_seti64(h2_dir_config *dconf, h2_config *conf, h2_config_va
|
||||
|
||||
static const h2_config *h2_config_get(conn_rec *c)
|
||||
{
|
||||
h2_ctx *ctx = h2_ctx_get(c, 0);
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
|
||||
|
||||
if (ctx) {
|
||||
if (ctx->config) {
|
||||
return ctx->config;
|
||||
}
|
||||
else if (ctx->server) {
|
||||
ctx->config = h2_config_sget(ctx->server);
|
||||
return ctx->config;
|
||||
}
|
||||
if (conn_ctx && conn_ctx->server) {
|
||||
return h2_config_sget(conn_ctx->server);
|
||||
}
|
||||
|
||||
return h2_config_sget(c->base_server);
|
||||
}
|
||||
|
||||
@@ -526,18 +502,6 @@ apr_array_header_t *h2_config_push_list(request_rec *r)
|
||||
return sconf? sconf->push_list : NULL;
|
||||
}
|
||||
|
||||
apr_array_header_t *h2_config_alt_svcs(request_rec *r)
|
||||
{
|
||||
const h2_config *sconf;
|
||||
const h2_dir_config *conf = h2_config_rget(r);
|
||||
|
||||
if (conf && conf->alt_svcs) {
|
||||
return conf->alt_svcs;
|
||||
}
|
||||
sconf = h2_config_sget(r->server);
|
||||
return sconf? sconf->alt_svcs : NULL;
|
||||
}
|
||||
|
||||
const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type)
|
||||
{
|
||||
const h2_config *conf = h2_config_get(c);
|
||||
@@ -615,41 +579,6 @@ static const char *h2_conf_set_stream_max_mem_size(cmd_parms *cmd,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *h2_add_alt_svc(cmd_parms *cmd,
|
||||
void *dirconf, const char *value)
|
||||
{
|
||||
if (value && *value) {
|
||||
h2_alt_svc *as = h2_alt_svc_parse(value, cmd->pool);
|
||||
if (!as) {
|
||||
return "unable to parse alt-svc specifier";
|
||||
}
|
||||
|
||||
if (cmd->path) {
|
||||
h2_dir_config *dcfg = (h2_dir_config *)dirconf;
|
||||
if (!dcfg->alt_svcs) {
|
||||
dcfg->alt_svcs = apr_array_make(cmd->pool, 5, sizeof(h2_alt_svc*));
|
||||
}
|
||||
APR_ARRAY_PUSH(dcfg->alt_svcs, h2_alt_svc*) = as;
|
||||
}
|
||||
else {
|
||||
h2_config *cfg = (h2_config *)h2_config_sget(cmd->server);
|
||||
if (!cfg->alt_svcs) {
|
||||
cfg->alt_svcs = apr_array_make(cmd->pool, 5, sizeof(h2_alt_svc*));
|
||||
}
|
||||
APR_ARRAY_PUSH(cfg->alt_svcs, h2_alt_svc*) = as;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *h2_conf_set_alt_svc_max_age(cmd_parms *cmd,
|
||||
void *dirconf, const char *value)
|
||||
{
|
||||
int val = (int)apr_atoi64(value);
|
||||
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_ALT_SVC_MAX_AGE, val);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *h2_conf_set_session_extra_files(cmd_parms *cmd,
|
||||
void *dirconf, const char *value)
|
||||
{
|
||||
@@ -661,18 +590,15 @@ static const char *h2_conf_set_session_extra_files(cmd_parms *cmd,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *h2_conf_set_serialize_headers(cmd_parms *cmd,
|
||||
static const char *h2_conf_set_serialize_headers(cmd_parms *parms,
|
||||
void *dirconf, const char *value)
|
||||
{
|
||||
if (!strcasecmp(value, "On")) {
|
||||
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_SER_HEADERS, 1);
|
||||
return NULL;
|
||||
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, parms->server, APLOGNO(10307)
|
||||
"%s: this feature has been disabled and the directive "
|
||||
"to enable it is ignored.", parms->cmd->name);
|
||||
}
|
||||
else if (!strcasecmp(value, "Off")) {
|
||||
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_SER_HEADERS, 0);
|
||||
return NULL;
|
||||
}
|
||||
return "value must be On or Off";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *h2_conf_set_direct(cmd_parms *cmd,
|
||||
@@ -928,6 +854,20 @@ static const char *h2_conf_set_output_buffer(cmd_parms *cmd,
|
||||
return "value must be On or Off";
|
||||
}
|
||||
|
||||
static const char *h2_conf_set_stream_timeout(cmd_parms *cmd,
|
||||
void *dirconf, const char *value)
|
||||
{
|
||||
apr_status_t rv;
|
||||
apr_interval_time_t timeout;
|
||||
|
||||
rv = ap_timeout_parameter_parse(value, &timeout, "s");
|
||||
if (rv != APR_SUCCESS) {
|
||||
return "Invalid timeout value";
|
||||
}
|
||||
CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_STREAM_TIMEOUT, timeout);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void h2_get_num_workers(server_rec *s, int *minw, int *maxw)
|
||||
{
|
||||
int threads_per_child = 0;
|
||||
@@ -966,12 +906,8 @@ const command_rec h2_cmds[] = {
|
||||
RSRC_CONF, "maximum number of idle seconds before a worker shuts down"),
|
||||
AP_INIT_TAKE1("H2StreamMaxMemSize", h2_conf_set_stream_max_mem_size, NULL,
|
||||
RSRC_CONF, "maximum number of bytes buffered in memory for a stream"),
|
||||
AP_INIT_TAKE1("H2AltSvc", h2_add_alt_svc, NULL,
|
||||
RSRC_CONF, "adds an Alt-Svc for this server"),
|
||||
AP_INIT_TAKE1("H2AltSvcMaxAge", h2_conf_set_alt_svc_max_age, NULL,
|
||||
RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"),
|
||||
AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
|
||||
RSRC_CONF, "on to enable header serialization for compatibility"),
|
||||
RSRC_CONF, "disabled, this directive has no longer an effect."),
|
||||
AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL,
|
||||
RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"),
|
||||
AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL,
|
||||
@@ -1000,6 +936,8 @@ const command_rec h2_cmds[] = {
|
||||
RSRC_CONF, "set payload padding"),
|
||||
AP_INIT_TAKE1("H2OutputBuffering", h2_conf_set_output_buffer, NULL,
|
||||
RSRC_CONF, "set stream output buffer on/off"),
|
||||
AP_INIT_TAKE1("H2StreamTimeout", h2_conf_set_stream_timeout, NULL,
|
||||
RSRC_CONF, "set stream timeout"),
|
||||
AP_END_CMD
|
||||
};
|
||||
|
||||
|
||||
@@ -30,9 +30,6 @@ typedef enum {
|
||||
H2_CONF_MAX_WORKERS,
|
||||
H2_CONF_MAX_WORKER_IDLE_SECS,
|
||||
H2_CONF_STREAM_MAX_MEM,
|
||||
H2_CONF_ALT_SVCS,
|
||||
H2_CONF_ALT_SVC_MAX_AGE,
|
||||
H2_CONF_SER_HEADERS,
|
||||
H2_CONF_DIRECT,
|
||||
H2_CONF_MODERN_TLS_ONLY,
|
||||
H2_CONF_UPGRADE,
|
||||
@@ -45,6 +42,7 @@ typedef enum {
|
||||
H2_CONF_PADDING_BITS,
|
||||
H2_CONF_PADDING_ALWAYS,
|
||||
H2_CONF_OUTPUT_BUFFER,
|
||||
H2_CONF_STREAM_TIMEOUT,
|
||||
} h2_config_var_t;
|
||||
|
||||
struct apr_hash_t;
|
||||
@@ -88,7 +86,6 @@ int h2_config_rgeti(request_rec *r, h2_config_var_t var);
|
||||
apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var);
|
||||
|
||||
apr_array_header_t *h2_config_push_list(request_rec *r);
|
||||
apr_array_header_t *h2_config_alt_svcs(request_rec *r);
|
||||
|
||||
|
||||
void h2_get_num_workers(server_rec *s, int *minw, int *maxw);
|
||||
|
||||
@@ -1,402 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <apr_strings.h>
|
||||
|
||||
#include <ap_mpm.h>
|
||||
#include <ap_mmn.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_config.h>
|
||||
#include <http_log.h>
|
||||
#include <http_connection.h>
|
||||
#include <http_protocol.h>
|
||||
#include <http_request.h>
|
||||
|
||||
#include <mpm_common.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_filter.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_workers.h"
|
||||
#include "h2_conn.h"
|
||||
#include "h2_version.h"
|
||||
|
||||
static struct h2_workers *workers;
|
||||
|
||||
static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
|
||||
static module *mpm_module;
|
||||
static int async_mpm;
|
||||
static int mpm_supported = 1;
|
||||
static apr_socket_t *dummy_socket;
|
||||
|
||||
static void check_modules(int force)
|
||||
{
|
||||
static int checked = 0;
|
||||
int i;
|
||||
|
||||
if (force || !checked) {
|
||||
for (i = 0; ap_loaded_modules[i]; ++i) {
|
||||
module *m = ap_loaded_modules[i];
|
||||
|
||||
if (!strcmp("event.c", m->name)) {
|
||||
mpm_type = H2_MPM_EVENT;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("motorz.c", m->name)) {
|
||||
mpm_type = H2_MPM_MOTORZ;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("mpm_netware.c", m->name)) {
|
||||
mpm_type = H2_MPM_NETWARE;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("prefork.c", m->name)) {
|
||||
mpm_type = H2_MPM_PREFORK;
|
||||
mpm_module = m;
|
||||
/* While http2 can work really well on prefork, it collides
|
||||
* today's use case for prefork: running single-thread app engines
|
||||
* like php. If we restrict h2_workers to 1 per process, php will
|
||||
* work fine, but browser will be limited to 1 active request at a
|
||||
* time. */
|
||||
mpm_supported = 0;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("simple_api.c", m->name)) {
|
||||
mpm_type = H2_MPM_SIMPLE;
|
||||
mpm_module = m;
|
||||
mpm_supported = 0;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("mpm_winnt.c", m->name)) {
|
||||
mpm_type = H2_MPM_WINNT;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
else if (!strcmp("worker.c", m->name)) {
|
||||
mpm_type = H2_MPM_WORKER;
|
||||
mpm_module = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
checked = 1;
|
||||
}
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s)
|
||||
{
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
int minw, maxw;
|
||||
int max_threads_per_child = 0;
|
||||
int idle_secs = 0;
|
||||
|
||||
check_modules(1);
|
||||
ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
|
||||
|
||||
status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
|
||||
if (status != APR_SUCCESS) {
|
||||
/* some MPMs do not implemnent this */
|
||||
async_mpm = 0;
|
||||
status = APR_SUCCESS;
|
||||
}
|
||||
|
||||
h2_config_init(pool);
|
||||
|
||||
h2_get_num_workers(s, &minw, &maxw);
|
||||
|
||||
idle_secs = h2_config_sgeti(s, H2_CONF_MAX_WORKER_IDLE_SECS);
|
||||
ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
|
||||
"h2_workers: min=%d max=%d, mthrpchild=%d, idle_secs=%d",
|
||||
minw, maxw, max_threads_per_child, idle_secs);
|
||||
workers = h2_workers_create(s, pool, minw, maxw, idle_secs);
|
||||
|
||||
ap_register_input_filter("H2_IN", h2_filter_core_input,
|
||||
NULL, AP_FTYPE_CONNECTION);
|
||||
|
||||
status = h2_mplx_m_child_init(pool, s);
|
||||
|
||||
if (status == APR_SUCCESS) {
|
||||
status = apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM,
|
||||
APR_PROTO_TCP, pool);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void h2_conn_child_stopping(apr_pool_t *pool, int graceful)
|
||||
{
|
||||
if (workers && graceful) {
|
||||
h2_workers_graceful_shutdown(workers);
|
||||
}
|
||||
}
|
||||
|
||||
h2_mpm_type_t h2_conn_mpm_type(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_type;
|
||||
}
|
||||
|
||||
const char *h2_conn_mpm_name(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_module? mpm_module->name : "unknown";
|
||||
}
|
||||
|
||||
int h2_mpm_supported(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_supported;
|
||||
}
|
||||
|
||||
static module *h2_conn_mpm_module(void)
|
||||
{
|
||||
check_modules(0);
|
||||
return mpm_module;
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_setup(conn_rec *c, request_rec *r, server_rec *s)
|
||||
{
|
||||
h2_session *session;
|
||||
h2_ctx *ctx;
|
||||
apr_status_t status;
|
||||
|
||||
if (!workers) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911)
|
||||
"workers not initialized");
|
||||
return APR_EGENERAL;
|
||||
}
|
||||
|
||||
if (APR_SUCCESS == (status = h2_session_create(&session, c, r, s, workers))) {
|
||||
ctx = h2_ctx_get(c, 1);
|
||||
h2_ctx_session_set(ctx, session);
|
||||
|
||||
/* remove the input filter of mod_reqtimeout, now that the connection
|
||||
* is established and we have swtiched to h2. reqtimeout has supervised
|
||||
* possibly configured handshake timeouts and needs to get out of the way
|
||||
* now since the rest of its state handling assumes http/1.x to take place. */
|
||||
ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_run(conn_rec *c)
|
||||
{
|
||||
apr_status_t status;
|
||||
int mpm_state = 0;
|
||||
h2_session *session = h2_ctx_get_session(c);
|
||||
|
||||
ap_assert(session);
|
||||
do {
|
||||
if (c->cs) {
|
||||
c->cs->sense = CONN_SENSE_DEFAULT;
|
||||
c->cs->state = CONN_STATE_HANDLER;
|
||||
}
|
||||
|
||||
status = h2_session_process(session, async_mpm);
|
||||
|
||||
if (APR_STATUS_IS_EOF(status)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
|
||||
H2_SSSN_LOG(APLOGNO(03045), session,
|
||||
"process, closing conn"));
|
||||
c->keepalive = AP_CONN_CLOSE;
|
||||
}
|
||||
else {
|
||||
c->keepalive = AP_CONN_KEEPALIVE;
|
||||
}
|
||||
|
||||
if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
|
||||
break;
|
||||
}
|
||||
} while (!async_mpm
|
||||
&& c->keepalive == AP_CONN_KEEPALIVE
|
||||
&& mpm_state != AP_MPMQ_STOPPING);
|
||||
|
||||
if (c->cs) {
|
||||
switch (session->state) {
|
||||
case H2_SESSION_ST_INIT:
|
||||
case H2_SESSION_ST_IDLE:
|
||||
case H2_SESSION_ST_BUSY:
|
||||
case H2_SESSION_ST_WAIT:
|
||||
c->cs->state = CONN_STATE_WRITE_COMPLETION;
|
||||
if (c->cs && (session->open_streams || !session->remote.emitted_count)) {
|
||||
/* let the MPM know that we are not done and want
|
||||
* the Timeout behaviour instead of a KeepAliveTimeout
|
||||
* See PR 63534.
|
||||
*/
|
||||
c->cs->sense = CONN_SENSE_WANT_READ;
|
||||
}
|
||||
break;
|
||||
case H2_SESSION_ST_CLEANUP:
|
||||
case H2_SESSION_ST_DONE:
|
||||
default:
|
||||
c->cs->state = CONN_STATE_LINGER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c)
|
||||
{
|
||||
h2_session *session = h2_ctx_get_session(c);
|
||||
|
||||
(void)c;
|
||||
if (session) {
|
||||
apr_status_t status = h2_session_pre_close(session, async_mpm);
|
||||
return (status == APR_SUCCESS)? DONE : status;
|
||||
}
|
||||
return DONE;
|
||||
}
|
||||
|
||||
/* APR callback invoked if allocation fails. */
|
||||
static int abort_on_oom(int retcode)
|
||||
{
|
||||
ap_abort_on_oom();
|
||||
return retcode; /* unreachable, hopefully. */
|
||||
}
|
||||
|
||||
conn_rec *h2_secondary_create(conn_rec *master, int sec_id, apr_pool_t *parent)
|
||||
{
|
||||
apr_allocator_t *allocator;
|
||||
apr_status_t status;
|
||||
apr_pool_t *pool;
|
||||
conn_rec *c;
|
||||
void *cfg;
|
||||
module *mpm;
|
||||
|
||||
ap_assert(master);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, master,
|
||||
"h2_stream(%ld-%d): create secondary", master->id, sec_id);
|
||||
|
||||
/* We create a pool with its own allocator to be used for
|
||||
* processing a request. This is the only way to have the processing
|
||||
* independent of its parent pool in the sense that it can work in
|
||||
* another thread. Also, the new allocator needs its own mutex to
|
||||
* synchronize sub-pools.
|
||||
*/
|
||||
apr_allocator_create(&allocator);
|
||||
apr_allocator_max_free_set(allocator, ap_max_mem_free);
|
||||
status = apr_pool_create_ex(&pool, parent, NULL, allocator);
|
||||
if (status != APR_SUCCESS) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, master,
|
||||
APLOGNO(10004) "h2_session(%ld-%d): create secondary pool",
|
||||
master->id, sec_id);
|
||||
return NULL;
|
||||
}
|
||||
apr_allocator_owner_set(allocator, pool);
|
||||
apr_pool_abort_set(abort_on_oom, pool);
|
||||
apr_pool_tag(pool, "h2_secondary_conn");
|
||||
|
||||
c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
|
||||
if (c == NULL) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, master,
|
||||
APLOGNO(02913) "h2_session(%ld-%d): create secondary",
|
||||
master->id, sec_id);
|
||||
apr_pool_destroy(pool);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(c, master, sizeof(conn_rec));
|
||||
|
||||
c->master = master;
|
||||
c->pool = pool;
|
||||
c->conn_config = ap_create_conn_config(pool);
|
||||
c->notes = apr_table_make(pool, 5);
|
||||
c->input_filters = NULL;
|
||||
c->output_filters = NULL;
|
||||
c->keepalives = 0;
|
||||
#if AP_MODULE_MAGIC_AT_LEAST(20180903, 1)
|
||||
c->filter_conn_ctx = NULL;
|
||||
#endif
|
||||
c->bucket_alloc = apr_bucket_alloc_create(pool);
|
||||
#if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1)
|
||||
c->data_in_input_filters = 0;
|
||||
c->data_in_output_filters = 0;
|
||||
#endif
|
||||
/* prevent mpm_event from making wrong assumptions about this connection,
|
||||
* like e.g. using its socket for an async read check. */
|
||||
c->clogging_input_filters = 1;
|
||||
c->log = NULL;
|
||||
c->log_id = apr_psprintf(pool, "%ld-%d",
|
||||
master->id, sec_id);
|
||||
c->aborted = 0;
|
||||
/* We cannot install the master connection socket on the secondary, as
|
||||
* modules mess with timeouts/blocking of the socket, with
|
||||
* unwanted side effects to the master connection processing.
|
||||
* Fortunately, since we never use the secondary socket, we can just install
|
||||
* a single, process-wide dummy and everyone is happy.
|
||||
*/
|
||||
ap_set_module_config(c->conn_config, &core_module, dummy_socket);
|
||||
/* TODO: these should be unique to this thread */
|
||||
c->sbh = master->sbh;
|
||||
/* TODO: not all mpm modules have learned about secondary connections yet.
|
||||
* copy their config from master to secondary.
|
||||
*/
|
||||
if ((mpm = h2_conn_mpm_module()) != NULL) {
|
||||
cfg = ap_get_module_config(master->conn_config, mpm);
|
||||
ap_set_module_config(c->conn_config, mpm, cfg);
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
|
||||
"h2_secondary(%s): created", c->log_id);
|
||||
return c;
|
||||
}
|
||||
|
||||
void h2_secondary_destroy(conn_rec *secondary)
|
||||
{
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, secondary,
|
||||
"h2_secondary(%s): destroy", secondary->log_id);
|
||||
secondary->sbh = NULL;
|
||||
apr_pool_destroy(secondary->pool);
|
||||
}
|
||||
|
||||
apr_status_t h2_secondary_run_pre_connection(conn_rec *secondary, apr_socket_t *csd)
|
||||
{
|
||||
if (secondary->keepalives == 0) {
|
||||
/* Simulate that we had already a request on this connection. Some
|
||||
* hooks trigger special behaviour when keepalives is 0.
|
||||
* (Not necessarily in pre_connection, but later. Set it here, so it
|
||||
* is in place.) */
|
||||
secondary->keepalives = 1;
|
||||
/* We signal that this connection will be closed after the request.
|
||||
* Which is true in that sense that we throw away all traffic data
|
||||
* on this secondary connection after each requests. Although we might
|
||||
* reuse internal structures like memory pools.
|
||||
* The wanted effect of this is that httpd does not try to clean up
|
||||
* any dangling data on this connection when a request is done. Which
|
||||
* is unnecessary on a h2 stream.
|
||||
*/
|
||||
secondary->keepalive = AP_CONN_CLOSE;
|
||||
return ap_run_pre_connection(secondary, csd);
|
||||
}
|
||||
ap_assert(secondary->output_filters);
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
147
modules/http2/h2_conn_ctx.c
Normal file
147
modules/http2/h2_conn_ctx.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <apr_strings.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_config.h>
|
||||
#include <http_log.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_bucket_beam.h"
|
||||
#include "h2_c2.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_util.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
|
||||
|
||||
void h2_conn_ctx_detach(conn_rec *c)
|
||||
{
|
||||
ap_set_module_config(c->conn_config, &http2_module, NULL);
|
||||
}
|
||||
|
||||
static h2_conn_ctx_t *ctx_create(conn_rec *c, const char *id)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx = apr_pcalloc(c->pool, sizeof(*conn_ctx));
|
||||
conn_ctx->id = id;
|
||||
conn_ctx->server = c->base_server;
|
||||
conn_ctx->started_at = apr_time_now();
|
||||
|
||||
ap_set_module_config(c->conn_config, &http2_module, conn_ctx);
|
||||
return conn_ctx;
|
||||
}
|
||||
|
||||
h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c1, server_rec *s, const char *protocol)
|
||||
{
|
||||
h2_conn_ctx_t *ctx;
|
||||
|
||||
ctx = ctx_create(c1, apr_psprintf(c1->pool, "%ld", c1->id));
|
||||
ctx->server = s;
|
||||
ctx->protocol = apr_pstrdup(c1->pool, protocol);
|
||||
|
||||
ctx->pfd_out_prod.desc_type = APR_POLL_SOCKET;
|
||||
ctx->pfd_out_prod.desc.s = ap_get_conn_socket(c1);
|
||||
apr_socket_opt_set(ctx->pfd_out_prod.desc.s, APR_SO_NONBLOCK, 1);
|
||||
ctx->pfd_out_prod.reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
|
||||
ctx->pfd_out_prod.client_data = ctx;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c2,
|
||||
struct h2_mplx *mplx, struct h2_stream *stream)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx;
|
||||
apr_status_t rv = APR_SUCCESS;
|
||||
|
||||
ap_assert(c2->master);
|
||||
conn_ctx = h2_conn_ctx_get(c2);
|
||||
if (!conn_ctx) {
|
||||
h2_conn_ctx_t *c1_ctx;
|
||||
|
||||
c1_ctx = h2_conn_ctx_get(c2->master);
|
||||
ap_assert(c1_ctx);
|
||||
ap_assert(c1_ctx->session);
|
||||
|
||||
conn_ctx = ctx_create(c2, c1_ctx->id);
|
||||
conn_ctx->server = c2->master->base_server;
|
||||
}
|
||||
|
||||
conn_ctx->mplx = mplx;
|
||||
conn_ctx->stream_id = stream->id;
|
||||
apr_pool_create(&conn_ctx->req_pool, c2->pool);
|
||||
apr_pool_tag(conn_ctx->req_pool, "H2_C2_REQ");
|
||||
conn_ctx->request = stream->request;
|
||||
conn_ctx->started_at = apr_time_now();
|
||||
conn_ctx->done = 0;
|
||||
conn_ctx->done_at = 0;
|
||||
|
||||
*pctx = conn_ctx;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void h2_conn_ctx_clear_for_c2(conn_rec *c2)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx;
|
||||
|
||||
ap_assert(c2->master);
|
||||
conn_ctx = h2_conn_ctx_get(c2);
|
||||
conn_ctx->stream_id = -1;
|
||||
conn_ctx->request = NULL;
|
||||
|
||||
if (conn_ctx->req_pool) {
|
||||
apr_pool_destroy(conn_ctx->req_pool);
|
||||
conn_ctx->req_pool = NULL;
|
||||
conn_ctx->beam_out = NULL;
|
||||
}
|
||||
memset(&conn_ctx->pfd_in_drain, 0, sizeof(conn_ctx->pfd_in_drain));
|
||||
memset(&conn_ctx->pfd_out_prod, 0, sizeof(conn_ctx->pfd_out_prod));
|
||||
conn_ctx->beam_in = NULL;
|
||||
}
|
||||
|
||||
void h2_conn_ctx_destroy(conn_rec *c)
|
||||
{
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
|
||||
|
||||
if (conn_ctx) {
|
||||
if (conn_ctx->mplx_pool) {
|
||||
apr_pool_destroy(conn_ctx->mplx_pool);
|
||||
conn_ctx->mplx_pool = NULL;
|
||||
}
|
||||
ap_set_module_config(c->conn_config, &http2_module, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout)
|
||||
{
|
||||
if (conn_ctx->beam_out) {
|
||||
h2_beam_timeout_set(conn_ctx->beam_out, timeout);
|
||||
}
|
||||
if (conn_ctx->pipe_out_prod[H2_PIPE_OUT]) {
|
||||
apr_file_pipe_timeout_set(conn_ctx->pipe_out_prod[H2_PIPE_OUT], timeout);
|
||||
}
|
||||
|
||||
if (conn_ctx->beam_in) {
|
||||
h2_beam_timeout_set(conn_ctx->beam_in, timeout);
|
||||
}
|
||||
if (conn_ctx->pipe_in_prod[H2_PIPE_OUT]) {
|
||||
apr_file_pipe_timeout_set(conn_ctx->pipe_in_prod[H2_PIPE_OUT], timeout);
|
||||
}
|
||||
}
|
||||
97
modules/http2/h2_conn_ctx.h
Normal file
97
modules/http2/h2_conn_ctx.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_conn_ctx__
|
||||
#define __mod_h2__h2_conn_ctx__
|
||||
|
||||
struct h2_session;
|
||||
struct h2_stream;
|
||||
struct h2_mplx;
|
||||
struct h2_bucket_beam;
|
||||
struct h2_response_parser;
|
||||
|
||||
#define H2_PIPE_OUT 0
|
||||
#define H2_PIPE_IN 1
|
||||
|
||||
/**
|
||||
* The h2 module context associated with a connection.
|
||||
*
|
||||
* It keeps track of the different types of connections:
|
||||
* - those from clients that use HTTP/2 protocol
|
||||
* - those from clients that do not use HTTP/2
|
||||
* - those created by ourself to perform work on HTTP/2 streams
|
||||
*/
|
||||
struct h2_conn_ctx_t {
|
||||
const char *id; /* c*: our identifier of this connection */
|
||||
server_rec *server; /* c*: httpd server selected. */
|
||||
const char *protocol; /* c1: the protocol negotiated */
|
||||
struct h2_session *session; /* c1: the h2 session established */
|
||||
struct h2_mplx *mplx; /* c2: the multiplexer */
|
||||
|
||||
int pre_conn_done; /* has pre_connection setup run? */
|
||||
int stream_id; /* c1: 0, c2: stream id processed */
|
||||
apr_pool_t *req_pool; /* c2: a c2 child pool for a request */
|
||||
const struct h2_request *request; /* c2: the request to process */
|
||||
struct h2_bucket_beam *beam_out; /* c2: data out, created from req_pool */
|
||||
struct h2_bucket_beam *beam_in; /* c2: data in or NULL, borrowed from request stream */
|
||||
|
||||
apr_pool_t *mplx_pool; /* c2: an mplx child pool for safe use inside mplx lock */
|
||||
apr_file_t *pipe_in_prod[2]; /* c2: input produced notification pipe */
|
||||
apr_file_t *pipe_in_drain[2]; /* c2: input drained notification pipe */
|
||||
apr_file_t *pipe_out_prod[2]; /* c2: output produced notification pipe */
|
||||
|
||||
apr_pollfd_t pfd_in_drain; /* c2: poll pipe_in_drain output */
|
||||
apr_pollfd_t pfd_out_prod; /* c2: poll pipe_out_prod output */
|
||||
|
||||
int has_final_response; /* final HTTP response passed on out */
|
||||
apr_status_t last_err; /* APR_SUCCES or last error encountered in filters */
|
||||
struct h2_response_parser *parser; /* optional parser to catch H1 responses */
|
||||
|
||||
volatile int done; /* c2: processing has finished */
|
||||
apr_time_t started_at; /* c2: when processing started */
|
||||
apr_time_t done_at; /* c2: when processing was done */
|
||||
};
|
||||
typedef struct h2_conn_ctx_t h2_conn_ctx_t;
|
||||
|
||||
/**
|
||||
* Get the h2 connection context.
|
||||
* @param c the connection to look at
|
||||
* @return h2 context of this connection
|
||||
*/
|
||||
#define h2_conn_ctx_get(c) \
|
||||
((c)? (h2_conn_ctx_t*)ap_get_module_config((c)->conn_config, &http2_module) : NULL)
|
||||
|
||||
/**
|
||||
* Create the h2 connection context.
|
||||
* @param c the connection to create it at
|
||||
* @param s the server in use
|
||||
* @param protocol the procotol selected
|
||||
* @return created h2 context of this connection
|
||||
*/
|
||||
h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c, server_rec *s, const char *protocol);
|
||||
|
||||
apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c,
|
||||
struct h2_mplx *mplx, struct h2_stream *stream);
|
||||
|
||||
void h2_conn_ctx_clear_for_c2(conn_rec *c2);
|
||||
|
||||
void h2_conn_ctx_detach(conn_rec *c);
|
||||
|
||||
void h2_conn_ctx_destroy(conn_rec *c);
|
||||
|
||||
void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout);
|
||||
|
||||
#endif /* defined(__mod_h2__h2_conn_ctx__) */
|
||||
@@ -1,106 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_config.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_ctx.h"
|
||||
|
||||
static h2_ctx *h2_ctx_create(const conn_rec *c)
|
||||
{
|
||||
h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx));
|
||||
ap_assert(ctx);
|
||||
h2_ctx_server_update(ctx, c->base_server);
|
||||
ap_set_module_config(c->conn_config, &http2_module, ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void h2_ctx_clear(const conn_rec *c)
|
||||
{
|
||||
ap_assert(c);
|
||||
ap_set_module_config(c->conn_config, &http2_module, NULL);
|
||||
}
|
||||
|
||||
h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task *task)
|
||||
{
|
||||
h2_ctx *ctx = h2_ctx_create(c);
|
||||
if (ctx) {
|
||||
ctx->task = task;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
h2_ctx *h2_ctx_get(const conn_rec *c, int create)
|
||||
{
|
||||
h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module);
|
||||
if (ctx == NULL && create) {
|
||||
ctx = h2_ctx_create(c);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
h2_ctx *h2_ctx_rget(const request_rec *r)
|
||||
{
|
||||
return h2_ctx_get(r->connection, 0);
|
||||
}
|
||||
|
||||
const char *h2_ctx_protocol_get(const conn_rec *c)
|
||||
{
|
||||
h2_ctx *ctx;
|
||||
if (c->master) {
|
||||
c = c->master;
|
||||
}
|
||||
ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module);
|
||||
return ctx? ctx->protocol : NULL;
|
||||
}
|
||||
|
||||
h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto)
|
||||
{
|
||||
ctx->protocol = proto;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
h2_session *h2_ctx_get_session(conn_rec *c)
|
||||
{
|
||||
h2_ctx *ctx = h2_ctx_get(c, 0);
|
||||
return ctx? ctx->session : NULL;
|
||||
}
|
||||
|
||||
void h2_ctx_session_set(h2_ctx *ctx, struct h2_session *session)
|
||||
{
|
||||
ctx->session = session;
|
||||
}
|
||||
|
||||
h2_ctx *h2_ctx_server_update(h2_ctx *ctx, server_rec *s)
|
||||
{
|
||||
if (ctx->server != s) {
|
||||
ctx->server = s;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
h2_task *h2_ctx_get_task(conn_rec *c)
|
||||
{
|
||||
h2_ctx *ctx = h2_ctx_get(c, 0);
|
||||
return ctx? ctx->task : NULL;
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_ctx__
|
||||
#define __mod_h2__h2_ctx__
|
||||
|
||||
struct h2_session;
|
||||
struct h2_task;
|
||||
struct h2_config;
|
||||
|
||||
/**
|
||||
* The h2 module context associated with a connection.
|
||||
*
|
||||
* It keeps track of the different types of connections:
|
||||
* - those from clients that use HTTP/2 protocol
|
||||
* - those from clients that do not use HTTP/2
|
||||
* - those created by ourself to perform work on HTTP/2 streams
|
||||
*/
|
||||
typedef struct h2_ctx {
|
||||
const char *protocol; /* the protocol negotiated */
|
||||
struct h2_session *session; /* the session established */
|
||||
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;
|
||||
|
||||
/**
|
||||
* Get (or create) a h2 context record for this connection.
|
||||
* @param c the connection to look at
|
||||
* @param create != 0 iff missing context shall be created
|
||||
* @return h2 context of this connection
|
||||
*/
|
||||
h2_ctx *h2_ctx_get(const conn_rec *c, int create);
|
||||
void h2_ctx_clear(const conn_rec *c);
|
||||
|
||||
h2_ctx *h2_ctx_rget(const request_rec *r);
|
||||
h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task *task);
|
||||
|
||||
|
||||
/* Set the h2 protocol established on this connection context or
|
||||
* NULL when other protocols are in place.
|
||||
*/
|
||||
h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto);
|
||||
|
||||
/* Update the server_rec relevant for this context. A server for
|
||||
* a connection may change during SNI handling, for example.
|
||||
*/
|
||||
h2_ctx *h2_ctx_server_update(h2_ctx *ctx, server_rec *s);
|
||||
|
||||
void h2_ctx_session_set(h2_ctx *ctx, struct h2_session *session);
|
||||
|
||||
/**
|
||||
* Get the h2 protocol negotiated for this connection, or NULL.
|
||||
*/
|
||||
const char *h2_ctx_protocol_get(const conn_rec *c);
|
||||
|
||||
struct h2_session *h2_ctx_get_session(conn_rec *c);
|
||||
struct h2_task *h2_ctx_get_task(conn_rec *c);
|
||||
|
||||
|
||||
#endif /* defined(__mod_h2__h2_ctx__) */
|
||||
@@ -1,613 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <apr_strings.h>
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_protocol.h>
|
||||
#include <http_log.h>
|
||||
#include <http_connection.h>
|
||||
#include <scoreboard.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_conn_io.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_push.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_request.h"
|
||||
#include "h2_headers.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_util.h"
|
||||
#include "h2_version.h"
|
||||
|
||||
#include "h2_filter.h"
|
||||
|
||||
#define UNSET -1
|
||||
#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
static apr_status_t recv_RAW_DATA(conn_rec *c, h2_filter_cin *cin,
|
||||
apr_bucket *b, apr_read_type_e block)
|
||||
{
|
||||
h2_session *session = cin->session;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_size_t len;
|
||||
const char *data;
|
||||
ssize_t n;
|
||||
|
||||
(void)c;
|
||||
status = apr_bucket_read(b, &data, &len, block);
|
||||
|
||||
while (status == APR_SUCCESS && len > 0) {
|
||||
n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
|
||||
H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
|
||||
(long)len, (long)n);
|
||||
if (n < 0) {
|
||||
if (nghttp2_is_fatal((int)n)) {
|
||||
h2_session_event(session, H2_SESSION_EV_PROTO_ERROR,
|
||||
(int)n, nghttp2_strerror((int)n));
|
||||
status = APR_EGENERAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
session->io.bytes_read += n;
|
||||
if ((apr_ssize_t)len <= n) {
|
||||
break;
|
||||
}
|
||||
len -= (apr_size_t)n;
|
||||
data += n;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static apr_status_t recv_RAW_brigade(conn_rec *c, h2_filter_cin *cin,
|
||||
apr_bucket_brigade *bb,
|
||||
apr_read_type_e block)
|
||||
{
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_bucket* b;
|
||||
int consumed = 0;
|
||||
|
||||
h2_util_bb_log(c, c->id, APLOG_TRACE2, "RAW_in", bb);
|
||||
while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
|
||||
b = APR_BRIGADE_FIRST(bb);
|
||||
|
||||
if (APR_BUCKET_IS_METADATA(b)) {
|
||||
/* nop */
|
||||
}
|
||||
else {
|
||||
status = recv_RAW_DATA(c, cin, b, block);
|
||||
}
|
||||
consumed = 1;
|
||||
apr_bucket_delete(b);
|
||||
}
|
||||
|
||||
if (!consumed && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
|
||||
return APR_EAGAIN;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
h2_filter_cin *h2_filter_cin_create(h2_session *session)
|
||||
{
|
||||
h2_filter_cin *cin;
|
||||
|
||||
cin = apr_pcalloc(session->pool, sizeof(*cin));
|
||||
if (!cin) {
|
||||
return NULL;
|
||||
}
|
||||
cin->session = session;
|
||||
return cin;
|
||||
}
|
||||
|
||||
void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout)
|
||||
{
|
||||
cin->timeout = timeout;
|
||||
}
|
||||
|
||||
apr_status_t h2_filter_core_input(ap_filter_t* f,
|
||||
apr_bucket_brigade* brigade,
|
||||
ap_input_mode_t mode,
|
||||
apr_read_type_e block,
|
||||
apr_off_t readbytes)
|
||||
{
|
||||
h2_filter_cin *cin = f->ctx;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_interval_time_t saved_timeout = UNSET;
|
||||
const int trace1 = APLOGctrace1(f->c);
|
||||
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_session(%ld): read, %s, mode=%d, readbytes=%ld",
|
||||
(long)f->c->id, (block == APR_BLOCK_READ)?
|
||||
"BLOCK_READ" : "NONBLOCK_READ", mode, (long)readbytes);
|
||||
}
|
||||
|
||||
if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
|
||||
return ap_get_brigade(f->next, brigade, mode, block, readbytes);
|
||||
}
|
||||
|
||||
if (mode != AP_MODE_READBYTES) {
|
||||
return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN;
|
||||
}
|
||||
|
||||
if (!cin->bb) {
|
||||
cin->bb = apr_brigade_create(cin->session->pool, f->c->bucket_alloc);
|
||||
}
|
||||
|
||||
if (!cin->socket) {
|
||||
cin->socket = ap_get_conn_socket(f->c);
|
||||
}
|
||||
|
||||
if (APR_BRIGADE_EMPTY(cin->bb)) {
|
||||
/* We only do a blocking read when we have no streams to process. So,
|
||||
* in httpd scoreboard lingo, we are in a KEEPALIVE connection state.
|
||||
*/
|
||||
if (block == APR_BLOCK_READ) {
|
||||
if (cin->timeout > 0) {
|
||||
apr_socket_timeout_get(cin->socket, &saved_timeout);
|
||||
apr_socket_timeout_set(cin->socket, cin->timeout);
|
||||
}
|
||||
}
|
||||
status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
|
||||
block, readbytes);
|
||||
if (saved_timeout != UNSET) {
|
||||
apr_socket_timeout_set(cin->socket, saved_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case APR_SUCCESS:
|
||||
status = recv_RAW_brigade(f->c, cin, cin->bb, block);
|
||||
break;
|
||||
case APR_EOF:
|
||||
case APR_EAGAIN:
|
||||
case APR_TIMEUP:
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_session(%ld): read", f->c->id);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, APLOGNO(03046)
|
||||
"h2_session(%ld): error reading", f->c->id);
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* http2 connection status handler + stream out source
|
||||
******************************************************************************/
|
||||
|
||||
typedef struct {
|
||||
apr_bucket_refcount refcount;
|
||||
h2_bucket_event_cb *cb;
|
||||
void *ctx;
|
||||
} h2_bucket_observer;
|
||||
|
||||
static apr_status_t bucket_read(apr_bucket *b, const char **str,
|
||||
apr_size_t *len, apr_read_type_e block)
|
||||
{
|
||||
(void)b;
|
||||
(void)block;
|
||||
*str = NULL;
|
||||
*len = 0;
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static void bucket_destroy(void *data)
|
||||
{
|
||||
h2_bucket_observer *h = data;
|
||||
if (apr_bucket_shared_destroy(h)) {
|
||||
if (h->cb) {
|
||||
h->cb(h->ctx, H2_BUCKET_EV_BEFORE_DESTROY, NULL);
|
||||
}
|
||||
apr_bucket_free(h);
|
||||
}
|
||||
}
|
||||
|
||||
apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
|
||||
void *ctx)
|
||||
{
|
||||
h2_bucket_observer *br;
|
||||
|
||||
br = apr_bucket_alloc(sizeof(*br), b->list);
|
||||
br->cb = cb;
|
||||
br->ctx = ctx;
|
||||
|
||||
b = apr_bucket_shared_make(b, br, 0, 0);
|
||||
b->type = &h2_bucket_type_observer;
|
||||
return b;
|
||||
}
|
||||
|
||||
apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list,
|
||||
h2_bucket_event_cb *cb, void *ctx)
|
||||
{
|
||||
apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
|
||||
|
||||
APR_BUCKET_INIT(b);
|
||||
b->free = apr_bucket_free;
|
||||
b->list = list;
|
||||
b = h2_bucket_observer_make(b, cb, ctx);
|
||||
return b;
|
||||
}
|
||||
|
||||
apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event)
|
||||
{
|
||||
if (H2_BUCKET_IS_OBSERVER(b)) {
|
||||
h2_bucket_observer *l = (h2_bucket_observer *)b->data;
|
||||
return l->cb(l->ctx, event, b);
|
||||
}
|
||||
return APR_EINVAL;
|
||||
}
|
||||
|
||||
const apr_bucket_type_t h2_bucket_type_observer = {
|
||||
"H2OBS", 5, APR_BUCKET_METADATA,
|
||||
bucket_destroy,
|
||||
bucket_read,
|
||||
apr_bucket_setaside_noop,
|
||||
apr_bucket_split_notimpl,
|
||||
apr_bucket_shared_copy
|
||||
};
|
||||
|
||||
apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
|
||||
apr_bucket_brigade *dest,
|
||||
const apr_bucket *src)
|
||||
{
|
||||
(void)beam;
|
||||
if (H2_BUCKET_IS_OBSERVER(src)) {
|
||||
h2_bucket_observer *l = (h2_bucket_observer *)src->data;
|
||||
apr_bucket *b = h2_bucket_observer_create(dest->bucket_alloc,
|
||||
l->cb, l->ctx);
|
||||
APR_BRIGADE_INSERT_TAIL(dest, b);
|
||||
l->cb = NULL;
|
||||
l->ctx = NULL;
|
||||
h2_bucket_observer_fire(b, H2_BUCKET_EV_BEFORE_MASTER_SEND);
|
||||
return b;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
|
||||
__attribute__((format(printf,2,3)));
|
||||
static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
apr_status_t rv;
|
||||
|
||||
va_start(args, fmt);
|
||||
rv = apr_brigade_vprintf(bb, NULL, NULL, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void add_settings(apr_bucket_brigade *bb, h2_session *s, int last)
|
||||
{
|
||||
h2_mplx *m = s->mplx;
|
||||
|
||||
bbout(bb, " \"settings\": {\n");
|
||||
bbout(bb, " \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", m->max_streams);
|
||||
bbout(bb, " \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 16*1024);
|
||||
bbout(bb, " \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n", h2_config_sgeti(s->s, H2_CONF_WIN_SIZE));
|
||||
bbout(bb, " \"SETTINGS_ENABLE_PUSH\": %d\n", h2_session_push_enabled(s));
|
||||
bbout(bb, " }%s\n", last? "" : ",");
|
||||
}
|
||||
|
||||
static void add_peer_settings(apr_bucket_brigade *bb, h2_session *s, int last)
|
||||
{
|
||||
bbout(bb, " \"peerSettings\": {\n");
|
||||
bbout(bb, " \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n",
|
||||
nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS));
|
||||
bbout(bb, " \"SETTINGS_MAX_FRAME_SIZE\": %d,\n",
|
||||
nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_FRAME_SIZE));
|
||||
bbout(bb, " \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n",
|
||||
nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE));
|
||||
bbout(bb, " \"SETTINGS_ENABLE_PUSH\": %d,\n",
|
||||
nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_ENABLE_PUSH));
|
||||
bbout(bb, " \"SETTINGS_HEADER_TABLE_SIZE\": %d,\n",
|
||||
nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE));
|
||||
bbout(bb, " \"SETTINGS_MAX_HEADER_LIST_SIZE\": %d\n",
|
||||
nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE));
|
||||
bbout(bb, " }%s\n", last? "" : ",");
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
apr_bucket_brigade *bb;
|
||||
h2_session *s;
|
||||
int idx;
|
||||
} stream_ctx_t;
|
||||
|
||||
static int add_stream(h2_stream *stream, void *ctx)
|
||||
{
|
||||
stream_ctx_t *x = ctx;
|
||||
int32_t flowIn, flowOut;
|
||||
|
||||
flowIn = nghttp2_session_get_stream_effective_local_window_size(x->s->ngh2, stream->id);
|
||||
flowOut = nghttp2_session_get_stream_remote_window_size(x->s->ngh2, stream->id);
|
||||
bbout(x->bb, "%s\n \"%d\": {\n", (x->idx? "," : ""), stream->id);
|
||||
bbout(x->bb, " \"state\": \"%s\",\n", h2_stream_state_str(stream));
|
||||
bbout(x->bb, " \"created\": %f,\n", ((double)stream->created)/APR_USEC_PER_SEC);
|
||||
bbout(x->bb, " \"flowIn\": %d,\n", flowIn);
|
||||
bbout(x->bb, " \"flowOut\": %d,\n", flowOut);
|
||||
bbout(x->bb, " \"dataIn\": %"APR_OFF_T_FMT",\n", stream->in_data_octets);
|
||||
bbout(x->bb, " \"dataOut\": %"APR_OFF_T_FMT"\n", stream->out_data_octets);
|
||||
bbout(x->bb, " }");
|
||||
|
||||
++x->idx;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void add_streams(apr_bucket_brigade *bb, h2_session *s, int last)
|
||||
{
|
||||
stream_ctx_t x;
|
||||
|
||||
x.bb = bb;
|
||||
x.s = s;
|
||||
x.idx = 0;
|
||||
bbout(bb, " \"streams\": {");
|
||||
h2_mplx_m_stream_do(s->mplx, add_stream, &x);
|
||||
bbout(bb, "\n }%s\n", last? "" : ",");
|
||||
}
|
||||
|
||||
static void add_push(apr_bucket_brigade *bb, h2_session *s,
|
||||
h2_stream *stream, int last)
|
||||
{
|
||||
h2_push_diary *diary;
|
||||
apr_status_t status;
|
||||
|
||||
bbout(bb, " \"push\": {\n");
|
||||
diary = s->push_diary;
|
||||
if (diary) {
|
||||
const char *data;
|
||||
const char *base64_digest;
|
||||
apr_size_t len;
|
||||
|
||||
status = h2_push_diary_digest_get(diary, bb->p, 256,
|
||||
stream->request->authority,
|
||||
&data, &len);
|
||||
if (status == APR_SUCCESS) {
|
||||
base64_digest = h2_util_base64url_encode(data, len, bb->p);
|
||||
bbout(bb, " \"cacheDigest\": \"%s\",\n", base64_digest);
|
||||
}
|
||||
}
|
||||
bbout(bb, " \"promises\": %d,\n", s->pushes_promised);
|
||||
bbout(bb, " \"submits\": %d,\n", s->pushes_submitted);
|
||||
bbout(bb, " \"resets\": %d\n", s->pushes_reset);
|
||||
bbout(bb, " }%s\n", last? "" : ",");
|
||||
}
|
||||
|
||||
static void add_in(apr_bucket_brigade *bb, h2_session *s, int last)
|
||||
{
|
||||
bbout(bb, " \"in\": {\n");
|
||||
bbout(bb, " \"requests\": %d,\n", s->remote.emitted_count);
|
||||
bbout(bb, " \"resets\": %d, \n", s->streams_reset);
|
||||
bbout(bb, " \"frames\": %ld,\n", (long)s->frames_received);
|
||||
bbout(bb, " \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_read);
|
||||
bbout(bb, " }%s\n", last? "" : ",");
|
||||
}
|
||||
|
||||
static void add_out(apr_bucket_brigade *bb, h2_session *s, int last)
|
||||
{
|
||||
bbout(bb, " \"out\": {\n");
|
||||
bbout(bb, " \"responses\": %d,\n", s->responses_submitted);
|
||||
bbout(bb, " \"frames\": %ld,\n", (long)s->frames_sent);
|
||||
bbout(bb, " \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_written);
|
||||
bbout(bb, " }%s\n", last? "" : ",");
|
||||
}
|
||||
|
||||
static void add_stats(apr_bucket_brigade *bb, h2_session *s,
|
||||
h2_stream *stream, int last)
|
||||
{
|
||||
bbout(bb, " \"stats\": {\n");
|
||||
add_in(bb, s, 0);
|
||||
add_out(bb, s, 0);
|
||||
add_push(bb, s, stream, 1);
|
||||
bbout(bb, " }%s\n", last? "" : ",");
|
||||
}
|
||||
|
||||
static apr_status_t h2_status_insert(h2_task *task, apr_bucket *b)
|
||||
{
|
||||
h2_mplx *m = task->mplx;
|
||||
h2_stream *stream = h2_mplx_t_stream_get(m, task);
|
||||
h2_session *s;
|
||||
conn_rec *c;
|
||||
|
||||
apr_bucket_brigade *bb;
|
||||
apr_bucket *e;
|
||||
int32_t connFlowIn, connFlowOut;
|
||||
|
||||
if (!stream) {
|
||||
/* stream already done */
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
s = stream->session;
|
||||
c = s->c;
|
||||
|
||||
bb = apr_brigade_create(stream->pool, c->bucket_alloc);
|
||||
|
||||
connFlowIn = nghttp2_session_get_effective_local_window_size(s->ngh2);
|
||||
connFlowOut = nghttp2_session_get_remote_window_size(s->ngh2);
|
||||
|
||||
bbout(bb, "{\n");
|
||||
bbout(bb, " \"version\": \"draft-01\",\n");
|
||||
add_settings(bb, s, 0);
|
||||
add_peer_settings(bb, s, 0);
|
||||
bbout(bb, " \"connFlowIn\": %d,\n", connFlowIn);
|
||||
bbout(bb, " \"connFlowOut\": %d,\n", connFlowOut);
|
||||
bbout(bb, " \"sentGoAway\": %d,\n", s->local.shutdown);
|
||||
|
||||
add_streams(bb, s, 0);
|
||||
|
||||
add_stats(bb, s, stream, 1);
|
||||
bbout(bb, "}\n");
|
||||
|
||||
while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
|
||||
APR_BUCKET_REMOVE(e);
|
||||
APR_BUCKET_INSERT_AFTER(b, e);
|
||||
b = e;
|
||||
}
|
||||
apr_brigade_destroy(bb);
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static apr_status_t status_event(void *ctx, h2_bucket_event event,
|
||||
apr_bucket *b)
|
||||
{
|
||||
h2_task *task = ctx;
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, task->c->master,
|
||||
"status_event(%s): %d", task->id, event);
|
||||
switch (event) {
|
||||
case H2_BUCKET_EV_BEFORE_MASTER_SEND:
|
||||
h2_status_insert(task, b);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static apr_status_t discard_body(request_rec *r, apr_off_t maxlen)
|
||||
{
|
||||
apr_bucket_brigade *bb;
|
||||
int seen_eos;
|
||||
apr_status_t rv;
|
||||
|
||||
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
|
||||
seen_eos = 0;
|
||||
do {
|
||||
apr_bucket *bucket;
|
||||
|
||||
rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
|
||||
APR_BLOCK_READ, HUGE_STRING_LEN);
|
||||
|
||||
if (rv != APR_SUCCESS) {
|
||||
apr_brigade_destroy(bb);
|
||||
return rv;
|
||||
}
|
||||
|
||||
for (bucket = APR_BRIGADE_FIRST(bb);
|
||||
bucket != APR_BRIGADE_SENTINEL(bb);
|
||||
bucket = APR_BUCKET_NEXT(bucket))
|
||||
{
|
||||
const char *data;
|
||||
apr_size_t len;
|
||||
|
||||
if (APR_BUCKET_IS_EOS(bucket)) {
|
||||
seen_eos = 1;
|
||||
break;
|
||||
}
|
||||
if (bucket->length == 0) {
|
||||
continue;
|
||||
}
|
||||
rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
|
||||
if (rv != APR_SUCCESS) {
|
||||
apr_brigade_destroy(bb);
|
||||
return rv;
|
||||
}
|
||||
maxlen -= bucket->length;
|
||||
}
|
||||
apr_brigade_cleanup(bb);
|
||||
} while (!seen_eos && maxlen >= 0);
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
int h2_filter_h2_status_handler(request_rec *r)
|
||||
{
|
||||
conn_rec *c = r->connection;
|
||||
h2_task *task;
|
||||
apr_bucket_brigade *bb;
|
||||
apr_bucket *b;
|
||||
apr_status_t status;
|
||||
|
||||
if (strcmp(r->handler, "http2-status")) {
|
||||
return DECLINED;
|
||||
}
|
||||
if (r->method_number != M_GET && r->method_number != M_POST) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
task = h2_ctx_get_task(r->connection);
|
||||
if (task) {
|
||||
/* In this handler, we do some special sauce to send footers back,
|
||||
* IFF we received footers in the request. This is used in our test
|
||||
* cases, since CGI has no way of handling those. */
|
||||
if ((status = discard_body(r, 1024)) != OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* We need to handle the actual output on the main thread, as
|
||||
* we need to access h2_session information. */
|
||||
r->status = 200;
|
||||
r->clength = -1;
|
||||
r->chunked = 1;
|
||||
apr_table_unset(r->headers_out, "Content-Length");
|
||||
/* Discourage content-encodings */
|
||||
apr_table_unset(r->headers_out, "Content-Encoding");
|
||||
apr_table_setn(r->subprocess_env, "no-brotli", "1");
|
||||
apr_table_setn(r->subprocess_env, "no-gzip", "1");
|
||||
|
||||
ap_set_content_type(r, "application/json");
|
||||
apr_table_setn(r->notes, H2_FILTER_DEBUG_NOTE, "on");
|
||||
|
||||
bb = apr_brigade_create(r->pool, c->bucket_alloc);
|
||||
b = h2_bucket_observer_create(c->bucket_alloc, status_event, task);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
b = apr_bucket_eos_create(c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
||||
"status_handler(%s): checking for incoming trailers",
|
||||
task->id);
|
||||
if (r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
||||
"status_handler(%s): seeing incoming trailers",
|
||||
task->id);
|
||||
apr_table_setn(r->trailers_out, "h2-trailers-in",
|
||||
apr_itoa(r->pool, 1));
|
||||
}
|
||||
|
||||
status = ap_pass_brigade(r->output_filters, bb);
|
||||
if (status == APR_SUCCESS
|
||||
|| r->status != HTTP_OK
|
||||
|| c->aborted) {
|
||||
return OK;
|
||||
}
|
||||
else {
|
||||
/* no way to know what type of error occurred */
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r,
|
||||
"status_handler(%s): ap_pass_brigade failed",
|
||||
task->id);
|
||||
return AP_FILTER_ERROR;
|
||||
}
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_filter__
|
||||
#define __mod_h2__h2_filter__
|
||||
|
||||
struct h2_bucket_beam;
|
||||
struct h2_headers;
|
||||
struct h2_stream;
|
||||
struct h2_session;
|
||||
|
||||
typedef struct h2_filter_cin {
|
||||
apr_pool_t *pool;
|
||||
apr_socket_t *socket;
|
||||
apr_interval_time_t timeout;
|
||||
apr_bucket_brigade *bb;
|
||||
struct h2_session *session;
|
||||
apr_bucket *cur;
|
||||
} h2_filter_cin;
|
||||
|
||||
h2_filter_cin *h2_filter_cin_create(struct h2_session *session);
|
||||
|
||||
void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout);
|
||||
|
||||
apr_status_t h2_filter_core_input(ap_filter_t* filter,
|
||||
apr_bucket_brigade* brigade,
|
||||
ap_input_mode_t mode,
|
||||
apr_read_type_e block,
|
||||
apr_off_t readbytes);
|
||||
|
||||
/******* observer bucket ******************************************************/
|
||||
|
||||
typedef enum {
|
||||
H2_BUCKET_EV_BEFORE_DESTROY,
|
||||
H2_BUCKET_EV_BEFORE_MASTER_SEND
|
||||
} h2_bucket_event;
|
||||
|
||||
extern const apr_bucket_type_t h2_bucket_type_observer;
|
||||
|
||||
typedef apr_status_t h2_bucket_event_cb(void *ctx, h2_bucket_event event, apr_bucket *b);
|
||||
|
||||
#define H2_BUCKET_IS_OBSERVER(e) (e->type == &h2_bucket_type_observer)
|
||||
|
||||
apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
|
||||
void *ctx);
|
||||
|
||||
apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list,
|
||||
h2_bucket_event_cb *cb, void *ctx);
|
||||
|
||||
apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event);
|
||||
|
||||
apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
|
||||
apr_bucket_brigade *dest,
|
||||
const apr_bucket *src);
|
||||
|
||||
/******* /.well-known/h2/state handler ****************************************/
|
||||
|
||||
int h2_filter_h2_status_handler(request_rec *r);
|
||||
|
||||
#endif /* __mod_h2__h2_filter__ */
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_util.h"
|
||||
#include "h2_request.h"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,23 +18,16 @@
|
||||
#define __mod_h2__h2_mplx__
|
||||
|
||||
/**
|
||||
* The stream multiplexer. It pushes buckets from the connection
|
||||
* thread to the stream threads and vice versa. It's thread-safe
|
||||
* to use.
|
||||
* The stream multiplexer. It performs communication between the
|
||||
* primary HTTP/2 connection (c1) to the secondary connections (c2)
|
||||
* that process the requests, aka. HTTP/2 streams.
|
||||
*
|
||||
* There is one h2_mplx instance for each h2_session, which sits on top
|
||||
* of a particular httpd conn_rec. Input goes from the connection to
|
||||
* the stream tasks. Output goes from the stream tasks to the connection,
|
||||
* e.g. the client.
|
||||
* There is one h2_mplx instance for each h2_session.
|
||||
*
|
||||
* For each stream, there can be at most "H2StreamMaxMemSize" output bytes
|
||||
* queued in the multiplexer. If a task thread tries to write more
|
||||
* data, it is blocked until space becomes available.
|
||||
*
|
||||
* Naming Convention:
|
||||
* "h2_mplx_m_" are methods only to be called by the main connection
|
||||
* "h2_mplx_s_" are method only to be called by a secondary connection
|
||||
* "h2_mplx_t_" are method only to be called by a task handler (can be master or secondary)
|
||||
* Naming Convention:
|
||||
* "h2_mplx_c1_" are methods only to be called by the primary connection
|
||||
* "h2_mplx_c2_" are methods only to be called by a secondary connection
|
||||
* "h2_mplx_worker_" are methods only to be called by a h2 worker thread
|
||||
*/
|
||||
|
||||
struct apr_pool_t;
|
||||
@@ -43,7 +36,6 @@ struct apr_thread_cond_t;
|
||||
struct h2_bucket_beam;
|
||||
struct h2_config;
|
||||
struct h2_ihash_t;
|
||||
struct h2_task;
|
||||
struct h2_stream;
|
||||
struct h2_request;
|
||||
struct apr_thread_cond_t;
|
||||
@@ -56,74 +48,71 @@ typedef struct h2_mplx h2_mplx;
|
||||
|
||||
struct h2_mplx {
|
||||
long id;
|
||||
conn_rec *c;
|
||||
conn_rec *c1; /* the main connection */
|
||||
apr_pool_t *pool;
|
||||
struct h2_stream *stream0; /* HTTP/2's stream 0 */
|
||||
server_rec *s; /* server for master conn */
|
||||
|
||||
unsigned int event_pending;
|
||||
unsigned int aborted;
|
||||
unsigned int is_registered; /* is registered at h2_workers */
|
||||
int aborted;
|
||||
int polling; /* is waiting/processing pollset events */
|
||||
int is_registered; /* is registered at h2_workers */
|
||||
|
||||
struct h2_ihash_t *streams; /* all streams currently processing */
|
||||
struct h2_ihash_t *shold; /* all streams done with task ongoing */
|
||||
struct h2_ihash_t *spurge; /* all streams done, ready for destroy */
|
||||
struct h2_ihash_t *streams; /* all streams active */
|
||||
struct h2_ihash_t *shold; /* all streams done with c2 processing ongoing */
|
||||
apr_array_header_t *spurge; /* all streams done, ready for destroy */
|
||||
|
||||
struct h2_iqueue *q; /* all stream ids that need to be started */
|
||||
struct h2_ififo *readyq; /* all stream ids ready for output */
|
||||
|
||||
struct h2_ihash_t *redo_tasks; /* all tasks that need to be redone */
|
||||
|
||||
apr_size_t stream_max_mem; /* max memory to buffer for a stream */
|
||||
int max_streams; /* max # of concurrent streams */
|
||||
int max_stream_id_started; /* highest stream id that started processing */
|
||||
|
||||
int processing_count; /* # of c2 working for this mplx */
|
||||
int processing_limit; /* current limit on processing c2s, dynamic */
|
||||
int processing_max; /* max, hard limit of processing c2s */
|
||||
|
||||
int max_streams; /* max # of concurrent streams */
|
||||
int max_stream_started; /* highest stream id that started processing */
|
||||
int tasks_active; /* # of tasks being processed from this mplx */
|
||||
int limit_active; /* current limit on active tasks, dynamic */
|
||||
int max_active; /* max, hard limit # of active tasks in a process */
|
||||
|
||||
apr_time_t last_mood_change; /* last time, we worker limit changed */
|
||||
apr_time_t last_mood_change; /* last time, processing limit changed */
|
||||
apr_interval_time_t mood_update_interval; /* how frequent we update at most */
|
||||
int irritations_since; /* irritations (>0) or happy events (<0) since last mood change */
|
||||
|
||||
apr_thread_mutex_t *lock;
|
||||
struct apr_thread_cond_t *added_output;
|
||||
struct apr_thread_cond_t *join_wait;
|
||||
|
||||
apr_size_t stream_max_mem;
|
||||
|
||||
apr_pool_t *spare_io_pool;
|
||||
apr_array_header_t *spare_secondary; /* spare secondary connections */
|
||||
|
||||
struct h2_workers *workers;
|
||||
apr_pollset_t *pollset; /* pollset for c1/c2 IO events */
|
||||
apr_array_header_t *streams_to_poll; /* streams to add to the pollset */
|
||||
apr_array_header_t *streams_ev_in;
|
||||
apr_array_header_t *streams_ev_out;
|
||||
|
||||
#if !H2_POLL_STREAMS
|
||||
apr_thread_mutex_t *poll_lock; /* not the painter */
|
||||
struct h2_iqueue *streams_input_read; /* streams whose input has been read from */
|
||||
struct h2_iqueue *streams_output_written; /* streams whose output has been written to */
|
||||
#endif
|
||||
struct h2_workers *workers; /* h2 workers process wide instance */
|
||||
};
|
||||
|
||||
/*******************************************************************************
|
||||
* From the main connection processing: h2_mplx_m_*
|
||||
******************************************************************************/
|
||||
|
||||
apr_status_t h2_mplx_m_child_init(apr_pool_t *pool, server_rec *s);
|
||||
apr_status_t h2_mplx_c1_child_init(apr_pool_t *pool, server_rec *s);
|
||||
|
||||
/**
|
||||
* Create the multiplexer for the given HTTP2 session.
|
||||
* Implicitly has reference count 1.
|
||||
*/
|
||||
h2_mplx *h2_mplx_m_create(conn_rec *c, server_rec *s, apr_pool_t *master,
|
||||
struct h2_workers *workers);
|
||||
h2_mplx *h2_mplx_c1_create(struct h2_stream *stream0, server_rec *s, apr_pool_t *master,
|
||||
struct h2_workers *workers);
|
||||
|
||||
/**
|
||||
* Decreases the reference counter of this mplx and waits for it
|
||||
* to reached 0, destroy the mplx afterwards.
|
||||
* This is to be called from the thread that created the mplx in
|
||||
* the first place.
|
||||
* @param m the mplx to be released and destroyed
|
||||
* Destroy the mplx, shutting down all ongoing processing.
|
||||
* @param m the mplx destroyed
|
||||
* @param wait condition var to wait on for ref counter == 0
|
||||
*/
|
||||
void h2_mplx_m_release_and_join(h2_mplx *m, struct apr_thread_cond_t *wait);
|
||||
void h2_mplx_c1_destroy(h2_mplx *m);
|
||||
|
||||
/**
|
||||
* Shut down the multiplexer gracefully. Will no longer schedule new streams
|
||||
* but let the ongoing ones finish normally.
|
||||
* @return the highest stream id being/been processed
|
||||
*/
|
||||
int h2_mplx_m_shutdown(h2_mplx *m);
|
||||
int h2_mplx_c1_shutdown(h2_mplx *m);
|
||||
|
||||
/**
|
||||
* Notifies mplx that a stream has been completely handled on the main
|
||||
@@ -131,29 +120,33 @@ int h2_mplx_m_shutdown(h2_mplx *m);
|
||||
*
|
||||
* @param m the mplx itself
|
||||
* @param stream the stream ready for cleanup
|
||||
* @param pstream_count return the number of streams active
|
||||
*/
|
||||
apr_status_t h2_mplx_m_stream_cleanup(h2_mplx *m, struct h2_stream *stream);
|
||||
apr_status_t h2_mplx_c1_stream_cleanup(h2_mplx *m, struct h2_stream *stream,
|
||||
int *pstream_count);
|
||||
|
||||
/**
|
||||
* Waits on output data from any stream in this session to become available.
|
||||
* Returns APR_TIMEUP if no data arrived in the given time.
|
||||
*/
|
||||
apr_status_t h2_mplx_m_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
|
||||
struct apr_thread_cond_t *iowait);
|
||||
|
||||
apr_status_t h2_mplx_m_keep_active(h2_mplx *m, struct h2_stream *stream);
|
||||
int h2_mplx_c1_stream_is_running(h2_mplx *m, struct h2_stream *stream);
|
||||
|
||||
/**
|
||||
* Process a stream request.
|
||||
*
|
||||
* @param m the multiplexer
|
||||
* @param stream the identifier of the stream
|
||||
* @param r the request to be processed
|
||||
* @param read_to_process
|
||||
* @param input_pending
|
||||
* @param cmp the stream priority compare function
|
||||
* @param ctx context data for the compare function
|
||||
* @param pstream_count on return the number of streams active in mplx
|
||||
*/
|
||||
apr_status_t h2_mplx_m_process(h2_mplx *m, struct h2_stream *stream,
|
||||
h2_stream_pri_cmp *cmp, void *ctx);
|
||||
apr_status_t h2_mplx_c1_process(h2_mplx *m,
|
||||
struct h2_iqueue *read_to_process,
|
||||
h2_stream_get_fn *get_stream,
|
||||
h2_stream_pri_cmp_fn *cmp,
|
||||
struct h2_session *session,
|
||||
int *pstream_count);
|
||||
|
||||
apr_status_t h2_mplx_c1_fwd_input(h2_mplx *m, struct h2_iqueue *input_pending,
|
||||
h2_stream_get_fn *get_stream,
|
||||
struct h2_session *session);
|
||||
|
||||
|
||||
/**
|
||||
* Stream priorities have changed, reschedule pending requests.
|
||||
@@ -162,62 +155,60 @@ apr_status_t h2_mplx_m_process(h2_mplx *m, struct h2_stream *stream,
|
||||
* @param cmp the stream priority compare function
|
||||
* @param ctx context data for the compare function
|
||||
*/
|
||||
apr_status_t h2_mplx_m_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx);
|
||||
apr_status_t h2_mplx_c1_reprioritize(h2_mplx *m, h2_stream_pri_cmp_fn *cmp,
|
||||
struct h2_session *session);
|
||||
|
||||
typedef apr_status_t stream_ev_callback(void *ctx, struct h2_stream *stream);
|
||||
|
||||
/**
|
||||
* Check if the multiplexer has events for the master connection pending.
|
||||
* @return != 0 iff there are events pending
|
||||
* Poll the primary connection for input and the active streams for output.
|
||||
* Invoke the callback for any stream where an event happened.
|
||||
*/
|
||||
int h2_mplx_m_has_master_events(h2_mplx *m);
|
||||
apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
|
||||
stream_ev_callback *on_stream_input,
|
||||
stream_ev_callback *on_stream_output,
|
||||
void *on_ctx);
|
||||
|
||||
void h2_mplx_c2_input_read(h2_mplx *m, conn_rec *c2);
|
||||
void h2_mplx_c2_output_written(h2_mplx *m, conn_rec *c2);
|
||||
|
||||
typedef int h2_mplx_stream_cb(struct h2_stream *s, void *userdata);
|
||||
|
||||
/**
|
||||
* Dispatch events for the master connection, such as
|
||||
± @param m the multiplexer
|
||||
* @param on_resume new output data has arrived for a suspended stream
|
||||
* @param ctx user supplied argument to invocation.
|
||||
* Iterate over all streams known to mplx from the primary connection.
|
||||
* @param m the mplx
|
||||
* @param cb the callback to invoke on each stream
|
||||
* @param ctx userdata passed to the callback
|
||||
*/
|
||||
apr_status_t h2_mplx_m_dispatch_master_events(h2_mplx *m, stream_ev_callback *on_resume,
|
||||
void *ctx);
|
||||
|
||||
int h2_mplx_m_awaits_data(h2_mplx *m);
|
||||
|
||||
typedef int h2_mplx_stream_cb(struct h2_stream *s, void *ctx);
|
||||
|
||||
apr_status_t h2_mplx_m_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
|
||||
|
||||
apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id);
|
||||
apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
|
||||
|
||||
/**
|
||||
* Master connection has entered idle mode.
|
||||
* @param m the mplx instance of the master connection
|
||||
* @return != SUCCESS iff connection should be terminated
|
||||
* A stream has been RST_STREAM by the client. Abort
|
||||
* any processing going on and remove from processing
|
||||
* queue.
|
||||
*/
|
||||
apr_status_t h2_mplx_m_idle(h2_mplx *m);
|
||||
|
||||
/*******************************************************************************
|
||||
* From a secondary connection processing: h2_mplx_s_*
|
||||
******************************************************************************/
|
||||
apr_status_t h2_mplx_s_pop_task(h2_mplx *m, struct h2_task **ptask);
|
||||
void h2_mplx_s_task_done(h2_mplx *m, struct h2_task *task, struct h2_task **ptask);
|
||||
|
||||
/*******************************************************************************
|
||||
* From a h2_task owner: h2_mplx_s_*
|
||||
* (a task is transfered from master to secondary connection and back in
|
||||
* its normal lifetime).
|
||||
******************************************************************************/
|
||||
apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id);
|
||||
|
||||
/**
|
||||
* Opens the output for the given stream with the specified response.
|
||||
* Get readonly access to a stream for a secondary connection.
|
||||
*/
|
||||
apr_status_t h2_mplx_t_out_open(h2_mplx *mplx, int stream_id,
|
||||
struct h2_bucket_beam *beam);
|
||||
const struct h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id);
|
||||
|
||||
/**
|
||||
* Get the stream that belongs to the given task.
|
||||
* A h2 worker asks for a secondary connection to process.
|
||||
* @param out_c2 non-NULL, a pointer where to reveive the next
|
||||
* secondary connection to process.
|
||||
*/
|
||||
struct h2_stream *h2_mplx_t_stream_get(h2_mplx *m, struct h2_task *task);
|
||||
apr_status_t h2_mplx_worker_pop_c2(h2_mplx *m, conn_rec **out_c2);
|
||||
|
||||
/**
|
||||
* A h2 worker reports a secondary connection processing done.
|
||||
* If it is will to do more work for this mplx (this c1 connection),
|
||||
* it provides `out_c`. Otherwise it passes NULL.
|
||||
* @param c2 the secondary connection finished processing
|
||||
* @param out_c2 NULL or a pointer where to reveive the next
|
||||
* secondary connection to process.
|
||||
*/
|
||||
void h2_mplx_worker_c2_done(conn_rec *c2, conn_rec **out_c2);
|
||||
|
||||
#endif /* defined(__mod_h2__h2_mplx__) */
|
||||
|
||||
@@ -34,23 +34,22 @@
|
||||
|
||||
#include "h2_bucket_beam.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_c2.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_conn.h"
|
||||
#include "h2_filter.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
#include "h2_c1.h"
|
||||
#include "h2_request.h"
|
||||
#include "h2_headers.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_util.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "mod_http2.h"
|
||||
|
||||
const char *h2_tls_protos[] = {
|
||||
const char *h2_protocol_ids_tls[] = {
|
||||
"h2", NULL
|
||||
};
|
||||
|
||||
const char *h2_clear_protos[] = {
|
||||
const char *h2_protocol_ids_clear[] = {
|
||||
"h2c", NULL
|
||||
};
|
||||
|
||||
@@ -76,7 +75,7 @@ static const char *h2_err_descr[] = {
|
||||
"http/1.1 required",
|
||||
};
|
||||
|
||||
const char *h2_h2_err_description(unsigned int h2_error)
|
||||
const char *h2_protocol_err_description(unsigned int h2_error)
|
||||
{
|
||||
if (h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
|
||||
return h2_err_descr[h2_error];
|
||||
@@ -421,19 +420,7 @@ static int cipher_is_blacklisted(const char *cipher, const char **psource)
|
||||
return !!*psource;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Hooks for processing incoming connections:
|
||||
* - process_conn take over connection in case of h2
|
||||
*/
|
||||
static int h2_h2_process_conn(conn_rec* c);
|
||||
static int h2_h2_pre_close_conn(conn_rec* c);
|
||||
static int h2_h2_post_read_req(request_rec *r);
|
||||
static int h2_h2_late_fixups(request_rec *r);
|
||||
|
||||
/*******************************************************************************
|
||||
* Once per lifetime init, retrieve optional functions
|
||||
*/
|
||||
apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s)
|
||||
apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s)
|
||||
{
|
||||
(void)pool;
|
||||
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_h2, child_init");
|
||||
@@ -442,7 +429,7 @@ apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s)
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all)
|
||||
int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all)
|
||||
{
|
||||
int is_tls = ap_ssl_conn_is_ssl(c);
|
||||
|
||||
@@ -473,265 +460,26 @@ int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check TLS cipher blacklist
|
||||
*/
|
||||
val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
|
||||
if (val && *val) {
|
||||
const char *source;
|
||||
if (cipher_is_blacklisted(val, &source)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052)
|
||||
"h2_h2(%ld): tls cipher %s blacklisted by %s",
|
||||
(long)c->id, val, source);
|
||||
if (!strcmp("TLSv1.2", val)) {
|
||||
/* Check TLS cipher blacklist, defined pre-TLSv1.3, so only
|
||||
* checking for 1.2 */
|
||||
val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
|
||||
if (val && *val) {
|
||||
const char *source;
|
||||
if (cipher_is_blacklisted(val, &source)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052)
|
||||
"h2_h2(%ld): tls cipher %s blacklisted by %s",
|
||||
(long)c->id, val, source);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (require_all) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053)
|
||||
"h2_h2(%ld): tls cipher is indetermined", (long)c->id);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (require_all) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053)
|
||||
"h2_h2(%ld): tls cipher is indetermined", (long)c->id);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int h2_allows_h2_direct(conn_rec *c)
|
||||
{
|
||||
int is_tls = ap_ssl_conn_is_ssl(c);
|
||||
const char *needed_protocol = is_tls? "h2" : "h2c";
|
||||
int h2_direct = h2_config_cgeti(c, H2_CONF_DIRECT);
|
||||
|
||||
if (h2_direct < 0) {
|
||||
h2_direct = is_tls? 0 : 1;
|
||||
}
|
||||
return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol));
|
||||
}
|
||||
|
||||
int h2_allows_h2_upgrade(request_rec *r)
|
||||
{
|
||||
int h2_upgrade = h2_config_rgeti(r, H2_CONF_UPGRADE);
|
||||
return h2_upgrade > 0 || (h2_upgrade < 0 && !ap_ssl_conn_is_ssl(r->connection));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Register various hooks
|
||||
*/
|
||||
static const char* const mod_ssl[] = { "mod_ssl.c", NULL};
|
||||
static const char* const mod_reqtimeout[] = { "mod_ssl.c", "mod_reqtimeout.c", NULL};
|
||||
|
||||
void h2_h2_register_hooks(void)
|
||||
{
|
||||
/* Our main processing needs to run quite late. Definitely after mod_ssl,
|
||||
* as we need its connection filters, but also before reqtimeout as its
|
||||
* method of timeouts is specific to HTTP/1.1 (as of now).
|
||||
* The core HTTP/1 processing run as REALLY_LAST, so we will have
|
||||
* a chance to take over before it.
|
||||
*/
|
||||
ap_hook_process_connection(h2_h2_process_conn,
|
||||
mod_reqtimeout, NULL, APR_HOOK_LAST);
|
||||
|
||||
/* One last chance to properly say goodbye if we have not done so
|
||||
* already. */
|
||||
ap_hook_pre_close_connection(h2_h2_pre_close_conn, NULL, mod_ssl, APR_HOOK_LAST);
|
||||
|
||||
/* With "H2SerializeHeaders On", we install the filter in this hook
|
||||
* that parses the response. This needs to happen before any other post
|
||||
* read function terminates the request with an error. Otherwise we will
|
||||
* never see the response.
|
||||
*/
|
||||
ap_hook_post_read_request(h2_h2_post_read_req, NULL, NULL, APR_HOOK_REALLY_FIRST);
|
||||
ap_hook_fixups(h2_h2_late_fixups, NULL, NULL, APR_HOOK_LAST);
|
||||
|
||||
/* special bucket type transfer through a h2_bucket_beam */
|
||||
h2_register_bucket_beamer(h2_bucket_headers_beam);
|
||||
h2_register_bucket_beamer(h2_bucket_observer_beam);
|
||||
}
|
||||
|
||||
int h2_h2_process_conn(conn_rec* c)
|
||||
{
|
||||
apr_status_t status;
|
||||
h2_ctx *ctx;
|
||||
server_rec *s;
|
||||
|
||||
if (c->master) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
ctx = h2_ctx_get(c, 0);
|
||||
s = ctx? ctx->server : c->base_server;
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
|
||||
if (ctx && ctx->task) {
|
||||
/* our stream pseudo connection */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, task, declined");
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
if (!ctx && c->keepalives == 0) {
|
||||
const char *proto = ap_get_protocol(c);
|
||||
|
||||
if (APLOGctrace1(c)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, "
|
||||
"new connection using protocol '%s', direct=%d, "
|
||||
"tls acceptable=%d", proto, h2_allows_h2_direct(c),
|
||||
h2_is_acceptable_connection(c, NULL, 1));
|
||||
}
|
||||
|
||||
if (!strcmp(AP_PROTOCOL_HTTP1, proto)
|
||||
&& h2_allows_h2_direct(c)
|
||||
&& h2_is_acceptable_connection(c, NULL, 1)) {
|
||||
/* Fresh connection still is on http/1.1 and H2Direct is enabled.
|
||||
* Otherwise connection is in a fully acceptable state.
|
||||
* -> peek at the first 24 incoming bytes
|
||||
*/
|
||||
apr_bucket_brigade *temp;
|
||||
char *peek = NULL;
|
||||
apr_size_t peeklen;
|
||||
|
||||
temp = apr_brigade_create(c->pool, c->bucket_alloc);
|
||||
status = ap_get_brigade(c->input_filters, temp,
|
||||
AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
|
||||
|
||||
if (status != APR_SUCCESS) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054)
|
||||
"h2_h2, error reading 24 bytes speculative");
|
||||
apr_brigade_destroy(temp);
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
apr_brigade_pflatten(temp, &peek, &peeklen, c->pool);
|
||||
if ((peeklen >= 24) && !memcmp(H2_MAGIC_TOKEN, peek, 24)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_h2, direct mode detected");
|
||||
if (!ctx) {
|
||||
ctx = h2_ctx_get(c, 1);
|
||||
}
|
||||
h2_ctx_protocol_set(ctx, ap_ssl_conn_is_ssl(c)? "h2" : "h2c");
|
||||
}
|
||||
else if (APLOGctrace2(c)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
|
||||
"h2_h2, not detected in %d bytes(base64): %s",
|
||||
(int)peeklen, h2_util_base64url_encode(peek, peeklen, c->pool));
|
||||
}
|
||||
|
||||
apr_brigade_destroy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn");
|
||||
|
||||
if (!h2_ctx_get_session(c)) {
|
||||
status = h2_conn_setup(c, NULL, s);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
|
||||
if (status != APR_SUCCESS) {
|
||||
h2_ctx_clear(c);
|
||||
return !OK;
|
||||
}
|
||||
}
|
||||
h2_conn_run(c);
|
||||
return OK;
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static int h2_h2_pre_close_conn(conn_rec *c)
|
||||
{
|
||||
h2_ctx *ctx;
|
||||
|
||||
/* secondary connection? */
|
||||
if (c->master) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
ctx = h2_ctx_get(c, 0);
|
||||
if (ctx) {
|
||||
/* If the session has been closed correctly already, we will not
|
||||
* find a h2_ctx here. The presence indicates that the session
|
||||
* is still ongoing. */
|
||||
return h2_conn_pre_close(ctx, c);
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static void check_push(request_rec *r, const char *tag)
|
||||
{
|
||||
apr_array_header_t *push_list = h2_config_push_list(r);
|
||||
|
||||
if (!r->expecting_100 && push_list && push_list->nelts > 0) {
|
||||
int i, old_status;
|
||||
const char *old_line;
|
||||
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
|
||||
"%s, early announcing %d resources for push",
|
||||
tag, push_list->nelts);
|
||||
for (i = 0; i < push_list->nelts; ++i) {
|
||||
h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
|
||||
apr_table_add(r->headers_out, "Link",
|
||||
apr_psprintf(r->pool, "<%s>; rel=preload%s",
|
||||
push->uri_ref, push->critical? "; critical" : ""));
|
||||
}
|
||||
old_status = r->status;
|
||||
old_line = r->status_line;
|
||||
r->status = 103;
|
||||
r->status_line = "103 Early Hints";
|
||||
ap_send_interim_response(r, 1);
|
||||
r->status = old_status;
|
||||
r->status_line = old_line;
|
||||
}
|
||||
}
|
||||
|
||||
static int h2_h2_post_read_req(request_rec *r)
|
||||
{
|
||||
/* secondary connection? */
|
||||
if (r->connection->master) {
|
||||
struct h2_task *task = h2_ctx_get_task(r->connection);
|
||||
/* This hook will get called twice on internal redirects. Take care
|
||||
* that we manipulate filters only once. */
|
||||
if (task && !task->filters_set) {
|
||||
ap_filter_t *f;
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
|
||||
"h2_task(%s): adding request filters", task->id);
|
||||
|
||||
/* setup the correct filters to process the request for h2 */
|
||||
ap_add_input_filter("H2_REQUEST", task, r, r->connection);
|
||||
|
||||
/* replace the core http filter that formats response headers
|
||||
* in HTTP/1 with our own that collects status and headers */
|
||||
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
|
||||
ap_add_output_filter("H2_RESPONSE", task, r, r->connection);
|
||||
|
||||
for (f = r->input_filters; f; f = f->next) {
|
||||
if (!strcmp("H2_SECONDARY_IN", f->frec->name)) {
|
||||
f->r = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ap_add_output_filter("H2_TRAILERS_OUT", task, r, r->connection);
|
||||
task->filters_set = 1;
|
||||
}
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static int h2_h2_late_fixups(request_rec *r)
|
||||
{
|
||||
/* secondary connection? */
|
||||
if (r->connection->master) {
|
||||
struct h2_task *task = h2_ctx_get_task(r->connection);
|
||||
if (task) {
|
||||
/* check if we copy vs. setaside files in this location */
|
||||
task->output.copy_files = h2_config_rgeti(r, H2_CONF_COPY_FILES);
|
||||
task->output.buffered = h2_config_rgeti(r, H2_CONF_OUTPUT_BUFFER);
|
||||
if (task->output.copy_files) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
|
||||
"h2_secondary_out(%s): copy_files on", task->id);
|
||||
h2_beam_on_file_beam(task->output.beam, h2_beam_no_files, NULL);
|
||||
}
|
||||
check_push(r, "late_fixup");
|
||||
}
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
@@ -14,54 +14,43 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_h2__
|
||||
#define __mod_h2__h2_h2__
|
||||
#ifndef __mod_h2__h2_protocol__
|
||||
#define __mod_h2__h2_protocol__
|
||||
|
||||
/**
|
||||
* List of ALPN protocol identifiers that we support in cleartext
|
||||
* List of protocol identifiers that we support in cleartext
|
||||
* negotiations. NULL terminated.
|
||||
*/
|
||||
extern const char *h2_clear_protos[];
|
||||
extern const char *h2_protocol_ids_clear[];
|
||||
|
||||
/**
|
||||
* List of ALPN protocol identifiers that we support in TLS encrypted
|
||||
* negotiations. NULL terminated.
|
||||
* List of protocol identifiers that we support in TLS encrypted
|
||||
* negotiations (ALPN). NULL terminated.
|
||||
*/
|
||||
extern const char *h2_tls_protos[];
|
||||
extern const char *h2_protocol_ids_tls[];
|
||||
|
||||
/**
|
||||
* Provide a user readable description of the HTTP/2 error code-
|
||||
* @param h2_error http/2 error code, as in rfc 7540, ch. 7
|
||||
* @return textual description of code or that it is unknown.
|
||||
*/
|
||||
const char *h2_h2_err_description(unsigned int h2_error);
|
||||
const char *h2_protocol_err_description(unsigned int h2_error);
|
||||
|
||||
/*
|
||||
* One time, post config initialization.
|
||||
*/
|
||||
apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s);
|
||||
|
||||
/* Register apache hooks for h2 protocol
|
||||
*/
|
||||
void h2_h2_register_hooks(void);
|
||||
apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s);
|
||||
|
||||
/**
|
||||
* Check if the given connection fulfills the requirements as configured.
|
||||
* Check if the given primary connection fulfills the protocol
|
||||
* requirements for HTTP/2.
|
||||
* @param c the connection
|
||||
* @param require_all != 0 iff any missing connection properties make
|
||||
* the test fail. For example, a cipher might not have been selected while
|
||||
* the handshake is still ongoing.
|
||||
* @return != 0 iff connection requirements are met
|
||||
* @return != 0 iff protocol requirements are met
|
||||
*/
|
||||
int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all);
|
||||
|
||||
/**
|
||||
* Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
|
||||
* for the given request.
|
||||
* @param r the request to check
|
||||
* @return != 0 iff Upgrade switching is enabled
|
||||
*/
|
||||
int h2_allows_h2_upgrade(request_rec *r);
|
||||
int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all);
|
||||
|
||||
|
||||
#endif /* defined(__mod_h2__h2_h2__) */
|
||||
#endif /* defined(__mod_h2__h2_protocol__) */
|
||||
@@ -692,8 +692,7 @@ static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id,
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef H2_NG2_INVALID_HEADER_CB
|
||||
static int on_invalid_header_cb(nghttp2_session *ngh2,
|
||||
static int on_invalid_header_cb(nghttp2_session *ngh2,
|
||||
const nghttp2_frame *frame,
|
||||
const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
@@ -711,7 +710,6 @@ static int on_invalid_header_cb(nghttp2_session *ngh2,
|
||||
frame->hd.stream_id,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
#endif
|
||||
|
||||
h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
|
||||
proxy_server_conf *conf,
|
||||
@@ -753,10 +751,8 @@ h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
|
||||
nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
|
||||
nghttp2_session_callbacks_set_before_frame_send_callback(cbs, before_frame_send);
|
||||
nghttp2_session_callbacks_set_send_callback(cbs, raw_send);
|
||||
#ifdef H2_NG2_INVALID_HEADER_CB
|
||||
nghttp2_session_callbacks_set_on_invalid_header_callback(cbs, on_invalid_header_cb);
|
||||
#endif
|
||||
|
||||
|
||||
nghttp2_option_new(&option);
|
||||
nghttp2_option_set_peer_max_concurrent_streams(option, 100);
|
||||
nghttp2_option_set_no_auto_window_update(option, 0);
|
||||
@@ -829,7 +825,7 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
|
||||
stream->input = apr_brigade_create(stream->pool, session->c->bucket_alloc);
|
||||
stream->output = apr_brigade_create(stream->pool, session->c->bucket_alloc);
|
||||
|
||||
stream->req = h2_proxy_req_create(1, stream->pool, 0);
|
||||
stream->req = h2_proxy_req_create(1, stream->pool);
|
||||
|
||||
status = apr_uri_parse(stream->pool, url, &puri);
|
||||
if (status != APR_SUCCESS)
|
||||
@@ -1141,7 +1137,7 @@ static apr_status_t session_shutdown(h2_proxy_session *session, int reason,
|
||||
if (!err && reason) {
|
||||
err = nghttp2_strerror(reason);
|
||||
}
|
||||
nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
|
||||
nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
|
||||
reason, (uint8_t*)err, err? strlen(err):0);
|
||||
status = nghttp2_session_send(session->ngh2);
|
||||
dispatch_event(session, H2_PROXYS_EV_LOCAL_GOAWAY, reason, err);
|
||||
@@ -1360,8 +1356,7 @@ static void ev_stream_done(h2_proxy_session *session, int stream_id,
|
||||
else if (!stream->data_received) {
|
||||
apr_bucket *b;
|
||||
/* if the response had no body, this is the time to flush
|
||||
* an empty brigade which will also write the resonse
|
||||
* headers */
|
||||
* an empty brigade which will also write the response headers */
|
||||
h2_proxy_stream_end_headers_out(stream);
|
||||
stream->data_received = 1;
|
||||
b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
|
||||
|
||||
@@ -583,8 +583,7 @@ static apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool,
|
||||
|
||||
static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const char *method,
|
||||
const char *scheme, const char *authority,
|
||||
const char *path, apr_table_t *header,
|
||||
int serialize)
|
||||
const char *path, apr_table_t *header)
|
||||
{
|
||||
h2_proxy_request *req = apr_pcalloc(pool, sizeof(h2_proxy_request));
|
||||
|
||||
@@ -594,14 +593,13 @@ static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const ch
|
||||
req->path = path;
|
||||
req->headers = header? header : apr_table_make(pool, 10);
|
||||
req->request_time = apr_time_now();
|
||||
req->serialize = serialize;
|
||||
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize)
|
||||
h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool)
|
||||
{
|
||||
return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL, serialize);
|
||||
return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -185,11 +185,10 @@ struct h2_proxy_request {
|
||||
|
||||
apr_time_t request_time;
|
||||
|
||||
unsigned int chunked : 1; /* iff request body needs to be forwarded as chunked */
|
||||
unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
|
||||
int chunked; /* iff request body needs to be forwarded as chunked */
|
||||
};
|
||||
|
||||
h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize);
|
||||
h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool);
|
||||
apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
|
||||
const char *method, const char *scheme,
|
||||
const char *authority, const char *path,
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include <http_log.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_util.h"
|
||||
#include "h2_push.h"
|
||||
#include "h2_request.h"
|
||||
@@ -348,9 +348,8 @@ static int add_push(link_ctx *ctx)
|
||||
}
|
||||
headers = apr_table_make(ctx->pool, 5);
|
||||
apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
|
||||
req = h2_req_create(0, ctx->pool, method, ctx->req->scheme,
|
||||
ctx->req->authority, path, headers,
|
||||
ctx->req->serialize);
|
||||
req = h2_request_create(0, ctx->pool, method, ctx->req->scheme,
|
||||
ctx->req->authority, path, headers);
|
||||
/* atm, we do not push on pushes */
|
||||
h2_request_end_headers(req, ctx->pool, 1, 0);
|
||||
push->req = req;
|
||||
@@ -657,13 +656,13 @@ apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t
|
||||
idx = h2_push_diary_find(session->push_diary, e.hash);
|
||||
if (idx >= 0) {
|
||||
/* Intentional no APLOGNO */
|
||||
ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
|
||||
ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
|
||||
"push_diary_update: already there PUSH %s", push->req->path);
|
||||
move_to_last(session->push_diary, (apr_size_t)idx);
|
||||
}
|
||||
else {
|
||||
/* Intentional no APLOGNO */
|
||||
ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
|
||||
ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
|
||||
"push_diary_update: adding PUSH %s", push->req->path);
|
||||
if (!npushes) {
|
||||
npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*));
|
||||
|
||||
@@ -38,6 +38,22 @@
|
||||
#include "h2_util.h"
|
||||
|
||||
|
||||
h2_request *h2_request_create(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));
|
||||
|
||||
req->method = method;
|
||||
req->scheme = scheme;
|
||||
req->authority = authority;
|
||||
req->path = path;
|
||||
req->headers = header? header : apr_table_make(pool, 10);
|
||||
req->request_time = apr_time_now();
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
apr_table_t *headers;
|
||||
apr_pool_t *pool;
|
||||
@@ -85,9 +101,6 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
|
||||
req->path = path;
|
||||
req->headers = apr_table_make(pool, 10);
|
||||
req->http_status = H2_HTTP_STATUS_UNSET;
|
||||
if (r->server) {
|
||||
req->serialize = h2_config_rgeti(r, H2_CONF_SER_HEADERS);
|
||||
}
|
||||
|
||||
x.pool = pool;
|
||||
x.headers = req->headers;
|
||||
@@ -195,7 +208,7 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos,
|
||||
}
|
||||
}
|
||||
req->raw_bytes += raw_bytes;
|
||||
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -267,7 +280,7 @@ static request_rec *my_ap_create_request(conn_rec *c)
|
||||
}
|
||||
#endif
|
||||
|
||||
request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
|
||||
request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c)
|
||||
{
|
||||
int access_status = HTTP_OK;
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
|
||||
#include "h2.h"
|
||||
|
||||
apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
|
||||
h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
|
||||
const char *scheme, const char *authority,
|
||||
const char *path, apr_table_t *header);
|
||||
|
||||
apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
|
||||
request_rec *r);
|
||||
|
||||
apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
|
||||
@@ -43,7 +47,7 @@ h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
|
||||
* @param conn the connection to process the request on
|
||||
* @return the request_rec representing the request
|
||||
*/
|
||||
request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn);
|
||||
request_rec *h2_create_request_rec(const h2_request *req, conn_rec *conn);
|
||||
|
||||
|
||||
#endif /* defined(__mod_h2__h2_request__) */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,25 +17,15 @@
|
||||
#ifndef __mod_h2__h2_session__
|
||||
#define __mod_h2__h2_session__
|
||||
|
||||
#include "h2_conn_io.h"
|
||||
#include "h2_c1_io.h"
|
||||
|
||||
/**
|
||||
* A HTTP/2 connection, a session with a specific client.
|
||||
*
|
||||
* h2_session sits on top of a httpd conn_rec* instance and takes complete
|
||||
* control of the connection data. It receives protocol frames from the
|
||||
* client. For new HTTP/2 streams it creates h2_task(s) that are sent
|
||||
* via callback to a dispatcher (see h2_conn.c).
|
||||
* h2_session keeps h2_io's for each ongoing stream which buffer the
|
||||
* payload for that stream.
|
||||
*
|
||||
* New incoming HEADER frames are converted into a h2_stream+h2_task instance
|
||||
* that both represent a HTTP/2 stream, but may have separate lifetimes. This
|
||||
* allows h2_task to be scheduled in other threads without semaphores
|
||||
* all over the place. It allows task memory to be freed independent of
|
||||
* session lifetime and sessions may close down while tasks are still running.
|
||||
*
|
||||
*
|
||||
* client. For new HTTP/2 streams it creates secondary connections
|
||||
* to execute the requests in h2 workers.
|
||||
*/
|
||||
|
||||
#include "h2.h"
|
||||
@@ -44,7 +34,6 @@ struct apr_thread_mutext_t;
|
||||
struct apr_thread_cond_t;
|
||||
struct h2_ctx;
|
||||
struct h2_config;
|
||||
struct h2_filter_cin;
|
||||
struct h2_ihash_t;
|
||||
struct h2_mplx;
|
||||
struct h2_priority;
|
||||
@@ -53,38 +42,37 @@ struct h2_push_diary;
|
||||
struct h2_session;
|
||||
struct h2_stream;
|
||||
struct h2_stream_monitor;
|
||||
struct h2_task;
|
||||
struct h2_workers;
|
||||
|
||||
struct nghttp2_session;
|
||||
|
||||
typedef enum {
|
||||
H2_SESSION_EV_INIT, /* session was initialized */
|
||||
H2_SESSION_EV_INPUT_PENDING, /* c1 input may have data pending */
|
||||
H2_SESSION_EV_INPUT_EXHAUSTED, /* c1 input exhausted */
|
||||
H2_SESSION_EV_LOCAL_GOAWAY, /* we send a GOAWAY */
|
||||
H2_SESSION_EV_REMOTE_GOAWAY, /* remote send us a GOAWAY */
|
||||
H2_SESSION_EV_CONN_ERROR, /* connection error */
|
||||
H2_SESSION_EV_PROTO_ERROR, /* protocol error */
|
||||
H2_SESSION_EV_CONN_TIMEOUT, /* connection timeout */
|
||||
H2_SESSION_EV_NO_IO, /* nothing has been read or written */
|
||||
H2_SESSION_EV_FRAME_RCVD, /* a frame has been received */
|
||||
H2_SESSION_EV_NGH2_DONE, /* nghttp2 wants neither read nor write anything */
|
||||
H2_SESSION_EV_MPM_STOPPING, /* the process is stopping */
|
||||
H2_SESSION_EV_PRE_CLOSE, /* connection will close after this */
|
||||
H2_SESSION_EV_STREAM_CHANGE, /* a stream (state/input/output) changed */
|
||||
H2_SESSION_EV_NO_MORE_STREAMS, /* no more streams to process */
|
||||
} h2_session_event_t;
|
||||
|
||||
typedef struct h2_session {
|
||||
long id; /* identifier of this session, unique
|
||||
* inside a httpd process */
|
||||
conn_rec *c; /* the connection this session serves */
|
||||
conn_rec *c1; /* the main connection this session serves */
|
||||
request_rec *r; /* the request that started this in case
|
||||
* of 'h2c', NULL otherwise */
|
||||
server_rec *s; /* server/vhost we're starting on */
|
||||
apr_pool_t *pool; /* pool to use in session */
|
||||
struct h2_mplx *mplx; /* multiplexer for stream data */
|
||||
struct h2_workers *workers; /* for executing stream tasks */
|
||||
struct h2_filter_cin *cin; /* connection input filter context */
|
||||
h2_conn_io io; /* io on httpd conn filters */
|
||||
struct h2_workers *workers; /* for executing streams */
|
||||
struct h2_c1_io_in_ctx_t *cin; /* connection input filter context */
|
||||
h2_c1_io io; /* io on httpd conn filters */
|
||||
int padding_max; /* max number of padding bytes */
|
||||
int padding_always; /* padding has precedence over I/O optimizations */
|
||||
struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */
|
||||
@@ -96,17 +84,13 @@ typedef struct h2_session {
|
||||
|
||||
unsigned int reprioritize : 1; /* scheduled streams priority changed */
|
||||
unsigned int flush : 1; /* flushing output necessary */
|
||||
unsigned int have_read : 1; /* session has read client data */
|
||||
unsigned int have_written : 1; /* session did write data to client */
|
||||
apr_interval_time_t wait_us; /* timeout during BUSY_WAIT state, micro secs */
|
||||
|
||||
struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */
|
||||
|
||||
struct h2_stream_monitor *monitor;/* monitor callbacks for streams */
|
||||
int open_streams; /* number of client streams open */
|
||||
int unsent_submits; /* number of submitted, but not yet written responses. */
|
||||
int unsent_promises; /* number of submitted, but not yet written push promises */
|
||||
|
||||
int open_streams; /* number of streams processing */
|
||||
|
||||
int responses_submitted; /* number of http/2 responses submitted */
|
||||
int streams_reset; /* number of http/2 streams reset by client */
|
||||
int pushes_promised; /* number of http/2 push promises submitted */
|
||||
@@ -119,20 +103,18 @@ typedef struct h2_session {
|
||||
apr_size_t max_stream_count; /* max number of open streams */
|
||||
apr_size_t max_stream_mem; /* max buffer memory for a single stream */
|
||||
|
||||
apr_time_t idle_until; /* Time we shut down due to sheer boredom */
|
||||
apr_time_t idle_sync_until; /* Time we sync wait until keepalive handling kicks in */
|
||||
apr_size_t idle_frames; /* number of rcvd frames that kept session in idle state */
|
||||
apr_interval_time_t idle_delay; /* Time we delay processing rcvd frames in idle state */
|
||||
|
||||
apr_bucket_brigade *bbtmp; /* brigade for keeping temporary data */
|
||||
struct apr_thread_cond_t *iowait; /* our cond when trywaiting for data */
|
||||
|
||||
|
||||
char status[64]; /* status message for scoreboard */
|
||||
int last_status_code; /* the one already reported */
|
||||
const char *last_status_msg; /* the one already reported */
|
||||
|
||||
struct h2_iqueue *in_pending; /* all streams with input pending */
|
||||
struct h2_iqueue *in_process; /* all streams ready for processing on a secondary */
|
||||
struct h2_iqueue *out_c1_blocked; /* all streams with output blocked on c1 buffer full */
|
||||
struct h2_iqueue *ready_to_process; /* all streams ready for processing */
|
||||
|
||||
} h2_session;
|
||||
|
||||
@@ -153,7 +135,7 @@ apr_status_t h2_session_create(h2_session **psession,
|
||||
struct h2_workers *workers);
|
||||
|
||||
void h2_session_event(h2_session *session, h2_session_event_t ev,
|
||||
int err, const char *msg);
|
||||
int err, const char *msg);
|
||||
|
||||
/**
|
||||
* Process the given HTTP/2 session until it is ended or a fatal
|
||||
@@ -176,11 +158,6 @@ apr_status_t h2_session_pre_close(h2_session *session, int async);
|
||||
*/
|
||||
void h2_session_abort(h2_session *session, apr_status_t reason);
|
||||
|
||||
/**
|
||||
* Close and deallocate the given session.
|
||||
*/
|
||||
void h2_session_close(h2_session *session);
|
||||
|
||||
/**
|
||||
* Returns if client settings have push enabled.
|
||||
* @param != 0 iff push is enabled in client settings
|
||||
@@ -203,6 +180,17 @@ apr_status_t h2_session_set_prio(h2_session *session,
|
||||
struct h2_stream *stream,
|
||||
const struct h2_priority *prio);
|
||||
|
||||
/**
|
||||
* Dispatch a event happending during session processing.
|
||||
* @param session the sessiont
|
||||
* @param ev the event that happened
|
||||
* @param arg integer argument (event type dependant)
|
||||
* @param msg destriptive message
|
||||
*/
|
||||
void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
|
||||
int arg, const char *msg);
|
||||
|
||||
|
||||
#define H2_SSSN_MSG(s, msg) \
|
||||
"h2_session(%ld,%s,%d): "msg, s->id, h2_session_state_str(s->state), \
|
||||
s->open_streams
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,6 @@ struct h2_priority;
|
||||
struct h2_request;
|
||||
struct h2_headers;
|
||||
struct h2_session;
|
||||
struct h2_task;
|
||||
struct h2_bucket_beam;
|
||||
|
||||
typedef struct h2_stream h2_stream;
|
||||
@@ -73,9 +72,11 @@ struct h2_stream {
|
||||
|
||||
const struct h2_request *request; /* the request made in this stream */
|
||||
struct h2_request *rtmp; /* request being assembled */
|
||||
apr_table_t *trailers; /* optional incoming trailers */
|
||||
apr_table_t *trailers_in; /* optional, incoming trailers */
|
||||
int request_headers_added; /* number of request headers added */
|
||||
|
||||
|
||||
struct h2_headers *response; /* the final, non-interim response or NULL */
|
||||
|
||||
struct h2_bucket_beam *input;
|
||||
apr_bucket_brigade *in_buffer;
|
||||
int in_window_size;
|
||||
@@ -83,18 +84,14 @@ struct h2_stream {
|
||||
|
||||
struct h2_bucket_beam *output;
|
||||
apr_bucket_brigade *out_buffer;
|
||||
apr_size_t max_mem; /* maximum amount of data buffered */
|
||||
|
||||
int rst_error; /* stream error for RST_STREAM */
|
||||
unsigned int aborted : 1; /* was aborted */
|
||||
unsigned int scheduled : 1; /* stream has been scheduled */
|
||||
unsigned int has_response : 1; /* response headers are known */
|
||||
unsigned int input_eof : 1; /* no more request data coming */
|
||||
unsigned int out_checked : 1; /* output eof was double checked */
|
||||
unsigned int input_closed : 1; /* no more request data/trailers coming */
|
||||
unsigned int push_policy; /* which push policy to use for this request */
|
||||
unsigned int input_buffering : 1; /* buffer request bodies for efficiency */
|
||||
|
||||
struct h2_task *task; /* assigned task to fullfill request */
|
||||
conn_rec *c2; /* connection processing stream */
|
||||
|
||||
const h2_priority *pref_priority; /* preferred priority for this stream */
|
||||
apr_off_t out_frames; /* # of frames sent out */
|
||||
@@ -133,13 +130,9 @@ h2_stream *h2_stream_create(int id, apr_pool_t *pool,
|
||||
void h2_stream_destroy(h2_stream *stream);
|
||||
|
||||
/**
|
||||
* Prepare the stream so that processing may start.
|
||||
*
|
||||
* This is the time to allocated resources not needed before.
|
||||
*
|
||||
* @param stream the stream to prep
|
||||
* Setup the input for the stream.
|
||||
*/
|
||||
apr_status_t h2_stream_prep_processing(h2_stream *stream);
|
||||
apr_status_t h2_stream_setup_input(h2_stream *stream);
|
||||
|
||||
/*
|
||||
* Set a new monitor for this stream, replacing any existing one. Can
|
||||
@@ -239,21 +232,10 @@ void h2_stream_rst(h2_stream *stream, int error_code);
|
||||
int h2_stream_was_closed(const h2_stream *stream);
|
||||
|
||||
/**
|
||||
* Do a speculative read on the stream output to determine the
|
||||
* amount of data that can be read.
|
||||
*
|
||||
* @param stream the stream to speculatively read from
|
||||
* @param plen (in-/out) number of bytes requested and on return amount of bytes that
|
||||
* may be read without blocking
|
||||
* @param peos (out) != 0 iff end of stream will be reached when reading plen
|
||||
* bytes (out value).
|
||||
* @param presponse (out) the response of one became available
|
||||
* @return APR_SUCCESS if out information was computed successfully.
|
||||
* APR_EAGAIN if not data is available and end of stream has not been
|
||||
* reached yet.
|
||||
* Inspect the c2 output for response(s) and data.
|
||||
* @param stream the stream to read output for
|
||||
*/
|
||||
apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen,
|
||||
int *peos, h2_headers **presponse);
|
||||
apr_status_t h2_stream_read_output(h2_stream *stream);
|
||||
|
||||
/**
|
||||
* Read a maximum number of bytes into the bucket brigade.
|
||||
@@ -282,7 +264,7 @@ apr_table_t *h2_stream_get_trailers(h2_stream *stream);
|
||||
|
||||
/**
|
||||
* Submit any server push promises on this stream and schedule
|
||||
* the tasks connection with these.
|
||||
* the streams for these.
|
||||
*
|
||||
* @param stream the stream for which to submit
|
||||
*/
|
||||
@@ -298,7 +280,7 @@ const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
|
||||
* Return a textual representation of the stream state as in RFC 7540
|
||||
* nomenclator, all caps, underscores.
|
||||
*/
|
||||
const char *h2_stream_state_str(h2_stream *stream);
|
||||
const char *h2_stream_state_str(const h2_stream *stream);
|
||||
|
||||
/**
|
||||
* Determine if stream is ready for submitting a response or a RST
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
#include "h2_private.h"
|
||||
|
||||
#include "h2_config.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_conn.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
#include "h2_c1.h"
|
||||
#include "h2_c2.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_switch.h"
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -54,7 +55,7 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
|
||||
{
|
||||
int proposed = 0;
|
||||
int is_tls = ap_ssl_conn_is_ssl(c);
|
||||
const char **protos = is_tls? h2_tls_protos : h2_clear_protos;
|
||||
const char **protos = is_tls? h2_protocol_ids_tls : h2_protocol_ids_clear;
|
||||
|
||||
if (!h2_mpm_supported()) {
|
||||
return DECLINED;
|
||||
@@ -68,7 +69,7 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
if (!h2_is_acceptable_connection(c, r, 0)) {
|
||||
if (!h2_protocol_is_acceptable_c1(c, r, 0)) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03084)
|
||||
"protocol propose: connection requirements not met");
|
||||
return DECLINED;
|
||||
@@ -81,7 +82,7 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
|
||||
*/
|
||||
const char *p;
|
||||
|
||||
if (!h2_allows_h2_upgrade(r)) {
|
||||
if (!h2_c1_can_upgrade(r)) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
|
||||
const char *protocol)
|
||||
{
|
||||
int found = 0;
|
||||
const char **protos = ap_ssl_conn_is_ssl(c)? h2_tls_protos : h2_clear_protos;
|
||||
const char **protos = ap_ssl_conn_is_ssl(c)? h2_protocol_ids_tls : h2_protocol_ids_clear;
|
||||
const char **p = protos;
|
||||
|
||||
(void)s;
|
||||
@@ -145,13 +146,12 @@ static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
|
||||
}
|
||||
|
||||
if (found) {
|
||||
h2_ctx *ctx = h2_ctx_get(c, 1);
|
||||
|
||||
h2_conn_ctx_t *ctx;
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"switching protocol to '%s'", protocol);
|
||||
h2_ctx_protocol_set(ctx, protocol);
|
||||
h2_ctx_server_update(ctx, s);
|
||||
|
||||
ctx = h2_conn_ctx_create_for_c1(c, s, protocol);
|
||||
|
||||
if (r != NULL) {
|
||||
apr_status_t status;
|
||||
/* Switching in the middle of a request means that
|
||||
@@ -163,16 +163,16 @@ static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
|
||||
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
|
||||
|
||||
/* Ok, start an h2_conn on this one. */
|
||||
status = h2_conn_setup(c, r, s);
|
||||
status = h2_c1_setup(c, r, s);
|
||||
|
||||
if (status != APR_SUCCESS) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03088)
|
||||
"session setup");
|
||||
h2_ctx_clear(c);
|
||||
h2_conn_ctx_detach(c);
|
||||
return !OK;
|
||||
}
|
||||
|
||||
h2_conn_run(c);
|
||||
h2_c1_run(c);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
@@ -182,7 +182,13 @@ static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
|
||||
|
||||
static const char *h2_protocol_get(const conn_rec *c)
|
||||
{
|
||||
return h2_ctx_protocol_get(c);
|
||||
h2_conn_ctx_t *ctx;
|
||||
|
||||
if (c->master) {
|
||||
c = c->master;
|
||||
}
|
||||
ctx = h2_conn_ctx_get(c);
|
||||
return ctx? ctx->protocol : NULL;
|
||||
}
|
||||
|
||||
void h2_switch_register_hooks(void)
|
||||
|
||||
@@ -1,725 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <apr_atomic.h>
|
||||
#include <apr_strings.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_core.h>
|
||||
#include <http_connection.h>
|
||||
#include <http_protocol.h>
|
||||
#include <http_request.h>
|
||||
#include <http_log.h>
|
||||
#include <http_vhost.h>
|
||||
#include <util_filter.h>
|
||||
#include <ap_mpm.h>
|
||||
#include <mod_core.h>
|
||||
#include <scoreboard.h>
|
||||
|
||||
#include "h2_private.h"
|
||||
#include "h2.h"
|
||||
#include "h2_bucket_beam.h"
|
||||
#include "h2_conn.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_from_h1.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_request.h"
|
||||
#include "h2_headers.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_stream.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_util.h"
|
||||
|
||||
static void H2_TASK_OUT_LOG(int lvl, h2_task *task, apr_bucket_brigade *bb,
|
||||
const char *tag)
|
||||
{
|
||||
if (APLOG_C_IS_LEVEL(task->c, lvl)) {
|
||||
conn_rec *c = task->c;
|
||||
char buffer[4 * 1024];
|
||||
const char *line = "(null)";
|
||||
apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
|
||||
|
||||
len = h2_util_bb_print(buffer, bmax, tag, "", bb);
|
||||
ap_log_cerror(APLOG_MARK, lvl, 0, c, "bb_dump(%s): %s",
|
||||
task->id, len? buffer : line);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* task input handling
|
||||
******************************************************************************/
|
||||
|
||||
static int input_ser_header(void *ctx, const char *name, const char *value)
|
||||
{
|
||||
h2_task *task = ctx;
|
||||
apr_brigade_printf(task->input.bb, NULL, NULL, "%s: %s\r\n", name, value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* task output handling
|
||||
******************************************************************************/
|
||||
|
||||
static apr_status_t open_output(h2_task *task)
|
||||
{
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03348)
|
||||
"h2_task(%s): open output to %s %s %s",
|
||||
task->id, task->request->method,
|
||||
task->request->authority,
|
||||
task->request->path);
|
||||
task->output.opened = 1;
|
||||
return h2_mplx_t_out_open(task->mplx, task->stream_id, task->output.beam);
|
||||
}
|
||||
|
||||
static void output_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
|
||||
{
|
||||
h2_task *task = ctx;
|
||||
if (task && h2_task_logio_add_bytes_out) {
|
||||
h2_task_logio_add_bytes_out(task->c, length);
|
||||
}
|
||||
}
|
||||
|
||||
static apr_status_t send_out(h2_task *task, apr_bucket_brigade* bb, int block)
|
||||
{
|
||||
apr_off_t written, left;
|
||||
apr_status_t status;
|
||||
|
||||
apr_brigade_length(bb, 0, &written);
|
||||
H2_TASK_OUT_LOG(APLOG_TRACE2, task, bb, "h2_task send_out");
|
||||
h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(before)");
|
||||
|
||||
status = h2_beam_send(task->output.beam, bb,
|
||||
block? APR_BLOCK_READ : APR_NONBLOCK_READ);
|
||||
h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(after)");
|
||||
|
||||
if (APR_STATUS_IS_EAGAIN(status)) {
|
||||
apr_brigade_length(bb, 0, &left);
|
||||
written -= left;
|
||||
status = APR_SUCCESS;
|
||||
}
|
||||
if (status == APR_SUCCESS) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
|
||||
"h2_task(%s): send_out done", task->id);
|
||||
}
|
||||
else {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, task->c,
|
||||
"h2_task(%s): send_out (%ld bytes)",
|
||||
task->id, (long)written);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Bring the data from the brigade (which represents the result of the
|
||||
* request_rec out filter chain) into the h2_mplx for further sending
|
||||
* on the master connection.
|
||||
*/
|
||||
static apr_status_t secondary_out(h2_task *task, ap_filter_t* f,
|
||||
apr_bucket_brigade* bb)
|
||||
{
|
||||
apr_bucket *b;
|
||||
apr_status_t rv = APR_SUCCESS;
|
||||
int flush = 0, blocking;
|
||||
|
||||
send:
|
||||
/* we send block once we opened the output, so someone is there reading it */
|
||||
blocking = task->output.opened;
|
||||
for (b = APR_BRIGADE_FIRST(bb);
|
||||
b != APR_BRIGADE_SENTINEL(bb);
|
||||
b = APR_BUCKET_NEXT(b)) {
|
||||
if (APR_BUCKET_IS_FLUSH(b) || APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
|
||||
flush = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (task->output.bb && !APR_BRIGADE_EMPTY(task->output.bb)) {
|
||||
/* still have data buffered from previous attempt.
|
||||
* setaside and append new data and try to pass the complete data */
|
||||
if (!APR_BRIGADE_EMPTY(bb)) {
|
||||
if (APR_SUCCESS != (rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool))) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
rv = send_out(task, task->output.bb, blocking);
|
||||
}
|
||||
else {
|
||||
/* no data buffered previously, pass brigade directly */
|
||||
rv = send_out(task, bb, blocking);
|
||||
|
||||
if (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
|
||||
/* output refused to buffer it all, time to open? */
|
||||
if (!task->output.opened && APR_SUCCESS == (rv = open_output(task))) {
|
||||
/* Make another attempt to send the data. With the output open,
|
||||
* the call might be blocking and send all data, so we do not need
|
||||
* to save the brigade */
|
||||
goto send;
|
||||
}
|
||||
else if (blocking && flush) {
|
||||
/* Need to keep on doing this. */
|
||||
goto send;
|
||||
}
|
||||
|
||||
if (APR_SUCCESS == rv) {
|
||||
/* could not write all, buffer the rest */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, task->c, APLOGNO(03405)
|
||||
"h2_secondary_out(%s): saving brigade", task->id);
|
||||
ap_assert(NULL);
|
||||
rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool);
|
||||
flush = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
|
||||
"h2_secondary_out(%s): buffered=%d", task->id, task->output.buffered);
|
||||
if (APR_SUCCESS == rv && !task->output.opened && (flush || !task->output.buffered)) {
|
||||
/* got a flush or could not write all, time to tell someone to read */
|
||||
rv = open_output(task);
|
||||
}
|
||||
out:
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, task->c,
|
||||
"h2_secondary_out(%s): secondary_out leave", task->id);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static apr_status_t output_finish(h2_task *task)
|
||||
{
|
||||
if (!task->output.opened) {
|
||||
return open_output(task);
|
||||
}
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* task secondary connection filters
|
||||
******************************************************************************/
|
||||
|
||||
static apr_status_t h2_filter_secondary_in(ap_filter_t* f,
|
||||
apr_bucket_brigade* bb,
|
||||
ap_input_mode_t mode,
|
||||
apr_read_type_e block,
|
||||
apr_off_t readbytes)
|
||||
{
|
||||
h2_task *task;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_bucket *b, *next;
|
||||
apr_off_t bblen;
|
||||
const int trace1 = APLOGctrace1(f->c);
|
||||
apr_size_t rmax = ((readbytes <= APR_SIZE_MAX)?
|
||||
(apr_size_t)readbytes : APR_SIZE_MAX);
|
||||
|
||||
task = h2_ctx_get_task(f->c);
|
||||
ap_assert(task);
|
||||
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_secondary_in(%s): read, mode=%d, block=%d, readbytes=%ld",
|
||||
task->id, mode, block, (long)readbytes);
|
||||
}
|
||||
|
||||
if (mode == AP_MODE_INIT) {
|
||||
return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
|
||||
}
|
||||
|
||||
if (f->c->aborted) {
|
||||
return APR_ECONNABORTED;
|
||||
}
|
||||
|
||||
if (!task->input.bb) {
|
||||
return APR_EOF;
|
||||
}
|
||||
|
||||
/* Cleanup brigades from those nasty 0 length non-meta buckets
|
||||
* that apr_brigade_split_line() sometimes produces. */
|
||||
for (b = APR_BRIGADE_FIRST(task->input.bb);
|
||||
b != APR_BRIGADE_SENTINEL(task->input.bb); b = next) {
|
||||
next = APR_BUCKET_NEXT(b);
|
||||
if (b->length == 0 && !APR_BUCKET_IS_METADATA(b)) {
|
||||
apr_bucket_delete(b);
|
||||
}
|
||||
}
|
||||
|
||||
while (APR_BRIGADE_EMPTY(task->input.bb)) {
|
||||
/* Get more input data for our request. */
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_secondary_in(%s): get more data from mplx, block=%d, "
|
||||
"readbytes=%ld", task->id, block, (long)readbytes);
|
||||
}
|
||||
if (task->input.beam) {
|
||||
status = h2_beam_receive(task->input.beam, task->input.bb, block,
|
||||
128*1024, NULL);
|
||||
}
|
||||
else {
|
||||
status = APR_EOF;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
|
||||
"h2_secondary_in(%s): read returned", task->id);
|
||||
}
|
||||
if (APR_STATUS_IS_EAGAIN(status)
|
||||
&& (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
|
||||
/* chunked input handling does not seem to like it if we
|
||||
* return with APR_EAGAIN from a GETLINE read...
|
||||
* upload 100k test on test-ser.example.org hangs */
|
||||
status = APR_SUCCESS;
|
||||
}
|
||||
else if (APR_STATUS_IS_EOF(status)) {
|
||||
break;
|
||||
}
|
||||
else if (status != APR_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2,
|
||||
"input.beam recv raw", task->input.bb);
|
||||
}
|
||||
if (h2_task_logio_add_bytes_in) {
|
||||
apr_brigade_length(bb, 0, &bblen);
|
||||
h2_task_logio_add_bytes_in(f->c, bblen);
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing there, no more data to get. Return. */
|
||||
if (status == APR_EOF && APR_BRIGADE_EMPTY(task->input.bb)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2,
|
||||
"task_input.bb", task->input.bb);
|
||||
}
|
||||
|
||||
if (APR_BRIGADE_EMPTY(task->input.bb)) {
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
|
||||
"h2_secondary_in(%s): no data", task->id);
|
||||
}
|
||||
return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
|
||||
}
|
||||
|
||||
if (mode == AP_MODE_EXHAUSTIVE) {
|
||||
/* return all we have */
|
||||
APR_BRIGADE_CONCAT(bb, task->input.bb);
|
||||
}
|
||||
else if (mode == AP_MODE_READBYTES) {
|
||||
status = h2_brigade_concat_length(bb, task->input.bb, rmax);
|
||||
}
|
||||
else if (mode == AP_MODE_SPECULATIVE) {
|
||||
status = h2_brigade_copy_length(bb, task->input.bb, rmax);
|
||||
}
|
||||
else if (mode == AP_MODE_GETLINE) {
|
||||
/* we are reading a single LF line, e.g. the HTTP headers.
|
||||
* this has the nasty side effect to split the bucket, even
|
||||
* though it ends with CRLF and creates a 0 length bucket */
|
||||
status = apr_brigade_split_line(bb, task->input.bb, block,
|
||||
HUGE_STRING_LEN);
|
||||
if (APLOGctrace1(f->c)) {
|
||||
char buffer[1024];
|
||||
apr_size_t len = sizeof(buffer)-1;
|
||||
apr_brigade_flatten(bb, buffer, &len);
|
||||
buffer[len] = 0;
|
||||
if (trace1) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_secondary_in(%s): getline: %s",
|
||||
task->id, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
|
||||
* to support it. Seems to work. */
|
||||
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
|
||||
APLOGNO(03472)
|
||||
"h2_secondary_in(%s), unsupported READ mode %d",
|
||||
task->id, mode);
|
||||
status = APR_ENOTIMPL;
|
||||
}
|
||||
|
||||
if (trace1) {
|
||||
apr_brigade_length(bb, 0, &bblen);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
|
||||
"h2_secondary_in(%s): %ld data bytes", task->id, (long)bblen);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static apr_status_t h2_filter_secondary_output(ap_filter_t* filter,
|
||||
apr_bucket_brigade* brigade)
|
||||
{
|
||||
h2_task *task = h2_ctx_get_task(filter->c);
|
||||
apr_status_t status;
|
||||
|
||||
ap_assert(task);
|
||||
status = secondary_out(task, filter, brigade);
|
||||
if (status != APR_SUCCESS) {
|
||||
h2_task_rst(task, H2_ERR_INTERNAL_ERROR);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static apr_status_t h2_filter_parse_h1(ap_filter_t* f, apr_bucket_brigade* bb)
|
||||
{
|
||||
h2_task *task = h2_ctx_get_task(f->c);
|
||||
apr_status_t status;
|
||||
|
||||
ap_assert(task);
|
||||
/* There are cases where we need to parse a serialized http/1.1
|
||||
* response. One example is a 100-continue answer in serialized mode
|
||||
* or via a mod_proxy setup */
|
||||
while (bb && !task->c->aborted && !task->output.sent_response) {
|
||||
status = h2_from_h1_parse_response(task, f, bb);
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
|
||||
"h2_task(%s): parsed response", task->id);
|
||||
if (APR_BRIGADE_EMPTY(bb) || status != APR_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return ap_pass_brigade(f->next, bb);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* task things
|
||||
******************************************************************************/
|
||||
|
||||
int h2_task_can_redo(h2_task *task) {
|
||||
if (task->input.beam && h2_beam_was_received(task->input.beam)) {
|
||||
/* cannot repeat that. */
|
||||
return 0;
|
||||
}
|
||||
return (!strcmp("GET", task->request->method)
|
||||
|| !strcmp("HEAD", task->request->method)
|
||||
|| !strcmp("OPTIONS", task->request->method));
|
||||
}
|
||||
|
||||
int h2_task_has_started(h2_task *task)
|
||||
{
|
||||
return task && task->started_at != 0;
|
||||
}
|
||||
|
||||
void h2_task_redo(h2_task *task)
|
||||
{
|
||||
task->started_at = 0;
|
||||
task->worker_done = 0;
|
||||
task->rst_error = 0;
|
||||
}
|
||||
|
||||
void h2_task_rst(h2_task *task, int error)
|
||||
{
|
||||
task->rst_error = error;
|
||||
if (task->input.beam) {
|
||||
h2_beam_leave(task->input.beam);
|
||||
}
|
||||
if (!task->worker_done) {
|
||||
h2_beam_abort(task->output.beam);
|
||||
}
|
||||
if (task->c) {
|
||||
task->c->aborted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Register various hooks
|
||||
*/
|
||||
static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
|
||||
static int h2_task_pre_conn(conn_rec* c, void *arg);
|
||||
static int h2_task_process_conn(conn_rec* c);
|
||||
|
||||
APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_task_logio_add_bytes_in;
|
||||
APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_task_logio_add_bytes_out;
|
||||
|
||||
void h2_task_register_hooks(void)
|
||||
{
|
||||
/* This hook runs on new connections before mod_ssl has a say.
|
||||
* Its purpose is to prevent mod_ssl from touching our pseudo-connections
|
||||
* for streams.
|
||||
*/
|
||||
ap_hook_pre_connection(h2_task_pre_conn,
|
||||
NULL, mod_ssl, APR_HOOK_FIRST);
|
||||
/* When the connection processing actually starts, we might
|
||||
* take over, if the connection is for a task.
|
||||
*/
|
||||
ap_hook_process_connection(h2_task_process_conn,
|
||||
NULL, NULL, APR_HOOK_FIRST);
|
||||
|
||||
ap_register_input_filter("H2_SECONDARY_IN", h2_filter_secondary_in,
|
||||
NULL, AP_FTYPE_NETWORK);
|
||||
ap_register_output_filter("H2_SECONDARY_OUT", h2_filter_secondary_output,
|
||||
NULL, AP_FTYPE_NETWORK);
|
||||
ap_register_output_filter("H2_PARSE_H1", h2_filter_parse_h1,
|
||||
NULL, AP_FTYPE_NETWORK);
|
||||
|
||||
ap_register_input_filter("H2_REQUEST", h2_filter_request_in,
|
||||
NULL, AP_FTYPE_PROTOCOL);
|
||||
ap_register_output_filter("H2_RESPONSE", h2_filter_headers_out,
|
||||
NULL, AP_FTYPE_PROTOCOL);
|
||||
ap_register_output_filter("H2_TRAILERS_OUT", h2_filter_trailers_out,
|
||||
NULL, AP_FTYPE_PROTOCOL);
|
||||
}
|
||||
|
||||
/* post config init */
|
||||
apr_status_t h2_task_init(apr_pool_t *pool, server_rec *s)
|
||||
{
|
||||
h2_task_logio_add_bytes_in = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_in);
|
||||
h2_task_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static int h2_task_pre_conn(conn_rec* c, void *arg)
|
||||
{
|
||||
h2_ctx *ctx;
|
||||
|
||||
if (!c->master) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
ctx = h2_ctx_get(c, 0);
|
||||
(void)arg;
|
||||
if (ctx->task) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
|
||||
"h2_secondary(%s), pre_connection, adding filters", c->log_id);
|
||||
ap_add_input_filter("H2_SECONDARY_IN", NULL, NULL, c);
|
||||
ap_add_output_filter("H2_PARSE_H1", NULL, NULL, c);
|
||||
ap_add_output_filter("H2_SECONDARY_OUT", NULL, NULL, c);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
h2_task *h2_task_create(conn_rec *secondary, int stream_id,
|
||||
const h2_request *req, h2_mplx *m,
|
||||
h2_bucket_beam *input,
|
||||
apr_interval_time_t timeout,
|
||||
apr_size_t output_max_mem)
|
||||
{
|
||||
apr_pool_t *pool;
|
||||
h2_task *task;
|
||||
|
||||
ap_assert(secondary);
|
||||
ap_assert(req);
|
||||
|
||||
apr_pool_create(&pool, secondary->pool);
|
||||
apr_pool_tag(pool, "h2_task");
|
||||
task = apr_pcalloc(pool, sizeof(h2_task));
|
||||
if (task == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
task->id = "000";
|
||||
task->stream_id = stream_id;
|
||||
task->c = secondary;
|
||||
task->mplx = m;
|
||||
task->pool = pool;
|
||||
task->request = req;
|
||||
task->timeout = timeout;
|
||||
task->input.beam = input;
|
||||
task->output.max_buffer = output_max_mem;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
void h2_task_destroy(h2_task *task)
|
||||
{
|
||||
if (task->output.beam) {
|
||||
h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "task_destroy");
|
||||
h2_beam_destroy(task->output.beam);
|
||||
task->output.beam = NULL;
|
||||
}
|
||||
|
||||
if (task->eor) {
|
||||
apr_bucket_destroy(task->eor);
|
||||
}
|
||||
if (task->pool) {
|
||||
apr_pool_destroy(task->pool);
|
||||
}
|
||||
}
|
||||
|
||||
apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id)
|
||||
{
|
||||
conn_rec *c;
|
||||
|
||||
ap_assert(task);
|
||||
c = task->c;
|
||||
task->worker_started = 1;
|
||||
|
||||
if (c->master) {
|
||||
/* See the discussion at <https://github.com/icing/mod_h2/issues/195>
|
||||
*
|
||||
* Each conn_rec->id is supposed to be unique at a point in time. Since
|
||||
* some modules (and maybe external code) uses this id as an identifier
|
||||
* for the request_rec they handle, it needs to be unique for secondary
|
||||
* connections also.
|
||||
*
|
||||
* The MPM module assigns the connection ids and mod_unique_id is using
|
||||
* that one to generate identifier for requests. While the implementation
|
||||
* works for HTTP/1.x, the parallel execution of several requests per
|
||||
* connection will generate duplicate identifiers on load.
|
||||
*
|
||||
* The original implementation for secondary connection identifiers used
|
||||
* to shift the master connection id up and assign the stream id to the
|
||||
* lower bits. This was cramped on 32 bit systems, but on 64bit there was
|
||||
* enough space.
|
||||
*
|
||||
* As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
|
||||
* connection id, even on 64bit systems. Therefore collisions in request ids.
|
||||
*
|
||||
* The way master connection ids are generated, there is some space "at the
|
||||
* top" of the lower 32 bits on allmost all systems. If you have a setup
|
||||
* with 64k threads per child and 255 child processes, you live on the edge.
|
||||
*
|
||||
* The new implementation shifts 8 bits and XORs in the worker
|
||||
* id. This will experience collisions with > 256 h2 workers and heavy
|
||||
* load still. There seems to be no way to solve this in all possible
|
||||
* configurations by mod_h2 alone.
|
||||
*/
|
||||
task->c->id = (c->master->id << 8)^worker_id;
|
||||
task->id = apr_psprintf(task->pool, "%ld-%d", task->mplx->id,
|
||||
task->stream_id);
|
||||
}
|
||||
|
||||
h2_beam_create(&task->output.beam, c->pool, task->stream_id, "output",
|
||||
H2_BEAM_OWNER_SEND, 0, task->timeout);
|
||||
if (!task->output.beam) {
|
||||
return APR_ENOMEM;
|
||||
}
|
||||
|
||||
h2_beam_buffer_size_set(task->output.beam, task->output.max_buffer);
|
||||
h2_beam_send_from(task->output.beam, task->pool);
|
||||
h2_beam_on_consumed(task->output.beam, NULL, output_consumed, task);
|
||||
|
||||
h2_ctx_create_for(c, task);
|
||||
apr_table_setn(c->notes, H2_TASK_ID_NOTE, task->id);
|
||||
|
||||
h2_secondary_run_pre_connection(c, ap_get_conn_socket(c));
|
||||
|
||||
task->input.bb = apr_brigade_create(task->pool, c->bucket_alloc);
|
||||
if (task->request->serialize) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): serialize request %s %s",
|
||||
task->id, task->request->method, task->request->path);
|
||||
apr_brigade_printf(task->input.bb, NULL,
|
||||
NULL, "%s %s HTTP/1.1\r\n",
|
||||
task->request->method, task->request->path);
|
||||
apr_table_do(input_ser_header, task, task->request->headers, NULL);
|
||||
apr_brigade_puts(task->input.bb, NULL, NULL, "\r\n");
|
||||
}
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): process connection", task->id);
|
||||
|
||||
task->c->current_thread = thread;
|
||||
ap_run_process_connection(c);
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): processing done", task->id);
|
||||
return output_finish(task);
|
||||
}
|
||||
|
||||
static apr_status_t h2_task_process_request(h2_task *task, conn_rec *c)
|
||||
{
|
||||
const h2_request *req = task->request;
|
||||
conn_state_t *cs = c->cs;
|
||||
request_rec *r;
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): create request_rec", task->id);
|
||||
r = h2_request_create_rec(req, c);
|
||||
if (r && (r->status == HTTP_OK)) {
|
||||
/* set timeouts for virtual host of request */
|
||||
if (task->timeout != r->server->timeout) {
|
||||
task->timeout = r->server->timeout;
|
||||
h2_beam_timeout_set(task->output.beam, task->timeout);
|
||||
if (task->input.beam) {
|
||||
h2_beam_timeout_set(task->input.beam, task->timeout);
|
||||
}
|
||||
}
|
||||
|
||||
ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
|
||||
|
||||
if (cs) {
|
||||
cs->state = CONN_STATE_HANDLER;
|
||||
}
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): start process_request", task->id);
|
||||
|
||||
/* Add the raw bytes of the request (e.g. header frame lengths to
|
||||
* the logio for this request. */
|
||||
if (req->raw_bytes && h2_task_logio_add_bytes_in) {
|
||||
h2_task_logio_add_bytes_in(c, req->raw_bytes);
|
||||
}
|
||||
|
||||
ap_process_request(r);
|
||||
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): process_request done", task->id);
|
||||
|
||||
/* After the call to ap_process_request, the
|
||||
* request pool may have been deleted. We set
|
||||
* r=NULL here to ensure that any dereference
|
||||
* of r that might be added later in this function
|
||||
* will result in a segfault immediately instead
|
||||
* of nondeterministic failures later.
|
||||
*/
|
||||
if (cs)
|
||||
cs->state = CONN_STATE_WRITE_COMPLETION;
|
||||
r = NULL;
|
||||
}
|
||||
else if (!r) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): create request_rec failed, r=NULL", task->id);
|
||||
}
|
||||
else {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s): create request_rec failed, r->status=%d",
|
||||
task->id, r->status);
|
||||
}
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
static int h2_task_process_conn(conn_rec* c)
|
||||
{
|
||||
h2_ctx *ctx;
|
||||
|
||||
if (!c->master) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
ctx = h2_ctx_get(c, 0);
|
||||
if (ctx->task) {
|
||||
if (!ctx->task->request->serialize) {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_h2, processing request directly");
|
||||
h2_task_process_request(ctx->task, c);
|
||||
return DONE;
|
||||
}
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"h2_task(%s), serialized handling", ctx->task->id);
|
||||
}
|
||||
else {
|
||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
|
||||
"secondary_conn(%ld): has no task", c->id);
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __mod_h2__h2_task__
|
||||
#define __mod_h2__h2_task__
|
||||
|
||||
#include <http_core.h>
|
||||
|
||||
/**
|
||||
* A h2_task fakes a HTTP/1.1 request from the data in a HTTP/2 stream
|
||||
* (HEADER+CONT.+DATA) the module receives.
|
||||
*
|
||||
* In order to answer a HTTP/2 stream, we want all Apache httpd infrastructure
|
||||
* to be involved as usual, as if this stream can as a separate HTTP/1.1
|
||||
* request. The basic trickery to do so was derived from google's mod_spdy
|
||||
* source. Basically, we fake a new conn_rec object, even with its own
|
||||
* socket and give it to ap_process_connection().
|
||||
*
|
||||
* Since h2_task instances are executed in separate threads, we may have
|
||||
* different lifetimes than our h2_stream or h2_session instances. Basically,
|
||||
* we would like to be as standalone as possible.
|
||||
*
|
||||
* Finally, to keep certain connection level filters, such as ourselves and
|
||||
* especially mod_ssl ones, from messing with our data, we need a filter
|
||||
* of our own to disable those.
|
||||
*/
|
||||
|
||||
struct h2_bucket_beam;
|
||||
struct h2_conn;
|
||||
struct h2_mplx;
|
||||
struct h2_task;
|
||||
struct h2_request;
|
||||
struct h2_response_parser;
|
||||
struct h2_stream;
|
||||
struct h2_worker;
|
||||
|
||||
typedef struct h2_task h2_task;
|
||||
|
||||
struct h2_task {
|
||||
const char *id;
|
||||
int stream_id;
|
||||
conn_rec *c;
|
||||
apr_pool_t *pool;
|
||||
|
||||
const struct h2_request *request;
|
||||
apr_interval_time_t timeout;
|
||||
int rst_error; /* h2 related stream abort error */
|
||||
|
||||
struct {
|
||||
struct h2_bucket_beam *beam;
|
||||
unsigned int eos : 1;
|
||||
apr_bucket_brigade *bb;
|
||||
apr_bucket_brigade *bbchunk;
|
||||
apr_off_t chunked_total;
|
||||
} input;
|
||||
struct {
|
||||
struct h2_bucket_beam *beam;
|
||||
unsigned int opened : 1;
|
||||
unsigned int sent_response : 1;
|
||||
unsigned int copy_files : 1;
|
||||
unsigned int buffered : 1;
|
||||
struct h2_response_parser *rparser;
|
||||
apr_bucket_brigade *bb;
|
||||
apr_size_t max_buffer;
|
||||
} output;
|
||||
|
||||
struct h2_mplx *mplx;
|
||||
|
||||
unsigned int filters_set : 1;
|
||||
unsigned int worker_started : 1; /* h2_worker started processing */
|
||||
unsigned int redo : 1; /* was throttled, should be restarted later */
|
||||
|
||||
int worker_done; /* h2_worker finished */
|
||||
int done_done; /* task_done has been handled */
|
||||
|
||||
apr_time_t started_at; /* when processing started */
|
||||
apr_time_t done_at; /* when processing was done */
|
||||
apr_bucket *eor;
|
||||
};
|
||||
|
||||
h2_task *h2_task_create(conn_rec *secondary, int stream_id,
|
||||
const h2_request *req, struct h2_mplx *m,
|
||||
struct h2_bucket_beam *input,
|
||||
apr_interval_time_t timeout,
|
||||
apr_size_t output_max_mem);
|
||||
|
||||
void h2_task_destroy(h2_task *task);
|
||||
|
||||
apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id);
|
||||
|
||||
void h2_task_redo(h2_task *task);
|
||||
int h2_task_can_redo(h2_task *task);
|
||||
int h2_task_has_started(h2_task *task);
|
||||
|
||||
/**
|
||||
* Reset the task with the given error code, resets all input/output.
|
||||
*/
|
||||
void h2_task_rst(h2_task *task, int error);
|
||||
|
||||
void h2_task_register_hooks(void);
|
||||
/*
|
||||
* One time, post config initialization.
|
||||
*/
|
||||
apr_status_t h2_task_init(apr_pool_t *pool, server_rec *s);
|
||||
|
||||
extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_task_logio_add_bytes_in;
|
||||
extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_task_logio_add_bytes_out;
|
||||
|
||||
#endif /* defined(__mod_h2__h2_task__) */
|
||||
@@ -352,11 +352,9 @@ static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
|
||||
h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity)
|
||||
{
|
||||
h2_iqueue *q = apr_pcalloc(pool, sizeof(h2_iqueue));
|
||||
if (q) {
|
||||
q->pool = pool;
|
||||
iq_grow(q, capacity);
|
||||
q->nelts = 0;
|
||||
}
|
||||
q->pool = pool;
|
||||
iq_grow(q, capacity);
|
||||
q->nelts = 0;
|
||||
return q;
|
||||
}
|
||||
|
||||
@@ -441,7 +439,7 @@ void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
|
||||
ni = iq_bubble_up(q, i, prev, cmp, ctx);
|
||||
if (ni == prev) {
|
||||
/* i bubbled one up, bubble the new i down, which
|
||||
* keeps all tasks below i sorted. */
|
||||
* keeps all ints below i sorted. */
|
||||
iq_bubble_down(q, i, last, cmp, ctx);
|
||||
}
|
||||
i = prev;
|
||||
@@ -1157,14 +1155,11 @@ apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra)
|
||||
|
||||
static apr_status_t last_not_included(apr_bucket_brigade *bb,
|
||||
apr_off_t maxlen,
|
||||
int same_alloc,
|
||||
apr_size_t *pfile_buckets_allowed,
|
||||
apr_bucket **pend)
|
||||
{
|
||||
apr_bucket *b;
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
int files_allowed = pfile_buckets_allowed? (int)*pfile_buckets_allowed : 0;
|
||||
|
||||
|
||||
if (maxlen >= 0) {
|
||||
/* Find the bucket, up to which we reach maxlen/mem bytes */
|
||||
for (b = APR_BRIGADE_FIRST(bb);
|
||||
@@ -1189,14 +1184,12 @@ static apr_status_t last_not_included(apr_bucket_brigade *bb,
|
||||
return status;
|
||||
}
|
||||
|
||||
if (same_alloc && APR_BUCKET_IS_FILE(b)) {
|
||||
/* we like it move it, always */
|
||||
}
|
||||
else if (files_allowed > 0 && APR_BUCKET_IS_FILE(b)) {
|
||||
/* this has no memory footprint really unless
|
||||
* it is read, disregard it in length count,
|
||||
* unless we do not move the file buckets */
|
||||
--files_allowed;
|
||||
if (APR_BUCKET_IS_FILE(b)
|
||||
#if APR_HAS_MMAP
|
||||
|| APR_BUCKET_IS_MMAP(b)
|
||||
#endif
|
||||
) {
|
||||
/* we like to move it, always */
|
||||
}
|
||||
else if (maxlen < (apr_off_t)b->length) {
|
||||
apr_bucket_split(b, (apr_size_t)maxlen);
|
||||
@@ -1308,7 +1301,7 @@ int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len)
|
||||
{
|
||||
apr_bucket *b, *end;
|
||||
|
||||
apr_status_t status = last_not_included(bb, len, 0, 0, &end);
|
||||
apr_status_t status = last_not_included(bb, len, &end);
|
||||
if (status != APR_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
@@ -1343,7 +1336,7 @@ apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
|
||||
else {
|
||||
/* data in the brigade, limit the length returned. Check for EOS
|
||||
* bucket only if we indicate data. This is required since plen == 0
|
||||
* means "the whole brigade" for h2_util_hash_eos()
|
||||
* means "the whole brigade" for h2_util_has_eos()
|
||||
*/
|
||||
if (blen < *plen || *plen < 0) {
|
||||
*plen = blen;
|
||||
@@ -1353,82 +1346,7 @@ apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb,
|
||||
h2_util_pass_cb *cb, void *ctx,
|
||||
apr_off_t *plen, int *peos)
|
||||
{
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
int consume = (cb != NULL);
|
||||
apr_off_t written = 0;
|
||||
apr_off_t avail = *plen;
|
||||
apr_bucket *next, *b;
|
||||
|
||||
/* Pass data in our brigade through the callback until the length
|
||||
* is satisfied or we encounter an EOS.
|
||||
*/
|
||||
*peos = 0;
|
||||
for (b = APR_BRIGADE_FIRST(bb);
|
||||
(status == APR_SUCCESS) && (b != APR_BRIGADE_SENTINEL(bb));
|
||||
b = next) {
|
||||
|
||||
if (APR_BUCKET_IS_METADATA(b)) {
|
||||
if (APR_BUCKET_IS_EOS(b)) {
|
||||
*peos = 1;
|
||||
}
|
||||
else {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
else if (avail <= 0) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
const char *data = NULL;
|
||||
apr_size_t data_len;
|
||||
|
||||
if (b->length == ((apr_size_t)-1)) {
|
||||
/* read to determine length */
|
||||
status = apr_bucket_read(b, &data, &data_len, APR_NONBLOCK_READ);
|
||||
}
|
||||
else {
|
||||
data_len = b->length;
|
||||
}
|
||||
|
||||
if (data_len > avail) {
|
||||
apr_bucket_split(b, avail);
|
||||
data_len = (apr_size_t)avail;
|
||||
}
|
||||
|
||||
if (consume) {
|
||||
if (!data) {
|
||||
status = apr_bucket_read(b, &data, &data_len,
|
||||
APR_NONBLOCK_READ);
|
||||
}
|
||||
if (status == APR_SUCCESS) {
|
||||
status = cb(ctx, data, data_len);
|
||||
}
|
||||
}
|
||||
else {
|
||||
data_len = b->length;
|
||||
}
|
||||
avail -= data_len;
|
||||
written += data_len;
|
||||
}
|
||||
|
||||
next = APR_BUCKET_NEXT(b);
|
||||
if (consume) {
|
||||
apr_bucket_delete(b);
|
||||
}
|
||||
}
|
||||
|
||||
*plen = written;
|
||||
if (status == APR_SUCCESS && !*peos && !*plen) {
|
||||
return APR_EAGAIN;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
|
||||
apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
|
||||
apr_bucket *b, const char *sep)
|
||||
{
|
||||
apr_size_t off = 0;
|
||||
@@ -1655,7 +1573,7 @@ static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p,
|
||||
return APR_ENOMEM;
|
||||
}
|
||||
|
||||
ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
|
||||
ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
|
||||
if (!ctx.ngh->nv) {
|
||||
return APR_ENOMEM;
|
||||
}
|
||||
@@ -1854,27 +1772,6 @@ apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* h2 request handling
|
||||
******************************************************************************/
|
||||
|
||||
h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method,
|
||||
const char *scheme, const char *authority,
|
||||
const char *path, apr_table_t *header, int serialize)
|
||||
{
|
||||
h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
|
||||
|
||||
req->method = method;
|
||||
req->scheme = scheme;
|
||||
req->authority = authority;
|
||||
req->path = path;
|
||||
req->headers = header? header : apr_table_make(pool, 10);
|
||||
req->request_time = apr_time_now();
|
||||
req->serialize = serialize;
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* frame logging
|
||||
******************************************************************************/
|
||||
@@ -1992,3 +1889,26 @@ int h2_push_policy_determine(apr_table_t *headers, apr_pool_t *p, int push_enabl
|
||||
return policy;
|
||||
}
|
||||
|
||||
void h2_util_drain_pipe(apr_file_t *pipe)
|
||||
{
|
||||
char rb[512];
|
||||
apr_size_t nr = sizeof(rb);
|
||||
|
||||
while (apr_file_read(pipe, rb, &nr) == APR_SUCCESS) {
|
||||
/* Although we write just one byte to the other end of the pipe
|
||||
* during wakeup, multiple threads could call the wakeup.
|
||||
* So simply drain out from the input side of the pipe all
|
||||
* the data.
|
||||
*/
|
||||
if (nr != sizeof(rb))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe)
|
||||
{
|
||||
char rb[512];
|
||||
apr_size_t nr = sizeof(rb);
|
||||
|
||||
return apr_file_read(pipe, rb, &nr);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ typedef int h2_iq_cmp(int i1, int i2, void *ctx);
|
||||
h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity);
|
||||
|
||||
/**
|
||||
* Return != 0 iff there are no tasks in the queue.
|
||||
* Return != 0 iff there are no ints in the queue.
|
||||
* @param q the queue to check
|
||||
*/
|
||||
int h2_iq_empty(h2_iqueue *q);
|
||||
@@ -134,11 +134,10 @@ int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx);
|
||||
int h2_iq_append(h2_iqueue *q, int sid);
|
||||
|
||||
/**
|
||||
* Remove the stream id from the queue. Return != 0 iff task
|
||||
* was found in queue.
|
||||
* @param q the task queue
|
||||
* Remove the int from the queue. Return != 0 iff it was found.
|
||||
* @param q the queue
|
||||
* @param sid the stream id to remove
|
||||
* @return != 0 iff task was found in queue
|
||||
* @return != 0 iff int was found in queue
|
||||
*/
|
||||
int h2_iq_remove(h2_iqueue *q, int sid);
|
||||
|
||||
@@ -148,7 +147,7 @@ int h2_iq_remove(h2_iqueue *q, int sid);
|
||||
void h2_iq_clear(h2_iqueue *q);
|
||||
|
||||
/**
|
||||
* Sort the stream idqueue again. Call if the task ordering
|
||||
* Sort the stream idqueue again. Call if the int ordering
|
||||
* has changed.
|
||||
*
|
||||
* @param q the queue to sort
|
||||
@@ -169,7 +168,7 @@ int h2_iq_shift(h2_iqueue *q);
|
||||
/**
|
||||
* Get the first max ids from the queue. All these ids will be removed.
|
||||
*
|
||||
* @param q the queue to get the first task from
|
||||
* @param q the queue to get the first ids from
|
||||
* @param pint the int array to receive the values
|
||||
* @param max the maximum number of ids to shift
|
||||
* @return the actual number of ids shifted
|
||||
@@ -419,15 +418,6 @@ apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
|
||||
const char *value, size_t vlen,
|
||||
size_t max_field_len, int *pwas_added);
|
||||
|
||||
/*******************************************************************************
|
||||
* h2_request helpers
|
||||
******************************************************************************/
|
||||
|
||||
struct h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method,
|
||||
const char *scheme, const char *authority,
|
||||
const char *path, apr_table_t *header,
|
||||
int serialize);
|
||||
|
||||
/*******************************************************************************
|
||||
* apr brigade helpers
|
||||
******************************************************************************/
|
||||
@@ -468,22 +458,6 @@ apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
|
||||
typedef apr_status_t h2_util_pass_cb(void *ctx,
|
||||
const char *data, apr_off_t len);
|
||||
|
||||
/**
|
||||
* Read at most *plen bytes from the brigade and pass them into the
|
||||
* given callback. If cb is NULL, just return the amount of data that
|
||||
* could have been read.
|
||||
* If an EOS was/would be encountered, set *peos != 0.
|
||||
* @param bb the brigade to read from
|
||||
* @param cb the callback to invoke for the read data
|
||||
* @param ctx optional data passed to callback
|
||||
* @param plen inout, as input gives the maximum number of bytes to read,
|
||||
* on return specifies the actual/would be number of bytes
|
||||
* @param peos != 0 iff an EOS bucket was/would be encountered.
|
||||
*/
|
||||
apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb,
|
||||
h2_util_pass_cb *cb, void *ctx,
|
||||
apr_off_t *plen, int *peos);
|
||||
|
||||
/**
|
||||
* Print a bucket's meta data (type and length) to the buffer.
|
||||
* @return number of characters printed
|
||||
@@ -509,14 +483,16 @@ apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
|
||||
* @param bb the brigade to log
|
||||
*/
|
||||
#define h2_util_bb_log(c, sid, level, tag, bb) \
|
||||
do { \
|
||||
char buffer[4 * 1024]; \
|
||||
const char *line = "(null)"; \
|
||||
apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
|
||||
len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
|
||||
ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
|
||||
((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
|
||||
} while(0)
|
||||
if (APLOG_C_IS_LEVEL(c, level)) { \
|
||||
do { \
|
||||
char buffer[4 * 1024]; \
|
||||
const char *line = "(null)"; \
|
||||
apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
|
||||
len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
|
||||
ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
|
||||
((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
|
||||
} while(0); \
|
||||
}
|
||||
|
||||
|
||||
typedef int h2_bucket_gate(apr_bucket *b);
|
||||
@@ -544,4 +520,14 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
|
||||
*/
|
||||
apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb);
|
||||
|
||||
/**
|
||||
* Drain a pipe used for notification.
|
||||
*/
|
||||
void h2_util_drain_pipe(apr_file_t *pipe);
|
||||
|
||||
/**
|
||||
* Wait on data arriving on a pipe.
|
||||
*/
|
||||
apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe);
|
||||
|
||||
#endif /* defined(__mod_h2__h2_util__) */
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
* @macro
|
||||
* Version number of the http2 module as c string
|
||||
*/
|
||||
#define MOD_HTTP2_VERSION "1.15.24"
|
||||
#define MOD_HTTP2_VERSION "2.0.0"
|
||||
|
||||
/**
|
||||
* @macro
|
||||
@@ -35,7 +35,7 @@
|
||||
* 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 0x010f18
|
||||
#define MOD_HTTP2_VERSION_NUM 0x020000
|
||||
|
||||
|
||||
#endif /* mod_h2_h2_version_h */
|
||||
|
||||
@@ -27,17 +27,16 @@
|
||||
#include "h2.h"
|
||||
#include "h2_private.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_c2.h"
|
||||
#include "h2_workers.h"
|
||||
#include "h2_util.h"
|
||||
|
||||
typedef struct h2_slot h2_slot;
|
||||
struct h2_slot {
|
||||
int id;
|
||||
int sticks;
|
||||
h2_slot *next;
|
||||
h2_workers *workers;
|
||||
h2_task *task;
|
||||
conn_rec *connection;
|
||||
apr_thread_t *thread;
|
||||
apr_thread_mutex_t *lock;
|
||||
apr_thread_cond_t *not_idle;
|
||||
@@ -79,7 +78,7 @@ static apr_status_t activate_slot(h2_workers *workers, h2_slot *slot)
|
||||
apr_status_t rv;
|
||||
|
||||
slot->workers = workers;
|
||||
slot->task = NULL;
|
||||
slot->connection = NULL;
|
||||
|
||||
apr_thread_mutex_lock(workers->lock);
|
||||
if (!slot->lock) {
|
||||
@@ -158,20 +157,14 @@ static void join_zombies(h2_workers *workers)
|
||||
}
|
||||
}
|
||||
|
||||
static apr_status_t slot_pull_task(h2_slot *slot, h2_mplx *m)
|
||||
static apr_status_t slot_pull_c2(h2_slot *slot, h2_mplx *m)
|
||||
{
|
||||
apr_status_t rv;
|
||||
|
||||
rv = h2_mplx_s_pop_task(m, &slot->task);
|
||||
if (slot->task) {
|
||||
/* Ok, we got something to give back to the worker for execution.
|
||||
* If we still have idle workers, we let the worker be sticky,
|
||||
* e.g. making it poll the task's h2_mplx instance for more work
|
||||
* before asking back here. */
|
||||
slot->sticks = slot->workers->max_workers;
|
||||
return rv;
|
||||
rv = h2_mplx_worker_pop_c2(m, &slot->connection);
|
||||
if (slot->connection) {
|
||||
return rv;
|
||||
}
|
||||
slot->sticks = 0;
|
||||
return APR_EOF;
|
||||
}
|
||||
|
||||
@@ -180,7 +173,7 @@ static h2_fifo_op_t mplx_peek(void *head, void *ctx)
|
||||
h2_mplx *m = head;
|
||||
h2_slot *slot = ctx;
|
||||
|
||||
if (slot_pull_task(slot, m) == APR_EAGAIN) {
|
||||
if (slot_pull_c2(slot, m) == APR_EAGAIN) {
|
||||
wake_idle_worker(slot->workers);
|
||||
return H2_FIFO_OP_REPUSH;
|
||||
}
|
||||
@@ -188,7 +181,7 @@ static h2_fifo_op_t mplx_peek(void *head, void *ctx)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next task for the given worker. Will block until a task arrives
|
||||
* Get the next c2 for the given worker. Will block until a c2 arrives
|
||||
* or the max_wait timer expires and more than min workers exist.
|
||||
*/
|
||||
static int get_next(h2_slot *slot)
|
||||
@@ -198,7 +191,7 @@ static int get_next(h2_slot *slot)
|
||||
apr_status_t rv;
|
||||
|
||||
while (!workers->aborted && !slot->timed_out) {
|
||||
ap_assert(slot->task == NULL);
|
||||
ap_assert(slot->connection == NULL);
|
||||
if (non_essential && workers->shutdown) {
|
||||
/* Terminate non-essential worker on shutdown */
|
||||
break;
|
||||
@@ -208,7 +201,7 @@ static int get_next(h2_slot *slot)
|
||||
* just leave. */
|
||||
break;
|
||||
}
|
||||
if (slot->task) {
|
||||
if (slot->connection) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -256,23 +249,20 @@ static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
|
||||
{
|
||||
h2_slot *slot = wctx;
|
||||
|
||||
/* Get the h2_task(s) from the ->mplxs queue. */
|
||||
/* Get the next c2 from mplx to process. */
|
||||
while (get_next(slot)) {
|
||||
ap_assert(slot->task != NULL);
|
||||
do {
|
||||
h2_task_do(slot->task, thread, slot->id);
|
||||
|
||||
/* Report the task as done. If stickyness is left, offer the
|
||||
* mplx the opportunity to give us back a new task right away.
|
||||
*/
|
||||
if (!slot->workers->aborted && --slot->sticks > 0) {
|
||||
h2_mplx_s_task_done(slot->task->mplx, slot->task, &slot->task);
|
||||
ap_assert(slot->connection != NULL);
|
||||
h2_c2_process(slot->connection, thread, slot->id);
|
||||
if (!slot->workers->aborted &&
|
||||
apr_atomic_read32(&slot->workers->worker_count) < slot->workers->max_workers) {
|
||||
h2_mplx_worker_c2_done(slot->connection, &slot->connection);
|
||||
}
|
||||
else {
|
||||
h2_mplx_s_task_done(slot->task->mplx, slot->task, NULL);
|
||||
slot->task = NULL;
|
||||
h2_mplx_worker_c2_done(slot->connection, NULL);
|
||||
slot->connection = NULL;
|
||||
}
|
||||
} while (slot->task);
|
||||
} while (slot->connection);
|
||||
}
|
||||
|
||||
if (!slot->timed_out) {
|
||||
@@ -396,17 +386,9 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
|
||||
workers->min_workers, workers->max_workers,
|
||||
(int)apr_time_sec(workers->max_idle_duration));
|
||||
/* FIXME: the fifo set we use here has limited capacity. Once the
|
||||
* set is full, connections with new requests do a wait. Unfortunately,
|
||||
* we have optimizations in place there that makes such waiting "unfair"
|
||||
* in the sense that it may take connections a looong time to get scheduled.
|
||||
*
|
||||
* Need to rewrite this to use one of our double-linked lists and a mutex
|
||||
* to have unlimited capacity and fair scheduling.
|
||||
*
|
||||
* For now, we just make enough room to have many connections inside one
|
||||
* process.
|
||||
* set is full, connections with new requests do a wait.
|
||||
*/
|
||||
rv = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
|
||||
rv = h2_fifo_set_create(&workers->mplxs, pool, 16 * 1024);
|
||||
if (rv != APR_SUCCESS) goto cleanup;
|
||||
|
||||
rv = apr_threadattr_create(&workers->thread_attr, workers->pool);
|
||||
|
||||
@@ -17,16 +17,15 @@
|
||||
#ifndef __mod_h2__h2_workers__
|
||||
#define __mod_h2__h2_workers__
|
||||
|
||||
/* Thread pool specific to executing h2_tasks. Has a minimum and maximum
|
||||
* number of workers it creates. Starts with minimum workers and adds
|
||||
* some on load, reduces the number again when idle.
|
||||
*
|
||||
/* Thread pool specific to executing secondary connections.
|
||||
* Has a minimum and maximum number of workers it creates.
|
||||
* Starts with minimum workers and adds some on load,
|
||||
* reduces the number again when idle.
|
||||
*/
|
||||
struct apr_thread_mutex_t;
|
||||
struct apr_thread_cond_t;
|
||||
struct h2_mplx;
|
||||
struct h2_request;
|
||||
struct h2_task;
|
||||
struct h2_fifo;
|
||||
|
||||
struct h2_slot;
|
||||
@@ -70,9 +69,9 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
|
||||
int min_size, int max_size, int idle_secs);
|
||||
|
||||
/**
|
||||
* Registers a h2_mplx for task scheduling. If this h2_mplx runs
|
||||
* out of tasks, it will be automatically be unregistered. Should
|
||||
* new tasks arrive, it needs to be registered again.
|
||||
* Registers a h2_mplx for scheduling. If this h2_mplx runs
|
||||
* out of work, it will be automatically be unregistered. Should
|
||||
* new work arrive, it needs to be registered again.
|
||||
*/
|
||||
apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m);
|
||||
|
||||
|
||||
@@ -30,19 +30,18 @@
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
#include "h2_stream.h"
|
||||
#include "h2_alt_svc.h"
|
||||
#include "h2_conn.h"
|
||||
#include "h2_filter.h"
|
||||
#include "h2_task.h"
|
||||
#include "h2_c1.h"
|
||||
#include "h2_c2.h"
|
||||
#include "h2_session.h"
|
||||
#include "h2_config.h"
|
||||
#include "h2_ctx.h"
|
||||
#include "h2_h2.h"
|
||||
#include "h2_conn_ctx.h"
|
||||
#include "h2_protocol.h"
|
||||
#include "h2_mplx.h"
|
||||
#include "h2_push.h"
|
||||
#include "h2_request.h"
|
||||
#include "h2_switch.h"
|
||||
#include "h2_version.h"
|
||||
#include "h2_bucket_beam.h"
|
||||
|
||||
|
||||
static void h2_hooks(apr_pool_t *pool);
|
||||
@@ -158,12 +157,12 @@ static int h2_post_config(apr_pool_t *p, apr_pool_t *plog,
|
||||
h2_conn_mpm_name());
|
||||
}
|
||||
|
||||
status = h2_h2_init(p, s);
|
||||
status = h2_protocol_init(p, s);
|
||||
if (status == APR_SUCCESS) {
|
||||
status = h2_switch_init(p, s);
|
||||
}
|
||||
if (status == APR_SUCCESS) {
|
||||
status = h2_task_init(p, s);
|
||||
status = h2_c2_init(p, s);
|
||||
}
|
||||
|
||||
return status;
|
||||
@@ -185,7 +184,7 @@ static void h2_child_init(apr_pool_t *pchild, server_rec *s)
|
||||
{
|
||||
apr_allocator_t *allocator;
|
||||
apr_thread_mutex_t *mutex;
|
||||
apr_status_t status;
|
||||
apr_status_t rv;
|
||||
|
||||
/* The allocator of pchild has no mutex with MPM prefork, but we need one
|
||||
* for h2 workers threads synchronization. Even though mod_http2 shouldn't
|
||||
@@ -203,9 +202,12 @@ static void h2_child_init(apr_pool_t *pchild, server_rec *s)
|
||||
}
|
||||
|
||||
/* Set up our connection processing */
|
||||
status = h2_conn_child_init(pchild, s);
|
||||
if (status != APR_SUCCESS) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
|
||||
rv = h2_c1_child_init(pchild, s);
|
||||
if (APR_SUCCESS == rv) {
|
||||
rv = h2_c2_child_init(pchild, s);
|
||||
}
|
||||
if (APR_SUCCESS != rv) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
|
||||
APLOGNO(02949) "initializing connection handling");
|
||||
}
|
||||
}
|
||||
@@ -230,41 +232,38 @@ static void h2_hooks(apr_pool_t *pool)
|
||||
*/
|
||||
ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
#if AP_MODULE_MAGIC_AT_LEAST(20120211, 110)
|
||||
ap_hook_child_stopping(h2_conn_child_stopping, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
ap_hook_child_stopping(h2_c1_child_stopping, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
#endif
|
||||
h2_h2_register_hooks();
|
||||
h2_switch_register_hooks();
|
||||
h2_task_register_hooks();
|
||||
|
||||
h2_alt_svc_register_hooks();
|
||||
|
||||
/* Setup subprocess env for certain variables
|
||||
h2_c1_register_hooks();
|
||||
h2_switch_register_hooks();
|
||||
h2_c2_register_hooks();
|
||||
|
||||
/* Setup subprocess env for certain variables
|
||||
*/
|
||||
ap_hook_fixups(h2_h2_fixups, NULL,NULL, APR_HOOK_MIDDLE);
|
||||
|
||||
/* test http2 connection status handler */
|
||||
ap_hook_handler(h2_filter_h2_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
}
|
||||
|
||||
static const char *val_HTTP2(apr_pool_t *p, server_rec *s,
|
||||
conn_rec *c, request_rec *r, h2_ctx *ctx)
|
||||
conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
|
||||
{
|
||||
return ctx? "on" : "off";
|
||||
}
|
||||
|
||||
static const char *val_H2_PUSH(apr_pool_t *p, server_rec *s,
|
||||
conn_rec *c, request_rec *r, h2_ctx *ctx)
|
||||
conn_rec *c, request_rec *r,
|
||||
h2_conn_ctx_t *conn_ctx)
|
||||
{
|
||||
if (ctx) {
|
||||
if (conn_ctx) {
|
||||
if (r) {
|
||||
if (ctx->task) {
|
||||
h2_stream *stream = h2_mplx_t_stream_get(ctx->task->mplx, ctx->task);
|
||||
if (conn_ctx->stream_id) {
|
||||
const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
|
||||
if (stream && stream->push_policy != H2_PUSH_NONE) {
|
||||
return "on";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c && h2_session_push_enabled(ctx->session)) {
|
||||
else if (c && h2_session_push_enabled(conn_ctx->session)) {
|
||||
return "on";
|
||||
}
|
||||
}
|
||||
@@ -277,10 +276,11 @@ static const char *val_H2_PUSH(apr_pool_t *p, server_rec *s,
|
||||
}
|
||||
|
||||
static const char *val_H2_PUSHED(apr_pool_t *p, server_rec *s,
|
||||
conn_rec *c, request_rec *r, h2_ctx *ctx)
|
||||
conn_rec *c, request_rec *r,
|
||||
h2_conn_ctx_t *conn_ctx)
|
||||
{
|
||||
if (ctx) {
|
||||
if (ctx->task && !H2_STREAM_CLIENT_INITIATED(ctx->task->stream_id)) {
|
||||
if (conn_ctx) {
|
||||
if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
|
||||
return "PUSHED";
|
||||
}
|
||||
}
|
||||
@@ -288,11 +288,12 @@ static const char *val_H2_PUSHED(apr_pool_t *p, server_rec *s,
|
||||
}
|
||||
|
||||
static const char *val_H2_PUSHED_ON(apr_pool_t *p, server_rec *s,
|
||||
conn_rec *c, request_rec *r, h2_ctx *ctx)
|
||||
conn_rec *c, request_rec *r,
|
||||
h2_conn_ctx_t *conn_ctx)
|
||||
{
|
||||
if (ctx) {
|
||||
if (ctx->task && !H2_STREAM_CLIENT_INITIATED(ctx->task->stream_id)) {
|
||||
h2_stream *stream = h2_mplx_t_stream_get(ctx->task->mplx, ctx->task);
|
||||
if (conn_ctx) {
|
||||
if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
|
||||
const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
|
||||
if (stream) {
|
||||
return apr_itoa(p, stream->initiated_on);
|
||||
}
|
||||
@@ -302,18 +303,20 @@ static const char *val_H2_PUSHED_ON(apr_pool_t *p, server_rec *s,
|
||||
}
|
||||
|
||||
static const char *val_H2_STREAM_TAG(apr_pool_t *p, server_rec *s,
|
||||
conn_rec *c, request_rec *r, h2_ctx *ctx)
|
||||
conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
|
||||
{
|
||||
if (ctx) {
|
||||
if (ctx->task) {
|
||||
return ctx->task->id;
|
||||
if (c) {
|
||||
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
|
||||
if (conn_ctx) {
|
||||
return conn_ctx->stream_id == 0? conn_ctx->id
|
||||
: apr_psprintf(p, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s,
|
||||
conn_rec *c, request_rec *r, h2_ctx *ctx)
|
||||
conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
|
||||
{
|
||||
const char *cp = val_H2_STREAM_TAG(p, s, c, r, ctx);
|
||||
if (cp && (cp = ap_strchr_c(cp, '-'))) {
|
||||
@@ -323,7 +326,7 @@ static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s,
|
||||
}
|
||||
|
||||
typedef const char *h2_var_lookup(apr_pool_t *p, server_rec *s,
|
||||
conn_rec *c, request_rec *r, h2_ctx *ctx);
|
||||
conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx);
|
||||
typedef struct h2_var_def {
|
||||
const char *name;
|
||||
h2_var_lookup *lookup;
|
||||
@@ -347,7 +350,7 @@ static h2_var_def H2_VARS[] = {
|
||||
|
||||
static int http2_is_h2(conn_rec *c)
|
||||
{
|
||||
return h2_ctx_get(c->master? c->master : c, 0) != NULL;
|
||||
return h2_conn_ctx_get(c->master? c->master : c) != NULL;
|
||||
}
|
||||
|
||||
static char *http2_var_lookup(apr_pool_t *p, server_rec *s,
|
||||
@@ -358,8 +361,8 @@ static char *http2_var_lookup(apr_pool_t *p, server_rec *s,
|
||||
for (i = 0; i < H2_ALEN(H2_VARS); ++i) {
|
||||
h2_var_def *vdef = &H2_VARS[i];
|
||||
if (!strcmp(vdef->name, name)) {
|
||||
h2_ctx *ctx = (r? h2_ctx_get(c, 0) :
|
||||
h2_ctx_get(c->master? c->master : c, 0));
|
||||
h2_conn_ctx_t *ctx = (r? h2_conn_ctx_get(c) :
|
||||
h2_conn_ctx_get(c->master? c->master : c));
|
||||
return (char *)vdef->lookup(p, s, c, r, ctx);
|
||||
}
|
||||
}
|
||||
@@ -369,8 +372,9 @@ static char *http2_var_lookup(apr_pool_t *p, server_rec *s,
|
||||
static int h2_h2_fixups(request_rec *r)
|
||||
{
|
||||
if (r->connection->master) {
|
||||
h2_ctx *ctx = h2_ctx_get(r->connection, 0);
|
||||
h2_conn_ctx_t *ctx = h2_conn_ctx_get(r->connection);
|
||||
int i;
|
||||
apr_interval_time_t stream_timeout;
|
||||
|
||||
for (i = 0; ctx && i < H2_ALEN(H2_VARS); ++i) {
|
||||
h2_var_def *vdef = &H2_VARS[i];
|
||||
@@ -380,6 +384,10 @@ static int h2_h2_fixups(request_rec *r)
|
||||
r, ctx));
|
||||
}
|
||||
}
|
||||
stream_timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT);
|
||||
if (stream_timeout > 0) {
|
||||
h2_conn_ctx_set_timeout(ctx, stream_timeout);
|
||||
}
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
@@ -101,10 +101,6 @@ PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).ma
|
||||
# Name "mod_http2 - Win32 Debug"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_alt_svc.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_bucket_beam.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
@@ -113,31 +109,31 @@ SOURCE=./h2_bucket_eos.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_c1.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_c1_io.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_c2.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_c2_filter.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_config.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_conn.c
|
||||
SOURCE=./h2_conn_ctx.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_conn_io.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_ctx.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_filter.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_from_h1.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_h2.c
|
||||
SOURCE=./h2_headers.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
@@ -145,6 +141,10 @@ SOURCE=./h2_mplx.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_protocol.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_push.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
@@ -153,10 +153,6 @@ SOURCE=./h2_request.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_headers.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_session.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
@@ -169,10 +165,6 @@ SOURCE=./h2_switch.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_task.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./h2_util.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
@@ -185,6 +177,10 @@ SOURCE=./mod_http2.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=./mod_http2.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\..\build\win32\httpd.rc
|
||||
# End Source File
|
||||
# End Target
|
||||
|
||||
@@ -291,7 +291,7 @@ static int proxy_http2_handler(request_rec *r,
|
||||
const char *proxyname,
|
||||
apr_port_t proxyport)
|
||||
{
|
||||
const char *proxy_func, *task_id;
|
||||
const char *proxy_func;
|
||||
char *locurl = url, *u;
|
||||
apr_size_t slen;
|
||||
int is_ssl = 0;
|
||||
@@ -324,11 +324,9 @@ static int proxy_http2_handler(request_rec *r,
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
task_id = apr_table_get(r->connection->notes, H2_TASK_ID_NOTE);
|
||||
|
||||
ctx = apr_pcalloc(r->pool, sizeof(*ctx));
|
||||
ctx->master = r->connection->master? r->connection->master : r->connection;
|
||||
ctx->id = task_id? task_id : apr_psprintf(r->pool, "%ld", (long)ctx->master->id);
|
||||
ctx->id = apr_psprintf(r->pool, "%ld", (long)ctx->master->id);
|
||||
ctx->owner = r->connection;
|
||||
ctx->pool = r->pool;
|
||||
ctx->server = r->server;
|
||||
|
||||
@@ -111,7 +111,6 @@ class TestStore:
|
||||
assert piper.exitcode == 0
|
||||
assert len("".join(stdout)) == 3 * 8192
|
||||
|
||||
@pytest.mark.skipif(True, reason="new feature in upcoming http2")
|
||||
def test_h2_105_11(self, env):
|
||||
# short connection timeout, longer stream delay
|
||||
# receiving the first response chunk, then timeout
|
||||
@@ -126,7 +125,6 @@ class TestStore:
|
||||
stdout, stderr = piper.close()
|
||||
assert len("".join(stdout)) == 8192
|
||||
|
||||
@pytest.mark.skipif(True, reason="new feature in upcoming http2")
|
||||
def test_h2_105_12(self, env):
|
||||
# long connection timeout, short stream timeout
|
||||
# sending a slow POST
|
||||
|
||||
@@ -37,7 +37,6 @@ class TestBuffering:
|
||||
piper = CurlPiper(env=env, url=url)
|
||||
piper.stutter_check(chunks, stutter)
|
||||
|
||||
@pytest.mark.skipif(True, reason="new feature in upcoming http2")
|
||||
def test_h2_712_02(self, env):
|
||||
# same as 712_01 but via mod_proxy_http2
|
||||
#
|
||||
@@ -48,7 +47,6 @@ class TestBuffering:
|
||||
piper = CurlPiper(env=env, url=url)
|
||||
piper.stutter_check(chunks, stutter)
|
||||
|
||||
@pytest.mark.skipif(True, reason="new feature in upcoming http2")
|
||||
def test_h2_712_03(self, env):
|
||||
# same as 712_02 but with smaller chunks
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user