-*- coding: utf-8 -*-
Changes with Apache 2.4.50
+ *) mod_http2: when a server is restarted gracefully, any idle h2 worker
+ threads are shut down immediately.
+ Also, change OpenSSL API use for deprecations in OpenSSL 3.0.
+ Adds all other, never proposed code changes to make a clean
+ sync of http2 sources. [Stefan Eissing]
+
*) mod_dav: Correctly handle errors returned by dav providers on REPORT
requests. [Ruediger Pluem]
PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
[ start all new proposals below, under PATCHES PROPOSED. ]
- *) mod_http2: when a server is restarted gracefully, any idle h2 worker
- threads are shut down immediately.
- Also, change OpenSSL API use for deprecations in OpenSLL 3.0.
- Adds all other, never proposed code changes to make a clean
- sync of http2 sources.
- trunk patch: http://svn.apache.org/r1893214
- http://svn.apache.org/r1893215
- http://svn.apache.org/r1893220
- and other never proposed code changes
- PR: https://github.com/apache/httpd/pull/270
- +1: icing, rpluem, minfrin
-
PATCHES PROPOSED TO BACKPORT FROM TRUNK:
[ New proposals should be added at the end of the list ]
{
const char *sep = ap_strchr_c(s, '=');
if (sep) {
- const char *alpn = apr_pstrmemdup(pool, s, sep - s);
+ 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, sep - s);
+ host = apr_pstrmemdup(pool, s, (apr_size_t)(sep - s));
}
s = sep + 1;
if (*s) { /* must be a port number */
static void leave_yellow(h2_bucket_beam *beam, h2_beam_lock *pbl)
{
+ (void)beam;
if (pbl->leave) {
pbl->leave(pbl->mutex);
}
}
else {
/* should all have determinate length */
- return b->length;
+ return (apr_off_t)b->length;
}
}
static apr_size_t calc_space_left(h2_bucket_beam *beam)
{
if (beam->max_buf_size > 0) {
- apr_off_t len = calc_buffered(beam);
+ apr_size_t len = calc_buffered(beam);
return (beam->max_buf_size > len? (beam->max_buf_size - len) : 0);
}
return APR_SIZE_MAX;
apr_status_t status;
int can_beam = 0, check_len;
+ (void)block;
+ (void)pbl;
if (beam->aborted) {
return APR_ECONNABORTED;
}
}
else {
if (b->length == ((apr_size_t)-1)) {
- const char *data;
- status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ const char *data2;
+ status = apr_bucket_read(b, &data2, &len, APR_BLOCK_READ);
if (status != APR_SUCCESS) {
return status;
}
/* Called from the receiver thread to take buckets from the beam */
if (enter_yellow(beam, &bl) == APR_SUCCESS) {
if (readbytes <= 0) {
- readbytes = APR_SIZE_MAX;
+ readbytes = (apr_off_t)APR_SIZE_MAX;
}
remain = readbytes;
}
++beam->files_beamed;
}
- ng = apr_brigade_insert_file(bb, fd, bsender->start, bsender->length,
+ ng = apr_brigade_insert_file(bb, fd, bsender->start, (apr_off_t)bsender->length,
bb->p);
#if APR_HAS_MMAP
/* disable mmap handling as this leads to segfaults when
brecv != APR_BRIGADE_SENTINEL(bb);
brecv = APR_BUCKET_NEXT(brecv)) {
remain -= (beam->tx_mem_limits? bucket_mem_used(brecv)
- : brecv->length);
+ : (apr_off_t)brecv->length);
if (remain < 0) {
- apr_bucket_split(brecv, brecv->length+remain);
+ apr_bucket_split(brecv, (apr_size_t)((apr_off_t)brecv->length+remain));
beam->recv_buffer = apr_brigade_split_ex(bb,
APR_BUCKET_NEXT(brecv),
beam->recv_buffer);
int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file)
{
+ (void)ctx; (void)beam; (void)file;
return 0;
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-/* 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>
{
const h2_config *conf = h2_config_get(c);
if (content_type && conf->priorities) {
- size_t len = strcspn(content_type, "; \t");
+ apr_ssize_t len = (apr_ssize_t)strcspn(content_type, "; \t");
h2_priority *prio = apr_hash_get(conf->priorities, content_type, len);
return prio? prio : apr_hash_get(conf->priorities, "*", 1);
}
h2_dependency dependency;
h2_priority *priority;
int weight;
-
+
+ (void)_cfg;
if (!*ctype) {
return "1st argument must be a mime-type, like 'text/css' or '*'";
}
else if (!strcasecmp("BEFORE", sdependency)) {
dependency = H2_DEPENDANT_BEFORE;
if (sweight) {
- return "dependecy 'Before' does not allow a weight";
+ return "dependency 'Before' does not allow a weight";
}
}
else if (!strcasecmp("INTERLEAVED", sdependency)) {
if (!cfg->priorities) {
cfg->priorities = apr_hash_make(cmd->pool);
}
- apr_hash_set(cfg->priorities, ctype, strlen(ctype), priority);
+ apr_hash_set(cfg->priorities, ctype, (apr_ssize_t)strlen(ctype), priority);
return NULL;
}
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);
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;
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,
*/
apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c);
-/* Initialize this child process for h2 connection work,
+/**
+ * Initialize this child process for h2 connection work,
* to be called once during child init before multi processing
* starts.
*/
apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s);
+/**
+ * Child is about to be stopped, release unused resources
+ */
+void h2_conn_child_stopping(apr_pool_t *pool, int graceful);
typedef enum {
H2_MPM_UNKNOWN,
*/
#define WRITE_SIZE_MAX (TLS_DATA_MAX)
+#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)
{
char buffer[16 * 1024];
const char *line = "(null)";
- apr_size_t bmax = sizeof(buffer)/sizeof(buffer[0]);
+ int bmax = sizeof(buffer)/sizeof(buffer[0]);
int off = 0;
apr_bucket *b;
+ (void)stream_id;
if (bb) {
memset(buffer, 0, bmax--);
for (b = APR_BRIGADE_FIRST(bb);
if (APR_BUCKET_IS_METADATA(b)) {
if (APR_BUCKET_IS_EOS(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "eos ");
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "eos ");
}
else if (APR_BUCKET_IS_FLUSH(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "flush ");
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "flush ");
}
else if (AP_BUCKET_IS_EOR(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "eor ");
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "eor ");
}
else if (H2_BUCKET_IS_H2EOS(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "h2eos ");
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "h2eos ");
}
else {
- off += apr_snprintf(buffer+off, bmax-off, "meta(unknown) ");
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "meta(unknown) ");
}
}
else {
btype = "pool";
}
- off += apr_snprintf(buffer+off, bmax-off, "%s[%ld] ",
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "%s[%ld] ",
btype,
- (long)(b->length == ((apr_size_t)-1)?
- -1 : b->length));
+ (long)(b->length == ((apr_size_t)-1)? -1UL : b->length));
}
}
line = *buffer? buffer : "(empty)";
"h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, "
"cd_secs=%f", io->c->id, io->buffer_output,
(long)io->warmup_size,
- ((float)io->cooldown_usecs/APR_USEC_PER_SEC));
+ ((double)io->cooldown_usecs/APR_USEC_PER_SEC));
}
return APR_SUCCESS;
apr_bucket_file *f = (apr_bucket_file *)b->data;
apr_file_t *fd = f->fd;
apr_off_t offset = b->start;
- apr_size_t len = b->length;
+ 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.
{
if (!io->is_flushed) {
apr_off_t len = h2_brigade_mem_size(io->output);
- if (len > io->flush_threshold) {
+ 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 > (4 * io->flush_threshold);
+ return len > (apr_off_t)(4 * io->flush_threshold);
}
return 0;
}
}
}
-static void set_basic_http_header(apr_table_t *headers, request_rec *r,
- apr_pool_t *pool)
-{
- char *date = NULL;
- const char *proxy_date = NULL;
- const char *server = NULL;
- const char *us = ap_get_server_banner();
-
- /*
- * keep the set-by-proxy server and date headers, otherwise
- * generate a new server header / date header
- */
- if (r && r->proxyreq != PROXYREQ_NONE) {
- proxy_date = apr_table_get(r->headers_out, "Date");
- if (!proxy_date) {
- /*
- * proxy_date needs to be const. So use date for the creation of
- * our own Date header and pass it over to proxy_date later to
- * avoid a compiler warning.
- */
- date = apr_palloc(pool, APR_RFC822_DATE_LEN);
- ap_recent_rfc822_date(date, r->request_time);
- }
- server = apr_table_get(r->headers_out, "Server");
- }
- else {
- date = apr_palloc(pool, APR_RFC822_DATE_LEN);
- ap_recent_rfc822_date(date, r? r->request_time : apr_time_now());
- }
-
- apr_table_setn(headers, "Date", proxy_date ? proxy_date : date );
- if (r) {
- apr_table_unset(r->headers_out, "Date");
- }
-
- if (!server && *us) {
- server = us;
- }
- if (server) {
- apr_table_setn(headers, "Server", server);
- if (r) {
- apr_table_unset(r->headers_out, "Server");
- }
- }
-}
-
-static int copy_header(void *ctx, const char *name, const char *value)
-{
- apr_table_t *headers = ctx;
-
- apr_table_add(headers, name, value);
- return 1;
-}
-
static h2_headers *create_response(h2_task *task, request_rec *r)
{
const char *clheader;
const char *ctype;
- apr_table_t *headers;
+
/*
* Now that we are ready to send a response, we need to combine the two
* header field tables into a single table. If we don't do this, our
apr_table_unset(r->headers_out, "Content-Length");
}
- headers = apr_table_make(r->pool, 10);
-
- set_basic_http_header(headers, r, r->pool);
- if (r->status == HTTP_NOT_MODIFIED) {
- apr_table_do(copy_header, headers, r->headers_out,
- "ETag",
- "Content-Location",
- "Expires",
- "Cache-Control",
- "Vary",
- "Warning",
- "WWW-Authenticate",
- "Proxy-Authenticate",
- "Set-Cookie",
- "Set-Cookie2",
- NULL);
+ /*
+ * 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")) {
+ 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 );
}
- else {
- apr_table_do(copy_header, headers, r->headers_out, NULL);
+ if (r->proxyreq != PROXYREQ_RESPONSE) {
+ const char *us = ap_get_server_banner();
+ if (us) {
+ apr_table_setn(r->headers_out, "Server", us);
+ }
}
- return h2_headers_rcreate(r, r->status, headers, r->pool);
+ return h2_headers_rcreate(r, r->status, r->headers_out, r->pool);
}
typedef enum {
* to the end of the brigade. */
char buffer[128];
apr_bucket *c;
- int len;
+ apr_size_t len;
- len = apr_snprintf(buffer, H2_ALEN(buffer),
- "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_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);
}
-h2_headers *h2_headers_create(int status, apr_table_t *headers_in,
- apr_table_t *notes, apr_off_t raw_bytes,
- apr_pool_t *pool)
+h2_headers *h2_headers_create(int status, const apr_table_t *headers_in,
+ const apr_table_t *notes, apr_off_t raw_bytes,
+ apr_pool_t *pool)
{
h2_headers *headers = apr_pcalloc(pool, sizeof(h2_headers));
headers->status = status;
}
h2_headers *h2_headers_rcreate(request_rec *r, int status,
- apr_table_t *header, apr_pool_t *pool)
+ const apr_table_t *header, apr_pool_t *pool)
{
h2_headers *headers = h2_headers_create(status, header, r->notes, 0, pool);
if (headers->status == HTTP_FORBIDDEN) {
h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h)
{
- return h2_headers_create(h->status, apr_table_copy(pool, h->headers),
- apr_table_copy(pool, h->notes), h->raw_bytes, pool);
+ return h2_headers_create(h->status, h->headers, h->notes, h->raw_bytes, pool);
}
h2_headers *h2_headers_clone(apr_pool_t *pool, h2_headers *h)
{
- return h2_headers_create(h->status, apr_table_clone(pool, h->headers),
- apr_table_clone(pool, h->notes), h->raw_bytes, pool);
+ return h2_headers_create(h->status, h->headers, h->notes, h->raw_bytes, pool);
}
h2_headers *h2_headers_die(apr_status_t type,
* @param raw_bytes the raw network bytes (if known) used to transmit these
* @param pool the memory pool to use
*/
-h2_headers *h2_headers_create(int status, apr_table_t *header,
- apr_table_t *notes, apr_off_t raw_bytes,
+h2_headers *h2_headers_create(int status, const apr_table_t *header,
+ const apr_table_t *notes, apr_off_t raw_bytes,
apr_pool_t *pool);
/**
* @param pool the memory pool to use
*/
h2_headers *h2_headers_rcreate(request_rec *r, int status,
- apr_table_t *header, apr_pool_t *pool);
+ const apr_table_t *header, apr_pool_t *pool);
/**
* Copy the headers into another pool. This will not copy any
/**
* Create the headers for the given error.
- * @param stream_id id of the stream to create the headers for
* @param type the error code
* @param req the original h2_request
* @param pool the memory pool to use
return stream->task;
}
}
+ if (m->tasks_active >= m->limit_active && !h2_iq_empty(m->q)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+ "h2_session(%ld): delaying request processing. "
+ "Current limit is %d and %d workers are in use.",
+ m->id, m->limit_active, m->tasks_active);
+ }
return NULL;
}
task->redo = 1;
h2_task_rst(task, H2_ERR_CANCEL);
}
-
return rv;
}
return waiting;
}
+static int reset_is_acceptable(h2_stream *stream)
+{
+ /* client may terminate a stream via H2 RST_STREAM message at any time.
+ * This is annyoing when we have committed resources (e.g. worker threads)
+ * to it, so our mood (e.g. willingness to commit resources on this
+ * connection in the future) goes down.
+ *
+ * This is a DoS protection. We do not want to make it too easy for
+ * a client to eat up server resources.
+ *
+ * However: there are cases where a RST_STREAM is the only way to end
+ * a request. This includes websockets and server-side-event streams (SSEs).
+ * The responses to such requests continue forever otherwise.
+ *
+ */
+ if (!stream->task) return 1; /* have not started or already ended for us. acceptable. */
+ if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
+ if (!stream->has_response) return 0; /* no response headers produced yet. bad. */
+ if (!stream->out_data_frames) return 0; /* no response body data sent yet. bad. */
+ return 1; /* otherwise, be forgiving */
+}
+
apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id)
{
h2_stream *stream;
apr_status_t status = APR_SUCCESS;
-
+
H2_MPLX_ENTER_ALWAYS(m);
stream = h2_ihash_get(m->streams, stream_id);
- if (stream && stream->task) {
+ if (stream && !reset_is_acceptable(stream)) {
status = m_be_annoyed(m);
}
H2_MPLX_LEAVE(m);
#include <apr_time.h>
#ifdef H2_OPENSSL
-#include <openssl/sha.h>
+#include <openssl/evp.h>
#endif
#include <httpd.h>
typedef struct {
const h2_request *req;
- int push_policy;
+ apr_uint32_t push_policy;
apr_pool_t *pool;
apr_array_header_t *pushes;
const char *s;
}
apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
- int push_policy, const h2_headers *res)
+ apr_uint32_t push_policy, const h2_headers *res)
{
if (req && push_policy != H2_PUSH_NONE) {
/* Collect push candidates from the request/response pair.
#ifdef H2_OPENSSL
-static void sha256_update(SHA256_CTX *ctx, const char *s)
+static void sha256_update(EVP_MD_CTX *ctx, const char *s)
{
- SHA256_Update(ctx, s, strlen(s));
+ EVP_DigestUpdate(ctx, s, strlen(s));
}
static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
{
- SHA256_CTX sha256;
+ EVP_MD_CTX *md;
apr_uint64_t val;
- unsigned char hash[SHA256_DIGEST_LENGTH];
+ unsigned char hash[EVP_MAX_MD_SIZE];
+ unsigned len;
int i;
-
- SHA256_Init(&sha256);
- sha256_update(&sha256, push->req->scheme);
- sha256_update(&sha256, "://");
- sha256_update(&sha256, push->req->authority);
- sha256_update(&sha256, push->req->path);
- SHA256_Final(hash, &sha256);
+
+ md = EVP_MD_CTX_create();
+ ap_assert(md != NULL);
+
+ i = EVP_DigestInit_ex(md, EVP_sha256(), NULL);
+ ap_assert(i == 1);
+ sha256_update(md, push->req->scheme);
+ sha256_update(md, "://");
+ sha256_update(md, push->req->authority);
+ sha256_update(md, push->req->path);
+ EVP_DigestFinal(md, hash, &len);
val = 0;
- for (i = 0; i != sizeof(val); ++i)
+ for (i = 0; i != len; ++i)
val = val * 256 + hash[i];
*phash = val >> (64 - diary->mask_bits);
}
static unsigned int val_apr_hash(const char *str)
{
- apr_ssize_t len = strlen(str);
+ apr_ssize_t len = (apr_ssize_t)strlen(str);
return apr_hashfunc_default(str, &len);
}
static void calc_apr_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
{
apr_uint64_t val;
+ (void)diary;
#if APR_UINT64_MAX > UINT_MAX
val = ((apr_uint64_t)(val_apr_hash(push->req->scheme)) << 32);
val ^= ((apr_uint64_t)(val_apr_hash(push->req->authority)) << 16);
/* Intentional no APLOGNO */
ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
"push_diary_update: already there PUSH %s", push->req->path);
- move_to_last(session->push_diary, idx);
+ move_to_last(session->push_diary, (apr_size_t)idx);
}
else {
/* Intentional no APLOGNO */
*/
apr_array_header_t *h2_push_collect(apr_pool_t *p,
const struct h2_request *req,
- int push_policy,
+ apr_uint32_t push_policy,
const struct h2_headers *res);
/**
return dst;
}
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
+static request_rec *my_ap_create_request(conn_rec *c)
+{
+ apr_pool_t *p;
+ request_rec *r;
+
+ apr_pool_create(&p, c->pool);
+ apr_pool_tag(p, "request");
+ r = apr_pcalloc(p, sizeof(request_rec));
+ AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)c);
+ r->pool = p;
+ r->connection = c;
+ r->server = c->base_server;
+
+ r->user = NULL;
+ r->ap_auth_type = NULL;
+
+ r->allowed_methods = ap_make_method_list(p, 2);
+
+ r->headers_in = apr_table_make(r->pool, 5);
+ r->trailers_in = apr_table_make(r->pool, 5);
+ r->subprocess_env = apr_table_make(r->pool, 25);
+ r->headers_out = apr_table_make(r->pool, 12);
+ r->err_headers_out = apr_table_make(r->pool, 5);
+ r->trailers_out = apr_table_make(r->pool, 5);
+ r->notes = apr_table_make(r->pool, 5);
+
+ r->request_config = ap_create_request_config(r->pool);
+ /* Must be set before we run create request hook */
+
+ r->proto_output_filters = c->output_filters;
+ r->output_filters = r->proto_output_filters;
+ r->proto_input_filters = c->input_filters;
+ r->input_filters = r->proto_input_filters;
+ ap_run_create_request(r);
+ r->per_dir_config = r->server->lookup_defaults;
+
+ r->sent_bodyct = 0; /* bytect isn't for body */
+
+ r->read_length = 0;
+ r->read_body = REQUEST_NO_BODY;
+
+ r->status = HTTP_OK; /* Until further notice */
+ r->header_only = 0;
+ r->the_request = NULL;
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+ r->useragent_addr = c->client_addr;
+ r->useragent_ip = c->client_ip;
+ return r;
+}
+#endif
+
request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
{
int access_status = HTTP_OK;
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
request_rec *r = ap_create_request(c);
+#else
+ request_rec *r = my_ap_create_request(c);
+#endif
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
ap_run_pre_read_request(r, c);
/* Time to populate r with the data we have. */
r->status = HTTP_OK;
goto die;
}
+#else
+ {
+ const char *s;
+
+ r->headers_in = apr_table_clone(r->pool, req->headers);
+ ap_run_pre_read_request(r, c);
+
+ /* Time to populate r with the data we have. */
+ r->request_time = req->request_time;
+ r->method = apr_pstrdup(r->pool, req->method);
+ /* Provide quick information about the request method as soon as known */
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_GET && r->method[0] == 'H') {
+ r->header_only = 1;
+ }
+ ap_parse_uri(r, req->path ? req->path : "");
+ r->protocol = (char*)"HTTP/2.0";
+ r->proto_num = HTTP_VERSION(2, 0);
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ r->method, req->path ? req->path : "");
+
+ /* Start with r->hostname = NULL, ap_check_request_header() will get it
+ * form Host: header, otherwise we get complains about port numbers.
+ */
+ r->hostname = NULL;
+ ap_update_vhost_from_headers(r);
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ s = apr_table_get(r->headers_in, "Expect");
+ if (s && s[0]) {
+ if (ap_cstr_casecmp(s, "100-continue") == 0) {
+ r->expecting_100 = 1;
+ }
+ else {
+ r->status = HTTP_EXPECTATION_FAILED;
+ access_status = r->status;
+ goto die;
+ }
+ }
+ }
+#endif
/* we may have switched to another server */
r->per_dir_config = r->server->lookup_defaults;
return NGHTTP2_ERR_PROTO;
}
-h2_stream *h2_session_stream_get(h2_session *session, int stream_id)
+static h2_stream *get_stream(h2_session *session, int stream_id)
{
return nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
}
h2_stream * stream;
int rv = 0;
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (stream) {
status = h2_stream_recv_DATA(stream, flags, data, len);
dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, "stream data rcvd");
h2_stream *stream;
(void)ngh2;
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (stream) {
if (error_code) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
/* We may see HEADERs at the start of a stream or after all DATA
* streams to carry trailers. */
(void)ngh2;
- s = h2_session_stream_get(session, frame->hd.stream_id);
+ s = get_stream(session, frame->hd.stream_id);
if (s) {
/* nop */
}
apr_status_t status;
(void)flags;
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ stream = get_stream(session, frame->hd.stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(02920)
"h2_stream(%ld-%d): on_header unknown stream",
/* This can be HEADERS for a new stream, defining the request,
* or HEADER may come after DATA at the end of a stream as in
* trailers */
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ stream = get_stream(session, frame->hd.stream_id);
if (stream) {
rv = h2_stream_recv_frame(stream, NGHTTP2_HEADERS, frame->hd.flags,
frame->hd.length + H2_FRAME_HDR_LEN);
}
break;
case NGHTTP2_DATA:
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ stream = get_stream(session, frame->hd.stream_id);
if (stream) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
H2_STRM_LOG(APLOGNO(02923), stream,
"h2_stream(%ld-%d): RST_STREAM by client, error=%d",
session->id, (int)frame->hd.stream_id,
(int)frame->rst_stream.error_code);
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ stream = get_stream(session, frame->hd.stream_id);
if (stream && stream->initiated_on) {
/* A stream reset on a request we sent it. Normal, when the
* client does not want it. */
ap_assert(frame->data.padlen <= (H2_MAX_PADLEN+1));
padlen = (unsigned char)frame->data.padlen;
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
APLOGNO(02924)
(long)session->frames_sent);
}
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (stream) {
h2_stream_send_frame(stream, frame->hd.type, frame->hd.flags,
frame->hd.length + H2_FRAME_HDR_LEN);
apr_pstrndup(session->pool, (const char *)name, namelen),
apr_pstrndup(session->pool, (const char *)value, valuelen));
}
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ stream = get_stream(session, frame->hd.stream_id);
if (stream) {
h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
}
* connection when sending the next request, this has the effect
* that at least this one request will fail.
*/
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
H2_SSSN_LOG(APLOGNO(03199), session,
"connection disappeared without proper "
"goodbye, clients will be confused, should not happen"));
h2_session *session;
if ((session = h2_ctx_get_session(c))) {
+ int mpm_state = 0;
+ int level;
+
+ ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state);
+ level = (AP_MPMQ_STOPPING == mpm_state)? APLOG_DEBUG : APLOG_WARNING;
/* if the session is still there, now is the last chance
* to perform cleanup. Normally, cleanup should have happened
- * earlier in the connection pre_close. Main reason is that
- * any ongoing requests on secondary connections might still access
- * data which has, at this time, already been freed. An example
- * is mod_ssl that uses request hooks. */
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
+ * earlier in the connection pre_close.
+ * However, when the server is stopping, it may shutdown connections
+ * without running the pre_close hooks. Do not want about that. */
+ ap_log_cerror(APLOG_MARK, level, 0, c,
H2_SSSN_LOG(APLOGNO(10020), session,
"session cleanup triggered by pool cleanup. "
"this should have happened earlier already."));
(void)ng2s;
(void)buf;
(void)source;
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
APLOGNO(02937)
int id;
while ((id = h2_iq_shift(session->in_process)) > 0) {
- h2_stream *stream = h2_session_stream_get(session, id);
+ h2_stream *stream = get_stream(session, id);
if (stream) {
ap_assert(!stream->scheduled);
if (h2_stream_prep_processing(stream) == APR_SUCCESS) {
}
while ((id = h2_iq_shift(session->in_pending)) > 0) {
- h2_stream *stream = h2_session_stream_get(session, id);
+ h2_stream *stream = get_stream(session, id);
if (stream) {
h2_stream_flush_input(stream);
}
timeout = session->s->timeout;
update_child_status(session, SERVER_BUSY_READ, "idle");
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- H2_SSSN_LOG("", session, "enter idle, timeout = %d sec"),
- (int)apr_time_sec(H2MAX(session->s->timeout, session->s->keep_alive_timeout)));
+ H2_SSSN_LOG("", session, "enter idle, timeout = %d sec"),
+ (int)apr_time_sec(timeout));
}
else if (session->open_streams) {
s = "timeout";
break;
default:
h2_session_shutdown_notice(session);
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 110)
+ h2_workers_graceful_shutdown(session->workers);
+#endif
break;
}
}
*/
int h2_session_push_enabled(h2_session *session);
-/**
- * Look up the stream in this session with the given id.
- */
-struct h2_stream *h2_session_stream_get(h2_session *session, int stream_id);
-
/**
* Submit a push promise on the stream and schedule the new steam for
* processing..
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+
#include <assert.h>
#include <stddef.h>
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;
}
* configurations by mod_h2 alone.
*/
task->c->id = (c->master->id << 8)^worker_id;
- task->id = apr_psprintf(task->pool, "%ld-%d", c->master->id,
+ task->id = apr_psprintf(task->pool, "%ld-%d", task->mplx->id,
task->stream_id);
}
/**
* A h2_task fakes a HTTP/1.1 request from the data in a HTTP/2 stream
- * (HEADER+CONT.+DATA) the module recieves.
+ * (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
void h2_task_register_hooks(void);
/*
- * One time, post config intialization.
+ * One time, post config initialization.
*/
apr_status_t h2_task_init(apr_pool_t *pool, server_rec *s);
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+
#include <assert.h>
#include <apr_strings.h>
#include <apr_thread_mutex.h>
void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
{
/* Assume that changes in ordering are minimal. This needs,
- * best case, q->nelts - 1 comparisions to check that nothing
+ * best case, q->nelts - 1 comparisons to check that nothing
* changed.
*/
if (q->nelts > 0) {
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "1.15.18"
+#define MOD_HTTP2_VERSION "1.15.24"
/**
* @macro
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_HTTP2_VERSION_NUM 0x010f12
+#define MOD_HTTP2_VERSION_NUM 0x010f18
#endif /* mod_h2_h2_version_h */
apr_thread_t *thread;
apr_thread_mutex_t *lock;
apr_thread_cond_t *not_idle;
+ volatile apr_uint32_t timed_out;
};
static h2_slot *pop_slot(h2_slot *volatile *phead)
}
static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx);
+static void slot_done(h2_slot *slot);
static apr_status_t activate_slot(h2_workers *workers, h2_slot *slot)
{
- apr_status_t status;
+ apr_status_t rv;
slot->workers = workers;
slot->task = NULL;
+ apr_thread_mutex_lock(workers->lock);
if (!slot->lock) {
- status = apr_thread_mutex_create(&slot->lock,
+ rv = apr_thread_mutex_create(&slot->lock,
APR_THREAD_MUTEX_DEFAULT,
workers->pool);
- if (status != APR_SUCCESS) {
- push_slot(&workers->free, slot);
- return status;
- }
+ if (rv != APR_SUCCESS) goto cleanup;
}
if (!slot->not_idle) {
- status = apr_thread_cond_create(&slot->not_idle, workers->pool);
- if (status != APR_SUCCESS) {
- push_slot(&workers->free, slot);
- return status;
- }
+ rv = apr_thread_cond_create(&slot->not_idle, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
}
- ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_workers: new thread for slot %d", slot->id);
/* thread will either immediately start work or add itself
* to the idle queue */
apr_atomic_inc32(&workers->worker_count);
- status = apr_thread_create(&slot->thread, workers->thread_attr,
+ slot->timed_out = 0;
+ rv = apr_thread_create(&slot->thread, workers->thread_attr,
slot_run, slot, workers->pool);
- if (status != APR_SUCCESS) {
+ if (rv != APR_SUCCESS) {
apr_atomic_dec32(&workers->worker_count);
+ }
+
+cleanup:
+ apr_thread_mutex_unlock(workers->lock);
+ if (rv != APR_SUCCESS) {
push_slot(&workers->free, slot);
- return status;
}
-
- return APR_SUCCESS;
+ return rv;
}
static apr_status_t add_worker(h2_workers *workers)
{
h2_slot *slot = pop_slot(&workers->idle);
if (slot) {
+ int timed_out = 0;
apr_thread_mutex_lock(slot->lock);
- apr_thread_cond_signal(slot->not_idle);
+ timed_out = slot->timed_out;
+ if (!timed_out) {
+ apr_thread_cond_signal(slot->not_idle);
+ }
apr_thread_mutex_unlock(slot->lock);
+ if (timed_out) {
+ slot_done(slot);
+ wake_idle_worker(workers);
+ }
}
- else if (workers->dynamic) {
+ else if (workers->dynamic && !workers->shutdown) {
add_worker(workers);
}
}
static int get_next(h2_slot *slot)
{
h2_workers *workers = slot->workers;
+ int non_essential = slot->id >= workers->min_workers;
+ apr_status_t rv;
- while (!workers->aborted) {
+ while (!workers->aborted && !slot->timed_out) {
ap_assert(slot->task == NULL);
+ if (non_essential && workers->shutdown) {
+ /* Terminate non-essential worker on shutdown */
+ break;
+ }
if (h2_fifo_try_peek(workers->mplxs, mplx_peek, slot) == APR_EOF) {
/* The queue is terminated with the MPM child being cleaned up,
- * just leave.
- */
+ * just leave. */
break;
}
if (slot->task) {
apr_thread_mutex_lock(slot->lock);
if (!workers->aborted) {
+
push_slot(&workers->idle, slot);
- apr_thread_cond_wait(slot->not_idle, slot->lock);
+ if (non_essential && workers->max_idle_duration) {
+ rv = apr_thread_cond_timedwait(slot->not_idle, slot->lock,
+ workers->max_idle_duration);
+ if (APR_TIMEUP == rv) {
+ slot->timed_out = 1;
+ }
+ }
+ else {
+ apr_thread_cond_wait(slot->not_idle, slot->lock);
+ }
}
apr_thread_mutex_unlock(slot->lock);
}
} while (slot->task);
}
- slot_done(slot);
+ if (!slot->timed_out) {
+ slot_done(slot);
+ }
apr_thread_exit(thread, APR_SUCCESS);
return NULL;
}
-static apr_status_t workers_pool_cleanup(void *data)
+static void wake_non_essential_workers(h2_workers *workers)
{
- h2_workers *workers = data;
h2_slot *slot;
-
+ /* pop all idle, signal the non essentials and add the others again */
+ if ((slot = pop_slot(&workers->idle))) {
+ wake_non_essential_workers(workers);
+ if (slot->id > workers->min_workers) {
+ apr_thread_mutex_lock(slot->lock);
+ apr_thread_cond_signal(slot->not_idle);
+ apr_thread_mutex_unlock(slot->lock);
+ }
+ else {
+ push_slot(&workers->idle, slot);
+ }
+ }
+}
+
+static void workers_abort_idle(h2_workers *workers)
+{
+ h2_slot *slot;
+
+ workers->shutdown = 1;
workers->aborted = 1;
h2_fifo_term(workers->mplxs);
apr_thread_cond_signal(slot->not_idle);
apr_thread_mutex_unlock(slot->lock);
}
+}
+
+static apr_status_t workers_pool_cleanup(void *data)
+{
+ h2_workers *workers = data;
+ apr_time_t end, timeout = apr_time_from_sec(1);
+ apr_status_t rv;
+ int n, wait_sec = 5;
- /* wait for all the workers to become zombies and join them */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: cleanup %d workers idling",
+ (int)apr_atomic_read32(&workers->worker_count));
+ workers_abort_idle(workers);
+
+ /* wait for all the workers to become zombies and join them.
+ * this gets called after the mpm shuts down and all connections
+ * have either been handled (graceful) or we are forced exiting
+ * (ungrateful). Either way, we show limited patience. */
apr_thread_mutex_lock(workers->lock);
- if (apr_atomic_read32(&workers->worker_count)) {
- apr_thread_cond_wait(workers->all_done, workers->lock);
+ end = apr_time_now() + apr_time_from_sec(wait_sec);
+ while ((n = apr_atomic_read32(&workers->worker_count)) > 0
+ && apr_time_now() < end) {
+ rv = apr_thread_cond_timedwait(workers->all_done, workers->lock, timeout);
+ if (APR_TIMEUP == rv) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ APLOGNO(10290) "h2_workers: waiting for idle workers to close, "
+ "still seeing %d workers living",
+ apr_atomic_read32(&workers->worker_count));
+ continue;
+ }
+ }
+ if (n) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
+ APLOGNO(10291) "h2_workers: cleanup, %d idle workers "
+ "did not exit after %d seconds.",
+ n, wait_sec);
}
apr_thread_mutex_unlock(workers->lock);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: cleanup all workers terminated");
join_zombies(workers);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: cleanup zombie workers joined");
return APR_SUCCESS;
}
int min_workers, int max_workers,
int idle_secs)
{
- apr_status_t status;
+ apr_status_t rv;
h2_workers *workers;
apr_pool_t *pool;
int i, n;
workers->pool = pool;
workers->min_workers = min_workers;
workers->max_workers = max_workers;
- workers->max_idle_secs = (idle_secs > 0)? idle_secs : 10;
+ workers->max_idle_duration = apr_time_from_sec((idle_secs > 0)? idle_secs : 10);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: created with min=%d max=%d idle_timeout=%d sec",
+ 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"
* For now, we just make enough room to have many connections inside one
* process.
*/
- status = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
- if (status != APR_SUCCESS) {
- return NULL;
- }
-
- status = apr_threadattr_create(&workers->thread_attr, workers->pool);
- if (status != APR_SUCCESS) {
- return NULL;
- }
-
+ rv = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
+ if (rv != APR_SUCCESS) goto cleanup;
+
+ rv = apr_threadattr_create(&workers->thread_attr, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+
if (ap_thread_stacksize != 0) {
apr_threadattr_stacksize_set(workers->thread_attr,
ap_thread_stacksize);
(long)ap_thread_stacksize);
}
- status = apr_thread_mutex_create(&workers->lock,
- APR_THREAD_MUTEX_DEFAULT,
- workers->pool);
- if (status == APR_SUCCESS) {
- status = apr_thread_cond_create(&workers->all_done, workers->pool);
+ rv = apr_thread_mutex_create(&workers->lock,
+ APR_THREAD_MUTEX_DEFAULT,
+ workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+ rv = apr_thread_cond_create(&workers->all_done, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+
+ n = workers->nslots = workers->max_workers;
+ workers->slots = apr_pcalloc(workers->pool, n * sizeof(h2_slot));
+ if (workers->slots == NULL) {
+ n = workers->nslots = 0;
+ rv = APR_ENOMEM;
+ goto cleanup;
}
- if (status == APR_SUCCESS) {
- n = workers->nslots = workers->max_workers;
- workers->slots = apr_pcalloc(workers->pool, n * sizeof(h2_slot));
- if (workers->slots == NULL) {
- workers->nslots = 0;
- status = APR_ENOMEM;
- }
- for (i = 0; i < n; ++i) {
- workers->slots[i].id = i;
- }
+ for (i = 0; i < n; ++i) {
+ workers->slots[i].id = i;
}
- if (status == APR_SUCCESS) {
- /* we activate all for now, TODO: support min_workers again.
- * do this in reverse for vanity reasons so slot 0 will most
- * likely be at head of idle queue. */
- n = workers->max_workers;
- for (i = n-1; i >= 0; --i) {
- status = activate_slot(workers, &workers->slots[i]);
- }
- /* the rest of the slots go on the free list */
- for(i = n; i < workers->nslots; ++i) {
- push_slot(&workers->free, &workers->slots[i]);
- }
- workers->dynamic = (workers->worker_count < workers->max_workers);
+ /* we activate all for now, TODO: support min_workers again.
+ * do this in reverse for vanity reasons so slot 0 will most
+ * likely be at head of idle queue. */
+ n = workers->min_workers;
+ for (i = n-1; i >= 0; --i) {
+ rv = activate_slot(workers, &workers->slots[i]);
+ if (rv != APR_SUCCESS) goto cleanup;
+ }
+ /* the rest of the slots go on the free list */
+ for(i = n; i < workers->nslots; ++i) {
+ push_slot(&workers->free, &workers->slots[i]);
}
- if (status == APR_SUCCESS) {
+ workers->dynamic = (workers->worker_count < workers->max_workers);
+
+cleanup:
+ if (rv == APR_SUCCESS) {
/* Stop/join the workers threads when the MPM child exits (pchild is
* destroyed), and as a pre_cleanup of pchild thus before the threads
* pools (children of workers->pool) so that they are not destroyed
{
return h2_fifo_remove(workers->mplxs, m);
}
+
+void h2_workers_graceful_shutdown(h2_workers *workers)
+{
+ workers->shutdown = 1;
+ workers->min_workers = 1;
+ workers->max_idle_duration = apr_time_from_sec(1);
+ h2_fifo_term(workers->mplxs);
+ wake_non_essential_workers(workers);
+}
apr_pool_t *pool;
int next_worker_id;
- int min_workers;
- int max_workers;
- int max_idle_secs;
+ apr_uint32_t max_workers;
+ volatile apr_uint32_t min_workers; /* is changed during graceful shutdown */
+ volatile apr_interval_time_t max_idle_duration; /* is changed during graceful shutdown */
volatile int aborted;
+ volatile int shutdown;
int dynamic;
apr_threadattr_t *thread_attr;
*/
apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m);
+/**
+ * Shut down processing gracefully by terminating all idle workers.
+ */
+void h2_workers_graceful_shutdown(h2_workers *workers);
+
#endif /* defined(__mod_h2__h2_workers__) */
#include <http_protocol.h>
#include <http_request.h>
#include <http_log.h>
+#include <mpm_common.h>
#include "mod_http2.h"
/* Run once after a child process has been created.
*/
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);
+#endif
h2_h2_register_hooks();
h2_switch_register_hooks();
h2_task_register_hooks();
if (!ctx->p_conn->data && ctx->is_ssl) {
/* New SSL connection: set a note on the connection about what
- * protocol we want.
- */
+ * protocol we need. */
apr_table_setn(ctx->p_conn->connection->notes,
"proxy-request-alpn-protos", "h2");
- if (ctx->p_conn->ssl_hostname) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->owner,
- "set SNI to %s for (%s)",
- ctx->p_conn->ssl_hostname,
- ctx->p_conn->hostname);
- apr_table_setn(ctx->p_conn->connection->notes,
- "proxy-request-hostname", ctx->p_conn->ssl_hostname);
- }
}
if (ctx->master->aborted) goto cleanup;