1
0
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:
Stefan Eissing
2021-10-12 13:34:01 +00:00
parent b1a8055dee
commit 6a355db082
54 changed files with 4878 additions and 6773 deletions

View File

@@ -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)

View 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]

View File

@@ -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)
#

View File

@@ -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
"

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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
View 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);
}

View File

@@ -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__) */

View File

@@ -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;
}

View File

@@ -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
View 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
View 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__) */

View File

@@ -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);

View File

@@ -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__) */

View File

@@ -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
};

View File

@@ -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);

View File

@@ -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
View 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);
}
}

View 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__) */

View File

@@ -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;
}

View File

@@ -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__) */

View File

@@ -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;
}

View File

@@ -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__ */

View File

@@ -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

View File

@@ -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__) */

View File

@@ -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;
}

View File

@@ -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__) */

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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*));

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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__) */

View File

@@ -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);
}

View File

@@ -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__) */

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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
#