###############################################################################
+Project: "mod_h2"=.\modules\http2\mod_h2.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+ Begin Project Dependency
+ Project_Dep_Name libapr
+ End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name libhttpd
+ End Project Dependency
+}}}
+
+###############################################################################
+
Project: "mod_headers"=.\modules\metadata\mod_headers.dsp - Package Owner=<4>
Package=<5>
###############################################################################
+Project: "mod_h2"=.\modules\http2\mod_h2.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+ Begin Project Dependency
+ Project_Dep_Name libapr
+ End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name libaprutil
+ End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name libhttpd
+ End Project Dependency
+}}}
+
+###############################################################################
+
Project: "mod_headers"=.\modules\metadata\mod_headers.dsp - Package Owner=<4>
Package=<5>
# PROP Use_Debug_Libraries 0
# PROP Output_Dir ""
# PROP Intermediate_Dir ""
-# PROP Cmd_Line "NMAKE /f makefile.win INSTDIR="\Apache2" LONG=Release _trydb _trylua _tryxml _tryssl _tryzlib _dummy"
+# PROP Cmd_Line "NMAKE /f makefile.win INSTDIR="\Apache2" LONG=Release _trydb _trylua _tryxml _tryssl _tryzlib _trynghttp2 _tryserf _dummy"
# PROP Rebuild_Opt ""
# PROP Target_File "\Apache2\bin\httpd.exe"
# PROP Bsc_Name ".\Browse\httpd.bsc"
# PROP Use_Debug_Libraries 1
# PROP Output_Dir ""
# PROP Intermediate_Dir ""
-# PROP Cmd_Line "NMAKE /f makefile.win INSTDIR="\Apache2" LONG=Debug _trydb _trylua _tryxml _tryssl _tryzlib _dummy"
+# PROP Cmd_Line "NMAKE /f makefile.win INSTDIR="\Apache2" LONG=Debug _trydb _trylua _tryxml _tryssl _tryzlib _trynghttp2 _tryserf _dummy"
# PROP Rebuild_Opt ""
# PROP Target_File "\Apache2\bin\httpd.exe"
# PROP Bsc_Name ".\Browse\httpd.bsc"
-*- coding: utf-8 -*-
Changes with Apache 2.4.17
+ *) mod_h2: added donated http/2 implementation to build system. Similar
+ configuration options to mod_ssl. [Stefan Eissing]
*) mod_rewrite: Allow cookies set by mod_rewrite to contain ':' by accepting
';' as an alternate separator. PR47241.
!ENDIF
+!IF EXIST("srclib\nghttp2")
+
+_trynghttp2:
+!IF $(USEMAK) == 1
+ cd modules\http2
+ $(MAKE) $(MAKEOPT) -f mod_h2.mak CFG="mod_h2 - Win32 $(LONG)" RECURSE=0 $(CTARGET)
+ cd ..\..
+!ELSEIF $(USESLN) == 1
+ devenv $(TLP).sln /useenv $(CTARGET) $(LONG) /project mod_h2
+!ELSE
+ @msdev $(TLP).dsw /USEENV /MAKE \
+ "mod_h2 - Win32 $(LONG)" /NORECURSE $(CTARGET)
+!ENDIF
+
+!ELSE
+# NOT EXIST("srclib\lua")
+
+_trynghttp2:
+ @echo -----
+ @echo mod_h2 will not build unless nghttp2 is installed in srclib\nghttp2.
+ @echo Version 1.0 includes an lib\makefile.msvc that will satisfy this
+ @echo requirement.
+
+!ENDIF
_trydb:
!IF $(USEMAK) == 1
copy modules\generators\$(LONG)\mod_info.$(src_so) "$(inst_so)" <.y
copy modules\generators\$(LONG)\mod_status.$(src_so) "$(inst_so)" <.y
copy modules\http\$(LONG)\mod_mime.$(src_so) "$(inst_so)" <.y
+!IF EXIST("srclib\nghttp2")
+ copy modules\http2\$(LONG)\mod_h2.$(src_so) "$(inst_so)" <.y
+!IF "$(SHORT)" == "D"
+ copy srclib\nghttp2\lib\MSVC_obj\nghttp2d.$(src_dll) "$(inst_dll)" <.y
+!ELSE
+ copy srclib\nghttp2\lib\MSVC_obj\nghttp2.$(src_dll) "$(inst_dll)" <.y
+!ENDIF
+!ENDIF
copy modules\ldap\$(LONG)\mod_ldap.$(src_so) "$(inst_so)" <.y
copy modules\loggers\$(LONG)\mod_log_config.$(src_so) "$(inst_so)" <.y
copy modules\loggers\$(LONG)\mod_log_debug.$(src_so) "$(inst_so)" <.y
$(error LDAPSDK does not point to a valid Novell CLDAP SDK)
endif
+ifdef WITH_HTTP2
+ifneq "$(wildcard $(NGH2SRC)/lib/nghttp2_hd.h)" "$(NGH2SRC)/lib/nghttp2_hd.h"
+$(error NGH2SRC does not point to a valid NGHTTP2 source tree)
+endif
+endif
+
ifndef PCRESRC
PCRESRC = $(AP_WORK)/srclib/pcre
endif
filelist["httpd-dav.conf"] = "httpd-dav.conf.in";
filelist["httpd-default.conf"] = "httpd-default.conf.in";
filelist["httpd-info.conf"] = "httpd-info.conf.in";
+ filelist["httpd-h2.conf"] = "httpd-h2.conf.in";
filelist["httpd-languages.conf"] = "httpd-languages.conf.in";
filelist["httpd-manual.conf"] = "httpd-manual.conf.in";
filelist["httpd-mpm.conf"] = "httpd-mpm.conf.in";
print "#LoadModule ext_filter_module modules/mod_ext_filter.so" > dstfl;
print "#LoadModule file_cache_module modules/mod_file_cache.so" > dstfl;
print "#LoadModule filter_module modules/mod_filter.so" > dstfl;
+ print "#LoadModule h2_module modules/mod_h2.so" > dstfl;
print "#LoadModule headers_module modules/mod_headers.so" > dstfl;
print "#LoadModule heartbeat_module modules/mod_heartbeat.so" > dstfl;
print "#LoadModule heartmonitor_module modules/mod_heartmonitor.so" > dstfl;
--- /dev/null
+#
+# This is the Apache server configuration file providing HTTP/2 support.
+# It contains the configuration directives to instruct the server how to
+# serve pages via the http/2 protocol. For detailed information about these
+# directives see <URL:http://httpd.apache.org/docs/trunk/mod/mod_h2.html>
+#
+# Required modules: mod_h2
+
+<IfModule h2_module>
+ # This can also set to "off" and turned on only for specific virtual hosts
+ H2Engine on
+</IfModule>
#RequestHeader unset DNT env=bad_DNT
#</IfModule>
+# h2/h2c (HTTP/2) connections
+#Include @rel_sysconfdir@/extra/httpd-h2.conf
+
<modulefile>mod_ext_filter.xml</modulefile>
<modulefile>mod_file_cache.xml</modulefile>
<modulefile>mod_filter.xml</modulefile>
+ <modulefile>mod_h2.xml</modulefile>
<modulefile>mod_headers.xml</modulefile>
<modulefile>mod_heartbeat.xml</modulefile>
<modulefile>mod_heartmonitor.xml</modulefile>
</directivesynopsis>
+<directivesynopsis>
+ <name>Protocols</name>
+ <description>Protocols available for a server/virtual host</description>
+ <syntax>Protocols <var>protocol</var> ...</syntax>
+ <default>Protocols http/1.1</default>
+ <contextlist><context>server config</context><context>virtual host</context></contextlist>
+ <compatibility>Only available from Apache 2.4.17 and later.</compatibility>
+
+ <usage>
+ <p>This directive specifies the list of protocols supported for a
+ server/virtual host. The list determines the allowed protocols
+ a client may negotiate for this server/host.</p>
+
+ <p>You need to set protocols if you want to extend the available
+ protocols for a server/host. By default, only the http/1.1 protocol
+ (which includes the compatibility with 1.0 and 0.9 clients) is
+ allowed.</p>
+
+ <p>For example, if you want to support HTTP/2 for a server with TLS,
+ specify:</p>
+
+ <highlight language="config">
+ Protocols h2 http/1.1
+ </highlight>
+
+ <p>Valid protocols are <code>http/1.1</code> for http and https connections,
+ <code>h2</code> on https connections and <code>h2c</code> for http
+ connections. Modules may enable more protocols.</p>
+
+ <p>It is safe to specify protocols that are unavailable/disabled. Such
+ protocol names will simply be ignored.</p>
+
+ <p>Protocols specified in base servers are inherited for virtual hosts
+ only if the virtual host has no own Protocols directive. Or, the other
+ way around, Protocols directives in virtual hosts replace any
+ such directive in the base server.
+ </p>
+
+ </usage>
+ <seealso><directive module="core">ProtocolsHonorOrder</directive></seealso>
+</directivesynopsis>
+
+
+<directivesynopsis>
+ <name>ProtocolsHonorOrder</name>
+ <description>Protocols available for a server/virtual host</description>
+ <syntax>ProtocolsHonorOrder On|Off</syntax>
+ <default>ProtocolsHonorOrder On</default>
+ <contextlist><context>server config</context><context>virtual host</context></contextlist>
+ <compatibility>Only available from Apache 2.4.17 and later.</compatibility>
+
+ <usage>
+ <p>This directive specifies if the server should honor the order in which
+ the <directive>Protocols</directive> directive lists protocols.</p>
+
+ <p>If configured Off, the client supplied list order of protocols has
+ precedence over the order in the server configuration.</p>
+
+ <p>With <directive>ProtocolsHonorOrder</directive> set to <code>on</code>
+ (default), the client ordering does not matter and only the ordering
+ in the server settings influences the outcome of the protocol
+ negotiation.</p>
+
+ </usage>
+ <seealso><directive module="core">Protocols</directive></seealso>
+</directivesynopsis>
+
+
<directivesynopsis>
<name>RLimitCPU</name>
<description>Limits the CPU consumption of processes launched
--- /dev/null
+# GENERATED FROM XML -- DO NOT EDIT
+
+URI: mod_h2.html.en
+Content-Language: en
+Content-type: text/html; charset=ISO-8859-1
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
+<meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type" />
+<!--
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ This file is generated from xml source: DO NOT EDIT
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ -->
+<title>mod_h2 - Apache HTTP Server Version 2.5</title>
+<link href="../style/css/manual.css" rel="stylesheet" media="all" type="text/css" title="Main stylesheet" />
+<link href="../style/css/manual-loose-100pc.css" rel="alternate stylesheet" media="all" type="text/css" title="No Sidebar - Default font size" />
+<link href="../style/css/manual-print.css" rel="stylesheet" media="print" type="text/css" /><link rel="stylesheet" type="text/css" href="../style/css/prettify.css" />
+<script src="../style/scripts/prettify.min.js" type="text/javascript">
+</script>
+
+<link href="../images/favicon.ico" rel="shortcut icon" /></head>
+<body>
+<div id="page-header">
+<p class="menu"><a href="../mod/">Modules</a> | <a href="../mod/quickreference.html">Directives</a> | <a href="http://wiki.apache.org/httpd/FAQ">FAQ</a> | <a href="../glossary.html">Glossary</a> | <a href="../sitemap.html">Sitemap</a></p>
+<p class="apache">Apache HTTP Server Version 2.5</p>
+<img alt="" src="../images/feather.gif" /></div>
+<div class="up"><a href="./"><img title="<-" alt="<-" src="../images/left.gif" /></a></div>
+<div id="path">
+<a href="http://www.apache.org/">Apache</a> > <a href="http://httpd.apache.org/">HTTP Server</a> > <a href="http://httpd.apache.org/docs/">Documentation</a> > <a href="../">Version 2.5</a> > <a href="./">Modules</a></div>
+<div id="page-content">
+<div id="preamble"><h1>Apache Module mod_h2</h1>
+<div class="toplang">
+<p><span>Available Languages: </span><a href="../en/mod/mod_h2.html" title="English"> en </a></p>
+</div>
+<table class="module"><tr><th><a href="module-dict.html#Description">Description:</a></th><td>Support for the HTTP/2 transport layer</td></tr>
+<tr><th><a href="module-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="module-dict.html#ModuleIdentifier">Module Identifier:</a></th><td>h2_module</td></tr>
+<tr><th><a href="module-dict.html#SourceFile">Source File:</a></th><td>mod_h2.c</td></tr></table>
+<h3>Summary</h3>
+
+ <p>This module provides HTTP/2 (RFC 7540) support for the Apache
+ HTTP Server.</p>
+
+ <p>This module relies on <a href="http://nghttp2.org/">libnghttp2</a>
+ to provide the core http/2 engine.</p>
+
+ </div>
+<div id="quickview"><h3 class="directives">Directives</h3>
+<ul id="toc">
+<li><img alt="" src="../images/down.gif" /> <a href="#h2bufferoutput">H2BufferOutput</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2buffersize">H2BufferSize</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2bufferwritemax">H2BufferWriteMax</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2direct">H2Direct</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2engine">H2Engine</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2maxheaderlistsize">H2MaxHeaderListSize</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2maxsessionstreams">H2MaxSessionStreams</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2maxworkeridleseconds">H2MaxWorkerIdleSeconds</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2maxworkers">H2MaxWorkers</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2minworkers">H2MinWorkers</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2serializeheaders">H2SerializeHeaders</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2sessionextrafiles">H2SessionExtraFiles</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2streammaxmemsize">H2StreamMaxMemSize</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#h2windowsize">H2WindowSize</a></li>
+</ul>
+<ul class="seealso"><li><a href="#comments_section">Comments</a></li></ul></div>
+
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2BufferOutput" id="H2BufferOutput">H2BufferOutput</a> <a name="h2bufferoutput" id="h2bufferoutput">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Output Buffering Switch</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2BufferOutput on|off</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive toggles if buffering of HTTP/2 output shall be used
+ or if data is written immediately when it arrives. Unless specified
+ otherwise, this directive is <code>on</code> for TLS connections and
+ <code>off</code> for plain connections.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2BufferOutput on</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2BufferSize" id="H2BufferSize">H2BufferSize</a> <a name="h2buffersize" id="h2buffersize">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Buffer size for outgoing data per HTTP/2 connection.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2BufferSize <em>bytes</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2BufferSize 65536</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the size of the buffer used to hold outgoing
+ HTTP/2 raw data, should <code>H2BufferOutput</code> be switched on.
+ This data is allocated per HTTP/2 connection, not stream and is
+ counted against the raw protocol data.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2BufferSize 128000</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2BufferWriteMax" id="H2BufferWriteMax">H2BufferWriteMax</a> <a name="h2bufferwritemax" id="h2bufferwritemax">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Maximum size of write on a HTTP/2 connection.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2BufferWriteMax <em>bytes</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2BufferWriteMax 16384</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets maximum amount of data sent out in a single
+ write on a http/2 connection. It only takes effect when
+ <code>H2BufferOutput</code> is switched on.
+ </p><p>
+ This directive affects performance of underlying TLS transports. TLS
+ transforms each write into an encrypted record. Clients need
+ to receive all of the record in order to decrypt it. Larger sizes
+ result in better server performance, shorter sizes can affect web
+ page paint timings.
+ </p><p>
+ <code>BufferSize</code> should be a multiple of <code>H2BufferWriteMax</code>.
+ <code>H2BufferWriteMax</code>, if larger than 16k, should be a multiple of 16k,
+ since this is the TLS max record size. Be aware that there are TLS
+ extensions to limit the record size to powers of 2 less than 16k.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2BufferWriteMax 8000</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2Direct" id="H2Direct">H2Direct</a> <a name="h2direct" id="h2direct">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>H2 Direct Protocol Switch</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2Direct on|off</code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2Direct on</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive toggles the usage of the HTTP/2 Direct Mode. This
+ should be used inside a
+ <code class="directive"><a href="../mod/core.html#virtualhost"><VirtualHost></a></code>
+ section to enable direct HTTP/2 communication for that virtual host.
+ Direct communication means that if the first bytes received by the
+ server on a connection match the HTTP/2 preamble, the HTTP/2
+ protocol is switched to immediately without further negotiation.
+ This mode falls outside the RFC 7540 but has become widely implemented
+ as it is very convenient for development and testing.
+ By default the direct HTTP/2 mode is enabled.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2Direct on</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2Engine" id="H2Engine">H2Engine</a> <a name="h2engine" id="h2engine">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>H2 Engine Operation Switch</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2Engine on|off</code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2Engine off</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive toggles the usage of the HTTP/2 Protocol Engine. This
+ should be used inside a
+ <code class="directive"><a href="../mod/core.html#virtualhost"><VirtualHost></a></code>
+ section to enable HTTP/2 for that virtual host. By default the
+ HTTP/2 Protocol Engine is disabled for both the main server and all
+ configured virtual hosts.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config"><VirtualHost _default_:443>
+ H2Engine on
+ #...
+</VirtualHost></pre>
+</div>
+ <p>
+ The HTTP/2 engine is usable in TLS and plain scenarios, supporting
+ the 'h2' and 'h2c' variants of the protocol.
+ </p>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2MaxHeaderListSize" id="H2MaxHeaderListSize">H2MaxHeaderListSize</a> <a name="h2maxheaderlistsize" id="h2maxheaderlistsize">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Maximum size of acceptable stream headers.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2MaxHeaderListSize <em>bytes</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2MaxHeaderListSize 16384</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the maximum amount of stream header bytes that
+ the server is willing to accept. It is announced to the client during
+ the initial HTTP/2 handshake.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2MaxHeaderListSize 10000</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2MaxSessionStreams" id="H2MaxSessionStreams">H2MaxSessionStreams</a> <a name="h2maxsessionstreams" id="h2maxsessionstreams">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Maximum number of active streams per HTTP/2 session.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2MaxSessionStreams <em>n</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2MaxSessionStreams 100</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the maximum number of active streams per HTTP/2 session (e.g. connection)
+ that the server allows. A stream is active if it is not <code>idle</code> or
+ <code>closed</code> according to RFC 7540.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2MaxSessionStreams 20</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2MaxWorkerIdleSeconds" id="H2MaxWorkerIdleSeconds">H2MaxWorkerIdleSeconds</a> <a name="h2maxworkeridleseconds" id="h2maxworkeridleseconds">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Maximum number of seconds h2 workers remain idle until shut down.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2MaxWorkerIdleSeconds <em>n</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2MaxWorkerIdleSeconds 600</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the maximum number of seconds a h2 worker may
+ idle until it shuts itself down. This only happens while the number of
+ h2 workers exceeds <code>H2MinWorkers</code>.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2MaxWorkerIdleSeconds 20</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2MaxWorkers" id="H2MaxWorkers">H2MaxWorkers</a> <a name="h2maxworkers" id="h2maxworkers">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Maximum number of worker threads to use per child process.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2MaxWorkers <em>n</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the maximum number of worker threads to spawn
+ per child process for HTTP/2 processing. If this directive is not used,
+ <code>mod_h2</code> will chose a value suitable for the <code>mpm</code>
+ module loaded.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2MaxWorkers 20</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2MinWorkers" id="H2MinWorkers">H2MinWorkers</a> <a name="h2minworkers" id="h2minworkers">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Minimal number of worker threads to use per child process.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2MinWorkers <em>n</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the minimum number of worker threads to spawn
+ per child process for HTTP/2 processing. If this directive is not used,
+ <code>mod_h2</code> will chose a value suitable for the <code>mpm</code>
+ module loaded.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2MinWorkers 10</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2SerializeHeaders" id="H2SerializeHeaders">H2SerializeHeaders</a> <a name="h2serializeheaders" id="h2serializeheaders">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Serialize Request/Resoonse Processing Switch</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2SerializeHeaders on|off</code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2SerializeHeaders off</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive toggles if HTTP/2 requests shall be serialized in
+ HTTP/1.1 format for processing by <code>httpd</code> core or if
+ received binary data shall be passed into the <code>request_rec</code>s
+ directly.
+ </p>
+ <p>
+ Serialization will lower performance, but gives more backward
+ compatibility in case custom filters/hooks need it.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2SerializeHeaders on</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2SessionExtraFiles" id="H2SessionExtraFiles">H2SessionExtraFiles</a> <a name="h2sessionextrafiles" id="h2sessionextrafiles">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Number of Extra File Handles</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2SessionExtraFiles <em>n</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2SessionExtraFiles 5</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets maximum number of <em>extra</em> file handles
+ a HTTP/2 session is allowed to use. A file handle is counted as
+ <em>extra</em> when it is transfered from a h2 worker thread to
+ the main HTTP/2 connection handling. This commonly happens when
+ serving static files.
+ </p><p>
+ Depending on the processing model configured on the server, the
+ number of connections times number of active streams may exceed
+ the number of file handles for the process. On the other hand,
+ converting every file into memory bytes early results in too
+ many buffer writes. This option helps to mitigate that.
+ </p><p>
+ The number of file handles used by a server process is then in
+ the order of:
+ </p>
+ <pre>(h2_connections * extra_files) + (h2_max_worker)</pre>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2SessionExtraFiles 10</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2StreamMaxMemSize" id="H2StreamMaxMemSize">H2StreamMaxMemSize</a> <a name="h2streammaxmemsize" id="h2streammaxmemsize">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Maximum amount of output data buffered per stream.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2StreamMaxMemSize <em>bytes</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2StreamMaxMemSize 65536</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the maximum number of outgoing data bytes buffered in memory
+ for an active streams. This memory is not allocated per stream as such. Allocations
+ are counted against this limit when they are about to be done. Stream processing
+ freezes when the limit has been reached and will only continue when buffered data
+ has been sent out to the client.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2StreamMaxMemSize 128000</pre>
+</div>
+
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="H2WindowSize" id="H2WindowSize">H2WindowSize</a> <a name="h2windowsize" id="h2windowsize">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Size of Stream Window for upstream data.</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>H2WindowSize <em>bytes</em></code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>H2WindowSize 65536</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_h2</td></tr>
+</table>
+ <p>
+ This directive sets the size of the window that is used for flow control
+ from client to server and limits the amount of data the server has to buffer.
+ The client will stop sending on a stream once the limit has been reached until
+ the server announces more available space (as it has processed some of the data).
+ </p><p>
+ This limit affects only request bodies, not its meta data such as headers. Also,
+ it has no effect on response bodies as the window size for those are managed
+ by the clients.
+ </p>
+ <div class="example"><h3>Example</h3><pre class="prettyprint lang-config">H2WindowSize 128000</pre>
+</div>
+
+</div>
+</div>
+<div class="bottomlang">
+<p><span>Available Languages: </span><a href="../en/mod/mod_h2.html" title="English"> en </a></p>
+</div><div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div><div class="section"><h2><a id="comments_section" name="comments_section">Comments</a></h2><div class="warning"><strong>Notice:</strong><br />This is not a Q&A section. Comments placed here should be pointed towards suggestions on improving the documentation or server, and may be removed again by our moderators if they are either implemented or considered invalid/off-topic. Questions on how to manage the Apache HTTP Server should be directed at either our IRC channel, #httpd, on Freenode, or sent to our <a href="http://httpd.apache.org/lists.html">mailing lists</a>.</div>
+<script type="text/javascript"><!--//--><![CDATA[//><!--
+var comments_shortname = 'httpd';
+var comments_identifier = 'http://httpd.apache.org/docs/trunk/mod/mod_h2.html';
+(function(w, d) {
+ if (w.location.hostname.toLowerCase() == "httpd.apache.org") {
+ d.write('<div id="comments_thread"><\/div>');
+ var s = d.createElement('script');
+ s.type = 'text/javascript';
+ s.async = true;
+ s.src = 'https://comments.apache.org/show_comments.lua?site=' + comments_shortname + '&page=' + comments_identifier;
+ (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(s);
+ }
+ else {
+ d.write('<div id="comments_thread">Comments are disabled for this page at the moment.<\/div>');
+ }
+})(window, document);
+//--><!]]></script></div><div id="footer">
+<p class="apache">Copyright 2015 The Apache Software Foundation.<br />Licensed under the <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>.</p>
+<p class="menu"><a href="../mod/">Modules</a> | <a href="../mod/quickreference.html">Directives</a> | <a href="http://wiki.apache.org/httpd/FAQ">FAQ</a> | <a href="../glossary.html">Glossary</a> | <a href="../sitemap.html">Sitemap</a></p></div><script type="text/javascript"><!--//--><![CDATA[//><!--
+if (typeof(prettyPrint) !== 'undefined') {
+ prettyPrint();
+}
+//--><!]]></script>
+</body></html>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision$ -->
+
+<!--
+ 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.
+ -->
+
+<modulesynopsis metafile="mod_h2.xml.meta">
+
+ <name>mod_h2</name>
+ <description>Support for the HTTP/2 transport layer</description>
+ <status>Extension</status>
+ <sourcefile>mod_h2.c</sourcefile>
+ <identifier>h2_module</identifier>
+
+ <summary>
+ <p>This module provides HTTP/2 (RFC 7540) support for the Apache
+ HTTP Server.</p>
+
+ <p>This module relies on <a href="http://nghttp2.org/">libnghttp2</a>
+ to provide the core http/2 engine.</p>
+
+ </summary>
+
+ <directivesynopsis>
+ <name>H2Direct</name>
+ <description>H2 Direct Protocol Switch</description>
+ <syntax>H2Direct on|off</syntax>
+ <default>H2Direct on (for non TLS)</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+
+ <usage>
+ <p>
+ This directive toggles the usage of the HTTP/2 Direct Mode. This
+ should be used inside a
+ <directive module="core" type="section">VirtualHost</directive>
+ section to enable direct HTTP/2 communication for that virtual host.
+ Direct communication means that if the first bytes received by the
+ server on a connection match the HTTP/2 preamble, the HTTP/2
+ protocol is switched to immediately without further negotiation.
+ This mode falls outside the RFC 7540 but has become widely implemented
+ as it is very convenient for development and testing.
+ By default the direct HTTP/2 mode is enabled.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2Direct on
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2MaxSessionStreams</name>
+ <description>Maximum number of active streams per HTTP/2 session.</description>
+ <syntax>H2MaxSessionStreams <em>n</em></syntax>
+ <default>H2MaxSessionStreams 100</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive sets the maximum number of active streams per HTTP/2 session (e.g. connection)
+ that the server allows. A stream is active if it is not <code>idle</code> or
+ <code>closed</code> according to RFC 7540.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2MaxSessionStreams 20
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2StreamMaxMemSize</name>
+ <description>Maximum amount of output data buffered per stream.</description>
+ <syntax>H2StreamMaxMemSize <em>bytes</em></syntax>
+ <default>H2StreamMaxMemSize 65536</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive sets the maximum number of outgoing data bytes buffered in memory
+ for an active streams. This memory is not allocated per stream as such. Allocations
+ are counted against this limit when they are about to be done. Stream processing
+ freezes when the limit has been reached and will only continue when buffered data
+ has been sent out to the client.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2StreamMaxMemSize 128000
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2WindowSize</name>
+ <description>Size of Stream Window for upstream data.</description>
+ <syntax>H2WindowSize <em>bytes</em></syntax>
+ <default>H2WindowSize 65536</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive sets the size of the window that is used for flow control
+ from client to server and limits the amount of data the server has to buffer.
+ The client will stop sending on a stream once the limit has been reached until
+ the server announces more available space (as it has processed some of the data).
+ </p><p>
+ This limit affects only request bodies, not its meta data such as headers. Also,
+ it has no effect on response bodies as the window size for those are managed
+ by the clients.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2WindowSize 128000
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2MinWorkers</name>
+ <description>Minimal number of worker threads to use per child process.</description>
+ <syntax>H2MinWorkers <em>n</em></syntax>
+ <contextlist>
+ <context>server config</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive sets the minimum number of worker threads to spawn
+ per child process for HTTP/2 processing. If this directive is not used,
+ <code>mod_h2</code> will chose a value suitable for the <code>mpm</code>
+ module loaded.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2MinWorkers 10
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2MaxWorkers</name>
+ <description>Maximum number of worker threads to use per child process.</description>
+ <syntax>H2MaxWorkers <em>n</em></syntax>
+ <contextlist>
+ <context>server config</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive sets the maximum number of worker threads to spawn
+ per child process for HTTP/2 processing. If this directive is not used,
+ <code>mod_h2</code> will chose a value suitable for the <code>mpm</code>
+ module loaded.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2MaxWorkers 20
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2MaxWorkerIdleSeconds</name>
+ <description>Maximum number of seconds h2 workers remain idle until shut down.</description>
+ <syntax>H2MaxWorkerIdleSeconds <em>n</em></syntax>
+ <default>H2MaxWorkerIdleSeconds 600</default>
+ <contextlist>
+ <context>server config</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive sets the maximum number of seconds a h2 worker may
+ idle until it shuts itself down. This only happens while the number of
+ h2 workers exceeds <code>H2MinWorkers</code>.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2MaxWorkerIdleSeconds 20
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2SessionExtraFiles</name>
+ <description>Number of Extra File Handles</description>
+ <syntax>H2SessionExtraFiles <em>n</em></syntax>
+ <default>H2SessionExtraFiles 5</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive sets maximum number of <em>extra</em> file handles
+ a HTTP/2 session is allowed to use. A file handle is counted as
+ <em>extra</em> when it is transfered from a h2 worker thread to
+ the main HTTP/2 connection handling. This commonly happens when
+ serving static files.
+ </p><p>
+ Depending on the processing model configured on the server, the
+ number of connections times number of active streams may exceed
+ the number of file handles for the process. On the other hand,
+ converting every file into memory bytes early results in too
+ many buffer writes. This option helps to mitigate that.
+ </p><p>
+ The number of file handles used by a server process is then in
+ the order of:
+ </p>
+ <pre>
+ (h2_connections * extra_files) + (h2_max_worker)
+ </pre>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2SessionExtraFiles 10
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2SerializeHeaders</name>
+ <description>Serialize Request/Resoonse Processing Switch</description>
+ <syntax>H2SerializeHeaders on|off</syntax>
+ <default>H2SerializeHeaders off</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <usage>
+ <p>
+ This directive toggles if HTTP/2 requests shall be serialized in
+ HTTP/1.1 format for processing by <code>httpd</code> core or if
+ received binary data shall be passed into the <code>request_rec</code>s
+ directly.
+ </p>
+ <p>
+ Serialization will lower performance, but gives more backward
+ compatibility in case custom filters/hooks need it.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2SerializeHeaders on
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
+
+</modulesynopsis>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- GENERATED FROM XML: DO NOT EDIT -->
+
+<metafile reference="mod_h2.xml">
+ <basename>mod_h2</basename>
+ <path>/mod/</path>
+ <relpath>..</relpath>
+
+ <variants>
+ <variant>en</variant>
+ </variants>
+</metafile>
<td>Balancer sticky session name. The value is usually set to something
like <code>JSESSIONID</code> or <code>PHPSESSIONID</code>,
and it depends on the backend application server that support sessions.
- If the backend application server uses different names for cookies
- and url encoded id (like servlet containers), use | to separate them.
- The first part is for the cookie; the second for the path.<br />
+ If the backend application server uses different name for cookies
+ and url encoded id (like servlet containers) use | to separate them.
+ The first part is for the cookie the second for the path.<br />
Available in Apache HTTP Server 2.4.4 and later.
</td></tr>
<tr><td>stickysessionsep</td>
will exit.</td></tr>
<tr class="odd"><td><a href="mod_unixd.html#group">Group <var>unix-group</var></a></td><td> #-1 </td><td>s</td><td>B</td></tr><tr class="odd"><td class="descr" colspan="4">Group under which the server will answer
requests</td></tr>
-<tr><td><a href="mod_headers.html#header" id="H" name="H">Header [<var>condition</var>] add|append|echo|edit|edit*|merge|set|setifempty|unset|note
+<tr><td><a href="mod_h2.html#h2bufferoutput" id="H" name="H">H2BufferOutput on|off</a></td><td></td><td>sv</td><td>E</td></tr><tr><td class="descr" colspan="4">Output Buffering Switch</td></tr>
+<tr class="odd"><td><a href="mod_h2.html#h2buffersize">H2BufferSize <em>bytes</em></a></td><td> 65536 </td><td>sv</td><td>E</td></tr><tr class="odd"><td class="descr" colspan="4">Buffer size for outgoing data per HTTP/2 connection.</td></tr>
+<tr><td><a href="mod_h2.html#h2bufferwritemax">H2BufferWriteMax <em>bytes</em></a></td><td> 16384 </td><td>sv</td><td>E</td></tr><tr><td class="descr" colspan="4">Maximum size of write on a HTTP/2 connection.</td></tr>
+<tr class="odd"><td><a href="mod_h2.html#h2direct">H2Direct on|off</a></td><td> on </td><td>sv</td><td>E</td></tr><tr class="odd"><td class="descr" colspan="4">H2 Direct Protocol Switch</td></tr>
+<tr><td><a href="mod_h2.html#h2engine">H2Engine on|off</a></td><td> off </td><td>sv</td><td>E</td></tr><tr><td class="descr" colspan="4">H2 Engine Operation Switch</td></tr>
+<tr class="odd"><td><a href="mod_h2.html#h2maxheaderlistsize">H2MaxHeaderListSize <em>bytes</em></a></td><td> 16384 </td><td>sv</td><td>E</td></tr><tr class="odd"><td class="descr" colspan="4">Maximum size of acceptable stream headers.</td></tr>
+<tr><td><a href="mod_h2.html#h2maxsessionstreams">H2MaxSessionStreams <em>n</em></a></td><td> 100 </td><td>sv</td><td>E</td></tr><tr><td class="descr" colspan="4">Maximum number of active streams per HTTP/2 session.</td></tr>
+<tr class="odd"><td><a href="mod_h2.html#h2maxworkeridleseconds">H2MaxWorkerIdleSeconds <em>n</em></a></td><td> 600 </td><td>s</td><td>E</td></tr><tr class="odd"><td class="descr" colspan="4">Maximum number of seconds h2 workers remain idle until shut down.</td></tr>
+<tr><td><a href="mod_h2.html#h2maxworkers">H2MaxWorkers <em>n</em></a></td><td></td><td>s</td><td>E</td></tr><tr><td class="descr" colspan="4">Maximum number of worker threads to use per child process.</td></tr>
+<tr class="odd"><td><a href="mod_h2.html#h2minworkers">H2MinWorkers <em>n</em></a></td><td></td><td>s</td><td>E</td></tr><tr class="odd"><td class="descr" colspan="4">Minimal number of worker threads to use per child process.</td></tr>
+<tr><td><a href="mod_h2.html#h2serializeheaders">H2SerializeHeaders on|off</a></td><td> off </td><td>sv</td><td>E</td></tr><tr><td class="descr" colspan="4">Serialize Request/Resoonse Processing Switch</td></tr>
+<tr class="odd"><td><a href="mod_h2.html#h2sessionextrafiles">H2SessionExtraFiles <em>n</em></a></td><td> 5 </td><td>sv</td><td>E</td></tr><tr class="odd"><td class="descr" colspan="4">Number of Extra File Handles</td></tr>
+<tr><td><a href="mod_h2.html#h2streammaxmemsize">H2StreamMaxMemSize <em>bytes</em></a></td><td> 65536 </td><td>sv</td><td>E</td></tr><tr><td class="descr" colspan="4">Maximum amount of output data buffered per stream.</td></tr>
+<tr class="odd"><td><a href="mod_h2.html#h2windowsize">H2WindowSize <em>bytes</em></a></td><td> 65536 </td><td>sv</td><td>E</td></tr><tr class="odd"><td class="descr" colspan="4">Size of Stream Window for upstream data.</td></tr>
+<tr><td><a href="mod_headers.html#header">Header [<var>condition</var>] add|append|echo|edit|edit*|merge|set|setifempty|unset|note
<var>header</var> [[expr=]<var>value</var> [<var>replacement</var>]
[early|env=[!]<var>varname</var>|expr=<var>expression</var>]]
</a></td><td></td><td>svdh</td><td>E</td></tr><tr><td class="descr" colspan="4">Configure HTTP response headers</td></tr>
* 20120211.46 (2.4.13-dev) Add ap_map_http_request_error()
* 20120211.47 (2.4.13-dev) Add ap_some_authn_required, ap_force_authn hook.
* Deprecate broken ap_some_auth_required.
+ * 20140627.4 (2.5.0-dev) Added ap_parse_token_list_strict() to httpd.h.
*/
#define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */
#define AP_MERGE_TRAILERS_DISABLE 2
int merge_trailers;
+
+
+ apr_array_header_t *protocols;
+ int protocols_honor_order;
} core_server_config;
/* for AddOutputFiltersByType in core.c */
*/
AP_DECLARE_HOOK(apr_port_t,default_port,(const request_rec *r))
+
+#define AP_PROTOCOL_HTTP1 "http/1.1"
+
+/**
+ * Determine the list of protocols available for a connection/request. This may
+ * be collected with or without any request sent, in which case the request is
+ * NULL. Or it may be triggered by the request received, e.g. through the
+ * "Upgrade" header.
+ *
+ * This hook will be run whenever protocols are being negotiated (ALPN as
+ * one example). It may also be invoked at other times, e.g. when the server
+ * wants to advertise protocols it is capable of switching to.
+ *
+ * The identifiers for protocols are taken from the TLS extension type ALPN:
+ * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xml
+ *
+ * If no protocols are added to the proposals, the server not perform any
+ * switch. If the protocol selected from the proposals is the protocol
+ * already in place, also no protocol switch will be invoked.
+ *
+ * The client may already have announced the protocols it is willing to
+ * accept. These will then be listed as offers. This parameter may also
+ * be NULL, indicating that offers from the client are not known and
+ * the hooks should propose all protocols that are valid for the
+ * current connection/request.
+ *
+ * All hooks are run, unless one returns an error. Proposals may contain
+ * duplicates. The order in which proposals are added is usually ignored.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param offers A list of protocol identifiers offered by the client or
+ * NULL to indicated that the hooks are free to propose
+ * @param proposals The list of protocol identifiers proposed by the hooks
+ * @return OK or DECLINED
+ */
+AP_DECLARE_HOOK(int,protocol_propose,(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *offers,
+ apr_array_header_t *proposals))
+
+/**
+ * Perform a protocol switch on the connection. The exact requirements for
+ * that depend on the protocol in place and the one switched to. The first
+ * protocol module to handle the switch is the last module run.
+ *
+ * For a connection level switch (r == NULL), the handler must on return
+ * leave the conn_rec in a state suitable for processing the switched
+ * protocol, e.g. correct filters in place.
+ *
+ * For a request triggered switch (r != NULL), the protocol switch is done
+ * before the response is sent out. When switching from "http/1.1" via Upgrade
+ * header, the 101 intermediate response will have been sent. The
+ * hook needs then to process the connection until it can be closed. Which
+ * the server will enforce on hook return.
+ * Any error the hook might encounter must already be sent by the hook itself
+ * to the client in whatever form the new protocol requires.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param choices A list of protocol identifiers, normally the clients whishes
+ * @param proposals the list of protocol identifiers proposed by the hooks
+ * @return OK or DECLINED
+ */
+AP_DECLARE_HOOK(int,protocol_switch,(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const char *protocol))
+
+/**
+ * Return the protocol used on the connection. Modules implementing
+ * protocol switching must register here and return the correct protocol
+ * identifier for connections they switched.
+ *
+ * To find out the protocol for the current connection, better call
+ * @see ap_get_protocol which internally uses this hook.
+ *
+ * @param c The current connection
+ * @return The identifier of the protocol in place or NULL
+ */
+AP_DECLARE_HOOK(const char *,protocol_get,(const conn_rec *c))
+
+/**
+ * Select a protocol for the given connection and optional request. Will return
+ * the protocol identifier selected which may be the protocol already in place
+ * on the connection. The selected protocol will be NULL if non of the given
+ * choices could be agreed upon (e.g. no proposal as made).
+ *
+ * A special case is where the choices itself is NULL (instead of empty). In
+ * this case there are no restrictions imposed on protocol selection.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param choices A list of protocol identifiers, normally the clients whishes
+ * @return The selected protocol or NULL if no protocol could be agreed upon
+ */
+AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *choices);
+
+/**
+ * Perform the actual protocol switch. The protocol given must have been
+ * selected before on the very same connection and request pair.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param protocol the protocol to switch to
+ * @return APR_SUCCESS, if caller may continue processing as usual
+ * APR_EOF, if caller needs to stop processing the connection
+ * APR_EINVAL, if the protocol is already in place
+ * APR_NOTIMPL, if no module performed the switch
+ * Other errors where appropriate
+ */
+AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const char *protocol);
+
+/**
+ * Call the protocol_get hook to determine the protocol currently in use
+ * for the given connection.
+ *
+ * Unless another protocol has been switch to, will default to
+ * @see AP_PROTOCOL_HTTP1 and modules implementing a new protocol must
+ * report a switched connection via the protocol_get hook.
+ *
+ * @param c The connection to determine the protocol for
+ * @return the protocol in use, never NULL
+ */
+AP_DECLARE(const char *) ap_get_protocol(conn_rec *c);
+
/** @see ap_bucket_type_error */
typedef struct ap_bucket_error ap_bucket_error;
* the response to the client
* @param r The current request
*/
-void ap_process_request(request_rec *r);
+AP_DECLARE(void) ap_process_request(request_rec *r);
/* For post-processing after a handler has finished with a request.
* (Commonly used after it was suspended)
#define HTTP_UNSUPPORTED_MEDIA_TYPE 415
#define HTTP_RANGE_NOT_SATISFIABLE 416
#define HTTP_EXPECTATION_FAILED 417
+#define HTTP_MISDIRECTED_REQUEST 421
#define HTTP_UNPROCESSABLE_ENTITY 422
#define HTTP_LOCKED 423
#define HTTP_FAILED_DEPENDENCY 424
*/
AP_DECLARE(int) ap_find_etag_strong(apr_pool_t *p, const char *line, const char *tok);
+/**
+ * Retrieve an array of tokens in the format "1#token" defined in RFC2616. Only
+ * accepts ',' as a delimiter, does not accept quoted strings, and errors on
+ * any separator.
+ * @param p The pool to allocate from
+ * @param tok The line to read tokens from
+ * @param tokens Pointer to an array of tokens. If not NULL, must be an array
+ * of char*, otherwise it will be allocated on @a p when a token is found
+ * @param skip_invalid If true, when an invalid separator is encountered, it
+ * will be ignored.
+ * @return NULL on success, an error string otherwise.
+ * @remark *tokens may be NULL on output if NULL in input and no token is found
+ */
+AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p, const char *tok,
+ apr_array_header_t **tokens,
+ int skip_invalid);
+
/**
* Retrieve a token, spacing over it and adjusting the pointer to
* the first non-white byte afterwards. Note that these tokens
#define AP_NORESTART APR_OS_START_USEERR + 1
+/**
+ * Get the first index of the string in the array or -1 if not found. Start
+ * searching a start.
+ * @param array The array the check
+ * @param s The string to find
+ * @param start Start index for search. If start is out of bounds (negative or
+ equal to array length or greater), -1 will be returned.
+ * @return index of string in array or -1
+ */
+AP_DECLARE(int) ap_array_str_index(const apr_array_header_t *array,
+ const char *s,
+ int start);
+
+/**
+ * Check if the string is member of the given array by strcmp.
+ * @param array The array the check
+ * @param s The string to find
+ * @return !=0 iff string is member of array (via strcmp)
+ */
+AP_DECLARE(int) ap_array_str_contains(const apr_array_header_t *array,
+ const char *s);
+
#ifdef __cplusplus
}
#endif
# variable WITH_SSL=1
# To build with the mod_lua module set the environment
# variable WITH_LUA=1
-
+# To build with the mod_h2 module set the environment
+# variable WITH_HTTP2=1
#
# Check if LDAP is enabled in APR-UTIL
#
endif
endif
+# Allow the mod_h2 module to be built if WITH_HTTP2 is defined
+ifeq "$(WITH_HTTP2)" "1"
+ifneq "$(NGH2SRC)" ""
+SUBDIRS += http2
+endif
+endif
+
# Allow the experimental modules to be built if WITH_EXPERIMENTAL is defined
ifeq "$(WITH_EXPERIMENTAL)" "1"
SUBDIRS += experimental
http/
This directory houses modules that basic HTTP protocol implementation.
+http2/
+ This directory houses modules that provide HTTP/2 protocol implementation.
+
loggers/
This directory houses modules that handle logging functions.
NULL, /* 418 */
NULL, /* 419 */
NULL, /* 420 */
- NULL, /* 421 */
+ "421 Misdirected Request",
"422 Unprocessable Entity",
"423 Locked",
"424 Failed Dependency",
case HTTP_NETWORK_AUTHENTICATION_REQUIRED:
return("<p>The client needs to authenticate to gain\n"
"network access.</p>\n");
+ case HTTP_MISDIRECTED_REQUEST:
+ return("<p>The client needs a new connection for this\n"
+ "request as the requested host name does not match\n"
+ "the Server Name Indication (SNI) in use for this\n"
+ "connection.</p>\n");
default: /* HTTP_INTERNAL_SERVER_ERROR */
/*
* This comparison to expose error-notes could be modified to
ap_process_request_after_handler(r);
}
-void ap_process_request(request_rec *r)
+AP_DECLARE(void) ap_process_request(request_rec *r)
{
apr_bucket_brigade *bb;
apr_bucket *b;
--- /dev/null
+*.xcuserstate
+sandbox/httpd/packages/httpd-2.4.x.tar.gz
+sandbox/test/conf/sites/mod-h2.greenbytes.de.conf
+*.o
+*.slo
+*.lo
+*.la
+*.pcap
+.libs
+.configured
+.deps
+compile
+aclocal.m4
+autom4te.cache
+autoscan.log
+config.guess
+config.log
+config.status
+config.sub
+config.h
+config.h.in
+config.h.in~
+configure
+configure.scan
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+Makefile.in
+Makefile
+mod_h2-*.tar.gz
+mod_h2/h2_version.h
+m4
--- /dev/null
+# 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.
+
+#
+# standard stuff
+#
+
+include $(top_srcdir)/build/special.mk
--- /dev/null
+#
+# This Makefile requires the environment var NGH2SRC
+# pointing to the base directory of nghttp2 source tree.
+#
+
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(NGH2SRC)/lib/ \
+ $(NGH2SRC)/lib/includes \
+ $(SERVER)/mpm/NetWare \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ -DHAVE_CONFIG_H \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ -L$(OBJDIR) \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_h2
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) HTTP2 Support module (w/ NGHTTP2 Lib)
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME)
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(OBJDIR)/nghttp2.lib \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs := $(sort $(patsubst %.c,$(OBJDIR)/%.o,$(wildcard *.c)))
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(OBJDIR)/nghttp2.lib \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Libc \
+ Apache2 \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ h2_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs := $(sort $(patsubst $(NGH2SRC)/lib/%.c,$(OBJDIR)/%.o,$(wildcard $(NGH2SRC)/lib/*.c)))
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(NGH2SRC)/lib/config.h $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+clean ::
+ $(call DEL,$(NGH2SRC)/lib/config.h)
+#
+# Any specialized rules here
+#
+vpath %.c $(NGH2SRC)/lib
+
+$(NGH2SRC)/lib/config.h : NWGNUmakefile
+ @echo $(DL)GEN $@$(DL)
+ @echo $(DL)/* For NetWare target.$(DL) > $@
+ @echo $(DL)** Do not edit - created by Make!$(DL) >> $@
+ @echo $(DL)*/$(DL) >> $@
+ @echo $(DL)#ifndef NGH2_CONFIG_H$(DL) >> $@
+ @echo $(DL)#define NGH2_CONFIG_H$(DL) >> $@
+ @echo #define HAVE_ARPA_INET_H 1 >> $@
+ @echo #define HAVE_CHOWN 1 >> $@
+ @echo #define HAVE_DECL_STRERROR_R 1 >> $@
+ @echo #define HAVE_DLFCN_H 1 >> $@
+ @echo #define HAVE_DUP2 1 >> $@
+ @echo #define HAVE_FCNTL_H 1 >> $@
+ @echo #define HAVE_GETCWD 1 >> $@
+ @echo #define HAVE_INTTYPES_H 1 >> $@
+ @echo #define HAVE_LIMITS_H 1 >> $@
+ @echo #define HAVE_LOCALTIME_R 1 >> $@
+ @echo #define HAVE_MALLOC 1 >> $@
+ @echo #define HAVE_MEMCHR 1 >> $@
+ @echo #define HAVE_MEMMOVE 1 >> $@
+ @echo #define HAVE_MEMORY_H 1 >> $@
+ @echo #define HAVE_MEMSET 1 >> $@
+ @echo #define HAVE_NETDB_H 1 >> $@
+ @echo #define HAVE_NETINET_IN_H 1 >> $@
+ @echo #define HAVE_PTRDIFF_T 1 >> $@
+ @echo #define HAVE_PWD_H 1 >> $@
+ @echo #define HAVE_SOCKET 1 >> $@
+ @echo #define HAVE_SQRT 1 >> $@
+ @echo #define HAVE_STDDEF_H 1 >> $@
+ @echo #define HAVE_STDINT_H 1 >> $@
+ @echo #define HAVE_STDLIB_H 1 >> $@
+ @echo #define HAVE_STRCHR 1 >> $@
+ @echo #define HAVE_STRDUP 1 >> $@
+ @echo #define HAVE_STRERROR 1 >> $@
+ @echo #define HAVE_STRERROR_R 1 >> $@
+ @echo #define HAVE_STRINGS_H 1 >> $@
+ @echo #define HAVE_STRING_H 1 >> $@
+ @echo #define HAVE_STRSTR 1 >> $@
+ @echo #define HAVE_STRTOL 1 >> $@
+ @echo #define HAVE_STRTOUL 1 >> $@
+ @echo #define HAVE_SYSLOG_H 1 >> $@
+ @echo #define HAVE_SYS_SOCKET_H 1 >> $@
+ @echo #define HAVE_SYS_STAT_H 1 >> $@
+ @echo #define HAVE_SYS_TIME_H 1 >> $@
+ @echo #define HAVE_SYS_TYPES_H 1 >> $@
+ @echo #define HAVE_TIME_H 1 >> $@
+ @echo #define HAVE_UNISTD_H 1 >> $@
+
+ @echo #define SIZEOF_INT_P 4 >> $@
+ @echo #define STDC_HEADERS 1 >> $@
+ @echo #define STRERROR_R_CHAR_P 4 >> $@
+
+# Hint to compiler a function parameter is not used
+ @echo #define _U_ >> $@
+
+ @echo #ifndef __cplusplus >> $@
+ @echo #define inline __inline >> $@
+ @echo #endif >> $@
+
+ @echo $(DL)#endif /* NGH2_CONFIG_H */$(DL) >> $@
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
--- /dev/null
+The h2 module adds support for the HTTP/2 protocol to the server.
+
+Specifically, it supports the protocols "h2" (HTTP2 over TLS) and "h2c"
+(HTTP2 over plain HTTP connections via Upgrade). Additionally it offers
+the "direct" mode for both encrypted and unencrypted connections.
+
+You may enable it for the whole server or specific virtual hosts only.
+
+
+BUILD
+
+If you have libnghttp2 (https://nghttp2.org) installed on your system, simply
+add
+
+ --enable-h2
+
+to your httpd ./configure invocation. Should libnghttp2 reside in a unusual
+location, add
+
+ --with-nghttp2=<path>
+
+to ./configure. <path> is expected to be the installation prefix, so there
+should be a <path>/lib/libnghttp2.*. If your system support pkg-config,
+<path>/lib/pkgconfig/libnghttp2.pc will be inspected.
+
+If you want to link nghttp2 statically into the mod_h2 module, you may
+similarly to mod_ssl add
+
+ --enable-nghttp2-staticlib-deps
+
+For this, the lib directory should only contain the libnghttp2.a, not its
+shared cousins.
+
+
+CONFIGURATION
+
+If mod_h2 is enabled for a site or not depends on the new "Protocols"
+directive. This directive list all protocols enabled for a server or
+virtual host.
+
+If you do not specify "Protocols" all available protocols are enabled. For
+sites using TLS, the protocol supported by mod_h2 is "h2". For cleartext
+http:, the offered protocol is "h2c".
+
+The following is an example of a server that only supports http/1.1 in
+general and offers h2 for a specific virtual host.
+
+ ...
+ Protocols http/1.1
+ <virtualhost *:443>
+ Protocols h2 http/1.1
+ ...
+ </virtualhost>
+
+Please see the documentation of mod_h2 for a complete list and explanation
+of all options.
+
+
+TLS CONFIGURATION
+
+If you want to use HTTP/2 with a browser, most modern browsers will support
+it without further configuration. However, browsers so far only support
+HTTP/2 over TLS and are expecially picky about the certificate and
+encryption ciphers used.
+
+Server admins may look for up-to-date information about "modern" TLS
+compatibility under:
+
+ https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
+
--- /dev/null
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements. See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License. You may obtain a copy of the License at
+dnl
+dnl http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl # start of module specific part
+APACHE_MODPATH_INIT(http2)
+
+dnl # list of module object files
+h2_objs="dnl
+mod_h2.lo dnl
+h2_alt_svc.lo dnl
+h2_config.lo dnl
+h2_conn.lo dnl
+h2_conn_io.lo dnl
+h2_ctx.lo dnl
+h2_from_h1.lo dnl
+h2_h2.lo dnl
+h2_io.lo dnl
+h2_io_set.lo dnl
+h2_mplx.lo dnl
+h2_request.lo dnl
+h2_response.lo dnl
+h2_session.lo dnl
+h2_stream.lo dnl
+h2_stream_set.lo dnl
+h2_switch.lo dnl
+h2_task.lo dnl
+h2_task_input.lo dnl
+h2_task_output.lo dnl
+h2_task_queue.lo dnl
+h2_to_h1.lo dnl
+h2_util.lo dnl
+h2_worker.lo dnl
+h2_workers.lo dnl
+"
+
+dnl
+dnl APACHE_CHECK_NGHTTP2
+dnl
+dnl Configure for nghttp2, giving preference to
+dnl "--with-nghttp2=<path>" if it was specified.
+dnl
+AC_DEFUN(APACHE_CHECK_NGHTTP2,[
+ AC_CACHE_CHECK([for nghttp2], [ac_cv_nghttp2], [
+ dnl initialise the variables we use
+ ac_cv_nghttp2=no
+ ap_nghttp2_found=""
+ ap_nghttp2_base=""
+ ap_nghttp2_libs=""
+
+ dnl Determine the nghttp2 base directory, if any
+ AC_MSG_CHECKING([for user-provided nghttp2 base directory])
+ AC_ARG_WITH(nghttp2, APACHE_HELP_STRING(--with-nghttp2=PATH, nghttp2 installation directory), [
+ dnl If --with-nghttp2 specifies a directory, we use that directory
+ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
+ dnl This ensures $withval is actually a directory and that it is absolute
+ ap_nghttp2_base="`cd $withval ; pwd`"
+ fi
+ ])
+ if test "x$ap_nghttp2_base" = "x"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($ap_nghttp2_base)
+ fi
+
+ dnl Run header and version checks
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LIBS="$LIBS"
+ saved_LDFLAGS="$LDFLAGS"
+
+ dnl Before doing anything else, load in pkg-config variables
+ if test -n "$PKGCONFIG"; then
+ saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
+ AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
+ if test "x$ap_nghttp2_base" != "x" -a \
+ -f "${ap_nghttp2_base}/lib/pkgconfig/libnghttp2.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system libnghttp2.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_nghttp2_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ fi
+ AC_ARG_ENABLE(nghttp2-staticlib-deps,APACHE_HELP_STRING(--enable-nghttp2-staticlib-deps,[link mod_h2 with dependencies of libnghttp2's static libraries (as indicated by "pkg-config --static"). Must be specified in addition to --enable-h2.]), [
+ if test "$enableval" = "yes"; then
+ PKGCONFIG_LIBOPTS="--static"
+ fi
+ ])
+ ap_nghttp2_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors libnghttp2`"
+ if test $? -eq 0; then
+ ap_nghttp2_found="yes"
+ pkglookup="`$PKGCONFIG --cflags-only-I libnghttp2`"
+ APR_ADDTO(CPPFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_CFLAGS, [$pkglookup])
+ APR_ADDTO(ab_CFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L libnghttp2`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other libnghttp2`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ fi
+ PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH"
+ fi
+
+ dnl fall back to the user-supplied directory if not found via pkg-config
+ if test "x$ap_nghttp2_base" != "x" -a "x$ap_nghttp2_found" = "x"; then
+ APR_ADDTO(CPPFLAGS, [-I$ap_nghttp2_base/include])
+ APR_ADDTO(MOD_CFLAGS, [-I$ap_nghttp2_base/include])
+ APR_ADDTO(ab_CFLAGS, [-I$ap_nghttp2_base/include])
+ APR_ADDTO(LDFLAGS, [-L$ap_nghttp2_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [-L$ap_nghttp2_base/lib])
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_nghttp2_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_nghttp2_base/lib])
+ fi
+ fi
+
+ AC_MSG_CHECKING([for nghttp2 version >= 1.0.0])
+ AC_TRY_COMPILE([#include <nghttp2/nghttp2ver.h>],[
+#if !defined(NGHTTP2_VERSION_NUM)
+#error "Missing nghttp2 version"
+#endif
+#if NGHTTP2_VERSION_NUM < 0x010000
+#error "Unsupported nghttp2 version " NGHTTP2_VERSION_TEXT
+#endif],
+ [AC_MSG_RESULT(OK)
+ ac_cv_nghttp2=yes],
+ [AC_MSG_RESULT(FAILED)])
+
+ if test "x$ac_cv_nghttp2" = "xyes"; then
+ ap_nghttp2_libs="${ap_nghttp2_libs:--lnghttp2} `$apr_config --libs`"
+ APR_ADDTO(MOD_LDFLAGS, [$ap_nghttp2_libs])
+ APR_ADDTO(LIBS, [$ap_nghttp2_libs])
+ APR_SETVAR(ab_LDFLAGS, [$MOD_LDFLAGS])
+ APACHE_SUBST(ab_CFLAGS)
+ APACHE_SUBST(ab_LDFLAGS)
+
+ dnl Run library and function checks
+ liberrors=""
+ AC_CHECK_HEADERS([nghttp2/nghttp2.h])
+ AC_CHECK_FUNCS([nghttp2_session_server_new2], [], [liberrors="yes"])
+ if test "x$liberrors" != "x"; then
+ AC_MSG_WARN([nghttp2 library is unusable])
+ fi
+ else
+ AC_MSG_WARN([nghttp2 version is too old])
+ fi
+
+ dnl restore
+ CPPFLAGS="$saved_CPPFLAGS"
+ LIBS="$saved_LIBS"
+ LDFLAGS="$saved_LDFLAGS"
+ ])
+ if test "x$ac_cv_nghttp2" = "xyes"; then
+ AC_DEFINE(HAVE_NGHTTP2, 1, [Define if nghttp2 is available])
+ fi
+])
+
+
+dnl # hook module into the Autoconf mechanism (--enable-h2 option)
+APACHE_MODULE(h2, [HTTP/2 support (mod_h2)], $h2_objs, , most, [
+ APACHE_CHECK_NGHTTP2
+ if test "$ac_cv_nghttp2" = "yes" ; then
+ if test "x$enable_ssl" = "xshared"; then
+ # The only symbol which needs to be exported is the module
+ # structure, so ask libtool to hide everything else:
+ APR_ADDTO(MOD_H2_LDADD, [-export-symbols-regex h2_module])
+ fi
+ else
+ enable_h2=no
+ fi
+])
+
+dnl # end of module specific part
+APACHE_MODPATH_FINISH
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_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_pstrndup(pool, s, 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_pstrndup(pool, s, 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)
+{
+ h2_ctx *ctx;
+ h2_config *cfg;
+ int i;
+
+ if (r->connection->keepalives > 0) {
+ /* Only announce Alt-Svc on the first response */
+ return DECLINED;
+ }
+
+ ctx = h2_ctx_rget(r);
+ if (h2_ctx_is_active(ctx) || h2_ctx_is_task(ctx)) {
+ return DECLINED;
+ }
+
+ cfg = h2_config_rget(r);
+ if (r->hostname && cfg && cfg->alt_svcs && cfg->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 = h2_h2_is_tls(r->connection);
+ int ma = h2_config_geti(cfg, 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,
+ "h2_alt_svc: announce %s for %s:%d",
+ (secure? "secure" : "insecure"),
+ r->hostname, (int)r->server->port);
+ for (i = 0; i < cfg->alt_svcs->nelts; ++i) {
+ h2_alt_svc *as = h2_alt_svc_IDX(cfg->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_set(r->headers_out, "Alt-Svc", alt_svc);
+ }
+ }
+ }
+
+ return DECLINED;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <http_log.h>
+#include <http_vhost.h>
+
+#include <ap_mpm.h>
+
+#include <apr_strings.h>
+
+#include "h2_alt_svc.h"
+#include "h2_ctx.h"
+#include "h2_conn.h"
+#include "h2_config.h"
+#include "h2_private.h"
+
+#define DEF_VAL (-1)
+
+#define H2_CONFIG_GET(a, b, n) \
+ (((a)->n == DEF_VAL)? (b) : (a))->n
+
+static h2_config defconf = {
+ "default",
+ 100, /* max_streams */
+ 64 * 1024, /* window_size */
+ -1, /* min workers */
+ -1, /* max workers */
+ 10 * 60, /* max workers idle secs */
+ 64 * 1024, /* stream max mem size */
+ NULL, /* no alt-svcs */
+ -1, /* alt-svc max age */
+ 0, /* serialize headers */
+ -1, /* h2 direct mode */
+ -1, /* # session extra files */
+};
+
+static int files_per_session = 0;
+
+void h2_config_init(apr_pool_t *pool) {
+ /* Determine a good default for this platform and mpm?
+ * TODO: not sure how APR wants to hand out this piece of
+ * information.
+ */
+ int max_files = 256;
+ int conn_threads = 1;
+ int tx_files = max_files / 4;
+
+ (void)pool;
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &conn_threads);
+ switch (h2_conn_mpm_type()) {
+ case H2_MPM_PREFORK:
+ case H2_MPM_WORKER:
+ case H2_MPM_EVENT:
+ /* allow that many transfer open files per mplx */
+ files_per_session = (tx_files / conn_threads);
+ break;
+ default:
+ /* don't know anything about it, stay safe */
+ break;
+ }
+}
+
+static void *h2_config_create(apr_pool_t *pool,
+ const char *prefix, const char *x)
+{
+ h2_config *conf = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
+
+ const char *s = x? x : "unknown";
+ char *name = apr_pcalloc(pool, strlen(prefix) + strlen(s) + 20);
+ strcpy(name, prefix);
+ strcat(name, "[");
+ strcat(name, s);
+ strcat(name, "]");
+
+ conf->name = name;
+ conf->h2_max_streams = DEF_VAL;
+ conf->h2_window_size = DEF_VAL;
+ conf->min_workers = DEF_VAL;
+ 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->session_extra_files = DEF_VAL;
+ return conf;
+}
+
+void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
+{
+ return h2_config_create(pool, "srv", s->defn_name);
+}
+
+void *h2_config_create_dir(apr_pool_t *pool, char *x)
+{
+ return h2_config_create(pool, "dir", x);
+}
+
+void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
+{
+ h2_config *base = (h2_config *)basev;
+ h2_config *add = (h2_config *)addv;
+ h2_config *n = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
+
+ char *name = apr_pcalloc(pool, 20 + strlen(add->name) + strlen(base->name));
+ strcpy(name, "merged[");
+ strcat(name, add->name);
+ strcat(name, ", ");
+ strcat(name, base->name);
+ strcat(name, "]");
+ n->name = name;
+
+ n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams);
+ n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
+ n->min_workers = H2_CONFIG_GET(add, base, min_workers);
+ 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->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
+
+ return n;
+}
+
+int h2_config_geti(h2_config *conf, h2_config_var_t var)
+{
+ int n;
+ switch(var) {
+ case H2_CONF_MAX_STREAMS:
+ return H2_CONFIG_GET(conf, &defconf, h2_max_streams);
+ case H2_CONF_WIN_SIZE:
+ return H2_CONFIG_GET(conf, &defconf, h2_window_size);
+ case H2_CONF_MIN_WORKERS:
+ return H2_CONFIG_GET(conf, &defconf, min_workers);
+ case H2_CONF_MAX_WORKERS:
+ return H2_CONFIG_GET(conf, &defconf, max_workers);
+ case H2_CONF_MAX_WORKER_IDLE_SECS:
+ 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_DIRECT:
+ return H2_CONFIG_GET(conf, &defconf, h2_direct);
+ case H2_CONF_SESSION_FILES:
+ n = H2_CONFIG_GET(conf, &defconf, session_extra_files);
+ if (n < 0) {
+ n = files_per_session;
+ }
+ return n;
+ default:
+ return DEF_VAL;
+ }
+}
+
+h2_config *h2_config_sget(server_rec *s)
+{
+ h2_config *cfg = (h2_config *)ap_get_module_config(s->module_config,
+ &h2_module);
+ AP_DEBUG_ASSERT(cfg);
+ return cfg;
+}
+
+
+static const char *h2_conf_set_max_streams(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ cfg->h2_max_streams = (int)apr_atoi64(value);
+ (void)arg;
+ if (cfg->h2_max_streams < 1) {
+ return "value must be > 0";
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_window_size(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ cfg->h2_window_size = (int)apr_atoi64(value);
+ (void)arg;
+ if (cfg->h2_window_size < 1024) {
+ return "value must be > 1k";
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_min_workers(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ cfg->min_workers = (int)apr_atoi64(value);
+ (void)arg;
+ if (cfg->min_workers < 1) {
+ return "value must be > 1";
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_max_workers(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ cfg->max_workers = (int)apr_atoi64(value);
+ (void)arg;
+ if (cfg->max_workers < 1) {
+ return "value must be > 1";
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_max_worker_idle_secs(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ cfg->max_worker_idle_secs = (int)apr_atoi64(value);
+ (void)arg;
+ if (cfg->max_worker_idle_secs < 1) {
+ return "value must be > 1";
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_stream_max_mem_size(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+
+
+ cfg->stream_max_mem_size = (int)apr_atoi64(value);
+ (void)arg;
+ if (cfg->stream_max_mem_size < 1024) {
+ return "value must be > 1k";
+ }
+ return NULL;
+}
+
+static const char *h2_add_alt_svc(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ if (value && strlen(value)) {
+ h2_config *cfg = h2_config_sget(parms->server);
+ h2_alt_svc *as = h2_alt_svc_parse(value, parms->pool);
+ if (!as) {
+ return "unable to parse alt-svc specifier";
+ }
+ if (!cfg->alt_svcs) {
+ cfg->alt_svcs = apr_array_make(parms->pool, 5, sizeof(h2_alt_svc*));
+ }
+ APR_ARRAY_PUSH(cfg->alt_svcs, h2_alt_svc*) = as;
+ }
+ (void)arg;
+ return NULL;
+}
+
+static const char *h2_conf_set_alt_svc_max_age(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ cfg->alt_svc_max_age = (int)apr_atoi64(value);
+ (void)arg;
+ return NULL;
+}
+
+static const char *h2_conf_set_session_extra_files(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ apr_int64_t max = (int)apr_atoi64(value);
+ if (max <= 0) {
+ return "value must be a positive number";
+ }
+ cfg->session_extra_files = (int)max;
+ (void)arg;
+ return NULL;
+}
+
+static const char *h2_conf_set_serialize_headers(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ if (!strcasecmp(value, "On")) {
+ cfg->serialize_headers = 1;
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ cfg->serialize_headers = 0;
+ return NULL;
+ }
+
+ (void)arg;
+ return "value must be On or Off";
+}
+
+static const char *h2_conf_set_direct(cmd_parms *parms,
+ void *arg, const char *value)
+{
+ h2_config *cfg = h2_config_sget(parms->server);
+ if (!strcasecmp(value, "On")) {
+ cfg->h2_direct = 1;
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ cfg->h2_direct = 0;
+ return NULL;
+ }
+
+ (void)arg;
+ return "value must be On or Off";
+}
+
+#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
+
+
+const command_rec h2_cmds[] = {
+ AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL,
+ RSRC_CONF, "maximum number of open streams per session"),
+ AP_INIT_TAKE1("H2WindowSize", h2_conf_set_window_size, NULL,
+ RSRC_CONF, "window size on client DATA"),
+ AP_INIT_TAKE1("H2MinWorkers", h2_conf_set_min_workers, NULL,
+ RSRC_CONF, "minimum number of worker threads per child"),
+ AP_INIT_TAKE1("H2MaxWorkers", h2_conf_set_max_workers, NULL,
+ RSRC_CONF, "maximum number of worker threads per child"),
+ AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_secs, NULL,
+ 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"),
+ AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
+ RSRC_CONF, "on to enable direct HTTP/2 mode"),
+ AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
+ RSRC_CONF, "number of extra file a session might keep open"),
+ AP_END_CMD
+};
+
+
+h2_config *h2_config_rget(request_rec *r)
+{
+ h2_config *cfg = (h2_config *)ap_get_module_config(r->per_dir_config,
+ &h2_module);
+ return cfg? cfg : h2_config_sget(r->server);
+}
+
+h2_config *h2_config_get(conn_rec *c)
+{
+ h2_ctx *ctx = h2_ctx_get(c);
+ if (ctx->config) {
+ return ctx->config;
+ }
+ if (!ctx->server && ctx->hostname) {
+ /* We have a host agreed upon via TLS SNI, but no request yet.
+ * The sni host was accepted and therefore does match a server record
+ * (vhost) for it. But we need to know which one.
+ * Normally, it is enough to be set on the initial request on a
+ * connection, but we need it earlier. Simulate a request and call
+ * the vhost matching stuff.
+ */
+ apr_uri_t uri;
+ request_rec r;
+ memset(&uri, 0, sizeof(uri));
+ uri.scheme = (char*)"https";
+ uri.hostinfo = (char*)ctx->hostname;
+ uri.hostname = (char*)ctx->hostname;
+ uri.port_str = (char*)"";
+ uri.port = c->local_addr->port;
+ uri.path = (char*)"/";
+
+ memset(&r, 0, sizeof(r));
+ r.uri = (char*)"/";
+ r.connection = c;
+ r.pool = c->pool;
+ r.hostname = ctx->hostname;
+ r.headers_in = apr_table_make(c->pool, 1);
+ r.parsed_uri = uri;
+ r.status = HTTP_OK;
+ r.server = r.connection->base_server;
+ ap_update_vhost_from_headers(&r);
+ ctx->server = r.server;
+ }
+
+ if (ctx->server) {
+ ctx->config = h2_config_sget(ctx->server);
+ return ctx->config;
+ }
+
+ return h2_config_sget(c->base_server);
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_config_h__
+#define __mod_h2__h2_config_h__
+
+#undef PACKAGE_VERSION
+#undef PACKAGE_TARNAME
+#undef PACKAGE_STRING
+#undef PACKAGE_NAME
+#undef PACKAGE_BUGREPORT
+
+typedef enum {
+ H2_CONF_MAX_STREAMS,
+ H2_CONF_WIN_SIZE,
+ H2_CONF_MIN_WORKERS,
+ 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_SESSION_FILES,
+} h2_config_var_t;
+
+/* 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 session_extra_files; /* # of extra files a session may keep open */
+} h2_config;
+
+
+void *h2_config_create_dir(apr_pool_t *pool, char *x);
+void *h2_config_create_svr(apr_pool_t *pool, server_rec *s);
+void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv);
+
+apr_status_t h2_config_apply_header(h2_config *config, request_rec *r);
+
+extern const command_rec h2_cmds[];
+
+h2_config *h2_config_get(conn_rec *c);
+h2_config *h2_config_sget(server_rec *s);
+h2_config *h2_config_rget(request_rec *r);
+
+int h2_config_geti(h2_config *conf, h2_config_var_t var);
+
+void h2_config_init(apr_pool_t *pool);
+
+#endif /* __mod_h2__h2_config_h__ */
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <ap_mpm.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 "h2_private.h"
+#include "h2_config.h"
+#include "h2_ctx.h"
+#include "h2_mplx.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_stream_set.h"
+#include "h2_task.h"
+#include "h2_worker.h"
+#include "h2_workers.h"
+#include "h2_conn.h"
+
+static struct h2_workers *workers;
+
+static apr_status_t h2_session_process(h2_session *session);
+
+static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
+static module *mpm_module;
+static module *ssl_module;
+static int checked;
+
+static void check_modules(void)
+{
+ int i;
+ if (!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;
+ }
+ else if (!strcmp("worker.c", m->name)) {
+ mpm_type = H2_MPM_WORKER;
+ mpm_module = m;
+ }
+ else if (!strcmp("prefork.c", m->name)) {
+ mpm_type = H2_MPM_PREFORK;
+ mpm_module = m;
+ }
+ else if (!strcmp("mod_ssl.c", m->name)) {
+ ssl_module = m;
+ }
+ }
+ checked = 1;
+ }
+}
+
+apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s)
+{
+ h2_config *config = h2_config_sget(s);
+ apr_status_t status = APR_SUCCESS;
+ int minw = h2_config_geti(config, H2_CONF_MIN_WORKERS);
+ int maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS);
+
+ int max_threads_per_child = 0;
+ int threads_limit = 0;
+ int idle_secs = 0;
+ int i;
+
+ h2_config_init(pool);
+
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &threads_limit);
+
+ 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;
+ }
+ else if (!strcmp("worker.c", m->name)) {
+ mpm_type = H2_MPM_WORKER;
+ mpm_module = m;
+ }
+ else if (!strcmp("prefork.c", m->name)) {
+ mpm_type = H2_MPM_PREFORK;
+ mpm_module = m;
+ }
+ else if (!strcmp("mod_ssl.c", m->name)) {
+ ssl_module = m;
+ }
+ }
+
+ if (minw <= 0) {
+ minw = max_threads_per_child;
+ }
+ if (maxw <= 0) {
+ maxw = threads_limit;
+ if (maxw < minw) {
+ maxw = minw;
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "h2_workers: min=%d max=%d, mthrpchild=%d, thr_limit=%d",
+ minw, maxw, max_threads_per_child, threads_limit);
+
+ workers = h2_workers_create(s, pool, minw, maxw);
+ idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS);
+ h2_workers_set_max_idle_secs(workers, idle_secs);
+
+ return status;
+}
+
+h2_mpm_type_t h2_conn_mpm_type(void) {
+ check_modules();
+ return mpm_type;
+}
+
+static module *h2_conn_mpm_module(void) {
+ check_modules();
+ return mpm_module;
+}
+
+apr_status_t h2_conn_rprocess(request_rec *r)
+{
+ h2_config *config = h2_config_rget(r);
+ h2_session *session;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_conn_process start");
+ if (!workers) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02911)
+ "workers not initialized");
+ return APR_EGENERAL;
+ }
+
+ session = h2_session_rcreate(r, config, workers);
+ if (!session) {
+ return APR_EGENERAL;
+ }
+
+ return h2_session_process(session);
+}
+
+apr_status_t h2_conn_main(conn_rec *c)
+{
+ h2_config *config = h2_config_get(c);
+ h2_session *session;
+ apr_status_t status;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_main start");
+ if (!workers) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02912)
+ "workers not initialized");
+ return APR_EGENERAL;
+ }
+
+ session = h2_session_create(c, config, workers);
+ if (!session) {
+ return APR_EGENERAL;
+ }
+
+ status = h2_session_process(session);
+
+ /* Make sure this connection gets closed properly. */
+ c->keepalive = AP_CONN_CLOSE;
+ if (c->cs) {
+ c->cs->state = CONN_STATE_WRITE_COMPLETION;
+ }
+
+ return status;
+}
+
+apr_status_t h2_session_process(h2_session *session)
+{
+ apr_status_t status = APR_SUCCESS;
+ int rv = 0;
+ apr_interval_time_t wait_micros = 0;
+ static const int MAX_WAIT_MICROS = 200 * 1000;
+
+ /* Start talking to the client. Apart from protocol meta data,
+ * we mainly will see new http/2 streams opened by the client, which
+ * basically are http requests we need to dispatch.
+ *
+ * There will be bursts of new streams, to be served concurrently,
+ * followed by long pauses of no activity.
+ *
+ * Since the purpose of http/2 is to allow siumultaneous streams, we
+ * need to dispatch the handling of each stream into a separate worker
+ * thread, keeping this thread open for sending responses back as
+ * soon as they arrive.
+ * At the same time, we need to continue reading new frames from
+ * our client, which may be meta (WINDOWS_UPDATEs, PING, SETTINGS) or
+ * new streams.
+ *
+ * As long as we have streams open in this session, we cannot really rest
+ * since there are two conditions to wait on: 1. new data from the client,
+ * 2. new data from the open streams to send back.
+ *
+ * Only when we have no more streams open, can we do a blocking read
+ * on our connection.
+ *
+ * TODO: implement graceful GO_AWAY after configurable idle time
+ */
+
+ ap_update_child_status_from_conn(session->c->sbh, SERVER_BUSY_READ,
+ session->c);
+
+ if (APLOGctrace2(session->c)) {
+ ap_filter_t *filter = session->c->input_filters;
+ while (filter) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_conn(%ld), has connection filter %s",
+ session->id, filter->frec->name);
+ filter = filter->next;
+ }
+ }
+
+ status = h2_session_start(session, &rv);
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+ "h2_session(%ld): starting on %s:%d", session->id,
+ session->c->base_server->defn_name,
+ session->c->local_addr->port);
+ if (status != APR_SUCCESS) {
+ h2_session_abort(session, status, rv);
+ h2_session_destroy(session);
+ return status;
+ }
+
+ while (!h2_session_is_done(session)) {
+ int have_written = 0;
+ int have_read = 0;
+ int got_streams;
+
+ status = h2_session_write(session, wait_micros);
+ if (status == APR_SUCCESS) {
+ have_written = 1;
+ wait_micros = 0;
+ }
+ else if (status == APR_EAGAIN) {
+ /* nop */
+ }
+ else if (status == APR_TIMEUP) {
+ wait_micros *= 2;
+ if (wait_micros > MAX_WAIT_MICROS) {
+ wait_micros = MAX_WAIT_MICROS;
+ }
+ }
+ else {
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
+ "h2_session(%ld): writing, terminating",
+ session->id);
+ h2_session_abort(session, status, 0);
+ break;
+ }
+
+ /* We would like to do blocking reads as often as possible as they
+ * are more efficient in regard to server resources.
+ * We can do them under the following circumstances:
+ * - we have no open streams and therefore have nothing to write
+ * - we have just started the session and are waiting for the first
+ * two frames to come in. There will always be at least 2 frames as
+ * * h2 will send SETTINGS and SETTINGS-ACK
+ * * h2c will count the header settings as one frame and we
+ * submit our settings and need the ACK.
+ */
+ got_streams = !h2_stream_set_is_empty(session->streams);
+ status = h2_session_read(session,
+ (!got_streams
+ || session->frames_received <= 1)?
+ APR_BLOCK_READ : APR_NONBLOCK_READ);
+ switch (status) {
+ case APR_SUCCESS:
+ /* successful read, reset our idle timers */
+ have_read = 1;
+ wait_micros = 0;
+ break;
+ case APR_EAGAIN:
+ break;
+ case APR_EBADF:
+ case APR_EOF:
+ case APR_ECONNABORTED:
+ case APR_ECONNRESET:
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
+ "h2_session(%ld): reading",
+ session->id);
+ h2_session_abort(session, status, 0);
+ break;
+ default:
+ ap_log_cerror( APLOG_MARK, APLOG_WARNING, status, session->c,
+ APLOGNO(02950)
+ "h2_session(%ld): error reading, terminating",
+ session->id);
+ h2_session_abort(session, status, 0);
+ break;
+ }
+
+ if (!have_read && !have_written
+ && !h2_stream_set_is_empty(session->streams)) {
+ /* Nothing to read or write, we have streams, but
+ * the have no data yet ready to be delivered. Slowly
+ * back off to give others a chance to do their work.
+ */
+ if (wait_micros == 0) {
+ wait_micros = 10;
+ }
+ }
+ }
+
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
+ "h2_session(%ld): done", session->id);
+
+ ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING,
+ session->c);
+
+ h2_session_close(session);
+ h2_session_destroy(session);
+
+ return DONE;
+}
+
+
+static void fix_event_conn(conn_rec *c, conn_rec *master);
+
+/*
+ * We would like to create the connection more lightweight like
+ * slave connections in 2.5-DEV. But we get 500 responses on long
+ * cgi tests in modules/h2.t as the script parsing seems to see an
+ * EOF from the cgi before anything is sent.
+ *
+conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool)
+{
+ conn_rec *c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
+
+ memcpy(c, master, sizeof(conn_rec));
+ c->id = (master->id & (long)pool);
+ c->slaves = NULL;
+ c->master = master;
+ c->input_filters = NULL;
+ c->output_filters = NULL;
+ c->pool = pool;
+
+ return c;
+}
+*/
+
+conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool)
+{
+ apr_socket_t *socket;
+ conn_rec *c;
+
+ AP_DEBUG_ASSERT(master);
+
+ /* CAVEAT: it seems necessary to setup the conn_rec in the master
+ * connection thread. Other attempts crashed.
+ * HOWEVER: we setup the connection using the pools and other items
+ * from the master connection, since we do not want to allocate
+ * lots of resources here.
+ * Lets allocated pools and everything else when we actually start
+ * working on this new connection.
+ */
+ /* Not sure about the scoreboard handle. Reusing the one from the main
+ * connection could make sense, is not really correct, but we cannot
+ * easily create new handles for our worker threads either.
+ * TODO
+ */
+ socket = ap_get_module_config(master->conn_config, &core_module);
+ c = ap_run_create_connection(pool, master->base_server,
+ socket,
+ master->id^((long)pool),
+ master->sbh,
+ master->bucket_alloc);
+ if (c == NULL) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool,
+ APLOGNO(02913) "h2_task: creating conn");
+ return NULL;
+ }
+ return c;
+}
+
+apr_status_t h2_conn_setup(h2_task_env *env, struct h2_worker *worker)
+{
+ conn_rec *master = env->mplx->c;
+
+ ap_log_perror(APLOG_MARK, APLOG_TRACE3, 0, env->pool,
+ "h2_conn(%ld): created from master", master->id);
+
+ /* Ok, we are just about to start processing the connection and
+ * the worker is calling us to setup all necessary resources.
+ * We can borrow some from the worker itself and some we do as
+ * sub-resources from it, so that we get a nice reuse of
+ * pools.
+ */
+ env->c.pool = env->pool;
+ env->c.bucket_alloc = h2_worker_get_bucket_alloc(worker);
+ env->c.current_thread = h2_worker_get_thread(worker);
+
+ env->c.conn_config = ap_create_conn_config(env->pool);
+ env->c.notes = apr_table_make(env->pool, 5);
+
+ ap_set_module_config(env->c.conn_config, &core_module,
+ h2_worker_get_socket(worker));
+
+ /* If we serve http:// requests over a TLS connection, we do
+ * not want any mod_ssl vars to be visible.
+ */
+ if (ssl_module && (!env->scheme || strcmp("http", env->scheme))) {
+ /* See #19, there is a range of SSL variables to be gotten from
+ * the main connection that should be available in request handlers
+ */
+ void *sslcfg = ap_get_module_config(master->conn_config, ssl_module);
+ if (sslcfg) {
+ ap_set_module_config(env->c.conn_config, ssl_module, sslcfg);
+ }
+ }
+
+ /* This works for mpm_worker so far. Other mpm modules have
+ * different needs, unfortunately. The most interesting one
+ * being mpm_event...
+ */
+ switch (h2_conn_mpm_type()) {
+ case H2_MPM_WORKER:
+ /* all fine */
+ break;
+ case H2_MPM_EVENT:
+ fix_event_conn(&env->c, master);
+ break;
+ default:
+ /* fingers crossed */
+ break;
+ }
+
+ /* TODO: we simulate that we had already a request on this connection.
+ * This keeps the mod_ssl SNI vs. Host name matcher from answering
+ * 400 Bad Request
+ * when names do not match. We prefer a predictable 421 status.
+ */
+ env->c.keepalives = 1;
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_conn_post(conn_rec *c, h2_worker *worker)
+{
+ (void)worker;
+
+ /* be sure no one messes with this any more */
+ memset(c, 0, sizeof(*c));
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_conn_process(conn_rec *c, apr_socket_t *socket)
+{
+ AP_DEBUG_ASSERT(c);
+
+ c->clogging_input_filters = 1;
+ ap_process_connection(c, socket);
+
+ return APR_SUCCESS;
+}
+
+/* This is an internal mpm event.c struct which is disguised
+ * as a conn_state_t so that mpm_event can have special connection
+ * state information without changing the struct seen on the outside.
+ *
+ * For our task connections we need to create a new beast of this type
+ * and fill it with enough meaningful things that mpm_event reads and
+ * starts processing out task request.
+ */
+typedef struct event_conn_state_t event_conn_state_t;
+struct event_conn_state_t {
+ /** APR_RING of expiration timeouts */
+ APR_RING_ENTRY(event_conn_state_t) timeout_list;
+ /** the expiration time of the next keepalive timeout */
+ apr_time_t expiration_time;
+ /** connection record this struct refers to */
+ conn_rec *c;
+ /** request record (if any) this struct refers to */
+ request_rec *r;
+ /** is the current conn_rec suspended? (disassociated with
+ * a particular MPM thread; for suspend_/resume_connection
+ * hooks)
+ */
+ int suspended;
+ /** memory pool to allocate from */
+ apr_pool_t *p;
+ /** bucket allocator */
+ apr_bucket_alloc_t *bucket_alloc;
+ /** poll file descriptor information */
+ apr_pollfd_t pfd;
+ /** public parts of the connection state */
+ conn_state_t pub;
+};
+APR_RING_HEAD(timeout_head_t, event_conn_state_t);
+
+static void fix_event_conn(conn_rec *c, conn_rec *master)
+{
+ event_conn_state_t *master_cs = ap_get_module_config(master->conn_config,
+ h2_conn_mpm_module());
+ event_conn_state_t *cs = apr_pcalloc(c->pool, sizeof(event_conn_state_t));
+ cs->bucket_alloc = apr_bucket_alloc_create(c->pool);
+
+ ap_set_module_config(c->conn_config, h2_conn_mpm_module(), cs);
+
+ cs->c = c;
+ cs->r = NULL;
+ cs->p = master_cs->p;
+ cs->pfd = master_cs->pfd;
+ cs->pub = master_cs->pub;
+ cs->pub.state = CONN_STATE_READ_REQUEST_LINE;
+
+ c->cs = &(cs->pub);
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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__
+#define __mod_h2__h2_conn__
+
+struct h2_task;
+struct h2_task_env;
+struct h2_worker;
+
+/* Process the connection that is now starting the HTTP/2
+ * conversation. Return when the HTTP/2 session is done
+ * and the connection will close.
+ */
+apr_status_t h2_conn_main(conn_rec *c);
+
+/* Process the request that has been upgraded to a HTTP/2
+ * conversation. Return when the HTTP/2 session is done
+ * and the connection will close.
+ */
+apr_status_t h2_conn_rprocess(request_rec *r);
+
+/* 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);
+
+
+typedef enum {
+ H2_MPM_UNKNOWN,
+ H2_MPM_WORKER,
+ H2_MPM_EVENT,
+ H2_MPM_PREFORK,
+} h2_mpm_type_t;
+
+/* Returns the type of MPM module detected */
+h2_mpm_type_t h2_conn_mpm_type(void);
+
+
+conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *stream_pool);
+
+apr_status_t h2_conn_setup(struct h2_task_env *env, struct h2_worker *worker);
+apr_status_t h2_conn_post(conn_rec *c, struct h2_worker *worker);
+
+apr_status_t h2_conn_process(conn_rec *c, apr_socket_t *socket);
+
+#endif /* defined(__mod_h2__h2_conn__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <ap_mpm.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+
+#include "h2_private.h"
+#include "h2_config.h"
+#include "h2_conn_io.h"
+#include "h2_h2.h"
+#include "h2_util.h"
+
+#define WRITE_BUFFER_SIZE (64*1024)
+#define WRITE_SIZE_INITIAL 1300
+#define WRITE_SIZE_MAX (16*1024)
+#define WRITE_SIZE_IDLE_USEC (1*APR_USEC_PER_SEC)
+#define WRITE_SIZE_THRESHOLD (1*1024*1024)
+
+apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c)
+{
+ io->connection = c;
+ io->input = apr_brigade_create(c->pool, c->bucket_alloc);
+ io->output = apr_brigade_create(c->pool, c->bucket_alloc);
+ io->buflen = 0;
+ /* That is where we start with,
+ * see https://issues.apache.org/jira/browse/TS-2503 */
+ io->write_size = WRITE_SIZE_INITIAL;
+ io->last_write = 0;
+ io->buffer_output = h2_h2_is_tls(c);
+
+ /* Currently we buffer only for TLS output. The reason this gives
+ * improved performance is that buckets send to the mod_ssl network
+ * filter will be encrypted in chunks. There is a special filter
+ * that tries to aggregate data, but that does not work well when
+ * bucket sizes alternate between tiny frame headers and large data
+ * chunks.
+ */
+ if (io->buffer_output) {
+ io->bufsize = WRITE_BUFFER_SIZE;
+ io->buffer = apr_pcalloc(c->pool, io->bufsize);
+ }
+ else {
+ io->bufsize = 0;
+ }
+
+ return APR_SUCCESS;
+}
+
+void h2_conn_io_destroy(h2_conn_io *io)
+{
+ io->input = NULL;
+ io->output = NULL;
+}
+
+static apr_status_t h2_conn_io_bucket_read(h2_conn_io *io,
+ apr_read_type_e block,
+ h2_conn_io_on_read_cb on_read_cb,
+ void *puser, int *pdone)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_size_t readlen = 0;
+ *pdone = 0;
+
+ while (status == APR_SUCCESS && !*pdone
+ && !APR_BRIGADE_EMPTY(io->input)) {
+
+ apr_bucket* bucket = APR_BRIGADE_FIRST(io->input);
+ if (APR_BUCKET_IS_METADATA(bucket)) {
+ /* we do nothing regarding any meta here */
+ }
+ else {
+ const char *bucket_data = NULL;
+ apr_size_t bucket_length = 0;
+ status = apr_bucket_read(bucket, &bucket_data,
+ &bucket_length, block);
+
+ if (status == APR_SUCCESS && bucket_length > 0) {
+ if (APLOGctrace2(io->connection)) {
+ char buffer[32];
+ h2_util_hex_dump(buffer, sizeof(buffer)/sizeof(buffer[0]),
+ bucket_data, bucket_length);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ "h2_conn_io(%ld): read %d bytes: %s",
+ io->connection->id, (int)bucket_length, buffer);
+ }
+
+ if (bucket_length > 0) {
+ apr_size_t consumed = 0;
+ status = on_read_cb(bucket_data, bucket_length,
+ &consumed, pdone, puser);
+ if (status == APR_SUCCESS && bucket_length > consumed) {
+ /* We have data left in the bucket. Split it. */
+ status = apr_bucket_split(bucket, consumed);
+ }
+ readlen += consumed;
+ }
+ }
+ }
+ apr_bucket_delete(bucket);
+ }
+ if (readlen == 0 && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
+ return APR_EAGAIN;
+ }
+ return status;
+}
+
+apr_status_t h2_conn_io_read(h2_conn_io *io,
+ apr_read_type_e block,
+ h2_conn_io_on_read_cb on_read_cb,
+ void *puser)
+{
+ apr_status_t status;
+ int done = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ "h2_conn_io: try read, block=%d", block);
+
+ if (!APR_BRIGADE_EMPTY(io->input)) {
+ /* Seems something is left from a previous read, lets
+ * satisfy our caller with the data we already have. */
+ status = h2_conn_io_bucket_read(io, block, on_read_cb, puser, &done);
+ if (status != APR_SUCCESS || done) {
+ return status;
+ }
+ apr_brigade_cleanup(io->input);
+ }
+
+ /* 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.
+ * When reading non-blocking, we do have streams to process and update
+ * child with NULL request. That way, any current request information
+ * in the scoreboard is preserved.
+ */
+ if (block == APR_BLOCK_READ) {
+ ap_update_child_status_from_conn(io->connection->sbh,
+ SERVER_BUSY_KEEPALIVE,
+ io->connection);
+ }
+ else {
+ ap_update_child_status(io->connection->sbh, SERVER_BUSY_READ, NULL);
+ }
+
+ status = ap_get_brigade(io->connection->input_filters,
+ io->input, AP_MODE_READBYTES,
+ block, 16 * 4096);
+ switch (status) {
+ case APR_SUCCESS:
+ return h2_conn_io_bucket_read(io, block, on_read_cb, puser, &done);
+ case APR_EOF:
+ case APR_EAGAIN:
+ break;
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection,
+ "h2_conn_io: error reading");
+ break;
+ }
+ return status;
+}
+
+static apr_status_t flush_out(apr_bucket_brigade *bb, void *ctx)
+{
+ h2_conn_io *io = (h2_conn_io*)ctx;
+ apr_status_t status;
+ apr_off_t bblen;
+
+ ap_update_child_status(io->connection->sbh, SERVER_BUSY_WRITE, NULL);
+ status = apr_brigade_length(bb, 1, &bblen);
+ if (status == APR_SUCCESS) {
+ status = ap_pass_brigade(io->connection->output_filters, bb);
+ if (status == APR_SUCCESS) {
+ io->bytes_written += (apr_size_t)bblen;
+ io->last_write = apr_time_now();
+ }
+ apr_brigade_cleanup(bb);
+ }
+ return status;
+}
+
+static apr_status_t bucketeer_buffer(h2_conn_io *io) {
+ const char *data = io->buffer;
+ apr_size_t remaining = io->buflen;
+ apr_bucket *b;
+ int bcount, i;
+
+ if (io->write_size > WRITE_SIZE_INITIAL
+ && (apr_time_now() - io->last_write) >= WRITE_SIZE_IDLE_USEC) {
+ /* long time not written, reset write size */
+ io->write_size = WRITE_SIZE_INITIAL;
+ io->bytes_written = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ "h2_conn_io(%ld): timeout write size reset to %ld",
+ (long)io->connection->id, (long)io->write_size);
+ }
+ else if (io->write_size < WRITE_SIZE_MAX
+ && io->bytes_written >= WRITE_SIZE_THRESHOLD) {
+ /* connection is hot, use max size */
+ io->write_size = WRITE_SIZE_MAX;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ "h2_conn_io(%ld): threshold reached, write size now %ld",
+ (long)io->connection->id, (long)io->write_size);
+ }
+
+ bcount = (int)(remaining / io->write_size);
+ for (i = 0; i < bcount; ++i) {
+ b = apr_bucket_transient_create(data, io->write_size,
+ io->output->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ data += io->write_size;
+ remaining -= io->write_size;
+ }
+
+ if (remaining > 0) {
+ b = apr_bucket_transient_create(data, remaining,
+ io->output->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_conn_io_write(h2_conn_io *io,
+ const char *buf, size_t length)
+{
+ apr_status_t status = APR_SUCCESS;
+ io->unflushed = 1;
+
+ if (io->buffer_output) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
+ "h2_conn_io: buffering %ld bytes", (long)length);
+ while (length > 0 && (status == APR_SUCCESS)) {
+ apr_size_t avail = io->bufsize - io->buflen;
+ if (avail <= 0) {
+ bucketeer_buffer(io);
+ status = flush_out(io->output, io);
+ io->buflen = 0;
+ }
+ else if (length > avail) {
+ memcpy(io->buffer + io->buflen, buf, avail);
+ io->buflen += avail;
+ length -= avail;
+ buf += avail;
+ }
+ else {
+ memcpy(io->buffer + io->buflen, buf, length);
+ io->buflen += length;
+ length = 0;
+ break;
+ }
+ }
+
+ }
+ else {
+ status = apr_brigade_write(io->output, flush_out, io, buf, length);
+ if (status == APR_SUCCESS
+ || APR_STATUS_IS_ECONNABORTED(status)
+ || APR_STATUS_IS_EPIPE(status)) {
+ /* These are all fine and no reason for concern. Everything else
+ * is interesting. */
+ status = APR_SUCCESS;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection,
+ "h2_conn_io: write error");
+ }
+ }
+
+ return status;
+}
+
+
+apr_status_t h2_conn_io_flush(h2_conn_io *io)
+{
+ if (io->unflushed) {
+ apr_status_t status;
+ if (io->buflen > 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
+ "h2_conn_io: flush, flushing %ld bytes", (long)io->buflen);
+ bucketeer_buffer(io);
+ io->buflen = 0;
+ }
+ /* Append flush.
+ */
+ APR_BRIGADE_INSERT_TAIL(io->output,
+ apr_bucket_flush_create(io->output->bucket_alloc));
+
+ /* Send it out through installed filters (TLS) to the client */
+ status = flush_out(io->output, io);
+
+ if (status == APR_SUCCESS
+ || APR_STATUS_IS_ECONNABORTED(status)
+ || APR_STATUS_IS_EPIPE(status)) {
+ /* These are all fine and no reason for concern. Everything else
+ * is interesting. */
+ io->unflushed = 0;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection,
+ "h2_conn_io: flush error");
+ }
+
+ return status;
+ }
+ return APR_SUCCESS;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_io__
+#define __mod_h2__h2_conn_io__
+
+/* h2_io is the basic handler of a httpd connection. It keeps two brigades,
+ * one for input, one for output and works with the installed connection
+ * filters.
+ * The read is done via a callback function, so that input can be processed
+ * directly without copying.
+ */
+typedef struct {
+ conn_rec *connection;
+ apr_bucket_brigade *input;
+ apr_bucket_brigade *output;
+ int buffer_output;
+ int write_size;
+ apr_time_t last_write;
+ apr_size_t bytes_written;
+
+ char *buffer;
+ apr_size_t buflen;
+ apr_size_t bufsize;
+ int unflushed;
+} h2_conn_io;
+
+apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c);
+void h2_conn_io_destroy(h2_conn_io *io);
+
+typedef apr_status_t (*h2_conn_io_on_read_cb)(const char *data, apr_size_t len,
+ apr_size_t *readlen, int *done,
+ void *puser);
+
+apr_status_t h2_conn_io_read(h2_conn_io *io,
+ apr_read_type_e block,
+ h2_conn_io_on_read_cb on_read_cb,
+ void *puser);
+
+apr_status_t h2_conn_io_write(h2_conn_io *io,
+ const char *buf,
+ size_t length);
+
+apr_status_t h2_conn_io_flush(h2_conn_io *io);
+
+#endif /* defined(__mod_h2__h2_conn_io__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_task.h"
+#include "h2_ctx.h"
+#include "h2_private.h"
+
+static h2_ctx *h2_ctx_create(const conn_rec *c)
+{
+ h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx));
+ AP_DEBUG_ASSERT(ctx);
+ ap_set_module_config(c->conn_config, &h2_module, ctx);
+ return ctx;
+}
+
+h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task_env *env)
+{
+ h2_ctx *ctx = h2_ctx_create(c);
+ if (ctx) {
+ ctx->task_env = env;
+ }
+ return ctx;
+}
+
+h2_ctx *h2_ctx_get(const conn_rec *c)
+{
+ h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &h2_module);
+ if (ctx == NULL) {
+ ctx = h2_ctx_create(c);
+ }
+ return ctx;
+}
+
+h2_ctx *h2_ctx_rget(const request_rec *r)
+{
+ return h2_ctx_get(r->connection);
+}
+
+const char *h2_ctx_protocol_get(const conn_rec *c)
+{
+ h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &h2_module);
+ return ctx? ctx->protocol : NULL;
+}
+
+h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto)
+{
+ ctx->protocol = proto;
+ ctx->is_h2 = (proto != NULL);
+ return ctx;
+}
+
+int h2_ctx_is_task(h2_ctx *ctx)
+{
+ return ctx && !!ctx->task_env;
+}
+
+int h2_ctx_is_active(h2_ctx *ctx)
+{
+ return ctx && ctx->is_h2;
+}
+
+struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx)
+{
+ return ctx->task_env;
+}
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_task_env;
+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 {
+ int is_h2; /* h2 engine is used */
+ const char *protocol; /* the protocol negotiated */
+ struct h2_task_env *task_env; /* the h2_task environment or NULL */
+ const char *hostname; /* hostname negotiated via SNI, optional */
+ server_rec *server; /* httpd server config selected. */
+ struct h2_config *config; /* effective config in this context */
+} h2_ctx;
+
+h2_ctx *h2_ctx_get(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_env *env);
+
+
+/* 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);
+
+/**
+ * Get the h2 protocol negotiated for this connection, or NULL.
+ */
+const char *h2_ctx_protocol_get(const conn_rec *c);
+
+int h2_ctx_is_task(h2_ctx *ctx);
+int h2_ctx_is_active(h2_ctx *ctx);
+
+struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx);
+
+#endif /* defined(__mod_h2__h2_ctx__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <util_time.h>
+
+#include "h2_private.h"
+#include "h2_response.h"
+#include "h2_from_h1.h"
+#include "h2_task.h"
+#include "h2_task_output.h"
+#include "h2_util.h"
+
+
+static void set_state(h2_from_h1 *from_h1, h2_from_h1_state_t state);
+
+h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool)
+{
+ h2_from_h1 *from_h1 = apr_pcalloc(pool, sizeof(h2_from_h1));
+ if (from_h1) {
+ from_h1->stream_id = stream_id;
+ from_h1->pool = pool;
+ from_h1->state = H2_RESP_ST_STATUS_LINE;
+ from_h1->hlines = apr_array_make(pool, 10, sizeof(char *));
+ }
+ return from_h1;
+}
+
+apr_status_t h2_from_h1_destroy(h2_from_h1 *from_h1)
+{
+ if (from_h1->response) {
+ h2_response_destroy(from_h1->response);
+ from_h1->response = NULL;
+ }
+ from_h1->bb = NULL;
+ return APR_SUCCESS;
+}
+
+h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1)
+{
+ return from_h1->state;
+}
+
+static void set_state(h2_from_h1 *from_h1, h2_from_h1_state_t state)
+{
+ if (from_h1->state != state) {
+ from_h1->state = state;
+ }
+}
+
+h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1)
+{
+ return from_h1->response;
+}
+
+static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r)
+{
+ from_h1->response = h2_response_create(from_h1->stream_id,
+ from_h1->status, from_h1->hlines,
+ from_h1->pool);
+ if (from_h1->response == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
+ APLOGNO(02915)
+ "h2_from_h1(%d): unable to create resp_head",
+ from_h1->stream_id);
+ return APR_EINVAL;
+ }
+ from_h1->content_length = from_h1->response->content_length;
+ from_h1->chunked = r->chunked;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection,
+ "h2_from_h1(%d): converted headers, content-length: %d"
+ ", chunked=%d",
+ from_h1->stream_id, (int)from_h1->content_length,
+ (int)from_h1->chunked);
+
+ set_state(from_h1, ((from_h1->chunked || from_h1->content_length > 0)?
+ H2_RESP_ST_BODY : H2_RESP_ST_DONE));
+ /* We are ready to be sent to the client */
+ return APR_SUCCESS;
+}
+
+static apr_status_t parse_header(h2_from_h1 *from_h1, ap_filter_t* f,
+ char *line) {
+ (void)f;
+
+ if (line[0] == ' ' || line[0] == '\t') {
+ char **plast;
+ /* continuation line from the header before this */
+ while (line[0] == ' ' || line[0] == '\t') {
+ ++line;
+ }
+
+ plast = apr_array_pop(from_h1->hlines);
+ if (plast == NULL) {
+ /* not well formed */
+ return APR_EINVAL;
+ }
+ APR_ARRAY_PUSH(from_h1->hlines, const char*) = apr_psprintf(from_h1->pool, "%s %s", *plast, line);
+ }
+ else {
+ /* new header line */
+ APR_ARRAY_PUSH(from_h1->hlines, const char*) = apr_pstrdup(from_h1->pool, line);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t get_line(h2_from_h1 *from_h1, apr_bucket_brigade *bb,
+ ap_filter_t* f, char *line, apr_size_t len)
+{
+ apr_status_t status;
+ if (!from_h1->bb) {
+ from_h1->bb = apr_brigade_create(from_h1->pool, f->c->bucket_alloc);
+ }
+ else {
+ apr_brigade_cleanup(from_h1->bb);
+ }
+ status = apr_brigade_split_line(from_h1->bb, bb,
+ APR_BLOCK_READ,
+ HUGE_STRING_LEN);
+ if (status == APR_SUCCESS) {
+ --len;
+ status = apr_brigade_flatten(from_h1->bb, line, &len);
+ if (status == APR_SUCCESS) {
+ /* we assume a non-0 containing line and remove
+ * trailing crlf. */
+ line[len] = '\0';
+ if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
+ len -= 2;
+ line[len] = '\0';
+ }
+
+ apr_brigade_cleanup(from_h1->bb);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_from_h1(%d): read line: %s",
+ from_h1->stream_id, line);
+ }
+ }
+ return status;
+}
+
+apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1, ap_filter_t* f,
+ apr_bucket_brigade* bb)
+{
+ apr_status_t status = APR_SUCCESS;
+ char line[HUGE_STRING_LEN];
+
+ if ((from_h1->state == H2_RESP_ST_BODY)
+ || (from_h1->state == H2_RESP_ST_DONE)) {
+ if (from_h1->chunked) {
+ /* The httpd core HTTP_HEADER filter has or will install the
+ * "CHUNK" output transcode filter, which appears further down
+ * the filter chain. We do not want it for HTTP/2.
+ * Once we successfully deinstalled it, this filter has no
+ * further function and we remove it.
+ */
+ status = ap_remove_output_filter_byhandle(f->r->output_filters,
+ "CHUNK");
+ if (status == APR_SUCCESS) {
+ ap_remove_output_filter(f);
+ }
+ }
+
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_from_h1(%d): read_response", from_h1->stream_id);
+
+ while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
+
+ switch (from_h1->state) {
+
+ case H2_RESP_ST_STATUS_LINE:
+ case H2_RESP_ST_HEADERS:
+ status = get_line(from_h1, bb, f, line, sizeof(line));
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ if (from_h1->state == H2_RESP_ST_STATUS_LINE) {
+ /* instead of parsing, just take it directly */
+ from_h1->status = apr_psprintf(from_h1->pool,
+ "%d", f->r->status);
+ from_h1->state = H2_RESP_ST_HEADERS;
+ }
+ else if (line[0] == '\0') {
+ /* end of headers, create the h2_response and
+ * pass the rest of the brigade down the filter
+ * chain.
+ */
+ status = make_h2_headers(from_h1, f->r);
+ if (from_h1->bb) {
+ apr_brigade_destroy(from_h1->bb);
+ from_h1->bb = NULL;
+ }
+ if (!APR_BRIGADE_EMPTY(bb)) {
+ return ap_pass_brigade(f->next, bb);
+ }
+ }
+ else {
+ status = parse_header(from_h1, f, line);
+ }
+ break;
+
+ default:
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ }
+
+ return status;
+}
+
+/* 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
+ * and recombine multiple Vary fields, though it is generic to any field
+ * consisting of comma/space-separated tokens.
+ */
+static int uniq_field_values(void *d, const char *key, const char *val)
+{
+ apr_array_header_t *values;
+ char *start;
+ char *e;
+ char **strpp;
+ int i;
+
+ (void)key;
+ values = (apr_array_header_t *)d;
+
+ e = apr_pstrdup(values->pool, val);
+
+ do {
+ /* Find a non-empty fieldname */
+
+ while (*e == ',' || apr_isspace(*e)) {
+ ++e;
+ }
+ if (*e == '\0') {
+ break;
+ }
+ start = e;
+ while (*e != '\0' && *e != ',' && !apr_isspace(*e)) {
+ ++e;
+ }
+ if (*e != '\0') {
+ *e++ = '\0';
+ }
+
+ /* Now add it to values if it isn't already represented.
+ * Could be replaced by a ap_array_strcasecmp() if we had one.
+ */
+ for (i = 0, strpp = (char **) values->elts; i < values->nelts;
+ ++i, ++strpp) {
+ if (*strpp && strcasecmp(*strpp, start) == 0) {
+ break;
+ }
+ }
+ if (i == values->nelts) { /* if not found */
+ *(char **)apr_array_push(values) = start;
+ }
+ } while (*e != '\0');
+
+ return 1;
+}
+
+/*
+ * Since some clients choke violently on multiple Vary fields, or
+ * Vary fields with duplicate tokens, combine any multiples and remove
+ * any duplicates.
+ */
+static void fix_vary(request_rec *r)
+{
+ apr_array_header_t *varies;
+
+ varies = apr_array_make(r->pool, 5, sizeof(char *));
+
+ /* Extract all Vary fields from the headers_out, separate each into
+ * its comma-separated fieldname values, and then add them to varies
+ * if not already present in the array.
+ */
+ apr_table_do((int (*)(void *, const char *, const char *))uniq_field_values,
+ (void *) varies, r->headers_out, "Vary", NULL);
+
+ /* If we found any, replace old Vary fields with unique-ified value */
+
+ if (varies->nelts > 0) {
+ apr_table_setn(r->headers_out, "Vary",
+ apr_array_pstrcat(r->pool, varies, ','));
+ }
+}
+
+static void set_basic_http_header(request_rec *r, apr_table_t *headers)
+{
+ 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->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(r->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(r->pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, r->request_time);
+ }
+
+ apr_table_setn(headers, "Date", proxy_date ? proxy_date : date );
+ apr_table_unset(r->headers_out, "Date");
+
+ if (!server && *us) {
+ server = us;
+ }
+ if (server) {
+ apr_table_setn(headers, "Server", server);
+ 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_addn(headers, name, value);
+ return 1;
+}
+
+static h2_response *create_response(h2_from_h1 *from_h1, 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
+ * later attempts to set or unset a given fieldname might be bypassed.
+ */
+ if (!apr_is_empty_table(r->err_headers_out)) {
+ r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+ r->headers_out);
+ }
+
+ /*
+ * Remove the 'Vary' header field if the client can't handle it.
+ * Since this will have nasty effects on HTTP/1.1 caches, force
+ * the response into HTTP/1.0 mode.
+ */
+ if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
+ apr_table_unset(r->headers_out, "Vary");
+ r->proto_num = HTTP_VERSION(1,0);
+ apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
+ }
+ else {
+ fix_vary(r);
+ }
+
+ /*
+ * Now remove any ETag response header field if earlier processing
+ * says so (such as a 'FileETag None' directive).
+ */
+ if (apr_table_get(r->notes, "no-etag") != NULL) {
+ apr_table_unset(r->headers_out, "ETag");
+ }
+
+ /* determine the protocol and whether we should use keepalives. */
+ ap_set_keepalive(r);
+
+ if (r->chunked) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ ctype = ap_make_content_type(r, r->content_type);
+ if (ctype) {
+ apr_table_setn(r->headers_out, "Content-Type", ctype);
+ }
+
+ if (r->content_encoding) {
+ apr_table_setn(r->headers_out, "Content-Encoding",
+ r->content_encoding);
+ }
+
+ if (!apr_is_empty_array(r->content_languages)) {
+ int i;
+ char *token;
+ char **languages = (char **)(r->content_languages->elts);
+ const char *field = apr_table_get(r->headers_out, "Content-Language");
+
+ while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
+ for (i = 0; i < r->content_languages->nelts; ++i) {
+ if (!strcasecmp(token, languages[i]))
+ break;
+ }
+ if (i == r->content_languages->nelts) {
+ *((char **) apr_array_push(r->content_languages)) = token;
+ }
+ }
+
+ field = apr_array_pstrcat(r->pool, r->content_languages, ',');
+ apr_table_setn(r->headers_out, "Content-Language", field);
+ }
+
+ /*
+ * Control cachability for non-cachable responses if not already set by
+ * some other part of the server configuration.
+ */
+ if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
+ char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, r->request_time);
+ apr_table_addn(r->headers_out, "Expires", date);
+ }
+
+ /* This is a hack, but I can't find anyway around it. The idea is that
+ * we don't want to send out 0 Content-Lengths if it is a head request.
+ * This happens when modules try to outsmart the server, and return
+ * if they see a HEAD request. Apache 1.3 handlers were supposed to
+ * just return in that situation, and the core handled the HEAD. In
+ * 2.0, if a handler returns, then the core sends an EOS bucket down
+ * the filter stack, and the content-length filter computes a C-L of
+ * zero and that gets put in the headers, and we end up sending a
+ * zero C-L to the client. We can't just remove the C-L filter,
+ * because well behaved 2.0 handlers will send their data down the stack,
+ * and we will compute a real C-L for the head request. RBB
+ */
+ if (r->header_only
+ && (clheader = apr_table_get(r->headers_out, "Content-Length"))
+ && !strcmp(clheader, "0")) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ headers = apr_table_make(r->pool, 10);
+
+ set_basic_http_header(r, headers);
+ if (r->status == HTTP_NOT_MODIFIED) {
+ apr_table_do((int (*)(void *, const char *, const char *)) copy_header,
+ (void *) headers, r->headers_out,
+ "ETag",
+ "Content-Location",
+ "Expires",
+ "Cache-Control",
+ "Vary",
+ "Warning",
+ "WWW-Authenticate",
+ "Proxy-Authenticate",
+ "Set-Cookie",
+ "Set-Cookie2",
+ NULL);
+ }
+ else {
+ apr_table_do((int (*)(void *, const char *, const char *)) copy_header,
+ (void *) headers, r->headers_out, NULL);
+ }
+
+ return h2_response_rcreate(from_h1->stream_id, r, headers, r->pool);
+}
+
+apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ h2_task_env *env = f->ctx;
+ h2_from_h1 *from_h1 = env->output? env->output->from_h1 : NULL;
+ request_rec *r = f->r;
+ apr_bucket *b;
+ ap_bucket_error *eb = NULL;
+
+ AP_DEBUG_ASSERT(from_h1 != NULL);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_from_h1(%d): output_filter called", from_h1->stream_id);
+
+ if (r->header_only && env->output && from_h1->response) {
+ /* throw away any data after we have compiled the response */
+ apr_brigade_cleanup(bb);
+ return OK;
+ }
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (AP_BUCKET_IS_ERROR(b) && !eb) {
+ eb = b->data;
+ continue;
+ }
+ /*
+ * If we see an EOC bucket it is a signal that we should get out
+ * of the way doing nothing.
+ */
+ if (AP_BUCKET_IS_EOC(b)) {
+ ap_remove_output_filter(f);
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c,
+ "h2_from_h1(%d): eoc bucket passed",
+ from_h1->stream_id);
+ return ap_pass_brigade(f->next, bb);
+ }
+ }
+
+ if (eb) {
+ int st = eb->status;
+ apr_brigade_cleanup(bb);
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c,
+ "h2_from_h1(%d): err bucket status=%d",
+ from_h1->stream_id, st);
+ ap_die(st, r);
+ return AP_FILTER_ERROR;
+ }
+
+ from_h1->response = create_response(from_h1, r);
+ if (from_h1->response == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c,
+ "h2_from_h1(%d): unable to create response",
+ from_h1->stream_id);
+ return APR_ENOMEM;
+ }
+
+ if (r->header_only) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_from_h1(%d): header_only, cleanup output brigade",
+ from_h1->stream_id);
+ apr_brigade_cleanup(bb);
+ return OK;
+ }
+
+ r->sent_bodyct = 1; /* Whatever follows is real body stuff... */
+
+ ap_remove_output_filter(f);
+ if (APLOGctrace1(f->c)) {
+ apr_off_t len = 0;
+ apr_brigade_length(bb, 0, &len);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_from_h1(%d): removed header filter, passing brigade "
+ "len=%ld", from_h1->stream_id, (long)len);
+ }
+ return ap_pass_brigade(f->next, bb);
+}
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_from_h1__
+#define __mod_h2__h2_from_h1__
+
+/**
+ * h2_from_h1 parses a HTTP/1.1 response into
+ * - response status
+ * - a list of header values
+ * - a series of bytes that represent the response body alone, without
+ * any meta data, such as inserted by chunked transfer encoding.
+ *
+ * All data is allocated from the stream memory pool.
+ *
+ * Again, see comments in h2_request: ideally we would take the headers
+ * and status from the httpd structures instead of parsing them here, but
+ * we need to have all handlers and filters involved in request/response
+ * processing, so this seems to be the way for now.
+ */
+
+typedef enum {
+ H2_RESP_ST_STATUS_LINE, /* parsing http/1 status line */
+ H2_RESP_ST_HEADERS, /* parsing http/1 response headers */
+ H2_RESP_ST_BODY, /* transferring response body */
+ H2_RESP_ST_DONE /* complete response converted */
+} h2_from_h1_state_t;
+
+struct h2_response;
+
+typedef struct h2_from_h1 h2_from_h1;
+
+struct h2_from_h1 {
+ int stream_id;
+ h2_from_h1_state_t state;
+ apr_pool_t *pool;
+ apr_bucket_brigade *bb;
+
+ apr_size_t content_length;
+ int chunked;
+
+ const char *status;
+ apr_array_header_t *hlines;
+
+ struct h2_response *response;
+};
+
+
+typedef void h2_from_h1_state_change_cb(struct h2_from_h1 *resp,
+ h2_from_h1_state_t prevstate,
+ void *cb_ctx);
+
+h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool);
+
+apr_status_t h2_from_h1_destroy(h2_from_h1 *response);
+
+void h2_from_h1_set_state_change_cb(h2_from_h1 *from_h1,
+ h2_from_h1_state_change_cb *callback,
+ void *cb_ctx);
+
+apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1,
+ ap_filter_t* f, apr_bucket_brigade* bb);
+
+struct h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1);
+
+h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1);
+
+apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb);
+
+#endif /* defined(__mod_h2__h2_from_h1__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <apr_optional.h>
+#include <apr_optional_hooks.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+
+#include "h2_stream.h"
+#include "h2_task.h"
+#include "h2_config.h"
+#include "h2_ctx.h"
+#include "h2_conn.h"
+#include "h2_h2.h"
+
+const char *h2_tls_protos[] = {
+ "h2", NULL
+};
+
+const char *h2_clear_protos[] = {
+ "h2c", NULL
+};
+
+const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+/*******************************************************************************
+ * The optional mod_ssl functions we need.
+ */
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec*));
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec*));
+
+static int (*opt_ssl_engine_disable)(conn_rec*);
+static int (*opt_ssl_is_https)(conn_rec*);
+/*******************************************************************************
+ * Hooks for processing incoming connections:
+ * - pre_conn_before_tls switches SSL off for stream connections
+ * - process_conn take over connection in case of h2
+ */
+static int h2_h2_process_conn(conn_rec* c);
+static int h2_h2_remove_timeout(conn_rec* c);
+static int h2_h2_post_read_req(request_rec *r);
+
+
+/*******************************************************************************
+ * Once per lifetime init, retrieve optional functions
+ */
+apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s)
+{
+ (void)pool;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_h2, child_init");
+ opt_ssl_engine_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
+ opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+
+ if (!opt_ssl_is_https) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ APLOGNO(02951) "mod_ssl does not seem to be enabled");
+ }
+
+ return APR_SUCCESS;
+}
+
+int h2_h2_is_tls(conn_rec *c)
+{
+ return opt_ssl_is_https && opt_ssl_is_https(c);
+}
+
+int h2_tls_disable(conn_rec *c)
+{
+ if (opt_ssl_engine_disable) {
+ return opt_ssl_engine_disable(c);
+ }
+ return 0;
+}
+
+/*******************************************************************************
+ * Register various hooks
+ */
+static const char *const mod_reqtimeout[] = { "reqtimeout.c", NULL};
+
+void h2_h2_register_hooks(void)
+{
+ /* When the connection processing actually starts, we might to
+ * take over, if h2* was selected as protocol.
+ */
+ ap_hook_process_connection(h2_h2_process_conn,
+ NULL, NULL, APR_HOOK_FIRST);
+ /* Perform connection cleanup before the actual processing happens.
+ */
+ ap_hook_process_connection(h2_h2_remove_timeout,
+ mod_reqtimeout, NULL, 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);
+}
+
+int h2_h2_remove_timeout(conn_rec* c)
+{
+ h2_ctx *ctx = h2_ctx_get(c);
+
+ if (h2_ctx_is_active(ctx) && !h2_ctx_is_task(ctx)) {
+ /* cleanup on master h2 connections */
+ ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
+ }
+
+ return DECLINED;
+}
+
+int h2_h2_process_conn(conn_rec* c)
+{
+ h2_ctx *ctx = h2_ctx_get(c);
+ h2_config *cfg = h2_config_get(c);
+ apr_bucket_brigade* temp;
+ int is_tls = h2_h2_is_tls(c);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
+ if (h2_ctx_is_task(ctx)) {
+ /* our stream pseudo connection */
+ return DECLINED;
+ }
+
+ /* If we have not already switched to a h2* protocol and the connection
+ * is on "http/1.1"
+ * -> sniff for the magic PRIamble. On TLS, this might trigger the ALPN.
+ */
+ if (!h2_ctx_protocol_get(c)
+ && !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
+ apr_status_t status;
+
+ 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) {
+ if (h2_ctx_protocol_get(c)
+ || strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
+ /* h2 or another protocol has been selected. */
+ }
+ else {
+ /* ALPN might have been triggered, but we're still on
+ * http/1.1. Check the actual bytes read for the H2 Magic
+ * Token, *if* H2Direct mode is enabled here.
+ */
+ int direct_mode = h2_config_geti(cfg, H2_CONF_DIRECT);
+ if (direct_mode > 0 || (direct_mode < 0 && !is_tls)) {
+ char *s = NULL;
+ apr_size_t slen;
+
+ apr_brigade_pflatten(temp, &s, &slen, c->pool);
+ if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_h2, direct mode detected");
+ h2_ctx_protocol_set(ctx, is_tls? "h2" : "h2c");
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_h2, not detected in %d bytes: %s",
+ (int)slen, s);
+ }
+ }
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ "h2_h2, error reading 24 bytes speculative");
+ }
+ apr_brigade_destroy(temp);
+ }
+
+ /* If "h2" was selected as protocol (by whatever mechanism), take over
+ * the connection.
+ */
+ if (h2_ctx_is_active(ctx)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_h2, connection, h2 active");
+
+ return h2_conn_main(c);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
+ return DECLINED;
+}
+
+static int h2_h2_post_read_req(request_rec *r)
+{
+ h2_ctx *ctx = h2_ctx_rget(r);
+ struct h2_task_env *env = h2_ctx_get_task(ctx);
+ if (env) {
+ /* h2_task connection for a stream, not for h2c */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "adding h1_to_h2_resp output filter");
+ if (env->serialize_headers) {
+ ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP");
+ ap_add_output_filter("H1_TO_H2_RESP", env, r, r->connection);
+ }
+ else {
+ /* 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_remove_output_filter_byhandle(r->output_filters, "H2_RESPONSE");
+ ap_add_output_filter("H2_RESPONSE", env, r, r->connection);
+ }
+ }
+ return DECLINED;
+}
+
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_h2__
+#define __mod_h2__h2_h2__
+
+/**
+ * List of ALPN protocol identifiers that we suport in cleartext
+ * negotiations. NULL terminated.
+ */
+extern const char *h2_clear_protos[];
+
+/**
+ * List of ALPN protocol identifiers that we support in TLS encrypted
+ * negotiations. NULL terminated.
+ */
+extern const char *h2_tls_protos[];
+
+/**
+ * The magic PRIamble of RFC 7540 that is always sent when starting
+ * a h2 communication.
+ */
+extern const char *H2_MAGIC_TOKEN;
+
+/*
+ * One time, post config intialization.
+ */
+apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s);
+
+/* Is the connection a TLS connection?
+ */
+int h2_h2_is_tls(conn_rec *c);
+
+/* Disable SSL for this connection, can only be invoked in a pre-
+ * connection hook before mod_ssl.
+ * @return != 0 iff disable worked
+ */
+int h2_tls_disable(conn_rec *c);
+
+/* Register apache hooks for h2 protocol
+ */
+void h2_h2_register_hooks(void);
+
+
+#endif /* defined(__mod_h2__h2_h2__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_log.h>
+#include <http_connection.h>
+
+#include "h2_private.h"
+#include "h2_io.h"
+#include "h2_response.h"
+#include "h2_util.h"
+
+h2_io *h2_io_create(int id, apr_pool_t *pool, apr_bucket_alloc_t *bucket_alloc)
+{
+ h2_io *io = apr_pcalloc(pool, sizeof(*io));
+ if (io) {
+ io->id = id;
+ io->pool = pool;
+ io->bbin = NULL;
+ io->bbout = apr_brigade_create(pool, bucket_alloc);
+ io->response = apr_pcalloc(pool, sizeof(h2_response));
+ }
+ return io;
+}
+
+static void h2_io_cleanup(h2_io *io)
+{
+ (void)io;
+}
+
+void h2_io_destroy(h2_io *io)
+{
+ h2_io_cleanup(io);
+}
+
+int h2_io_in_has_eos_for(h2_io *io)
+{
+ return io->eos_in || (io->bbin && h2_util_has_eos(io->bbin, 0));
+}
+
+int h2_io_out_has_data(h2_io *io)
+{
+ return h2_util_bb_has_data_or_eos(io->bbout);
+}
+
+apr_size_t h2_io_out_length(h2_io *io)
+{
+ if (io->bbout) {
+ apr_off_t len = 0;
+ apr_brigade_length(io->bbout, 0, &len);
+ return (len > 0)? len : 0;
+ }
+ return 0;
+}
+
+apr_status_t h2_io_in_read(h2_io *io, apr_bucket_brigade *bb,
+ apr_size_t maxlen)
+{
+ apr_off_t start_len = 0;
+ apr_bucket *last;
+ apr_status_t status;
+
+ if (!io->bbin || APR_BRIGADE_EMPTY(io->bbin)) {
+ return io->eos_in? APR_EOF : APR_EAGAIN;
+ }
+
+ apr_brigade_length(bb, 1, &start_len);
+ last = APR_BRIGADE_LAST(bb);
+ status = h2_util_move(bb, io->bbin, maxlen, 0,
+ "h2_io_in_read");
+ if (status == APR_SUCCESS) {
+ apr_bucket *nlast = APR_BRIGADE_LAST(bb);
+ apr_off_t end_len = 0;
+ apr_brigade_length(bb, 1, &end_len);
+ if (last == nlast) {
+ return APR_EAGAIN;
+ }
+ io->input_consumed += (end_len - start_len);
+ }
+ return status;
+}
+
+apr_status_t h2_io_in_write(h2_io *io, apr_bucket_brigade *bb)
+{
+ if (io->eos_in) {
+ return APR_EOF;
+ }
+ io->eos_in = h2_util_has_eos(bb, 0);
+ if (!APR_BRIGADE_EMPTY(bb)) {
+ if (!io->bbin) {
+ io->bbin = apr_brigade_create(io->bbout->p,
+ io->bbout->bucket_alloc);
+ }
+ return h2_util_move(io->bbin, bb, 0, 0, "h2_io_in_write");
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_io_in_close(h2_io *io)
+{
+ if (io->bbin) {
+ APR_BRIGADE_INSERT_TAIL(io->bbin,
+ apr_bucket_eos_create(io->bbin->bucket_alloc));
+ }
+ io->eos_in = 1;
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_io_out_readx(h2_io *io,
+ h2_io_data_cb *cb, void *ctx,
+ apr_size_t *plen, int *peos)
+{
+ if (cb == NULL) {
+ /* just checking length available */
+ return h2_util_bb_avail(io->bbout, plen, peos);
+ }
+ return h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
+}
+
+apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb,
+ apr_size_t maxlen, int *pfile_handles_allowed)
+{
+ /* Let's move the buckets from the request processing in here, so
+ * that the main thread can read them when it has time/capacity.
+ *
+ * Move at most "maxlen" memory bytes. If buckets remain, it is
+ * the caller's responsibility to take care of this.
+ *
+ * We allow passing of file buckets as long as we do not have too
+ * many open files already buffered. Otherwise we will run out of
+ * file handles.
+ */
+ int start_allowed = *pfile_handles_allowed;
+ apr_status_t status;
+ status = h2_util_move(io->bbout, bb, maxlen, pfile_handles_allowed,
+ "h2_io_out_write");
+ /* track # file buckets moved into our pool */
+ if (start_allowed != *pfile_handles_allowed) {
+ io->files_handles_owned += (start_allowed - *pfile_handles_allowed);
+ }
+ return status;
+}
+
+
+apr_status_t h2_io_out_close(h2_io *io)
+{
+ APR_BRIGADE_INSERT_TAIL(io->bbout,
+ apr_bucket_eos_create(io->bbout->bucket_alloc));
+ return APR_SUCCESS;
+}
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_io__
+#define __mod_h2__h2_io__
+
+struct h2_response;
+struct apr_thread_cond_t;
+struct h2_task;
+
+
+typedef apr_status_t h2_io_data_cb(void *ctx,
+ const char *data, apr_size_t len);
+
+
+typedef struct h2_io h2_io;
+
+struct h2_io {
+ int id; /* stream identifier */
+ apr_pool_t *pool; /* stream pool */
+ apr_bucket_brigade *bbin; /* input data for stream */
+ int eos_in;
+ int task_done;
+
+ apr_size_t input_consumed; /* how many bytes have been read */
+ struct apr_thread_cond_t *input_arrived; /* block on reading */
+
+ apr_bucket_brigade *bbout; /* output data from stream */
+ struct apr_thread_cond_t *output_drained; /* block on writing */
+
+ struct h2_response *response;/* submittable response created */
+ int files_handles_owned;
+};
+
+/*******************************************************************************
+ * Object lifecycle and information.
+ ******************************************************************************/
+
+/**
+ * Creates a new h2_io for the given stream id.
+ */
+h2_io *h2_io_create(int id, apr_pool_t *pool, apr_bucket_alloc_t *bucket_alloc);
+
+/**
+ * Frees any resources hold by the h2_io instance.
+ */
+void h2_io_destroy(h2_io *io);
+
+/**
+ * The input data is completely queued. Blocked reads will return immediately
+ * and give either data or EOF.
+ */
+int h2_io_in_has_eos_for(h2_io *io);
+/**
+ * Output data is available.
+ */
+int h2_io_out_has_data(h2_io *io);
+
+/*******************************************************************************
+ * Input handling of streams.
+ ******************************************************************************/
+/**
+ * Reads the next bucket from the input. Returns APR_EAGAIN if none
+ * is currently available, APR_EOF if end of input has been reached.
+ */
+apr_status_t h2_io_in_read(h2_io *io, apr_bucket_brigade *bb,
+ apr_size_t maxlen);
+
+/**
+ * Appends given bucket to the input.
+ */
+apr_status_t h2_io_in_write(h2_io *io, apr_bucket_brigade *bb);
+
+/**
+ * Closes the input. After existing data has been read, APR_EOF will
+ * be returned.
+ */
+apr_status_t h2_io_in_close(h2_io *io);
+
+/*******************************************************************************
+ * Output handling of streams.
+ ******************************************************************************/
+
+/**
+ * Read a bucket from the output head. Return APR_EAGAIN if non is available,
+ * APR_EOF if none available and output has been closed.
+ * May be called with buffer == NULL in order to find out how much data
+ * is available.
+ * @param io the h2_io to read output from
+ * @param buffer the buffer to copy the data to, may be NULL
+ * @param plen the requested max len, set to amount of data on return
+ * @param peos != 0 iff the end of stream has been reached
+ */
+apr_status_t h2_io_out_readx(h2_io *io,
+ h2_io_data_cb *cb, void *ctx,
+ apr_size_t *plen, int *peos);
+
+apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb,
+ apr_size_t maxlen, int *pfile_buckets_allowed);
+
+/**
+ * Closes the input. After existing data has been read, APR_EOF will
+ * be returned.
+ */
+apr_status_t h2_io_out_close(h2_io *io);
+
+/**
+ * Gives the overall length of the data that is currently queued for
+ * output.
+ */
+apr_size_t h2_io_out_length(h2_io *io);
+
+
+#endif /* defined(__mod_h2__h2_io__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_io.h"
+#include "h2_io_set.h"
+
+#define h2_io_IDX(list, i) ((h2_io**)(list)->elts)[i]
+
+struct h2_io_set {
+ apr_array_header_t *list;
+};
+
+h2_io_set *h2_io_set_create(apr_pool_t *pool)
+{
+ h2_io_set *sp = apr_pcalloc(pool, sizeof(h2_io_set));
+ if (sp) {
+ sp->list = apr_array_make(pool, 100, sizeof(h2_io*));
+ if (!sp->list) {
+ return NULL;
+ }
+ }
+ return sp;
+}
+
+void h2_io_set_destroy(h2_io_set *sp)
+{
+ int i;
+ for (i = 0; i < sp->list->nelts; ++i) {
+ h2_io *io = h2_io_IDX(sp->list, i);
+ h2_io_destroy(io);
+ }
+ sp->list->nelts = 0;
+}
+
+static int h2_stream_id_cmp(const void *s1, const void *s2)
+{
+ h2_io **pio1 = (h2_io **)s1;
+ h2_io **pio2 = (h2_io **)s2;
+ return (*pio1)->id - (*pio2)->id;
+}
+
+h2_io *h2_io_set_get(h2_io_set *sp, int stream_id)
+{
+ /* we keep the array sorted by id, so lookup can be done
+ * by bsearch.
+ */
+ h2_io **ps;
+ h2_io key;
+ h2_io *pkey = &key;
+
+ memset(&key, 0, sizeof(key));
+ key.id = stream_id;
+ ps = bsearch(&pkey, sp->list->elts, sp->list->nelts,
+ sp->list->elt_size, h2_stream_id_cmp);
+ return ps? *ps : NULL;
+}
+
+h2_io *h2_io_set_get_highest_prio(h2_io_set *set)
+{
+ h2_io *highest = NULL;
+ int i;
+ for (i = 0; i < set->list->nelts; ++i) {
+ h2_io *io = h2_io_IDX(set->list, i);
+ if (!highest /*|| io-prio even higher */ ) {
+ highest = io;
+ }
+ }
+ return highest;
+}
+
+static void h2_io_set_sort(h2_io_set *sp)
+{
+ qsort(sp->list->elts, sp->list->nelts, sp->list->elt_size,
+ h2_stream_id_cmp);
+}
+
+apr_status_t h2_io_set_add(h2_io_set *sp, h2_io *io)
+{
+ h2_io *existing = h2_io_set_get(sp, io->id);
+ if (!existing) {
+ int last;
+ APR_ARRAY_PUSH(sp->list, h2_io*) = io;
+ /* Normally, streams get added in ascending order if id. We
+ * keep the array sorted, so we just need to check of the newly
+ * appended stream has a lower id than the last one. if not,
+ * sorting is not necessary.
+ */
+ last = sp->list->nelts - 1;
+ if (last > 0
+ && (h2_io_IDX(sp->list, last)->id
+ < h2_io_IDX(sp->list, last-1)->id)) {
+ h2_io_set_sort(sp);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+h2_io *h2_io_set_remove(h2_io_set *sp, h2_io *io)
+{
+ int i;
+ for (i = 0; i < sp->list->nelts; ++i) {
+ h2_io *e = h2_io_IDX(sp->list, i);
+ if (e == io) {
+ int n;
+ --sp->list->nelts;
+ n = sp->list->nelts - i;
+ if (n > 0) {
+ /* Close the hole in the array by moving the upper
+ * parts down one step.
+ */
+ h2_io **selts = (h2_io**)sp->list->elts;
+ memmove(selts+i, selts+i+1, n * sizeof(h2_io*));
+ }
+ return e;
+ }
+ }
+ return NULL;
+}
+
+void h2_io_set_destroy_all(h2_io_set *sp)
+{
+ int i;
+ for (i = 0; i < sp->list->nelts; ++i) {
+ h2_io *io = h2_io_IDX(sp->list, i);
+ h2_io_destroy(io);
+ }
+ sp->list->nelts = 0;
+}
+
+void h2_io_set_remove_all(h2_io_set *sp)
+{
+ sp->list->nelts = 0;
+}
+
+int h2_io_set_is_empty(h2_io_set *sp)
+{
+ AP_DEBUG_ASSERT(sp);
+ return sp->list->nelts == 0;
+}
+
+void h2_io_set_iter(h2_io_set *sp,
+ h2_io_set_iter_fn *iter, void *ctx)
+{
+ int i;
+ for (i = 0; i < sp->list->nelts; ++i) {
+ h2_io *s = h2_io_IDX(sp->list, i);
+ if (!iter(ctx, s)) {
+ break;
+ }
+ }
+}
+
+apr_size_t h2_io_set_size(h2_io_set *sp)
+{
+ return sp->list->nelts;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_io_set__
+#define __mod_h2__h2_io_set__
+
+struct h2_io;
+
+/**
+ * A set of h2_io instances. Allows lookup by stream id
+ * and other criteria.
+ */
+typedef struct h2_io_set h2_io_set;
+
+h2_io_set *h2_io_set_create(apr_pool_t *pool);
+
+void h2_io_set_destroy(h2_io_set *set);
+
+apr_status_t h2_io_set_add(h2_io_set *set, struct h2_io *io);
+h2_io *h2_io_set_get(h2_io_set *set, int stream_id);
+h2_io *h2_io_set_get_highest_prio(h2_io_set *set);
+h2_io *h2_io_set_remove(h2_io_set *set, struct h2_io *io);
+
+void h2_io_set_remove_all(h2_io_set *set);
+void h2_io_set_destroy_all(h2_io_set *set);
+int h2_io_set_is_empty(h2_io_set *set);
+apr_size_t h2_io_set_size(h2_io_set *set);
+
+
+typedef int h2_io_set_iter_fn(void *ctx, struct h2_io *io);
+
+void h2_io_set_iter(h2_io_set *set,
+ h2_io_set_iter_fn *iter, void *ctx);
+
+#endif /* defined(__mod_h2__h2_io_set__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_thread_mutex.h>
+#include <apr_thread_cond.h>
+#include <apr_strings.h>
+#include <apr_time.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_config.h"
+#include "h2_conn.h"
+#include "h2_io.h"
+#include "h2_io_set.h"
+#include "h2_response.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_stream.h"
+#include "h2_stream_set.h"
+#include "h2_task.h"
+#include "h2_task_input.h"
+#include "h2_task_output.h"
+#include "h2_task_queue.h"
+#include "h2_workers.h"
+
+
+static int is_aborted(h2_mplx *m, apr_status_t *pstatus) {
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ *pstatus = APR_ECONNABORTED;
+ return 1;
+ }
+ return 0;
+}
+
+static void have_out_data_for(h2_mplx *m, int stream_id);
+
+static void h2_mplx_destroy(h2_mplx *m)
+{
+ AP_DEBUG_ASSERT(m);
+ m->aborted = 1;
+ if (m->q) {
+ h2_tq_destroy(m->q);
+ m->q = NULL;
+ }
+ if (m->ready_ios) {
+ h2_io_set_destroy(m->ready_ios);
+ m->ready_ios = NULL;
+ }
+ if (m->stream_ios) {
+ h2_io_set_destroy(m->stream_ios);
+ m->stream_ios = NULL;
+ }
+
+ if (m->lock) {
+ apr_thread_mutex_destroy(m->lock);
+ m->lock = NULL;
+ }
+
+ if (m->pool) {
+ apr_pool_destroy(m->pool);
+ }
+}
+
+/**
+ * A h2_mplx needs to be thread-safe *and* if will be called by
+ * the h2_session thread *and* the h2_worker threads. Therefore:
+ * - calls are protected by a mutex lock, m->lock
+ * - the pool needs its own allocator, since apr_allocator_t are
+ * not re-entrant. The separate allocator works without a
+ * separate lock since we already protect h2_mplx itself.
+ * Since HTTP/2 connections can be expected to live longer than
+ * their HTTP/1 cousins, the separate allocator seems to work better
+ * than protecting a shared h2_session one with an own lock.
+ */
+h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, h2_workers *workers)
+{
+ apr_status_t status = APR_SUCCESS;
+ h2_config *conf = h2_config_get(c);
+ apr_allocator_t *allocator = NULL;
+ h2_mplx *m;
+ AP_DEBUG_ASSERT(conf);
+
+ status = apr_allocator_create(&allocator);
+ if (status != APR_SUCCESS) {
+ return NULL;
+ }
+
+ m = apr_pcalloc(parent, sizeof(h2_mplx));
+ if (m) {
+ m->id = c->id;
+ APR_RING_ELEM_INIT(m, link);
+ apr_atomic_set32(&m->refs, 1);
+ m->c = c;
+ apr_pool_create_ex(&m->pool, parent, NULL, allocator);
+ if (!m->pool) {
+ return NULL;
+ }
+ apr_allocator_owner_set(allocator, m->pool);
+
+ status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT,
+ m->pool);
+ if (status != APR_SUCCESS) {
+ h2_mplx_destroy(m);
+ return NULL;
+ }
+
+ m->bucket_alloc = apr_bucket_alloc_create(m->pool);
+
+ m->q = h2_tq_create(m->id, m->pool);
+ m->stream_ios = h2_io_set_create(m->pool);
+ m->ready_ios = h2_io_set_create(m->pool);
+ m->closed = h2_stream_set_create(m->pool);
+ m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
+ m->workers = workers;
+
+ m->file_handles_allowed = h2_config_geti(conf, H2_CONF_SESSION_FILES);
+ }
+ return m;
+}
+
+static void reference(h2_mplx *m)
+{
+ apr_atomic_inc32(&m->refs);
+}
+
+static void release(h2_mplx *m)
+{
+ if (!apr_atomic_dec32(&m->refs)) {
+ if (m->join_wait) {
+ apr_thread_cond_signal(m->join_wait);
+ }
+ }
+}
+
+void h2_mplx_reference(h2_mplx *m)
+{
+ reference(m);
+}
+void h2_mplx_release(h2_mplx *m)
+{
+ release(m);
+}
+
+static void workers_register(h2_mplx *m) {
+ /* Initially, there was ref count increase for this as well, but
+ * this is not needed, even harmful.
+ * h2_workers is only a hub for all the h2_worker instances.
+ * At the end-of-life of this h2_mplx, we always unregister at
+ * the workers. The thing to manage are all the h2_worker instances
+ * out there. Those may hold a reference to this h2_mplx and we cannot
+ * call them to unregister.
+ *
+ * Therefore: ref counting for h2_workers in not needed, ref counting
+ * for h2_worker using this is critical.
+ */
+ h2_workers_register(m->workers, m);
+}
+
+static void workers_unregister(h2_mplx *m) {
+ h2_workers_unregister(m->workers, m);
+}
+
+apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
+{
+ apr_status_t status;
+ workers_unregister(m);
+
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ int attempts = 0;
+
+ release(m);
+ while (apr_atomic_read32(&m->refs) > 0) {
+ m->join_wait = wait;
+ ap_log_cerror(APLOG_MARK, (attempts? APLOG_INFO : APLOG_DEBUG),
+ 0, m->c,
+ "h2_mplx(%ld): release_join, refs=%d, waiting...",
+ m->id, m->refs);
+ apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(10));
+ if (++attempts >= 6) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c,
+ APLOGNO(02952)
+ "h2_mplx(%ld): join attempts exhausted, refs=%d",
+ m->id, m->refs);
+ break;
+ }
+ }
+ if (m->join_wait) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+ "h2_mplx(%ld): release_join -> destroy", m->id);
+ }
+ m->join_wait = NULL;
+ apr_thread_mutex_unlock(m->lock);
+ h2_mplx_destroy(m);
+ }
+ return status;
+}
+
+void h2_mplx_abort(h2_mplx *m)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ m->aborted = 1;
+ h2_io_set_destroy_all(m->stream_ios);
+ apr_thread_mutex_unlock(m->lock);
+ }
+ workers_unregister(m);
+}
+
+
+h2_stream *h2_mplx_open_io(h2_mplx *m, int stream_id)
+{
+ h2_stream *stream = NULL;
+ apr_status_t status;
+ h2_io *io;
+
+ if (m->aborted) {
+ return NULL;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ apr_pool_t *stream_pool = m->spare_pool;
+
+ if (!stream_pool) {
+ apr_pool_create(&stream_pool, m->pool);
+ }
+ else {
+ m->spare_pool = NULL;
+ }
+
+ stream = h2_stream_create(stream_id, stream_pool, m);
+ stream->state = H2_STREAM_ST_OPEN;
+
+ io = h2_io_set_get(m->stream_ios, stream_id);
+ if (!io) {
+ io = h2_io_create(stream_id, stream_pool, m->bucket_alloc);
+ h2_io_set_add(m->stream_ios, io);
+ }
+ status = io? APR_SUCCESS : APR_ENOMEM;
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return stream;
+}
+
+static void stream_destroy(h2_mplx *m, h2_stream *stream, h2_io *io)
+{
+ apr_pool_t *pool = h2_stream_detach_pool(stream);
+ if (pool) {
+ apr_pool_clear(pool);
+ if (m->spare_pool) {
+ apr_pool_destroy(m->spare_pool);
+ }
+ m->spare_pool = pool;
+ }
+ h2_stream_destroy(stream);
+ if (io) {
+ /* The pool is cleared/destroyed which also closes all
+ * allocated file handles. Give this count back to our
+ * file handle pool. */
+ m->file_handles_allowed += io->files_handles_owned;
+ h2_io_set_remove(m->stream_ios, io);
+ h2_io_destroy(io);
+ }
+}
+
+apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, h2_stream *stream)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream->id);
+ if (!io || io->task_done) {
+ /* No more io or task already done -> cleanup immediately */
+ stream_destroy(m, stream, io);
+ }
+ else {
+ /* Add stream to closed set for cleanup when task is done */
+ h2_stream_set_add(m->closed, stream);
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+void h2_mplx_task_done(h2_mplx *m, int stream_id)
+{
+ apr_status_t status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_stream *stream = h2_stream_set_get(m->closed, stream_id);
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+ "h2_mplx(%ld): task(%d) done", m->id, stream_id);
+ if (stream) {
+ /* stream was already closed by main connection and is in
+ * zombie state. Now that the task is done with it, we
+ * can free its resources. */
+ h2_stream_set_remove(m->closed, stream);
+ stream_destroy(m, stream, io);
+ }
+ else if (io) {
+ /* main connection has not finished stream. Mark task as done
+ * so that eventual cleanup can start immediately. */
+ io->task_done = 1;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+}
+
+apr_status_t h2_mplx_in_read(h2_mplx *m, apr_read_type_e block,
+ int stream_id, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ io->input_arrived = iowait;
+ status = h2_io_in_read(io, bb, 0);
+ while (status == APR_EAGAIN
+ && !is_aborted(m, &status)
+ && block == APR_BLOCK_READ) {
+ apr_thread_cond_wait(io->input_arrived, m->lock);
+ status = h2_io_in_read(io, bb, 0);
+ }
+ io->input_arrived = NULL;
+ }
+ else {
+ status = APR_EOF;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+apr_status_t h2_mplx_in_write(h2_mplx *m, int stream_id,
+ apr_bucket_brigade *bb)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ status = h2_io_in_write(io, bb);
+ if (io->input_arrived) {
+ apr_thread_cond_signal(io->input_arrived);
+ }
+ }
+ else {
+ status = APR_EOF;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+apr_status_t h2_mplx_in_close(h2_mplx *m, int stream_id)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ status = h2_io_in_close(io);
+ if (io->input_arrived) {
+ apr_thread_cond_signal(io->input_arrived);
+ }
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+typedef struct {
+ h2_mplx_consumed_cb *cb;
+ void *cb_ctx;
+ int streams_updated;
+} update_ctx;
+
+static int update_window(void *ctx, h2_io *io)
+{
+ if (io->input_consumed) {
+ update_ctx *uctx = (update_ctx*)ctx;
+ uctx->cb(uctx->cb_ctx, io->id, io->input_consumed);
+ io->input_consumed = 0;
+ ++uctx->streams_updated;
+ }
+ return 1;
+}
+
+apr_status_t h2_mplx_in_update_windows(h2_mplx *m,
+ h2_mplx_consumed_cb *cb, void *cb_ctx)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ update_ctx ctx;
+
+ ctx.cb = cb;
+ ctx.cb_ctx = cb_ctx;
+ ctx.streams_updated = 0;
+
+ status = APR_EAGAIN;
+ h2_io_set_iter(m->stream_ios, update_window, &ctx);
+
+ if (ctx.streams_updated) {
+ status = APR_SUCCESS;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id,
+ h2_io_data_cb *cb, void *ctx,
+ apr_size_t *plen, int *peos)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ status = h2_io_out_readx(io, cb, ctx, plen, peos);
+ if (status == APR_SUCCESS && io->output_drained) {
+ apr_thread_cond_signal(io->output_drained);
+ }
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams)
+{
+ apr_status_t status;
+ h2_stream *stream = NULL;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return NULL;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get_highest_prio(m->ready_ios);
+ if (io) {
+ h2_response *response = io->response;
+ h2_io_set_remove(m->ready_ios, io);
+
+ stream = h2_stream_set_get(streams, response->stream_id);
+ if (stream) {
+ h2_stream_set_response(stream, response, io->bbout);
+ if (io->output_drained) {
+ apr_thread_cond_signal(io->output_drained);
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c,
+ APLOGNO(02953) "h2_mplx(%ld): stream for response %d",
+ m->id, response->stream_id);
+ }
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return stream;
+}
+
+static apr_status_t out_write(h2_mplx *m, h2_io *io,
+ ap_filter_t* f, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait)
+{
+ apr_status_t status = APR_SUCCESS;
+ /* We check the memory footprint queued for this stream_id
+ * and block if it exceeds our configured limit.
+ * We will not split buckets to enforce the limit to the last
+ * byte. After all, the bucket is already in memory.
+ */
+ while (!APR_BRIGADE_EMPTY(bb)
+ && (status == APR_SUCCESS)
+ && !is_aborted(m, &status)) {
+
+ status = h2_io_out_write(io, bb, m->stream_max_mem,
+ &m->file_handles_allowed);
+
+ /* Wait for data to drain until there is room again */
+ while (!APR_BRIGADE_EMPTY(bb)
+ && iowait
+ && status == APR_SUCCESS
+ && (m->stream_max_mem <= h2_io_out_length(io))
+ && !is_aborted(m, &status)) {
+ io->output_drained = iowait;
+ if (f) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_mplx(%ld-%d): waiting for out drain",
+ m->id, io->id);
+ }
+ apr_thread_cond_wait(io->output_drained, m->lock);
+ io->output_drained = NULL;
+ }
+ }
+ apr_brigade_cleanup(bb);
+ return status;
+}
+
+static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response,
+ ap_filter_t* f, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ if (f) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
+ "h2_mplx(%ld-%d): open response: %s",
+ m->id, stream_id, response->status);
+ }
+
+ io->response = h2_response_copy(io->pool, response);
+ h2_io_set_add(m->ready_ios, io);
+ if (bb) {
+ status = out_write(m, io, f, bb, iowait);
+ }
+ have_out_data_for(m, stream_id);
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ return status;
+}
+
+apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_response *response,
+ ap_filter_t* f, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ status = out_open(m, stream_id, response, f, bb, iowait);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+
+apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id,
+ ap_filter_t* f, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ if (!m->aborted) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ status = out_write(m, io, f, bb, iowait);
+ have_out_data_for(m, stream_id);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ }
+
+ if (m->lock) {
+ apr_thread_mutex_unlock(m->lock);
+ }
+ }
+ return status;
+}
+
+apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ if (!m->aborted) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ if (!io->response->ngheader) {
+ /* In case a close comes before a response was created,
+ * insert an error one so that our streams can properly
+ * reset.
+ */
+ h2_response *r = h2_response_create(stream_id,
+ "500", NULL, m->pool);
+ status = out_open(m, stream_id, r, NULL, NULL, NULL);
+ }
+ status = h2_io_out_close(io);
+ have_out_data_for(m, stream_id);
+ if (m->aborted) {
+ /* if we were the last output, the whole session might
+ * have gone down in the meantime.
+ */
+ return APR_SUCCESS;
+ }
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+int h2_mplx_in_has_eos_for(h2_mplx *m, int stream_id)
+{
+ int has_eos = 0;
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return 0;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ has_eos = h2_io_in_has_eos_for(io);
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return has_eos;
+}
+
+int h2_mplx_out_has_data_for(h2_mplx *m, int stream_id)
+{
+ apr_status_t status;
+ int has_data = 0;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return 0;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io) {
+ has_data = h2_io_out_has_data(io);
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return has_data;
+}
+
+apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
+ apr_thread_cond_t *iowait)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ m->added_output = iowait;
+ status = apr_thread_cond_timedwait(m->added_output, m->lock, timeout);
+ if (APLOGctrace2(m->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+ "h2_mplx(%ld): trywait on data for %f ms)",
+ m->id, timeout/1000.0);
+ }
+ m->added_output = NULL;
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
+static void have_out_data_for(h2_mplx *m, int stream_id)
+{
+ (void)stream_id;
+ AP_DEBUG_ASSERT(m);
+ if (m->added_output) {
+ apr_thread_cond_signal(m->added_output);
+ }
+}
+
+apr_status_t h2_mplx_do_task(h2_mplx *m, struct h2_task *task)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ /* TODO: needs to sort queue by priority */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+ "h2_mplx: do task(%s)", task->id);
+ h2_tq_append(m->q, task);
+ apr_thread_mutex_unlock(m->lock);
+ }
+ workers_register(m);
+ return status;
+}
+
+h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
+{
+ h2_task *task = NULL;
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ *has_more = 0;
+ return NULL;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ task = h2_tq_pop_first(m->q);
+ if (task) {
+ h2_task_set_started(task);
+ }
+ *has_more = !h2_tq_empty(m->q);
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return task;
+}
+
+apr_status_t h2_mplx_create_task(h2_mplx *m, struct h2_stream *stream)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ conn_rec *c = h2_conn_create(m->c, stream->pool);
+ stream->task = h2_task_create(m->id, stream->id,
+ stream->pool, m, c);
+
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_mplx__
+#define __mod_h2__h2_mplx__
+
+/**
+ * The stream multiplexer. It pushes buckets from the connection
+ * thread to the stream task threads and vice versa. It's thread-safe
+ * to use.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Writing input is never blocked. In order to use flow control on the input,
+ * the mplx can be polled for input data consumption.
+ */
+
+struct apr_pool_t;
+struct apr_thread_mutex_t;
+struct apr_thread_cond_t;
+struct h2_config;
+struct h2_response;
+struct h2_task;
+struct h2_stream;
+struct h2_io_set;
+struct apr_thread_cond_t;
+struct h2_workers;
+struct h2_stream_set;
+struct h2_task_queue;
+
+#include "h2_io.h"
+
+typedef struct h2_mplx h2_mplx;
+
+struct h2_mplx {
+ long id;
+ APR_RING_ENTRY(h2_mplx) link;
+ volatile apr_uint32_t refs;
+ conn_rec *c;
+ apr_pool_t *pool;
+ apr_bucket_alloc_t *bucket_alloc;
+
+ struct h2_task_queue *q;
+ struct h2_io_set *stream_ios;
+ struct h2_io_set *ready_ios;
+
+ apr_thread_mutex_t *lock;
+ struct apr_thread_cond_t *added_output;
+ struct apr_thread_cond_t *join_wait;
+
+ int aborted;
+ apr_size_t stream_max_mem;
+
+ apr_pool_t *spare_pool; /* spare pool, ready for next stream */
+ struct h2_stream_set *closed; /* streams closed, but task ongoing */
+ struct h2_workers *workers;
+ int file_handles_allowed;
+};
+
+/*******************************************************************************
+ * Object lifecycle and information.
+ ******************************************************************************/
+
+/**
+ * Create the multiplexer for the given HTTP2 session.
+ * Implicitly has reference count 1.
+ */
+h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *master,
+ struct h2_workers *workers);
+
+/**
+ * Increase the reference counter of this mplx.
+ */
+void h2_mplx_reference(h2_mplx *m);
+
+/**
+ * Decreases the reference counter of this mplx.
+ */
+void h2_mplx_release(h2_mplx *m);
+/**
+ * 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
+ * @param wait condition var to wait on for ref counter == 0
+ */
+apr_status_t h2_mplx_release_and_join(h2_mplx *m, struct apr_thread_cond_t *wait);
+
+/**
+ * Aborts the multiplexer. It will answer all future invocation with
+ * APR_ECONNABORTED, leading to early termination of ongoing tasks.
+ */
+void h2_mplx_abort(h2_mplx *mplx);
+
+void h2_mplx_task_done(h2_mplx *m, int stream_id);
+
+/*******************************************************************************
+ * IO lifetime of streams.
+ ******************************************************************************/
+/**
+ * Prepares the multiplexer to handle in-/output on the given stream id.
+ */
+struct h2_stream *h2_mplx_open_io(h2_mplx *mplx, int stream_id);
+
+/**
+ * Ends cleanup of a stream in sync with execution thread.
+ */
+apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, struct h2_stream *stream);
+
+/* Return != 0 iff the multiplexer has data for the given stream.
+ */
+int h2_mplx_out_has_data_for(h2_mplx *m, int stream_id);
+
+/**
+ * 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_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
+ struct apr_thread_cond_t *iowait);
+
+/*******************************************************************************
+ * Stream processing.
+ ******************************************************************************/
+
+/**
+ * Perform the task on the given stream.
+ */
+apr_status_t h2_mplx_do_task(h2_mplx *mplx, struct h2_task *task);
+
+struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
+
+apr_status_t h2_mplx_create_task(h2_mplx *mplx, struct h2_stream *stream);
+
+/*******************************************************************************
+ * Input handling of streams.
+ ******************************************************************************/
+
+/**
+ * Reads a buckets for the given stream_id. Will return ARP_EAGAIN when
+ * called with APR_NONBLOCK_READ and no data present. Will return APR_EOF
+ * when the end of the stream input has been reached.
+ * The condition passed in will be used for blocking/signalling and will
+ * be protected by the mplx's own mutex.
+ */
+apr_status_t h2_mplx_in_read(h2_mplx *m, apr_read_type_e block,
+ int stream_id, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait);
+
+/**
+ * Appends data to the input of the given stream. Storage of input data is
+ * not subject to flow control.
+ */
+apr_status_t h2_mplx_in_write(h2_mplx *mplx, int stream_id,
+ apr_bucket_brigade *bb);
+
+/**
+ * Closes the input for the given stream_id.
+ */
+apr_status_t h2_mplx_in_close(h2_mplx *m, int stream_id);
+
+/**
+ * Returns != 0 iff the input for the given stream has been closed. There
+ * could still be data queued, but it can be read without blocking.
+ */
+int h2_mplx_in_has_eos_for(h2_mplx *m, int stream_id);
+
+/**
+ * Callback invoked for every stream that had input data read since
+ * the last invocation.
+ */
+typedef void h2_mplx_consumed_cb(void *ctx, int stream_id, apr_size_t consumed);
+
+/**
+ * Invoke the callback for all streams that had bytes read since the last
+ * call to this function. If no stream had input data consumed, the callback
+ * is not invoked.
+ * Returns APR_SUCCESS when an update happened, APR_EAGAIN if no update
+ * happened.
+ */
+apr_status_t h2_mplx_in_update_windows(h2_mplx *m,
+ h2_mplx_consumed_cb *cb, void *ctx);
+
+/*******************************************************************************
+ * Output handling of streams.
+ ******************************************************************************/
+
+/**
+ * Get a stream whose response is ready for submit. Will set response and
+ * any out data available in stream.
+ * @param m the mplxer to get a response from
+ * @param bb the brigade to place any existing repsonse body data into
+ */
+struct h2_stream *h2_mplx_next_submit(h2_mplx *m,
+ struct h2_stream_set *streams);
+
+/**
+ * Reads output data from the given stream. Will never block, but
+ * return APR_EAGAIN until data arrives or the stream is closed.
+ */
+apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id,
+ h2_io_data_cb *cb, void *ctx,
+ apr_size_t *plen, int *peos);
+
+/**
+ * Opens the output for the given stream with the specified response.
+ */
+apr_status_t h2_mplx_out_open(h2_mplx *mplx, int stream_id,
+ struct h2_response *response,
+ ap_filter_t* filter, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait);
+
+/**
+ * Append the brigade to the stream output. Might block if amount
+ * of bytes buffered reaches configured max.
+ * @param stream_id the stream identifier
+ * @param filter the apache filter context of the data
+ * @param bb the bucket brigade to append
+ * @param iowait a conditional used for block/signalling in h2_mplx
+ */
+apr_status_t h2_mplx_out_write(h2_mplx *mplx, int stream_id,
+ ap_filter_t* filter, apr_bucket_brigade *bb,
+ struct apr_thread_cond_t *iowait);
+
+/**
+ * Closes the output stream. Readers of this stream will get all pending
+ * data and then only APR_EOF as result.
+ */
+apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id);
+
+/*******************************************************************************
+ * h2_mplx list Manipulation.
+ ******************************************************************************/
+
+/**
+ * The magic pointer value that indicates the head of a h2_mplx list
+ * @param b The mplx list
+ * @return The magic pointer value
+ */
+#define H2_MPLX_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_mplx, link)
+
+/**
+ * Determine if the mplx list is empty
+ * @param b The list to check
+ * @return true or false
+ */
+#define H2_MPLX_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_mplx, link)
+
+/**
+ * Return the first mplx in a list
+ * @param b The list to query
+ * @return The first mplx in the list
+ */
+#define H2_MPLX_LIST_FIRST(b) APR_RING_FIRST(b)
+
+/**
+ * Return the last mplx in a list
+ * @param b The list to query
+ * @return The last mplx int he list
+ */
+#define H2_MPLX_LIST_LAST(b) APR_RING_LAST(b)
+
+/**
+ * Insert a single mplx at the front of a list
+ * @param b The list to add to
+ * @param e The mplx to insert
+ */
+#define H2_MPLX_LIST_INSERT_HEAD(b, e) do { \
+h2_mplx *ap__b = (e); \
+APR_RING_INSERT_HEAD((b), ap__b, h2_mplx, link); \
+} while (0)
+
+/**
+ * Insert a single mplx at the end of a list
+ * @param b The list to add to
+ * @param e The mplx to insert
+ */
+#define H2_MPLX_LIST_INSERT_TAIL(b, e) do { \
+h2_mplx *ap__b = (e); \
+APR_RING_INSERT_TAIL((b), ap__b, h2_mplx, link); \
+} while (0)
+
+/**
+ * Get the next mplx in the list
+ * @param e The current mplx
+ * @return The next mplx
+ */
+#define H2_MPLX_NEXT(e) APR_RING_NEXT((e), link)
+/**
+ * Get the previous mplx in the list
+ * @param e The current mplx
+ * @return The previous mplx
+ */
+#define H2_MPLX_PREV(e) APR_RING_PREV((e), link)
+
+/**
+ * Remove a mplx from its list
+ * @param e The mplx to remove
+ */
+#define H2_MPLX_REMOVE(e) APR_RING_REMOVE((e), link)
+
+
+#endif /* defined(__mod_h2__h2_mplx__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_private_h
+#define mod_h2_h2_private_h
+
+#include <nghttp2/nghttp2.h>
+
+extern module AP_MODULE_DECLARE_DATA h2_module;
+
+APLOG_USE_MODULE(h2);
+
+
+#define H2_HEADER_METHOD ":method"
+#define H2_HEADER_METHOD_LEN 7
+#define H2_HEADER_SCHEME ":scheme"
+#define H2_HEADER_SCHEME_LEN 7
+#define H2_HEADER_AUTH ":authority"
+#define H2_HEADER_AUTH_LEN 10
+#define H2_HEADER_PATH ":path"
+#define H2_HEADER_PATH_LEN 5
+#define H2_CRLF "\r\n"
+
+#endif
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_mplx.h"
+#include "h2_to_h1.h"
+#include "h2_request.h"
+#include "h2_task.h"
+#include "h2_util.h"
+
+
+h2_request *h2_request_create(int id, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc)
+{
+ h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
+ if (req) {
+ req->id = id;
+ req->pool = pool;
+ req->bucket_alloc = bucket_alloc;
+ }
+ return req;
+}
+
+void h2_request_destroy(h2_request *req)
+{
+ if (req->to_h1) {
+ h2_to_h1_destroy(req->to_h1);
+ req->to_h1 = NULL;
+ }
+}
+
+static apr_status_t insert_request_line(h2_request *req, h2_mplx *m);
+
+apr_status_t h2_request_rwrite(h2_request *req, request_rec *r, h2_mplx *m)
+{
+ apr_status_t status;
+ req->method = r->method;
+ req->authority = r->hostname;
+ req->path = r->uri;
+ if (!ap_strchr_c(req->authority, ':') && r->parsed_uri.port_str) {
+ req->authority = apr_psprintf(req->pool, "%s:%s", req->authority,
+ r->parsed_uri.port_str);
+ }
+ req->scheme = NULL;
+
+
+ status = insert_request_line(req, m);
+ if (status == APR_SUCCESS) {
+ status = h2_to_h1_add_headers(req->to_h1, r->headers_in);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ "h2_request(%d): written request %s %s, host=%s",
+ req->id, req->method, req->path, req->authority);
+
+ return status;
+}
+
+apr_status_t h2_request_write_header(h2_request *req,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ h2_mplx *m)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ if (nlen <= 0) {
+ return status;
+ }
+
+ if (name[0] == ':') {
+ /* pseudo header, see ch. 8.1.2.3, always should come first */
+ if (req->to_h1) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, req->pool,
+ APLOGNO(02917)
+ "h2_request(%d): pseudo header after request start",
+ req->id);
+ return APR_EGENERAL;
+ }
+
+ if (H2_HEADER_METHOD_LEN == nlen
+ && !strncmp(H2_HEADER_METHOD, name, nlen)) {
+ req->method = apr_pstrndup(req->pool, value, vlen);
+ }
+ else if (H2_HEADER_SCHEME_LEN == nlen
+ && !strncmp(H2_HEADER_SCHEME, name, nlen)) {
+ req->scheme = apr_pstrndup(req->pool, value, vlen);
+ }
+ else if (H2_HEADER_PATH_LEN == nlen
+ && !strncmp(H2_HEADER_PATH, name, nlen)) {
+ req->path = apr_pstrndup(req->pool, value, vlen);
+ }
+ else if (H2_HEADER_AUTH_LEN == nlen
+ && !strncmp(H2_HEADER_AUTH, name, nlen)) {
+ req->authority = apr_pstrndup(req->pool, value, vlen);
+ }
+ else {
+ char buffer[32];
+ memset(buffer, 0, 32);
+ strncpy(buffer, name, (nlen > 31)? 31 : nlen);
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, req->pool,
+ APLOGNO(02954)
+ "h2_request(%d): ignoring unknown pseudo header %s",
+ req->id, buffer);
+ }
+ }
+ else {
+ /* non-pseudo header, append to work bucket of stream */
+ if (!req->to_h1) {
+ status = insert_request_line(req, m);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+
+ if (status == APR_SUCCESS) {
+ status = h2_to_h1_add_header(req->to_h1,
+ name, nlen, value, vlen);
+ }
+ }
+
+ return status;
+}
+
+apr_status_t h2_request_write_data(h2_request *req,
+ const char *data, size_t len)
+{
+ return h2_to_h1_add_data(req->to_h1, data, len);
+}
+
+apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m,
+ h2_task *task, int eos)
+{
+ if (!req->to_h1) {
+ apr_status_t status = insert_request_line(req, m);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+ return h2_to_h1_end_headers(req->to_h1, task, eos);
+}
+
+apr_status_t h2_request_close(h2_request *req)
+{
+ return h2_to_h1_close(req->to_h1);
+}
+
+static apr_status_t insert_request_line(h2_request *req, h2_mplx *m)
+{
+ req->to_h1 = h2_to_h1_create(req->id, req->pool, req->bucket_alloc,
+ req->method,
+ req->scheme,
+ req->authority,
+ req->path, m);
+ return req->to_h1? APR_SUCCESS : APR_ENOMEM;
+}
+
+apr_status_t h2_request_flush(h2_request *req)
+{
+ return h2_to_h1_flush(req->to_h1);
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_request__
+#define __mod_h2__h2_request__
+
+/* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal
+ * format that will be fed to various httpd input filters to finally
+ * become a request_rec to be handled by soemone.
+ *
+ * Ideally, we would make a request_rec without serializing the headers
+ * we have only to make someone else parse them back.
+ */
+struct h2_to_h1;
+struct h2_mplx;
+struct h2_task;
+
+typedef struct h2_request h2_request;
+
+struct h2_request {
+ int id; /* http2 stream id */
+ apr_pool_t *pool;
+ apr_bucket_alloc_t *bucket_alloc;
+ struct h2_to_h1 *to_h1; /* Converter to HTTP/1.1 format*/
+
+ /* pseudo header values, see ch. 8.1.2.3 */
+ const char *method;
+ const char *scheme;
+ const char *authority;
+ const char *path;
+};
+
+h2_request *h2_request_create(int id, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc);
+void h2_request_destroy(h2_request *req);
+
+apr_status_t h2_request_flush(h2_request *req);
+
+apr_status_t h2_request_write_header(h2_request *req,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ struct h2_mplx *m);
+
+apr_status_t h2_request_write_data(h2_request *request,
+ const char *data, size_t len);
+
+apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m,
+ struct h2_task *task, int eos);
+
+apr_status_t h2_request_close(h2_request *req);
+
+apr_status_t h2_request_rwrite(h2_request *req, request_rec *r,
+ struct h2_mplx *m);
+
+#endif /* defined(__mod_h2__h2_request__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <stdio.h>
+
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2_private.h"
+#include "h2_util.h"
+#include "h2_response.h"
+
+static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
+ apr_table_t *header);
+
+static int ignore_header(const char *name)
+{
+ return (H2_HD_MATCH_LIT_CS("connection", name)
+ || H2_HD_MATCH_LIT_CS("proxy-connection", name)
+ || H2_HD_MATCH_LIT_CS("upgrade", name)
+ || H2_HD_MATCH_LIT_CS("keep-alive", name)
+ || H2_HD_MATCH_LIT_CS("transfer-encoding", name));
+}
+
+h2_response *h2_response_create(int stream_id,
+ const char *http_status,
+ apr_array_header_t *hlines,
+ apr_pool_t *pool)
+{
+ apr_table_t *header;
+ h2_response *response = apr_pcalloc(pool, sizeof(h2_response));
+ int i;
+ if (response == NULL) {
+ return NULL;
+ }
+
+ response->stream_id = stream_id;
+ response->status = http_status;
+ response->content_length = -1;
+
+ if (hlines) {
+ header = apr_table_make(pool, hlines->nelts);
+ for (i = 0; i < hlines->nelts; ++i) {
+ char *hline = ((char **)hlines->elts)[i];
+ char *sep = ap_strchr(hline, ':');
+ if (!sep) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
+ APLOGNO(02955) "h2_response(%d): invalid header[%d] '%s'",
+ response->stream_id, i, (char*)hline);
+ /* not valid format, abort */
+ return NULL;
+ }
+ (*sep++) = '\0';
+ while (*sep == ' ' || *sep == '\t') {
+ ++sep;
+ }
+ if (ignore_header(hline)) {
+ /* never forward, ch. 8.1.2.2 */
+ }
+ else {
+ apr_table_merge(header, hline, sep);
+ if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) {
+ char *end;
+ response->content_length = apr_strtoi64(sep, &end, 10);
+ if (sep == end) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL,
+ pool, APLOGNO(02956)
+ "h2_response(%d): content-length"
+ " value not parsed: %s",
+ response->stream_id, sep);
+ response->content_length = -1;
+ }
+ }
+ }
+ }
+ }
+ else {
+ header = apr_table_make(pool, 0);
+ }
+
+ response->rheader = header;
+ return response;
+}
+
+h2_response *h2_response_rcreate(int stream_id, request_rec *r,
+ apr_table_t *header, apr_pool_t *pool)
+{
+ h2_response *response = apr_pcalloc(pool, sizeof(h2_response));
+ if (response == NULL) {
+ return NULL;
+ }
+
+ response->stream_id = stream_id;
+ response->status = apr_psprintf(pool, "%d", r->status);
+ response->content_length = -1;
+ response->rheader = header;
+
+ return response;
+}
+
+void h2_response_destroy(h2_response *response)
+{
+ (void)response;
+}
+
+h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from)
+{
+ h2_response *to = apr_pcalloc(pool, sizeof(h2_response));
+ to->stream_id = from->stream_id;
+ to->status = apr_pstrdup(pool, from->status);
+ to->content_length = from->content_length;
+ if (from->rheader) {
+ to->ngheader = make_ngheader(pool, to->status, from->rheader);
+ }
+ return to;
+}
+
+typedef struct {
+ nghttp2_nv *nv;
+ size_t nvlen;
+ size_t nvstrlen;
+ size_t offset;
+ char *strbuf;
+ apr_pool_t *pool;
+} nvctx_t;
+
+static int count_header(void *ctx, const char *key, const char *value)
+{
+ if (!ignore_header(key)) {
+ nvctx_t *nvctx = (nvctx_t*)ctx;
+ nvctx->nvlen++;
+ nvctx->nvstrlen += strlen(key) + strlen(value) + 2;
+ }
+ return 1;
+}
+
+#define NV_ADD_LIT_CS(nv, k, v) addnv_lit_cs(nv, k, sizeof(k) - 1, v, strlen(v))
+#define NV_ADD_CS_CS(nv, k, v) addnv_cs_cs(nv, k, strlen(k), v, strlen(v))
+#define NV_BUF_ADD(nv, s, len) memcpy(nv->strbuf, s, len); \
+s = nv->strbuf; \
+nv->strbuf += len + 1
+
+static void addnv_cs_cs(nvctx_t *ctx, const char *key, size_t key_len,
+ const char *value, size_t val_len)
+{
+ nghttp2_nv *nv = &ctx->nv[ctx->offset];
+
+ NV_BUF_ADD(ctx, key, key_len);
+ NV_BUF_ADD(ctx, value, val_len);
+
+ nv->name = (uint8_t*)key;
+ nv->namelen = key_len;
+ nv->value = (uint8_t*)value;
+ nv->valuelen = val_len;
+
+ ctx->offset++;
+}
+
+static void addnv_lit_cs(nvctx_t *ctx, const char *key, size_t key_len,
+ const char *value, size_t val_len)
+{
+ nghttp2_nv *nv = &ctx->nv[ctx->offset];
+
+ NV_BUF_ADD(ctx, value, val_len);
+
+ nv->name = (uint8_t*)key;
+ nv->namelen = key_len;
+ nv->value = (uint8_t*)value;
+ nv->valuelen = val_len;
+
+ ctx->offset++;
+}
+
+static int add_header(void *ctx, const char *key, const char *value)
+{
+ if (!ignore_header(key)) {
+ nvctx_t *nvctx = (nvctx_t*)ctx;
+ NV_ADD_CS_CS(nvctx, key, value);
+ }
+ return 1;
+}
+
+static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
+ apr_table_t *header)
+{
+ size_t n;
+ h2_ngheader *h;
+ nvctx_t ctx;
+
+ ctx.nv = NULL;
+ ctx.nvlen = 1;
+ ctx.nvstrlen = strlen(status) + 1;
+ ctx.offset = 0;
+ ctx.strbuf = NULL;
+ ctx.pool = pool;
+
+ apr_table_do(count_header, &ctx, header, NULL);
+
+ n = (sizeof(h2_ngheader)
+ + (ctx.nvlen * sizeof(nghttp2_nv)) + ctx.nvstrlen);
+ h = apr_pcalloc(pool, n);
+ if (h) {
+ ctx.nv = (nghttp2_nv*)(h + 1);
+ ctx.strbuf = (char*)&ctx.nv[ctx.nvlen];
+
+ NV_ADD_LIT_CS(&ctx, ":status", status);
+ apr_table_do(add_header, &ctx, header, NULL);
+
+ h->nv = ctx.nv;
+ h->nvlen = ctx.nvlen;
+ }
+ return h;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_response__
+#define __mod_h2__h2_response__
+
+/* h2_response is just the data belonging the the head of a HTTP response,
+ * suitable prepared to be fed to nghttp2 for response submit.
+ */
+typedef struct h2_ngheader {
+ nghttp2_nv *nv;
+ apr_size_t nvlen;
+} h2_ngheader;
+
+typedef struct h2_response {
+ int stream_id;
+ const char *status;
+ apr_off_t content_length;
+ apr_table_t *rheader;
+ h2_ngheader *ngheader;
+} h2_response;
+
+h2_response *h2_response_create(int stream_id,
+ const char *http_status,
+ apr_array_header_t *hlines,
+ apr_pool_t *pool);
+
+h2_response *h2_response_rcreate(int stream_id, request_rec *r,
+ apr_table_t *header, apr_pool_t *pool);
+
+void h2_response_destroy(h2_response *response);
+
+h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from);
+
+#endif /* defined(__mod_h2__h2_response__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_thread_cond.h>
+#include <apr_base64.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_config.h"
+#include "h2_h2.h"
+#include "h2_mplx.h"
+#include "h2_response.h"
+#include "h2_stream.h"
+#include "h2_stream_set.h"
+#include "h2_from_h1.h"
+#include "h2_task.h"
+#include "h2_session.h"
+#include "h2_util.h"
+#include "h2_version.h"
+#include "h2_workers.h"
+
+static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
+
+static int h2_session_status_from_apr_status(apr_status_t rv)
+{
+ switch (rv) {
+ case APR_SUCCESS:
+ return NGHTTP2_NO_ERROR;
+ case APR_EAGAIN:
+ case APR_TIMEUP:
+ return NGHTTP2_ERR_WOULDBLOCK;
+ case APR_EOF:
+ return NGHTTP2_ERR_EOF;
+ default:
+ return NGHTTP2_ERR_PROTO;
+ }
+}
+
+static int stream_open(h2_session *session, int stream_id)
+{
+ h2_stream * stream;
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ stream = h2_mplx_open_io(session->mplx, stream_id);
+ if (stream) {
+ h2_stream_set_add(session->streams, stream);
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_session: stream(%ld-%d): opened",
+ session->id, stream_id);
+
+ return 0;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
+ APLOGNO(02918)
+ "h2_session: stream(%ld-%d): unable to create",
+ session->id, stream_id);
+ return NGHTTP2_ERR_INVALID_STREAM_ID;
+}
+
+static apr_status_t stream_end_headers(h2_session *session,
+ h2_stream *stream, int eos)
+{
+ (void)session;
+ return h2_stream_write_eoh(stream, eos);
+}
+
+static apr_status_t send_data(h2_session *session, const char *data,
+ apr_size_t length);
+
+/*
+ * Callback when nghttp2 wants to send bytes back to the client.
+ */
+static ssize_t send_cb(nghttp2_session *ngh2,
+ const uint8_t *data, size_t length,
+ int flags, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ apr_status_t status = send_data(session, (const char *)data, length);
+
+ (void)ngh2;
+ (void)flags;
+ if (status == APR_SUCCESS) {
+ return length;
+ }
+ if (status == APR_EAGAIN || status == APR_TIMEUP) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+ "h2_session: send error");
+ return h2_session_status_from_apr_status(status);
+}
+
+static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ int error, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ (void)ngh2;
+
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ if (APLOGctrace2(session->c)) {
+ char buffer[256];
+
+ frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_session: callback on_invalid_frame_recv error=%d %s",
+ error, buffer);
+ }
+ return 0;
+}
+
+static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *data, size_t len, void *userp)
+{
+ int rv;
+ h2_session *session = (h2_session *)userp;
+ h2_stream * stream;
+ apr_status_t status;
+
+ (void)flags;
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ stream = h2_stream_set_get(session->streams, stream_id);
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02919)
+ "h2_session: stream(%ld-%d): on_data_chunk for unknown stream",
+ session->id, (int)stream_id);
+ rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+
+ status = h2_stream_write_data(stream, (const char *)data, len);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+ "h2_stream(%ld-%d): written DATA, length %d",
+ session->id, stream_id, (int)len);
+ if (status != APR_SUCCESS) {
+ rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return 0;
+}
+
+static int before_frame_send_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ (void)ngh2;
+
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ if (APLOGctrace2(session->c)) {
+ char buffer[256];
+ frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_session(%ld): before_frame_send %s",
+ session->id, buffer);
+ }
+ return 0;
+}
+
+static int on_frame_send_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ (void)ngh2; (void)frame;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_session(%ld): on_frame_send", session->id);
+ return 0;
+}
+
+static int on_frame_not_send_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ int lib_error_code, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ (void)ngh2;
+
+ if (APLOGctrace2(session->c)) {
+ char buffer[256];
+
+ frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_session: callback on_frame_not_send error=%d %s",
+ lib_error_code, buffer);
+ }
+ return 0;
+}
+
+static apr_status_t stream_destroy(h2_session *session, h2_stream *stream)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): closing", session->id, (int)stream->id);
+ h2_stream_set_remove(session->streams, stream);
+ return h2_mplx_cleanup_stream(session->mplx, stream);
+}
+
+static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
+ uint32_t error_code, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ h2_stream *stream;
+
+ (void)ngh2;
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ stream = h2_stream_set_get(session->streams, stream_id);
+ if (stream) {
+ stream_destroy(session, stream);
+ }
+
+ if (error_code) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): close error %d",
+ session->id, (int)stream_id, error_code);
+ }
+
+ return 0;
+}
+
+static int on_begin_headers_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame, void *userp)
+{
+ /* This starts a new stream. */
+ int rv;
+ (void)ngh2;
+ rv = stream_open((h2_session *)userp, frame->hd.stream_id);
+ if (rv != NGHTTP2_ERR_CALLBACK_FAILURE) {
+ /* on_header_cb or on_frame_recv_cb will dectect that stream
+ does not exist and submit RST_STREAM. */
+ return 0;
+ }
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+}
+
+static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ uint8_t flags,
+ void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ h2_stream * stream;
+ apr_status_t status;
+
+ (void)ngh2;
+ (void)flags;
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ stream = h2_stream_set_get(session->streams,
+ frame->hd.stream_id);
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02920)
+ "h2_session: stream(%ld-%d): on_header for unknown stream",
+ session->id, (int)frame->hd.stream_id);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ status = h2_stream_write_header(stream,
+ (const char *)name, namelen,
+ (const char *)value, valuelen);
+ if (status != APR_SUCCESS) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+
+/**
+ * nghttp2 session has received a complete frame. Most, it uses
+ * for processing of internal state. HEADER and DATA frames however
+ * we need to handle ourself.
+ */
+static int on_frame_recv_cb(nghttp2_session *ng2s,
+ const nghttp2_frame *frame,
+ void *userp)
+{
+ int rv;
+ h2_session *session = (h2_session *)userp;
+ apr_status_t status = APR_SUCCESS;
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ ++session->frames_received;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id,
+ (long)session->frames_received, frame->hd.type);
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS: {
+ int eos;
+ h2_stream * stream = h2_stream_set_get(session->streams,
+ frame->hd.stream_id);
+ if (stream == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02921)
+ "h2_session: stream(%ld-%d): HEADERS frame "
+ "for unknown stream", session->id,
+ (int)frame->hd.stream_id);
+ rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
+ frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+
+ eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
+ status = stream_end_headers(session, stream, eos);
+
+ break;
+ }
+ case NGHTTP2_DATA: {
+ h2_stream * stream = h2_stream_set_get(session->streams,
+ frame->hd.stream_id);
+ if (stream == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02922)
+ "h2_session: stream(%ld-%d): DATA frame "
+ "for unknown stream", session->id,
+ (int)frame->hd.stream_id);
+ rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
+ frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+ break;
+ }
+ case NGHTTP2_PRIORITY: {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_session: stream(%ld-%d): PRIORITY frame "
+ " weight=%d, dependsOn=%d, exclusive=%d",
+ session->id, (int)frame->hd.stream_id,
+ frame->priority.pri_spec.weight,
+ frame->priority.pri_spec.stream_id,
+ frame->priority.pri_spec.exclusive);
+ break;
+ }
+ default:
+ if (APLOGctrace2(session->c)) {
+ char buffer[256];
+
+ frame_print(frame, buffer,
+ sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_session: on_frame_rcv %s", buffer);
+ }
+ break;
+ }
+
+ /* only DATA and HEADERS frame can bear END_STREAM flag. Other
+ frame types may have other flag which has the same value, so we
+ have to check the frame type first. */
+ if ((frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) &&
+ frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ h2_stream * stream = h2_stream_set_get(session->streams,
+ frame->hd.stream_id);
+ if (stream != NULL) {
+ status = h2_stream_write_eos(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+ "h2_stream(%ld-%d): input closed",
+ session->id, (int)frame->hd.stream_id);
+ }
+ }
+
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02923)
+ "h2_session: stream(%ld-%d): error handling frame",
+ session->id, (int)frame->hd.stream_id);
+ rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
+ frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+static apr_status_t send_data(h2_session *session, const char *data,
+ apr_size_t length)
+{
+ return h2_conn_io_write(&session->io, data, length);
+}
+
+static apr_status_t pass_data(void *ctx,
+ const char *data, apr_size_t length)
+{
+ return send_data((h2_session*)ctx, data, length);
+}
+
+static int on_send_data_cb(nghttp2_session *ngh2,
+ nghttp2_frame *frame,
+ const uint8_t *framehd,
+ size_t length,
+ nghttp2_data_source *source,
+ void *userp)
+{
+ apr_status_t status = APR_SUCCESS;
+ h2_session *session = (h2_session *)userp;
+ int stream_id = (int)frame->hd.stream_id;
+ const unsigned char padlen = frame->data.padlen;
+ int eos;
+ h2_stream *stream;
+
+ (void)ngh2;
+ (void)source;
+ if (session->aborted) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ stream = h2_stream_set_get(session->streams, stream_id);
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
+ APLOGNO(02924)
+ "h2_stream(%ld-%d): send_data",
+ session->id, (int)stream_id);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ status = send_data(session, (const char *)framehd, 9);
+ if (status == APR_SUCCESS) {
+ if (padlen) {
+ status = send_data(session, (const char *)&padlen, 1);
+ }
+
+ if (status == APR_SUCCESS) {
+ apr_size_t len = length;
+ status = h2_stream_readx(stream, pass_data, session,
+ &len, &eos);
+ if (status == APR_SUCCESS && len != length) {
+ status = APR_EINVAL;
+ }
+ }
+
+ if (status == APR_SUCCESS && padlen) {
+ if (padlen) {
+ char pad[256];
+ memset(pad, 0, padlen);
+ status = send_data(session, pad, padlen);
+ }
+ }
+ }
+
+ if (status == APR_SUCCESS) {
+ return 0;
+ }
+ else if (status != APR_EOF) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02925)
+ "h2_stream(%ld-%d): failed send_data_cb",
+ session->id, (int)stream_id);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return h2_session_status_from_apr_status(status);
+}
+
+
+#define NGH2_SET_CALLBACK(callbacks, name, fn)\
+nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
+
+static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
+{
+ int rv = nghttp2_session_callbacks_new(pcb);
+ if (rv != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+ APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
+ nghttp2_strerror(rv));
+ return APR_EGENERAL;
+ }
+
+ NGH2_SET_CALLBACK(*pcb, send, send_cb);
+ NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb);
+ NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb);
+ NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb);
+ NGH2_SET_CALLBACK(*pcb, before_frame_send, before_frame_send_cb);
+ NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
+ NGH2_SET_CALLBACK(*pcb, on_frame_not_send, on_frame_not_send_cb);
+ NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb);
+ NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
+ NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
+ NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
+
+ return APR_SUCCESS;
+}
+
+static h2_session *h2_session_create_int(conn_rec *c,
+ request_rec *r,
+ h2_config *config,
+ h2_workers *workers)
+{
+ nghttp2_session_callbacks *callbacks = NULL;
+ nghttp2_option *options = NULL;
+
+ apr_pool_t *pool = NULL;
+ apr_status_t status = apr_pool_create(&pool, r? r->pool : c->pool);
+ h2_session *session;
+ if (status != APR_SUCCESS) {
+ return NULL;
+ }
+
+ session = apr_pcalloc(pool, sizeof(h2_session));
+ if (session) {
+ int rv;
+ session->id = c->id;
+ session->c = c;
+ session->r = r;
+
+ session->max_stream_count = h2_config_geti(config, H2_CONF_MAX_STREAMS);
+ session->max_stream_mem = h2_config_geti(config, H2_CONF_STREAM_MAX_MEM);
+
+ session->pool = pool;
+
+ status = apr_thread_cond_create(&session->iowait, session->pool);
+ if (status != APR_SUCCESS) {
+ return NULL;
+ }
+
+ session->streams = h2_stream_set_create(session->pool);
+
+ session->workers = workers;
+ session->mplx = h2_mplx_create(c, session->pool, workers);
+
+ h2_conn_io_init(&session->io, c);
+ session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
+
+ status = init_callbacks(c, &callbacks);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927)
+ "nghttp2: error in init_callbacks");
+ h2_session_destroy(session);
+ return NULL;
+ }
+
+ rv = nghttp2_option_new(&options);
+ if (rv != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+ APLOGNO(02928) "nghttp2_option_new: %s",
+ nghttp2_strerror(rv));
+ h2_session_destroy(session);
+ return NULL;
+ }
+
+ nghttp2_option_set_peer_max_concurrent_streams(options,
+ (uint32_t)session->max_stream_count);
+
+ /* We need to handle window updates ourself, otherwise we
+ * get flooded by nghttp2. */
+ nghttp2_option_set_no_auto_window_update(options, 1);
+
+ rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
+ session, options);
+ nghttp2_session_callbacks_del(callbacks);
+ nghttp2_option_del(options);
+
+ if (rv != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+ APLOGNO(02929) "nghttp2_session_server_new: %s",
+ nghttp2_strerror(rv));
+ h2_session_destroy(session);
+ return NULL;
+ }
+
+ }
+ return session;
+}
+
+h2_session *h2_session_create(conn_rec *c, h2_config *config,
+ h2_workers *workers)
+{
+ return h2_session_create_int(c, NULL, config, workers);
+}
+
+h2_session *h2_session_rcreate(request_rec *r, h2_config *config,
+ h2_workers *workers)
+{
+ return h2_session_create_int(r->connection, r, config, workers);
+}
+
+void h2_session_destroy(h2_session *session)
+{
+ AP_DEBUG_ASSERT(session);
+ if (session->mplx) {
+ h2_mplx_release_and_join(session->mplx, session->iowait);
+ session->mplx = NULL;
+ }
+ if (session->streams) {
+ if (h2_stream_set_size(session->streams)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_session(%ld): destroy, %d streams open",
+ session->id, (int)h2_stream_set_size(session->streams));
+ }
+ h2_stream_set_destroy(session->streams);
+ session->streams = NULL;
+ }
+ if (session->ngh2) {
+ nghttp2_session_del(session->ngh2);
+ session->ngh2 = NULL;
+ }
+ h2_conn_io_destroy(&session->io);
+
+ if (session->iowait) {
+ apr_thread_cond_destroy(session->iowait);
+ session->iowait = NULL;
+ }
+
+ if (session->pool) {
+ apr_pool_destroy(session->pool);
+ }
+}
+
+apr_status_t h2_session_goaway(h2_session *session, apr_status_t reason)
+{
+ apr_status_t status = APR_SUCCESS;
+ int rv;
+ AP_DEBUG_ASSERT(session);
+ if (session->aborted) {
+ return APR_EINVAL;
+ }
+
+ rv = 0;
+ if (reason == APR_SUCCESS) {
+ rv = nghttp2_submit_shutdown_notice(session->ngh2);
+ }
+ else {
+ int err = 0;
+ int last_id = nghttp2_session_get_last_proc_stream_id(session->ngh2);
+ rv = nghttp2_submit_goaway(session->ngh2, last_id,
+ NGHTTP2_FLAG_NONE, err, NULL, 0);
+ }
+ if (rv != 0) {
+ status = APR_EGENERAL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02930) "session(%ld): submit goaway: %s",
+ session->id, nghttp2_strerror(rv));
+ }
+ return status;
+}
+
+static apr_status_t h2_session_abort_int(h2_session *session, int reason)
+{
+ AP_DEBUG_ASSERT(session);
+ if (!session->aborted) {
+ session->aborted = 1;
+ if (session->ngh2) {
+ if (reason) {
+ ap_log_cerror(APLOG_MARK, (reason == NGHTTP2_ERR_EOF)?
+ APLOG_DEBUG : APLOG_INFO, 0, session->c,
+ "session(%ld): aborting session, reason=%d %s",
+ session->id, reason, nghttp2_strerror(reason));
+ }
+ nghttp2_session_terminate_session(session->ngh2, reason);
+ nghttp2_submit_goaway(session->ngh2, 0, 0, reason, NULL, 0);
+ nghttp2_session_send(session->ngh2);
+ h2_conn_io_flush(&session->io);
+ }
+ h2_mplx_abort(session->mplx);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv)
+{
+ AP_DEBUG_ASSERT(session);
+ if (rv == 0) {
+ rv = NGHTTP2_ERR_PROTO;
+ switch (reason) {
+ case APR_ENOMEM:
+ rv = NGHTTP2_ERR_NOMEM;
+ break;
+ case APR_EOF:
+ rv = 0;
+ break;
+ case APR_EBADF:
+ case APR_ECONNABORTED:
+ rv = NGHTTP2_ERR_EOF;
+ break;
+ default:
+ break;
+ }
+ }
+ return h2_session_abort_int(session, rv);
+}
+
+apr_status_t h2_session_start(h2_session *session, int *rv)
+{
+ apr_status_t status = APR_SUCCESS;
+ h2_config *config;
+ nghttp2_settings_entry settings[3];
+
+ AP_DEBUG_ASSERT(session);
+ /* Start the conversation by submitting our SETTINGS frame */
+ *rv = 0;
+ config = h2_config_get(session->c);
+ if (session->r) {
+ const char *s, *cs;
+ apr_size_t dlen;
+ h2_stream * stream;
+
+ /* better for vhost matching */
+ config = h2_config_rget(session->r);
+
+ /* 'h2c' mode: we should have a 'HTTP2-Settings' header with
+ * base64 encoded client settings. */
+ s = apr_table_get(session->r->headers_in, "HTTP2-Settings");
+ if (!s) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r,
+ APLOGNO(02931)
+ "HTTP2-Settings header missing in request");
+ return APR_EINVAL;
+ }
+ cs = NULL;
+ dlen = h2_util_base64url_decode(&cs, s, session->pool);
+
+ if (APLOGrdebug(session->r)) {
+ char buffer[128];
+ h2_util_hex_dump(buffer, 128, (char*)cs, dlen);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r,
+ "upgrading h2c session with HTTP2-Settings: %s -> %s (%d)",
+ s, buffer, (int)dlen);
+ }
+
+ *rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, dlen, NULL);
+ if (*rv != 0) {
+ status = APR_EINVAL;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
+ APLOGNO(02932) "nghttp2_session_upgrade: %s",
+ nghttp2_strerror(*rv));
+ return status;
+ }
+
+ /* Now we need to auto-open stream 1 for the request we got. */
+ *rv = stream_open(session, 1);
+ if (*rv != 0) {
+ status = APR_EGENERAL;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
+ APLOGNO(02933) "open stream 1: %s",
+ nghttp2_strerror(*rv));
+ return status;
+ }
+
+ stream = h2_stream_set_get(session->streams, 1);
+ if (stream == NULL) {
+ status = APR_EGENERAL;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
+ APLOGNO(02934) "lookup of stream 1");
+ return status;
+ }
+
+ status = h2_stream_rwrite(stream, session->r);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ status = stream_end_headers(session, stream, 1);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+
+ settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ settings[0].value = (uint32_t)session->max_stream_count;
+ settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ settings[1].value = h2_config_geti(config, H2_CONF_WIN_SIZE);
+ settings[2].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE;
+ settings[2].value = 64*1024;
+
+ *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE,
+ settings,
+ sizeof(settings)/sizeof(settings[0]));
+ if (*rv != 0) {
+ status = APR_EGENERAL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02935) "nghttp2_submit_settings: %s",
+ nghttp2_strerror(*rv));
+ }
+
+ return status;
+}
+
+static int h2_session_want_write(h2_session *session)
+{
+ return nghttp2_session_want_write(session->ngh2);
+}
+
+typedef struct {
+ h2_session *session;
+ int resume_count;
+} resume_ctx;
+
+static int resume_on_data(void *ctx, h2_stream *stream) {
+ resume_ctx *rctx = (resume_ctx*)ctx;
+ h2_session *session = rctx->session;
+ AP_DEBUG_ASSERT(session);
+ AP_DEBUG_ASSERT(stream);
+
+ if (h2_stream_is_suspended(stream)) {
+ if (h2_mplx_out_has_data_for(stream->m, stream->id)) {
+ int rv;
+ h2_stream_set_suspended(stream, 0);
+ ++rctx->resume_count;
+
+ rv = nghttp2_session_resume_data(session->ngh2, stream->id);
+ ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
+ APLOG_ERR : APLOG_DEBUG, 0, session->c,
+ APLOGNO(02936)
+ "h2_stream(%ld-%d): resuming stream %s",
+ session->id, stream->id, nghttp2_strerror(rv));
+ }
+ }
+ return 1;
+}
+
+static int h2_session_resume_streams_with_data(h2_session *session) {
+ AP_DEBUG_ASSERT(session);
+ if (!h2_stream_set_is_empty(session->streams)
+ && session->mplx && !session->aborted) {
+ resume_ctx ctx;
+
+ ctx.session = session;
+ ctx.resume_count = 0;
+
+ /* Resume all streams where we have data in the out queue and
+ * which had been suspended before. */
+ h2_stream_set_iter(session->streams, resume_on_data, &ctx);
+ return ctx.resume_count;
+ }
+ return 0;
+}
+
+static void update_window(void *ctx, int stream_id, apr_size_t bytes_read)
+{
+ h2_session *session = (h2_session*)ctx;
+ nghttp2_session_consume(session->ngh2, stream_id, bytes_read);
+}
+
+static apr_status_t h2_session_update_windows(h2_session *session)
+{
+ return h2_mplx_in_update_windows(session->mplx, update_window, session);
+}
+
+apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout)
+{
+ apr_status_t status = APR_EAGAIN;
+ h2_stream *stream = NULL;
+ int flush_output = 0;
+
+ AP_DEBUG_ASSERT(session);
+
+ /* Check that any pending window updates are sent. */
+ status = h2_session_update_windows(session);
+ if (status == APR_SUCCESS) {
+ flush_output = 1;
+ }
+ else if (status != APR_EAGAIN) {
+ return status;
+ }
+
+ if (h2_session_want_write(session)) {
+ int rv;
+ status = APR_SUCCESS;
+ rv = nghttp2_session_send(session->ngh2);
+ if (rv != 0) {
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_session: send: %s", nghttp2_strerror(rv));
+ if (nghttp2_is_fatal(rv)) {
+ h2_session_abort_int(session, rv);
+ status = APR_ECONNABORTED;
+ }
+ }
+ flush_output = 1;
+ }
+
+ /* If we have responses ready, submit them now. */
+ while ((stream = h2_mplx_next_submit(session->mplx,
+ session->streams)) != NULL) {
+ status = h2_session_handle_response(session, stream);
+ flush_output = 1;
+ }
+
+ if (h2_session_resume_streams_with_data(session) > 0) {
+ flush_output = 1;
+ }
+
+ if (!flush_output && timeout > 0 && !h2_session_want_write(session)) {
+ status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait);
+
+ if (status != APR_TIMEUP
+ && h2_session_resume_streams_with_data(session) > 0) {
+ flush_output = 1;
+ }
+ else {
+ /* nothing happened to ongoing streams, do some house-keeping */
+ }
+ }
+
+ if (h2_session_want_write(session)) {
+ int rv;
+ status = APR_SUCCESS;
+ rv = nghttp2_session_send(session->ngh2);
+ if (rv != 0) {
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_session: send2: %s", nghttp2_strerror(rv));
+ if (nghttp2_is_fatal(rv)) {
+ h2_session_abort_int(session, rv);
+ status = APR_ECONNABORTED;
+ }
+ }
+ flush_output = 1;
+ }
+
+ if (flush_output) {
+ h2_conn_io_flush(&session->io);
+ }
+
+ return status;
+}
+
+h2_stream *h2_session_get_stream(h2_session *session, int stream_id)
+{
+ AP_DEBUG_ASSERT(session);
+ return h2_stream_set_get(session->streams, stream_id);
+}
+
+/* h2_io_on_read_cb implementation that offers the data read
+ * directly to the session for consumption.
+ */
+static apr_status_t session_receive(const char *data, apr_size_t len,
+ apr_size_t *readlen, int *done,
+ void *puser)
+{
+ h2_session *session = (h2_session *)puser;
+ AP_DEBUG_ASSERT(session);
+ if (len > 0) {
+ ssize_t n = nghttp2_session_mem_recv(session->ngh2,
+ (const uint8_t *)data, len);
+ if (n < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL,
+ session->c,
+ "h2_session: nghttp2_session_mem_recv error %d",
+ (int)n);
+ if (nghttp2_is_fatal((int)n)) {
+ *done = 1;
+ h2_session_abort_int(session, (int)n);
+ return APR_EGENERAL;
+ }
+ }
+ else {
+ *readlen = n;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_session_read(h2_session *session, apr_read_type_e block)
+{
+ AP_DEBUG_ASSERT(session);
+ return h2_conn_io_read(&session->io, block, session_receive, session);
+}
+
+apr_status_t h2_session_close(h2_session *session)
+{
+ AP_DEBUG_ASSERT(session);
+ return h2_conn_io_flush(&session->io);
+}
+
+/* The session wants to send more DATA for the given stream.
+ */
+static ssize_t stream_data_cb(nghttp2_session *ng2s,
+ int32_t stream_id,
+ uint8_t *buf,
+ size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *puser)
+{
+ h2_session *session = (h2_session *)puser;
+ apr_size_t nread = length;
+ int eos = 0;
+ apr_status_t status;
+ h2_stream *stream;
+ AP_DEBUG_ASSERT(session);
+
+ (void)ng2s;
+ (void)buf;
+ (void)source;
+ stream = h2_stream_set_get(session->streams, stream_id);
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
+ APLOGNO(02937)
+ "h2_stream(%ld-%d): data requested but stream not found",
+ session->id, (int)stream_id);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream));
+
+ status = h2_stream_prep_read(stream, &nread, &eos);
+ if (nread) {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+ }
+
+ switch (status) {
+ case APR_SUCCESS:
+ break;
+
+ case APR_EAGAIN:
+ /* If there is no data available, our session will automatically
+ * suspend this stream and not ask for more data until we resume
+ * it. Remember at our h2_stream that we need to do this.
+ */
+ nread = 0;
+ h2_stream_set_suspended(stream, 1);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): suspending stream",
+ session->id, (int)stream_id);
+ return NGHTTP2_ERR_DEFERRED;
+
+ case APR_EOF:
+ nread = 0;
+ eos = 1;
+ break;
+
+ default:
+ nread = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02938) "h2_stream(%ld-%d): reading data",
+ session->id, (int)stream_id);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (eos) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+
+ return (ssize_t)nread;
+}
+
+typedef struct {
+ nghttp2_nv *nv;
+ size_t nvlen;
+ size_t offset;
+} nvctx_t;
+
+static int submit_response(h2_session *session, h2_response *response)
+{
+ nghttp2_data_provider provider;
+ int rv;
+
+ memset(&provider, 0, sizeof(provider));
+ provider.source.fd = response->stream_id;
+ provider.read_callback = stream_data_cb;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_stream(%ld-%d): submitting response %s",
+ session->id, response->stream_id, response->status);
+
+ rv = nghttp2_submit_response(session->ngh2, response->stream_id,
+ response->ngheader->nv,
+ response->ngheader->nvlen, &provider);
+
+ if (rv != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02939) "h2_stream(%ld-%d): submit_response: %s",
+ session->id, response->stream_id, nghttp2_strerror(rv));
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): submitted response %s, rv=%d",
+ session->id, response->stream_id,
+ response->status, rv);
+ }
+ return rv;
+}
+
+/* Start submitting the response to a stream request. This is possible
+ * once we have all the response headers. The response body will be
+ * read by the session using the callback we supply.
+ */
+apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream)
+{
+ apr_status_t status = APR_SUCCESS;
+ int rv = 0;
+ AP_DEBUG_ASSERT(session);
+ AP_DEBUG_ASSERT(stream);
+ AP_DEBUG_ASSERT(stream->response);
+
+ if (stream->response->ngheader) {
+ rv = submit_response(session, stream->response);
+ }
+ else {
+ rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+ stream->id, NGHTTP2_PROTOCOL_ERROR);
+ }
+
+ if (nghttp2_is_fatal(rv)) {
+ status = APR_EGENERAL;
+ h2_session_abort_int(session, rv);
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02940) "submit_response: %s",
+ nghttp2_strerror(rv));
+ }
+ return status;
+}
+
+int h2_session_is_done(h2_session *session)
+{
+ AP_DEBUG_ASSERT(session);
+ return (session->aborted
+ || !session->ngh2
+ || (!nghttp2_session_want_read(session->ngh2)
+ && !nghttp2_session_want_write(session->ngh2)));
+}
+
+static int log_stream(void *ctx, h2_stream *stream)
+{
+ h2_session *session = (h2_session *)ctx;
+ AP_DEBUG_ASSERT(session);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_stream(%ld-%d): in set, suspended=%d, aborted=%d, "
+ "has_data=%d",
+ session->id, stream->id, stream->suspended, stream->aborted,
+ h2_mplx_out_has_data_for(session->mplx, stream->id));
+ return 1;
+}
+
+void h2_session_log_stats(h2_session *session)
+{
+ AP_DEBUG_ASSERT(session);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_session(%ld): %d open streams",
+ session->id, (int)h2_stream_set_size(session->streams));
+ h2_stream_set_iter(session->streams, log_stream, session);
+}
+
+static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
+{
+ char scratch[128];
+ size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ return apr_snprintf(buffer, maxlen,
+ "DATA[length=%d, flags=%d, stream=%d, padlen=%d]",
+ (int)frame->hd.length, frame->hd.flags,
+ frame->hd.stream_id, (int)frame->data.padlen);
+ }
+ case NGHTTP2_HEADERS: {
+ return apr_snprintf(buffer, maxlen,
+ "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]",
+ (int)frame->hd.length,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
+ frame->hd.stream_id,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
+ }
+ case NGHTTP2_PRIORITY: {
+ return apr_snprintf(buffer, maxlen,
+ "PRIORITY[length=%d, flags=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+ case NGHTTP2_RST_STREAM: {
+ return apr_snprintf(buffer, maxlen,
+ "RST_STREAM[length=%d, flags=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+ case NGHTTP2_SETTINGS: {
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ return apr_snprintf(buffer, maxlen,
+ "SETTINGS[ack=1, stream=%d]",
+ frame->hd.stream_id);
+ }
+ return apr_snprintf(buffer, maxlen,
+ "SETTINGS[length=%d, stream=%d]",
+ (int)frame->hd.length, frame->hd.stream_id);
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ return apr_snprintf(buffer, maxlen,
+ "PUSH_PROMISE[length=%d, hend=%d, stream=%d]",
+ (int)frame->hd.length,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
+ frame->hd.stream_id);
+ }
+ case NGHTTP2_PING: {
+ return apr_snprintf(buffer, maxlen,
+ "PING[length=%d, ack=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags&NGHTTP2_FLAG_ACK,
+ frame->hd.stream_id);
+ }
+ case NGHTTP2_GOAWAY: {
+ size_t len = (frame->goaway.opaque_data_len < s_len)?
+ frame->goaway.opaque_data_len : s_len-1;
+ memcpy(scratch, frame->goaway.opaque_data, len);
+ scratch[len+1] = '\0';
+ return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s']",
+ frame->goaway.error_code, scratch);
+ }
+ case NGHTTP2_WINDOW_UPDATE: {
+ return apr_snprintf(buffer, maxlen,
+ "WINDOW_UPDATE[length=%d, stream=%d]",
+ (int)frame->hd.length, frame->hd.stream_id);
+ }
+ default:
+ return apr_snprintf(buffer, maxlen,
+ "FRAME[type=%d, length=%d, flags=%d, stream=%d]",
+ frame->hd.type, (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_session__
+#define __mod_h2__h2_session__
+
+#include "h2_conn_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 independant of
+ * session lifetime and sessions may close down while tasks are still running.
+ *
+ *
+ */
+
+struct apr_thread_mutext_t;
+struct apr_thread_cond_t;
+struct h2_config;
+struct h2_mplx;
+struct h2_response;
+struct h2_session;
+struct h2_stream;
+struct h2_task;
+struct h2_workers;
+
+struct nghttp2_session;
+
+typedef struct h2_session h2_session;
+
+struct h2_session {
+ long id; /* identifier of this session, unique
+ * inside a httpd process */
+ conn_rec *c; /* the connection this session serves */
+ request_rec *r; /* the request that started this in case
+ * of 'h2c', NULL otherwise */
+ int aborted; /* this session is being aborted */
+ apr_size_t frames_received; /* number of http/2 frames received */
+ 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_pool_t *pool; /* pool to use in session handling */
+ apr_bucket_brigade *bbtmp; /* brigade for keeping temporary data */
+ struct apr_thread_cond_t *iowait; /* our cond when trywaiting for data */
+
+ h2_conn_io io; /* io on httpd conn filters */
+ struct h2_mplx *mplx; /* multiplexer for stream data */
+
+ struct h2_stream_set *streams; /* streams handled by this session */
+
+ struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */
+ struct h2_workers *workers; /* for executing stream tasks */
+};
+
+
+/* Create a new h2_session for the given connection (mode 'h2').
+ * The session will apply the configured parameter.
+ */
+h2_session *h2_session_create(conn_rec *c, struct h2_config *cfg,
+ struct h2_workers *workers);
+
+/* Create a new h2_session for the given request (mode 'h2c').
+ * The session will apply the configured parameter.
+ */
+h2_session *h2_session_rcreate(request_rec *r, struct h2_config *cfg,
+ struct h2_workers *workers);
+
+/* Destroy the session and all object it still contains. This will not
+ * destroy h2_task instances that not finished yet. */
+void h2_session_destroy(h2_session *session);
+
+/* Called once at start of session. Performs initial client thingies. */
+apr_status_t h2_session_start(h2_session *session, int *rv);
+
+/* Return != 0 iff session is finished and connection can be closed.
+ */
+int h2_session_is_done(h2_session *session);
+
+/* Called when the session will shutdown after all open streams
+ * are handled. New streams will no longer be accepted.
+ * Call with reason APR_SUCCESS to initiate a graceful shutdown. */
+apr_status_t h2_session_goaway(h2_session *session, apr_status_t reason);
+
+/* Called when an error occured and the session needs to shut down.
+ * Status indicates the reason of the error. */
+apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv);
+
+/* Called before a session gets destroyed, might flush output etc. */
+apr_status_t h2_session_close(h2_session *session);
+
+/* Read more data from the client connection. Used normally with blocking
+ * APR_NONBLOCK_READ, which will return APR_EAGAIN when no data is available.
+ * Use with APR_BLOCK_READ only when certain that no data needs to be written
+ * while waiting. */
+apr_status_t h2_session_read(h2_session *session, apr_read_type_e block);
+
+/* Write data out to the client, if there is any. Otherwise, wait for
+ * a maximum of timeout micro-seconds and return to the caller. If timeout
+ * occurred, APR_TIMEUP will be returned.
+ */
+apr_status_t h2_session_write(h2_session *session,
+ apr_interval_time_t timeout);
+
+/* Start submitting the response to a stream request. This is possible
+ * once we have all the response headers. */
+apr_status_t h2_session_handle_response(h2_session *session,
+ struct h2_stream *stream);
+
+/* Get the h2_stream for the given stream idenrtifier. */
+struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id);
+
+void h2_session_log_stats(h2_session *session);
+
+#endif /* defined(__mod_h2__h2_session__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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>
+
+#define APR_POOL_DEBUG 7
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_log.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2_private.h"
+#include "h2_conn.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_response.h"
+#include "h2_stream.h"
+#include "h2_task.h"
+#include "h2_ctx.h"
+#include "h2_task_input.h"
+#include "h2_task.h"
+#include "h2_util.h"
+
+
+static void set_state(h2_stream *stream, h2_stream_state_t state)
+{
+ AP_DEBUG_ASSERT(stream);
+ if (stream->state != state) {
+ stream->state = state;
+ }
+}
+
+h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m)
+{
+ h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
+ if (stream != NULL) {
+ stream->id = id;
+ stream->state = H2_STREAM_ST_IDLE;
+ stream->pool = pool;
+ stream->m = m;
+ stream->request = h2_request_create(id, pool, m->c->bucket_alloc);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+ "h2_stream(%ld-%d): created", m->id, stream->id);
+ }
+ return stream;
+}
+
+void h2_stream_cleanup(h2_stream *stream)
+{
+ if (stream->request) {
+ h2_request_destroy(stream->request);
+ stream->request = NULL;
+ }
+}
+
+apr_status_t h2_stream_destroy(h2_stream *stream)
+{
+ AP_DEBUG_ASSERT(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
+ "h2_stream(%ld-%d): destroy", stream->m->id, stream->id);
+ h2_stream_cleanup(stream);
+
+ if (stream->task) {
+ h2_task_destroy(stream->task);
+ stream->task = NULL;
+ }
+ if (stream->pool) {
+ apr_pool_destroy(stream->pool);
+ }
+ return APR_SUCCESS;
+}
+
+void h2_stream_attach_pool(h2_stream *stream, apr_pool_t *pool)
+{
+ stream->pool = pool;
+}
+
+apr_pool_t *h2_stream_detach_pool(h2_stream *stream)
+{
+ apr_pool_t *pool = stream->pool;
+ stream->pool = NULL;
+ return pool;
+}
+
+void h2_stream_abort(h2_stream *stream)
+{
+ AP_DEBUG_ASSERT(stream);
+ stream->aborted = 1;
+}
+
+apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response,
+ apr_bucket_brigade *bb)
+{
+ stream->response = response;
+ if (bb && !APR_BRIGADE_EMPTY(bb)) {
+ if (!stream->bbout) {
+ stream->bbout = apr_brigade_create(stream->pool,
+ stream->m->c->bucket_alloc);
+ }
+ return h2_util_move(stream->bbout, bb, 16 * 1024, NULL,
+ "h2_stream_set_response");
+ }
+ return APR_SUCCESS;
+}
+
+static int set_closed(h2_stream *stream)
+{
+ switch (stream->state) {
+ case H2_STREAM_ST_CLOSED_INPUT:
+ case H2_STREAM_ST_CLOSED:
+ return 0; /* ignore, idempotent */
+ case H2_STREAM_ST_CLOSED_OUTPUT:
+ /* both closed now */
+ set_state(stream, H2_STREAM_ST_CLOSED);
+ break;
+ default:
+ /* everything else we jump to here */
+ set_state(stream, H2_STREAM_ST_CLOSED_INPUT);
+ break;
+ }
+ return 1;
+}
+
+apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(stream);
+ set_state(stream, H2_STREAM_ST_OPEN);
+ status = h2_request_rwrite(stream->request, r, stream->m);
+ return status;
+}
+
+apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(stream);
+
+ /* Seeing the end-of-headers, we have everything we need to
+ * start processing it.
+ */
+ status = h2_mplx_create_task(stream->m, stream);
+ if (status == APR_SUCCESS) {
+ status = h2_request_end_headers(stream->request,
+ stream->m, stream->task, eos);
+ if (status == APR_SUCCESS) {
+ status = h2_mplx_do_task(stream->m, stream->task);
+ }
+ if (eos) {
+ status = h2_stream_write_eos(stream);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->m->c,
+ "h2_mplx(%ld-%d): start stream, task %s %s (%s)",
+ stream->m->id, stream->id,
+ stream->request->method, stream->request->path,
+ stream->request->authority);
+
+ }
+ return status;
+}
+
+apr_status_t h2_stream_write_eos(h2_stream *stream)
+{
+ AP_DEBUG_ASSERT(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
+ "h2_stream(%ld-%d): closing input",
+ stream->m->id, stream->id);
+ if (set_closed(stream)) {
+ return h2_request_close(stream->request);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_stream_write_header(h2_stream *stream,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen)
+{
+ AP_DEBUG_ASSERT(stream);
+ switch (stream->state) {
+ case H2_STREAM_ST_IDLE:
+ set_state(stream, H2_STREAM_ST_OPEN);
+ break;
+ case H2_STREAM_ST_OPEN:
+ break;
+ default:
+ return APR_EINVAL;
+ }
+ return h2_request_write_header(stream->request, name, nlen,
+ value, vlen, stream->m);
+}
+
+apr_status_t h2_stream_write_data(h2_stream *stream,
+ const char *data, size_t len)
+{
+ AP_DEBUG_ASSERT(stream);
+ AP_DEBUG_ASSERT(stream);
+ switch (stream->state) {
+ case H2_STREAM_ST_OPEN:
+ break;
+ default:
+ return APR_EINVAL;
+ }
+ return h2_request_write_data(stream->request, data, len);
+}
+
+apr_status_t h2_stream_prep_read(h2_stream *stream,
+ apr_size_t *plen, int *peos)
+{
+ apr_status_t status = APR_SUCCESS;
+ const char *src;
+
+ if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
+ src = "stream";
+ status = h2_util_bb_avail(stream->bbout, plen, peos);
+ if (status == APR_SUCCESS && !*peos && !*plen) {
+ apr_brigade_cleanup(stream->bbout);
+ return h2_stream_prep_read(stream, plen, peos);
+ }
+ }
+ else {
+ src = "mplx";
+ status = h2_mplx_out_readx(stream->m, stream->id,
+ NULL, NULL, plen, peos);
+ }
+ if (status == APR_SUCCESS && !*peos && !*plen) {
+ status = APR_EAGAIN;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->m->c,
+ "h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d",
+ stream->m->id, stream->id,
+ src, (long)*plen, *peos);
+ return status;
+}
+
+apr_status_t h2_stream_readx(h2_stream *stream,
+ h2_io_data_cb *cb, void *ctx,
+ apr_size_t *plen, int *peos)
+{
+ if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
+ return h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos);
+ }
+ return h2_mplx_out_readx(stream->m, stream->id,
+ cb, ctx, plen, peos);
+}
+
+
+void h2_stream_set_suspended(h2_stream *stream, int suspended)
+{
+ AP_DEBUG_ASSERT(stream);
+ stream->suspended = !!suspended;
+}
+
+int h2_stream_is_suspended(h2_stream *stream)
+{
+ AP_DEBUG_ASSERT(stream);
+ return stream->suspended;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_stream__
+#define __mod_h2__h2_stream__
+
+/**
+ * A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms.
+ *
+ * Ok, not quite, but close enough, since we do not implement server
+ * pushes yet.
+ *
+ * A stream always belongs to a h2_session, the one managing the
+ * connection to the client. The h2_session writes to the h2_stream,
+ * adding HEADERS and DATA and finally an EOS. When headers are done,
+ * h2_stream can create a h2_task that can be scheduled to fullfill the
+ * request.
+ *
+ * This response headers are added directly to the h2_mplx of the session,
+ * but the response DATA can be read via h2_stream. Reading data will
+ * never block but return APR_EAGAIN when there currently is no data (and
+ * no eos) in the multiplexer for this stream.
+ */
+#include "h2_io.h"
+
+typedef enum {
+ H2_STREAM_ST_IDLE,
+ H2_STREAM_ST_OPEN,
+ H2_STREAM_ST_RESV_LOCAL,
+ H2_STREAM_ST_RESV_REMOTE,
+ H2_STREAM_ST_CLOSED_INPUT,
+ H2_STREAM_ST_CLOSED_OUTPUT,
+ H2_STREAM_ST_CLOSED,
+} h2_stream_state_t;
+
+struct h2_mplx;
+struct h2_request;
+struct h2_response;
+struct h2_task;
+
+typedef struct h2_stream h2_stream;
+
+struct h2_stream {
+ int id; /* http2 stream id */
+ h2_stream_state_t state; /* http/2 state of this stream */
+ struct h2_mplx *m; /* the multiplexer to work with */
+
+ int aborted; /* was aborted */
+ int suspended; /* DATA sending has been suspended */
+
+ apr_pool_t *pool; /* the memory pool for this stream */
+ struct h2_request *request; /* the request made in this stream */
+
+ struct h2_task *task; /* task created for this stream */
+ struct h2_response *response; /* the response, once ready */
+ apr_bucket_brigade *bbout; /* output DATA */
+};
+
+
+h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m);
+
+apr_status_t h2_stream_destroy(h2_stream *stream);
+void h2_stream_cleanup(h2_stream *stream);
+
+apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
+void h2_stream_attach_pool(h2_stream *stream, apr_pool_t *pool);
+
+void h2_stream_abort(h2_stream *stream);
+
+apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r);
+
+apr_status_t h2_stream_write_eos(h2_stream *stream);
+
+apr_status_t h2_stream_write_header(h2_stream *stream,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen);
+
+apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos);
+
+apr_status_t h2_stream_write_data(h2_stream *stream,
+ const char *data, size_t len);
+
+apr_status_t h2_stream_set_response(h2_stream *stream,
+ struct h2_response *response,
+ apr_bucket_brigade *bb);
+
+apr_status_t h2_stream_prep_read(h2_stream *stream,
+ apr_size_t *plen, int *peos);
+
+apr_status_t h2_stream_readx(h2_stream *stream, h2_io_data_cb *cb,
+ void *ctx, apr_size_t *plen, int *peos);
+
+void h2_stream_set_suspended(h2_stream *stream, int suspended);
+int h2_stream_is_suspended(h2_stream *stream);
+
+#endif /* defined(__mod_h2__h2_stream__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_task.h"
+#include "h2_stream_set.h"
+
+#define H2_STREAM_IDX(list, i) ((h2_stream**)(list)->elts)[i]
+
+struct h2_stream_set {
+ apr_array_header_t *list;
+};
+
+h2_stream_set *h2_stream_set_create(apr_pool_t *pool)
+{
+ h2_stream_set *sp = apr_pcalloc(pool, sizeof(h2_stream_set));
+ if (sp) {
+ sp->list = apr_array_make(pool, 100, sizeof(h2_stream*));
+ if (!sp->list) {
+ return NULL;
+ }
+ }
+ return sp;
+}
+
+void h2_stream_set_destroy(h2_stream_set *sp)
+{
+ (void)sp;
+}
+
+static int h2_stream_id_cmp(const void *s1, const void *s2)
+{
+ h2_stream **pstream1 = (h2_stream **)s1;
+ h2_stream **pstream2 = (h2_stream **)s2;
+ return (*pstream1)->id - (*pstream2)->id;
+}
+
+h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id)
+{
+ /* we keep the array sorted by id, so lookup can be done
+ * by bsearch.
+ */
+ h2_stream key;
+ h2_stream *pkey, **ps;
+ memset(&key, 0, sizeof(key));
+ key.id = stream_id;
+ pkey = &key;
+ ps = bsearch(&pkey, sp->list->elts, sp->list->nelts,
+ sp->list->elt_size, h2_stream_id_cmp);
+ return ps? *ps : NULL;
+}
+
+static void h2_stream_set_sort(h2_stream_set *sp)
+{
+ qsort(sp->list->elts, sp->list->nelts, sp->list->elt_size,
+ h2_stream_id_cmp);
+}
+
+apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream)
+{
+ h2_stream *existing = h2_stream_set_get(sp, stream->id);
+ if (!existing) {
+ int last;
+ APR_ARRAY_PUSH(sp->list, h2_stream*) = stream;
+ /* Normally, streams get added in ascending order if id. We
+ * keep the array sorted, so we just need to check of the newly
+ * appended stream has a lower id than the last one. if not,
+ * sorting is not necessary.
+ */
+ last = sp->list->nelts - 1;
+ if (last > 0
+ && (H2_STREAM_IDX(sp->list, last)->id
+ < H2_STREAM_IDX(sp->list, last-1)->id)) {
+ h2_stream_set_sort(sp);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+h2_stream *h2_stream_set_remove(h2_stream_set *sp, h2_stream *stream)
+{
+ int i;
+ for (i = 0; i < sp->list->nelts; ++i) {
+ h2_stream *s = H2_STREAM_IDX(sp->list, i);
+ if (s == stream) {
+ int n;
+ --sp->list->nelts;
+ n = sp->list->nelts - i;
+ if (n > 0) {
+ /* Close the hole in the array by moving the upper
+ * parts down one step.
+ */
+ h2_stream **selts = (h2_stream**)sp->list->elts;
+ memmove(selts+i, selts+i+1, n * sizeof(h2_stream*));
+ }
+ return s;
+ }
+ }
+ return NULL;
+}
+
+void h2_stream_set_remove_all(h2_stream_set *sp)
+{
+ sp->list->nelts = 0;
+}
+
+int h2_stream_set_is_empty(h2_stream_set *sp)
+{
+ AP_DEBUG_ASSERT(sp);
+ return sp->list->nelts == 0;
+}
+
+h2_stream *h2_stream_set_find(h2_stream_set *sp,
+ h2_stream_set_match_fn *match, void *ctx)
+{
+ h2_stream *s = NULL;
+ int i;
+ for (i = 0; !s && i < sp->list->nelts; ++i) {
+ s = match(ctx, H2_STREAM_IDX(sp->list, i));
+ }
+ return s;
+}
+
+void h2_stream_set_iter(h2_stream_set *sp,
+ h2_stream_set_iter_fn *iter, void *ctx)
+{
+ int i;
+ for (i = 0; i < sp->list->nelts; ++i) {
+ h2_stream *s = H2_STREAM_IDX(sp->list, i);
+ if (!iter(ctx, s)) {
+ break;
+ }
+ }
+}
+
+apr_size_t h2_stream_set_size(h2_stream_set *sp)
+{
+ return sp->list->nelts;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_stream_set__
+#define __mod_h2__h2_stream_set__
+
+/**
+ * A set of h2_stream instances. Allows lookup by stream id
+ * and other criteria.
+ */
+
+typedef h2_stream *h2_stream_set_match_fn(void *ctx, h2_stream *stream);
+typedef int h2_stream_set_iter_fn(void *ctx, h2_stream *stream);
+
+typedef struct h2_stream_set h2_stream_set;
+
+
+h2_stream_set *h2_stream_set_create(apr_pool_t *pool);
+
+void h2_stream_set_destroy(h2_stream_set *sp);
+
+apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream);
+
+h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id);
+
+h2_stream *h2_stream_set_remove(h2_stream_set *sp,h2_stream *stream);
+
+void h2_stream_set_remove_all(h2_stream_set *sp);
+
+int h2_stream_set_is_empty(h2_stream_set *sp);
+
+apr_size_t h2_stream_set_size(h2_stream_set *sp);
+
+h2_stream *h2_stream_set_find(h2_stream_set *sp,
+ h2_stream_set_match_fn *match, void *ctx);
+
+void h2_stream_set_iter(h2_stream_set *sp,
+ h2_stream_set_iter_fn *iter, void *ctx);
+
+#endif /* defined(__mod_h2__h2_stream_set__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <apr_optional.h>
+#include <apr_optional_hooks.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+
+#include "h2_config.h"
+#include "h2_ctx.h"
+#include "h2_conn.h"
+#include "h2_h2.h"
+#include "h2_switch.h"
+
+/*******************************************************************************
+ * SSL var lookup
+ */
+APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
+ (apr_pool_t *, server_rec *,
+ conn_rec *, request_rec *,
+ char *));
+static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *,
+ conn_rec *, request_rec *,
+ char *);
+
+/*******************************************************************************
+ * Once per lifetime init, retrieve optional functions
+ */
+apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s)
+{
+ (void)pool;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_switch init");
+ opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
+
+ return APR_SUCCESS;
+}
+
+static int h2_protocol_propose(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *offers,
+ apr_array_header_t *proposals)
+{
+ int proposed = 0;
+ const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
+
+ (void)s;
+ if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
+ /* We do not know how to switch from anything else but http/1.1.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "protocol switch: current proto != http/1.1, declined");
+ return DECLINED;
+ }
+
+ if (r) {
+ const char *p;
+ /* So far, this indicates an HTTP/1 Upgrade header initiated
+ * protocol switch. For that, the HTTP2-Settings header needs
+ * to be present and valid for the connection.
+ */
+ p = apr_table_get(r->headers_in, "HTTP2-Settings");
+ if (!p) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "upgrade without HTTP2-Settings declined");
+ return DECLINED;
+ }
+
+ p = apr_table_get(r->headers_in, "Connection");
+ if (!ap_find_token(r->pool, p, "http2-settings")) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "upgrade without HTTP2-Settings declined");
+ return DECLINED;
+ }
+
+ /* We also allow switching only for requests that have no body.
+ */
+ p = apr_table_get(r->headers_in, "Content-Length");
+ if (p && strcmp(p, "0")) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "upgrade with content-length: %s, declined", p);
+ return DECLINED;
+ }
+ }
+
+ while (*protos) {
+ /* Add all protocols we know (tls or clear) and that
+ * are part of the offerings (if there have been any).
+ */
+ if (!offers || ap_array_str_contains(offers, *protos)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "proposing protocol '%s'", *protos);
+ APR_ARRAY_PUSH(proposals, const char*) = *protos;
+ proposed = 1;
+ }
+ ++protos;
+ }
+ return proposed? DECLINED : OK;
+}
+
+static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
+ const char *protocol)
+{
+ int found = 0;
+ const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
+ const char **p = protos;
+
+ (void)s;
+ while (*p) {
+ if (!strcmp(*p, protocol)) {
+ found = 1;
+ break;
+ }
+ p++;
+ }
+
+ if (found) {
+ h2_ctx *ctx = h2_ctx_get(c);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "switching protocol to '%s'", protocol);
+ h2_ctx_protocol_set(ctx, protocol);
+
+ if (r != NULL) {
+ apr_status_t status;
+ /* Switching in the middle of a request means that
+ * we have to send out the response to this one in h2
+ * format. So we need to take over the connection
+ * right away.
+ */
+ ap_remove_input_filter_byhandle(r->input_filters, "http_in");
+ ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
+
+ /* Ok, start an h2_conn on this one. */
+ status = h2_conn_rprocess(r);
+ if (status != DONE) {
+ /* Nothing really to do about this. */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ "session proessed, unexpected status");
+ }
+ }
+ return DONE;
+ }
+
+ return DECLINED;
+}
+
+static const char *h2_protocol_get(const conn_rec *c)
+{
+ return h2_ctx_protocol_get(c);
+}
+
+void h2_switch_register_hooks(void)
+{
+ ap_hook_protocol_propose(h2_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_protocol_switch(h2_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_protocol_get(h2_protocol_get, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_switch__
+#define __mod_h2__h2_switch__
+
+/*
+ * One time, post config intialization.
+ */
+apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s);
+
+/* Register apache hooks for protocol switching
+ */
+void h2_switch_register_hooks(void);
+
+
+#endif /* defined(__mod_h2__h2_switch__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_thread_cond.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_conn.h"
+#include "h2_config.h"
+#include "h2_from_h1.h"
+#include "h2_h2.h"
+#include "h2_mplx.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_task_input.h"
+#include "h2_task_output.h"
+#include "h2_task.h"
+#include "h2_ctx.h"
+#include "h2_worker.h"
+
+
+static apr_status_t h2_filter_stream_input(ap_filter_t* filter,
+ apr_bucket_brigade* brigade,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes) {
+ h2_task_env *env = filter->ctx;
+ AP_DEBUG_ASSERT(env);
+ if (!env->input) {
+ return APR_ECONNABORTED;
+ }
+ return h2_task_input_read(env->input, filter, brigade,
+ mode, block, readbytes);
+}
+
+static apr_status_t h2_filter_stream_output(ap_filter_t* filter,
+ apr_bucket_brigade* brigade) {
+ h2_task_env *env = filter->ctx;
+ AP_DEBUG_ASSERT(env);
+ if (!env->output) {
+ return APR_ECONNABORTED;
+ }
+ return h2_task_output_write(env->output, filter, brigade);
+}
+
+static apr_status_t h2_filter_read_response(ap_filter_t* f,
+ apr_bucket_brigade* bb) {
+ h2_task_env *env = f->ctx;
+ AP_DEBUG_ASSERT(env);
+ if (!env->output || !env->output->from_h1) {
+ return APR_ECONNABORTED;
+ }
+ return h2_from_h1_read_response(env->output->from_h1, f, bb);
+}
+
+/*******************************************************************************
+ * 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);
+
+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 to
+ * take over, if the connection is for a task.
+ */
+ ap_hook_process_connection(h2_task_process_conn,
+ NULL, NULL, APR_HOOK_FIRST);
+
+ ap_register_output_filter("H2_RESPONSE", h2_response_output_filter,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_register_input_filter("H2_TO_H1", h2_filter_stream_input,
+ NULL, AP_FTYPE_NETWORK);
+ ap_register_output_filter("H1_TO_H2", h2_filter_stream_output,
+ NULL, AP_FTYPE_NETWORK);
+ ap_register_output_filter("H1_TO_H2_RESP", h2_filter_read_response,
+ NULL, AP_FTYPE_PROTOCOL);
+}
+
+static int h2_task_pre_conn(conn_rec* c, void *arg)
+{
+
+ h2_ctx *ctx = h2_ctx_get(c);
+
+ (void)arg;
+ if (h2_ctx_is_task(ctx)) {
+ h2_task_env *env = h2_ctx_get_task(ctx);
+
+ /* This connection is a pseudo-connection used for a h2_task.
+ * Since we read/write directly from it ourselves, we need
+ * to disable a possible ssl connection filter.
+ */
+ h2_tls_disable(c);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_h2, pre_connection, found stream task");
+
+ /* Add our own, network level in- and output filters.
+ */
+ ap_add_input_filter("H2_TO_H1", env, NULL, c);
+ ap_add_output_filter("H1_TO_H2", env, NULL, c);
+ }
+ return OK;
+}
+
+static int h2_task_process_conn(conn_rec* c)
+{
+ h2_ctx *ctx = h2_ctx_get(c);
+
+ if (h2_ctx_is_task(ctx)) {
+ if (!ctx->task_env->serialize_headers) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_h2, processing request directly");
+ h2_task_process_request(ctx->task_env);
+ return DONE;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_task(%s), serialized handling", ctx->task_env->id);
+ }
+ return DECLINED;
+}
+
+
+h2_task *h2_task_create(long session_id,
+ int stream_id,
+ apr_pool_t *stream_pool,
+ h2_mplx *mplx, conn_rec *c)
+{
+ h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task));
+ if (task == NULL) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, stream_pool,
+ APLOGNO(02941) "h2_task(%ld-%d): create stream task",
+ session_id, stream_id);
+ h2_mplx_out_close(mplx, stream_id);
+ return NULL;
+ }
+
+ APR_RING_ELEM_INIT(task, link);
+
+ task->id = apr_psprintf(stream_pool, "%ld-%d", session_id, stream_id);
+ task->stream_id = stream_id;
+ task->mplx = mplx;
+
+ task->c = c;
+
+ ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, stream_pool,
+ "h2_task(%s): created", task->id);
+ return task;
+}
+
+void h2_task_set_request(h2_task *task,
+ const char *method,
+ const char *scheme,
+ const char *authority,
+ const char *path,
+ apr_table_t *headers, int eos)
+{
+ task->method = method;
+ task->scheme = scheme;
+ task->authority = authority;
+ task->path = path;
+ task->headers = headers;
+ task->input_eos = eos;
+}
+
+apr_status_t h2_task_destroy(h2_task *task)
+{
+ (void)task;
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_task_do(h2_task *task, h2_worker *worker)
+{
+ apr_status_t status = APR_SUCCESS;
+ h2_config *cfg = h2_config_get(task->mplx->c);
+ h2_task_env env;
+
+ AP_DEBUG_ASSERT(task);
+
+ memset(&env, 0, sizeof(env));
+
+ env.id = task->id;
+ env.stream_id = task->stream_id;
+ env.mplx = task->mplx;
+ task->mplx = NULL;
+
+ env.input_eos = task->input_eos;
+ env.serialize_headers = !!h2_config_geti(cfg, H2_CONF_SER_HEADERS);
+
+ /* Create a subpool from the worker one to be used for all things
+ * with life-time of this task_env execution.
+ */
+ apr_pool_create(&env.pool, h2_worker_get_pool(worker));
+
+ /* Link the env to the worker which provides useful things such
+ * as mutex, a socket etc. */
+ env.io = h2_worker_get_cond(worker);
+
+ /* Clone fields, so that lifetimes become (more) independent. */
+ env.method = apr_pstrdup(env.pool, task->method);
+ env.scheme = apr_pstrdup(env.pool, task->scheme);
+ env.authority = apr_pstrdup(env.pool, task->authority);
+ env.path = apr_pstrdup(env.pool, task->path);
+ env.headers = apr_table_clone(env.pool, task->headers);
+
+ /* Setup the pseudo connection to use our own pool and bucket_alloc */
+ env.c = *task->c;
+ task->c = NULL;
+ status = h2_conn_setup(&env, worker);
+
+ /* save in connection that this one is a pseudo connection, prevents
+ * other hooks from messing with it. */
+ h2_ctx_create_for(&env.c, &env);
+
+ if (status == APR_SUCCESS) {
+ env.input = h2_task_input_create(&env, env.pool,
+ env.c.bucket_alloc);
+ env.output = h2_task_output_create(&env, env.pool,
+ env.c.bucket_alloc);
+ status = h2_conn_process(&env.c, h2_worker_get_socket(worker));
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, &env.c,
+ "h2_task(%s): processing done", env.id);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c,
+ APLOGNO(02957) "h2_task(%s): error setting up h2_task_env",
+ env.id);
+ }
+
+ if (env.input) {
+ h2_task_input_destroy(env.input);
+ env.input = NULL;
+ }
+
+ if (env.output) {
+ h2_task_output_close(env.output);
+ h2_task_output_destroy(env.output);
+ env.output = NULL;
+ }
+
+ h2_task_set_finished(task);
+ if (env.io) {
+ apr_thread_cond_signal(env.io);
+ }
+
+ if (env.pool) {
+ apr_pool_destroy(env.pool);
+ env.pool = NULL;
+ }
+
+ if (env.c.id) {
+ h2_conn_post(&env.c, worker);
+ }
+
+ h2_mplx_task_done(env.mplx, env.stream_id);
+
+ return status;
+}
+
+int h2_task_has_started(h2_task *task)
+{
+ AP_DEBUG_ASSERT(task);
+ return apr_atomic_read32(&task->has_started);
+}
+
+void h2_task_set_started(h2_task *task)
+{
+ AP_DEBUG_ASSERT(task);
+ apr_atomic_set32(&task->has_started, 1);
+}
+
+int h2_task_has_finished(h2_task *task)
+{
+ return apr_atomic_read32(&task->has_finished);
+}
+
+void h2_task_set_finished(h2_task *task)
+{
+ apr_atomic_set32(&task->has_finished, 1);
+}
+
+void h2_task_die(h2_task_env *env, int status, request_rec *r)
+{
+ (void)env;
+ ap_die(status, r);
+}
+
+static request_rec *h2_task_create_request(h2_task_env *env)
+{
+ conn_rec *conn = &env->c;
+ request_rec *r;
+ apr_pool_t *p;
+ int access_status = HTTP_OK;
+
+ apr_pool_create(&p, conn->pool);
+ apr_pool_tag(p, "request");
+ r = apr_pcalloc(p, sizeof(request_rec));
+ AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn);
+ r->pool = p;
+ r->connection = conn;
+ r->server = conn->base_server;
+
+ r->user = NULL;
+ r->ap_auth_type = NULL;
+
+ r->allowed_methods = ap_make_method_list(p, 2);
+
+ r->headers_in = apr_table_copy(r->pool, env->headers);
+ 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 = conn->output_filters;
+ r->output_filters = r->proto_output_filters;
+ r->proto_input_filters = conn->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 = conn->client_addr;
+ r->useragent_ip = conn->client_ip;
+
+ ap_run_pre_read_request(r, conn);
+
+ /* Time to populate r with the data we have. */
+ r->request_time = apr_time_now();
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/1.1",
+ env->method, env->path);
+ r->method = env->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, env->path);
+ r->protocol = (char*)"HTTP/1.1";
+ r->proto_num = HTTP_VERSION(1, 1);
+
+ /* update what we think the virtual host is based on the headers we've
+ * now read. may update status.
+ * Leave r->hostname empty, vhost will parse if form our 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;
+
+ /*
+ * Add the HTTP_IN filter here to ensure that ap_discard_request_body
+ * called by ap_die and by ap_send_error_response works correctly on
+ * status codes that do not cause the connection to be dropped and
+ * in situations where the connection should be kept alive.
+ */
+ ap_add_input_filter_handle(ap_http_input_filter_handle,
+ NULL, r, r->connection);
+
+ if (access_status != HTTP_OK
+ || (access_status = ap_run_post_read_request(r))) {
+ /* Request check post hooks failed. An example of this would be a
+ * request for a vhost where h2 is disabled --> 421.
+ */
+ h2_task_die(env, access_status, r);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ r = NULL;
+ goto traceout;
+ }
+
+ AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
+ (char *)r->uri, (char *)r->server->defn_name,
+ r->status);
+ return r;
+traceout:
+ AP_READ_REQUEST_FAILURE((uintptr_t)r);
+ return r;
+}
+
+
+apr_status_t h2_task_process_request(h2_task_env *env)
+{
+ conn_rec *c = &env->c;
+ request_rec *r;
+ conn_state_t *cs = c->cs;
+
+ r = h2_task_create_request(env);
+ if (r && (r->status == HTTP_OK)) {
+ ap_update_child_status(c->sbh, SERVER_BUSY_READ, r);
+
+ if (cs)
+ cs->state = CONN_STATE_HANDLER;
+ ap_process_request(r);
+ /* After the call to ap_process_request, the
+ * request pool will 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.
+ */
+ r = NULL;
+ }
+ ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
+ c->sbh = NULL;
+
+ return APR_SUCCESS;
+}
+
+
+
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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__
+
+/**
+ * A h2_task fakes a HTTP/1.1 request from the data in a HTTP/2 stream
+ * (HEADER+CONT.+DATA) the module recieves.
+ *
+ * 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 disble those.
+ */
+
+struct apr_thread_cond_t;
+struct h2_conn;
+struct h2_mplx;
+struct h2_task;
+struct h2_resp_head;
+struct h2_worker;
+
+typedef struct h2_task h2_task;
+
+struct h2_task {
+ /** Links to the rest of the tasks */
+ APR_RING_ENTRY(h2_task) link;
+
+ const char *id;
+ int stream_id;
+ struct h2_mplx *mplx;
+
+ volatile apr_uint32_t has_started;
+ volatile apr_uint32_t has_finished;
+
+ const char *method;
+ const char *scheme;
+ const char *authority;
+ const char *path;
+ apr_table_t *headers;
+ int input_eos;
+
+ struct conn_rec *c;
+};
+
+typedef struct h2_task_env h2_task_env;
+
+struct h2_task_env {
+ const char *id;
+ int stream_id;
+ struct h2_mplx *mplx;
+
+ apr_pool_t *pool; /* pool for task lifetime things */
+ apr_bucket_alloc_t *bucket_alloc;
+
+ const char *method;
+ const char *scheme;
+ const char *authority;
+ const char *path;
+ apr_table_t *headers;
+ int input_eos;
+
+ int serialize_headers;
+
+ struct conn_rec c;
+ struct h2_task_input *input;
+ struct h2_task_output *output;
+
+ struct apr_thread_cond_t *io; /* used to wait for events on */
+};
+
+/**
+ * The magic pointer value that indicates the head of a h2_task list
+ * @param b The task list
+ * @return The magic pointer value
+ */
+#define H2_TASK_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_task, link)
+
+/**
+ * Determine if the task list is empty
+ * @param b The list to check
+ * @return true or false
+ */
+#define H2_TASK_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_task, link)
+
+/**
+ * Return the first task in a list
+ * @param b The list to query
+ * @return The first task in the list
+ */
+#define H2_TASK_LIST_FIRST(b) APR_RING_FIRST(b)
+
+/**
+ * Return the last task in a list
+ * @param b The list to query
+ * @return The last task int he list
+ */
+#define H2_TASK_LIST_LAST(b) APR_RING_LAST(b)
+
+/**
+ * Insert a single task at the front of a list
+ * @param b The list to add to
+ * @param e The task to insert
+ */
+#define H2_TASK_LIST_INSERT_HEAD(b, e) do { \
+ h2_task *ap__b = (e); \
+ APR_RING_INSERT_HEAD((b), ap__b, h2_task, link); \
+} while (0)
+
+/**
+ * Insert a single task at the end of a list
+ * @param b The list to add to
+ * @param e The task to insert
+ */
+#define H2_TASK_LIST_INSERT_TAIL(b, e) do { \
+ h2_task *ap__b = (e); \
+ APR_RING_INSERT_TAIL((b), ap__b, h2_task, link); \
+} while (0)
+
+/**
+ * Get the next task in the list
+ * @param e The current task
+ * @return The next task
+ */
+#define H2_TASK_NEXT(e) APR_RING_NEXT((e), link)
+/**
+ * Get the previous task in the list
+ * @param e The current task
+ * @return The previous task
+ */
+#define H2_TASK_PREV(e) APR_RING_PREV((e), link)
+
+/**
+ * Remove a task from its list
+ * @param e The task to remove
+ */
+#define H2_TASK_REMOVE(e) APR_RING_REMOVE((e), link)
+
+
+h2_task *h2_task_create(long session_id, int stream_id,
+ apr_pool_t *pool, struct h2_mplx *mplx,
+ conn_rec *c);
+
+apr_status_t h2_task_destroy(h2_task *task);
+
+void h2_task_set_request(h2_task *task,
+ const char *method,
+ const char *scheme,
+ const char *authority,
+ const char *path,
+ apr_table_t *headers, int eos);
+
+
+apr_status_t h2_task_do(h2_task *task, struct h2_worker *worker);
+apr_status_t h2_task_process_request(h2_task_env *env);
+
+int h2_task_has_started(h2_task *task);
+void h2_task_set_started(h2_task *task);
+int h2_task_has_finished(h2_task *task);
+void h2_task_set_finished(h2_task *task);
+
+void h2_task_register_hooks(void);
+void h2_task_die(h2_task_env *env, int status, request_rec *r);
+
+#endif /* defined(__mod_h2__h2_task__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_log.h>
+#include <http_connection.h>
+
+#include "h2_private.h"
+#include "h2_conn.h"
+#include "h2_mplx.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_task_input.h"
+#include "h2_task.h"
+#include "h2_util.h"
+
+
+static int is_aborted(ap_filter_t *f)
+{
+ return (f->c->aborted);
+}
+
+static int ser_header(void *ctx, const char *name, const char *value)
+{
+ h2_task_input *input = (h2_task_input*)ctx;
+ apr_brigade_printf(input->bb, NULL, NULL, "%s: %s\r\n", name, value);
+ return 1;
+}
+
+h2_task_input *h2_task_input_create(h2_task_env *env, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc)
+{
+ h2_task_input *input = apr_pcalloc(pool, sizeof(h2_task_input));
+ if (input) {
+ input->env = env;
+ input->bb = NULL;
+
+ if (env->serialize_headers) {
+ input->bb = apr_brigade_create(pool, bucket_alloc);
+ apr_brigade_printf(input->bb, NULL, NULL, "%s %s HTTP/1.1\r\n",
+ env->method, env->path);
+ apr_table_do(ser_header, input, env->headers, NULL);
+ apr_brigade_puts(input->bb, NULL, NULL, "\r\n");
+ if (input->env->input_eos) {
+ APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc));
+ }
+ }
+ else if (!input->env->input_eos) {
+ input->bb = apr_brigade_create(pool, bucket_alloc);
+ }
+ else {
+ /* We do not serialize and have eos already, no need to
+ * create a bucket brigade. */
+ }
+
+ if (APLOGcdebug(&env->c)) {
+ char buffer[1024];
+ apr_size_t len = sizeof(buffer)-1;
+ if (input->bb) {
+ apr_brigade_flatten(input->bb, buffer, &len);
+ }
+ else {
+ len = 0;
+ }
+ buffer[len] = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, &env->c,
+ "h2_task_input(%s): request is: %s",
+ env->id, buffer);
+ }
+ }
+ return input;
+}
+
+void h2_task_input_destroy(h2_task_input *input)
+{
+ input->bb = NULL;
+}
+
+apr_status_t h2_task_input_read(h2_task_input *input,
+ ap_filter_t* f,
+ apr_bucket_brigade* bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_off_t bblen = 0;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
+ "h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld",
+ input->env->id, block, mode, (long)readbytes);
+
+ if (is_aborted(f)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_task_input(%s): is aborted",
+ input->env->id);
+ return APR_ECONNABORTED;
+ }
+
+ if (mode == AP_MODE_INIT) {
+ return APR_SUCCESS;
+ }
+
+ if (input->bb) {
+ status = apr_brigade_length(input->bb, 1, &bblen);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, f->c,
+ APLOGNO(02958) "h2_task_input(%s): brigade length fail",
+ input->env->id);
+ return status;
+ }
+ }
+
+ if ((bblen == 0) && input->env->input_eos) {
+ return APR_EOF;
+ }
+
+ while ((bblen == 0) || (mode == AP_MODE_READBYTES && bblen < readbytes)) {
+ /* Get more data for our stream from mplx.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_task_input(%s): get more data from mplx, block=%d, "
+ "readbytes=%ld, queued=%ld",
+ input->env->id, block,
+ (long)readbytes, (long)bblen);
+
+ /* Although we sometimes get called with APR_NONBLOCK_READs,
+ we seem to fill our buffer blocking. Otherwise we get EAGAIN,
+ return that to our caller and everyone throws up their hands,
+ never calling us again. */
+ status = h2_mplx_in_read(input->env->mplx, APR_BLOCK_READ,
+ input->env->stream_id, input->bb,
+ input->env->io);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_task_input(%s): mplx in read returned",
+ input->env->id);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ status = apr_brigade_length(input->bb, 1, &bblen);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ if ((bblen == 0) && (block == APR_NONBLOCK_READ)) {
+ return h2_util_has_eos(input->bb, 0)? APR_EOF : APR_EAGAIN;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_task_input(%s): mplx in read, %ld bytes in brigade",
+ input->env->id, (long)bblen);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_task_input(%s): read, mode=%d, block=%d, "
+ "readbytes=%ld, queued=%ld",
+ input->env->id, mode, block,
+ (long)readbytes, (long)bblen);
+
+ if (!APR_BRIGADE_EMPTY(input->bb)) {
+ if (mode == AP_MODE_EXHAUSTIVE) {
+ /* return all we have */
+ return h2_util_move(bb, input->bb, readbytes, 0,
+ "task_input_read(exhaustive)");
+ }
+ else if (mode == AP_MODE_READBYTES) {
+ return h2_util_move(bb, input->bb, readbytes, 0,
+ "task_input_read(readbytes)");
+ }
+ else if (mode == AP_MODE_SPECULATIVE) {
+ /* return not more than was asked for */
+ return h2_util_copy(bb, input->bb, readbytes,
+ "task_input_read(speculative)");
+ }
+ else if (mode == AP_MODE_GETLINE) {
+ /* we are reading a single LF line, e.g. the HTTP headers */
+ status = apr_brigade_split_line(bb, 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;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_task_input(%s): getline: %s",
+ input->env->id, buffer);
+ }
+ return status;
+ }
+ 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(02942)
+ "h2_task_input, unsupported READ mode %d", mode);
+ return APR_ENOTIMPL;
+ }
+ }
+
+ if (is_aborted(f)) {
+ return APR_ECONNABORTED;
+ }
+
+ return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_input__
+#define __mod_h2__h2_task_input__
+
+/* h2_task_input places the HEADER+DATA, formatted in HTTP/1.1, into
+ * a bucket brigade. The brigade is setup as the input brigade for our
+ * pseudo httpd conn_rec that is handling a specific h2_task.
+ */
+struct apr_thread_cond_t;
+struct h2_mplx;
+struct h2_task_env;
+
+typedef struct h2_task_input h2_task_input;
+struct h2_task_input {
+ struct h2_task_env *env;
+ apr_bucket_brigade *bb;
+};
+
+
+h2_task_input *h2_task_input_create(struct h2_task_env *env, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc);
+
+void h2_task_input_destroy(h2_task_input *input);
+
+apr_status_t h2_task_input_read(h2_task_input *input,
+ ap_filter_t* filter,
+ apr_bucket_brigade* brigade,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes);
+
+#endif /* defined(__mod_h2__h2_task_input__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_thread_cond.h>
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+
+#include "h2_private.h"
+#include "h2_conn.h"
+#include "h2_mplx.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_from_h1.h"
+#include "h2_response.h"
+#include "h2_task_output.h"
+#include "h2_task.h"
+#include "h2_util.h"
+
+
+h2_task_output *h2_task_output_create(h2_task_env *env, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc)
+{
+ h2_task_output *output = apr_pcalloc(pool, sizeof(h2_task_output));
+
+ (void)bucket_alloc;
+ if (output) {
+ output->env = env;
+ output->state = H2_TASK_OUT_INIT;
+ output->from_h1 = h2_from_h1_create(env->stream_id, pool);
+ if (!output->from_h1) {
+ return NULL;
+ }
+ }
+ return output;
+}
+
+void h2_task_output_destroy(h2_task_output *output)
+{
+ if (output->from_h1) {
+ h2_from_h1_destroy(output->from_h1);
+ output->from_h1 = NULL;
+ }
+}
+
+static apr_status_t open_if_needed(h2_task_output *output, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ if (output->state == H2_TASK_OUT_INIT) {
+ h2_response *response;
+ output->state = H2_TASK_OUT_STARTED;
+ response = h2_from_h1_get_response(output->from_h1);
+ if (!response) {
+ if (f) {
+ /* This happens currently when ap_die(status, r) is invoked
+ * by a read request filter.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
+ "h2_task_output(%s): write without response "
+ "for %s %s %s",
+ output->env->id, output->env->method,
+ output->env->authority, output->env->path);
+ f->c->aborted = 1;
+ }
+ if (output->env->io) {
+ apr_thread_cond_broadcast(output->env->io);
+ }
+ return APR_ECONNABORTED;
+ }
+
+ return h2_mplx_out_open(output->env->mplx, output->env->stream_id,
+ response, f, bb, output->env->io);
+ }
+ return APR_EOF;
+}
+
+void h2_task_output_close(h2_task_output *output)
+{
+ open_if_needed(output, NULL, NULL);
+ if (output->state != H2_TASK_OUT_DONE) {
+ h2_mplx_out_close(output->env->mplx, output->env->stream_id);
+ output->state = H2_TASK_OUT_DONE;
+ }
+}
+
+int h2_task_output_has_started(h2_task_output *output)
+{
+ return output->state >= H2_TASK_OUT_STARTED;
+}
+
+/* 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.
+ */
+apr_status_t h2_task_output_write(h2_task_output *output,
+ ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ apr_status_t status;
+ if (APR_BRIGADE_EMPTY(bb)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_task_output(%s): empty write", output->env->id);
+ return APR_SUCCESS;
+ }
+
+ status = open_if_needed(output, f, bb);
+ if (status != APR_EOF) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_task_output(%s): opened and passed brigade",
+ output->env->id);
+ return status;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_task_output(%s): write brigade", output->env->id);
+ return h2_mplx_out_write(output->env->mplx, output->env->stream_id,
+ f, bb, output->env->io);
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_output__
+#define __mod_h2__h2_task_output__
+
+/* h2_task_output reads a HTTP/1 response from the brigade and applies
+ * them to a h2_output_converter. The brigade is setup as the output brigade
+ * for our pseudo httpd conn_rec that is handling a specific h2_task.
+ *
+ */
+struct apr_thread_cond_t;
+struct h2_mplx;
+struct h2_task_env;
+struct h2_from_h1;
+
+typedef enum {
+ H2_TASK_OUT_INIT,
+ H2_TASK_OUT_STARTED,
+ H2_TASK_OUT_DONE,
+} h2_task_output_state_t;
+
+typedef struct h2_task_output h2_task_output;
+
+struct h2_task_output {
+ struct h2_task_env *env;
+ h2_task_output_state_t state;
+ struct h2_from_h1 *from_h1;
+};
+
+h2_task_output *h2_task_output_create(struct h2_task_env *env, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc);
+
+void h2_task_output_destroy(h2_task_output *output);
+
+apr_status_t h2_task_output_write(h2_task_output *output,
+ ap_filter_t* filter,
+ apr_bucket_brigade* brigade);
+
+void h2_task_output_close(h2_task_output *output);
+
+int h2_task_output_has_started(h2_task_output *output);
+
+#endif /* defined(__mod_h2__h2_task_output__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <httpd.h>
+#include <http_core.h>
+
+#include "h2_task.h"
+#include "h2_task_queue.h"
+
+
+h2_task_queue *h2_tq_create(long id, apr_pool_t *pool)
+{
+ h2_task_queue *q = apr_pcalloc(pool, sizeof(h2_task_queue));
+ if (q) {
+ q->id = id;
+ APR_RING_ELEM_INIT(q, link);
+ APR_RING_INIT(&q->tasks, h2_task, link);
+ }
+ return q;
+}
+
+void h2_tq_destroy(h2_task_queue *q)
+{
+ while (!H2_TASK_LIST_EMPTY(&q->tasks)) {
+ h2_task *task = H2_TASK_LIST_FIRST(&q->tasks);
+ H2_TASK_REMOVE(task);
+ }
+}
+
+static int in_list(h2_task_queue *q, h2_task *task)
+{
+ h2_task *e;
+ for (e = H2_TASK_LIST_FIRST(&q->tasks);
+ e != H2_TASK_LIST_SENTINEL(&q->tasks);
+ e = H2_TASK_NEXT(e)) {
+ if (e == task) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int h2_tq_empty(h2_task_queue *q)
+{
+ return H2_TASK_LIST_EMPTY(&q->tasks);
+}
+
+void h2_tq_append(h2_task_queue *q, struct h2_task *task)
+{
+ H2_TASK_LIST_INSERT_TAIL(&q->tasks, task);
+}
+
+apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task)
+{
+ if (in_list(q, task)) {
+ H2_TASK_REMOVE(task);
+ return APR_SUCCESS;
+ }
+ return APR_NOTFOUND;
+}
+
+h2_task *h2_tq_pop_first(h2_task_queue *q)
+{
+ if (!H2_TASK_LIST_EMPTY(&q->tasks)) {
+ h2_task *task = H2_TASK_LIST_FIRST(&q->tasks);
+ H2_TASK_REMOVE(task);
+ return task;
+ }
+ return NULL;
+}
+
+
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_queue__
+#define __mod_h2__h2_task_queue__
+
+struct h2_task;
+
+/**
+ * A simple ring of rings that keeps a list of h2_tasks and can
+ * be ringed itself, using the APR RING macros.
+ */
+typedef struct h2_task_queue h2_task_queue;
+
+struct h2_task_queue {
+ APR_RING_ENTRY(h2_task_queue) link;
+ APR_RING_HEAD(h2_tasks, h2_task) tasks;
+ long id;
+};
+
+/**
+ * Allocate a new queue from the pool and initialize.
+ * @param id the identifier of the queue
+ * @param pool the memory pool
+ */
+h2_task_queue *h2_tq_create(long id, apr_pool_t *pool);
+
+/**
+ * Release all queue tasks.
+ * @param q the queue to destroy
+ */
+void h2_tq_destroy(h2_task_queue *q);
+
+/**
+ * Return != 0 iff there are no tasks in the queue.
+ * @param q the queue to check
+ */
+int h2_tq_empty(h2_task_queue *q);
+
+/**
+ * Append the task to the end of the queue.
+ * @param q the queue to append the task to
+ * @param task the task to append
+ */
+void h2_tq_append(h2_task_queue *q, struct h2_task *task);
+
+/**
+ * Remove a task from the queue. Return APR_SUCCESS if the task
+ * was indeed queued, APR_NOTFOUND otherwise.
+ * @param q the queue to remove from
+ * @param task the task to remove
+ */
+apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task);
+
+/**
+ * Get the first task from the queue or NULL if the queue is empty. The
+ * task will be removed.
+ * @param q the queue to pop the first task from
+ */
+h2_task *h2_tq_pop_first(h2_task_queue *q);
+
+/*******************************************************************************
+ * Queue Manipulation.
+ ******************************************************************************/
+
+/**
+ * The magic pointer value that indicates the head of a h2_task_queue list
+ * @param b The queue list
+ * @return The magic pointer value
+ */
+#define H2_TQ_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_task_queue, link)
+
+/**
+ * Determine if the queue list is empty
+ * @param b The list to check
+ * @return true or false
+ */
+#define H2_TQ_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_task_queue, link)
+
+/**
+ * Return the first queue in a list
+ * @param b The list to query
+ * @return The first queue in the list
+ */
+#define H2_TQ_LIST_FIRST(b) APR_RING_FIRST(b)
+
+/**
+ * Return the last queue in a list
+ * @param b The list to query
+ * @return The last queue int he list
+ */
+#define H2_TQ_LIST_LAST(b) APR_RING_LAST(b)
+
+/**
+ * Insert a single queue at the front of a list
+ * @param b The list to add to
+ * @param e The queue to insert
+ */
+#define H2_TQ_LIST_INSERT_HEAD(b, e) do { \
+h2_task_queue *ap__b = (e); \
+APR_RING_INSERT_HEAD((b), ap__b, h2_task_queue, link); \
+} while (0)
+
+/**
+ * Insert a single queue at the end of a list
+ * @param b The list to add to
+ * @param e The queue to insert
+ */
+#define H2_TQ_LIST_INSERT_TAIL(b, e) do { \
+h2_task_queue *ap__b = (e); \
+APR_RING_INSERT_TAIL((b), ap__b, h2_task_queue, link); \
+} while (0)
+
+/**
+ * Get the next queue in the list
+ * @param e The current queue
+ * @return The next queue
+ */
+#define H2_TQ_NEXT(e) APR_RING_NEXT((e), link)
+/**
+ * Get the previous queue in the list
+ * @param e The current queue
+ * @return The previous queue
+ */
+#define H2_TQ_PREV(e) APR_RING_PREV((e), link)
+
+/**
+ * Remove a queue from its list
+ * @param e The queue to remove
+ */
+#define H2_TQ_REMOVE(e) APR_RING_REMOVE((e), link)
+
+
+#define H2_TQ_EMPTY(e) H2_TASK_LIST_EMPTY(&(e)->tasks)
+
+#endif /* defined(__mod_h2__h2_task_queue__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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 <stdio.h>
+
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+
+#include "h2_private.h"
+#include "h2_mplx.h"
+#include "h2_response.h"
+#include "h2_task.h"
+#include "h2_to_h1.h"
+#include "h2_util.h"
+
+
+h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc,
+ const char *method,
+ const char *scheme,
+ const char *authority,
+ const char *path,
+ struct h2_mplx *m)
+{
+ h2_to_h1 *to_h1;
+ if (!method) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
+ APLOGNO(02943)
+ "h2_to_h1: header start but :method missing");
+ return NULL;
+ }
+ if (!path) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
+ APLOGNO(02944)
+ "h2_to_h1: header start but :path missing");
+ return NULL;
+ }
+
+ to_h1 = apr_pcalloc(pool, sizeof(h2_to_h1));
+ if (to_h1) {
+ to_h1->stream_id = stream_id;
+ to_h1->pool = pool;
+ to_h1->method = method;
+ to_h1->scheme = scheme;
+ to_h1->authority = authority;
+ to_h1->path = path;
+ to_h1->m = m;
+ to_h1->headers = apr_table_make(to_h1->pool, 10);
+ to_h1->bb = apr_brigade_create(pool, bucket_alloc);
+ to_h1->chunked = 0; /* until we see a content-type and no length */
+ to_h1->content_len = -1;
+ }
+ return to_h1;
+}
+
+void h2_to_h1_destroy(h2_to_h1 *to_h1)
+{
+ to_h1->bb = NULL;
+}
+
+apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen)
+{
+ char *hname, *hvalue;
+ if (H2_HD_MATCH_LIT("transfer-encoding", name, nlen)) {
+ if (!apr_strnatcasecmp("chunked", value)) {
+ /* This should never arrive here in a HTTP/2 request */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_BADARG, to_h1->m->c,
+ APLOGNO(02945)
+ "h2_to_h1: 'transfer-encoding: chunked' received");
+ return APR_BADARG;
+ }
+ }
+ else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
+ char *end;
+ to_h1->content_len = apr_strtoi64(value, &end, 10);
+ if (value == end) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c,
+ APLOGNO(02959)
+ "h2_request(%d): content-length value not parsed: %s",
+ to_h1->stream_id, value);
+ return APR_EINVAL;
+ }
+ to_h1->remain_len = to_h1->content_len;
+ to_h1->chunked = 0;
+ }
+ else if (H2_HD_MATCH_LIT("content-type", name, nlen)) {
+ /* If we see a content-type and have no length (yet),
+ * we need to chunk. */
+ to_h1->chunked = (to_h1->content_len == -1);
+ }
+ else if ((to_h1->seen_host && H2_HD_MATCH_LIT("host", name, nlen))
+ || H2_HD_MATCH_LIT("expect", name, nlen)
+ || H2_HD_MATCH_LIT("upgrade", name, nlen)
+ || H2_HD_MATCH_LIT("connection", name, nlen)
+ || H2_HD_MATCH_LIT("proxy-connection", name, nlen)
+ || H2_HD_MATCH_LIT("keep-alive", name, nlen)
+ || H2_HD_MATCH_LIT("http2-settings", name, nlen)) {
+ /* ignore these. */
+ return APR_SUCCESS;
+ }
+ else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
+ const char *existing = apr_table_get(to_h1->headers, "cookie");
+ if (existing) {
+ char *nval;
+
+ /* Cookie headers come separately in HTTP/2, but need
+ * to be merged by "; " (instead of default ", ")
+ */
+ hvalue = apr_pstrndup(to_h1->pool, value, vlen);
+ nval = apr_psprintf(to_h1->pool, "%s; %s", existing, hvalue);
+ apr_table_setn(to_h1->headers, "Cookie", nval);
+ return APR_SUCCESS;
+ }
+ }
+ else if (H2_HD_MATCH_LIT("host", name, nlen)) {
+ to_h1->seen_host = 1;
+ }
+
+ hname = apr_pstrndup(to_h1->pool, name, nlen);
+ hvalue = apr_pstrndup(to_h1->pool, value, vlen);
+ h2_util_camel_case_header(hname, nlen);
+ apr_table_mergen(to_h1->headers, hname, hvalue);
+
+ return APR_SUCCESS;
+}
+
+static int set_header(void *ctx, const char *key, const char *value)
+{
+ h2_to_h1 *to_h1 = (h2_to_h1*)ctx;
+ h2_to_h1_add_header(to_h1, key, strlen(key), value, strlen(value));
+ return 1;
+}
+
+apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers)
+{
+ apr_table_do(set_header, to_h1, headers, NULL);
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, h2_task *task, int eos)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
+ "h2_to_h1(%ld-%d): end headers",
+ to_h1->m->id, to_h1->stream_id);
+
+ if (to_h1->eoh) {
+ return APR_EINVAL;
+ }
+
+ if (!to_h1->seen_host) {
+ /* Need to add a "Host" header if not already there to
+ * make virtual hosts work correctly. */
+ if (!to_h1->authority) {
+ return APR_BADARG;
+ }
+ apr_table_set(to_h1->headers, "Host", to_h1->authority);
+ }
+
+ if (eos && to_h1->chunked) {
+ /* We had chunking figured out, but the EOS is already there.
+ * unmark chunking and set a definitive content-length.
+ */
+ to_h1->chunked = 0;
+ apr_table_setn(to_h1->headers, "Content-Length", "0");
+ }
+ else if (to_h1->chunked) {
+ /* We have not seen a content-length. We therefore must
+ * pass any request content in chunked form.
+ */
+ apr_table_mergen(to_h1->headers, "Transfer-Encoding", "chunked");
+ }
+
+ h2_task_set_request(task, to_h1->method,
+ to_h1->scheme,
+ to_h1->authority,
+ to_h1->path,
+ to_h1->headers, eos);
+ to_h1->eoh = 1;
+
+ if (eos) {
+ apr_status_t status = h2_to_h1_close(to_h1);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, to_h1->m->c,
+ APLOGNO(02960)
+ "h2_to_h1(%ld-%d): end headers, eos=%d",
+ to_h1->m->id, to_h1->stream_id, eos);
+ }
+ return status;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t flush(apr_bucket_brigade *bb, void *ctx)
+{
+ (void)bb;
+ return h2_to_h1_flush((h2_to_h1*)ctx);
+}
+
+static apr_status_t h2_to_h1_add_data_raw(h2_to_h1 *to_h1,
+ const char *data, size_t len)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ if (to_h1->eos || !to_h1->eoh) {
+ return APR_EINVAL;
+ }
+
+ status = apr_brigade_write(to_h1->bb, flush, to_h1, data, len);
+ if (status == APR_SUCCESS) {
+ status = h2_to_h1_flush(to_h1);
+ }
+ return status;
+}
+
+
+apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
+ const char *data, size_t len)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
+ "h2_to_h1(%ld-%d): add %ld data bytes",
+ to_h1->m->id, to_h1->stream_id, (long)len);
+
+ if (to_h1->chunked) {
+ /* if input may have a body and we have not seen any
+ * content-length header, we need to chunk the input data.
+ */
+ apr_status_t status = apr_brigade_printf(to_h1->bb, NULL, NULL,
+ "%lx\r\n", (unsigned long)len);
+ if (status == APR_SUCCESS) {
+ status = h2_to_h1_add_data_raw(to_h1, data, len);
+ if (status == APR_SUCCESS) {
+ status = apr_brigade_puts(to_h1->bb, NULL, NULL, "\r\n");
+ }
+ }
+ return status;
+ }
+ else {
+ to_h1->remain_len -= len;
+ if (to_h1->remain_len < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, to_h1->m->c,
+ APLOGNO(02961)
+ "h2_to_h1(%ld-%d): got %ld more content bytes than announced "
+ "in content-length header: %ld",
+ to_h1->m->id, to_h1->stream_id,
+ (long)to_h1->content_len, -(long)to_h1->remain_len);
+ }
+ return h2_to_h1_add_data_raw(to_h1, data, len);
+ }
+}
+
+apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1)
+{
+ apr_status_t status = APR_SUCCESS;
+ if (!APR_BRIGADE_EMPTY(to_h1->bb)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
+ "h2_to_h1(%ld-%d): flush request bytes",
+ to_h1->m->id, to_h1->stream_id);
+
+ status = h2_mplx_in_write(to_h1->m, to_h1->stream_id, to_h1->bb);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, to_h1->m->c,
+ APLOGNO(02946) "h2_request(%d): pushing request data",
+ to_h1->stream_id);
+ }
+ }
+ return status;
+}
+
+apr_status_t h2_to_h1_close(h2_to_h1 *to_h1)
+{
+ apr_status_t status = APR_SUCCESS;
+ if (!to_h1->eos) {
+ if (to_h1->chunked) {
+ status = h2_to_h1_add_data_raw(to_h1, "0\r\n\r\n", 5);
+ }
+ to_h1->eos = 1;
+ status = h2_to_h1_flush(to_h1);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
+ "h2_to_h1(%d): close", to_h1->stream_id);
+
+ status = h2_mplx_in_close(to_h1->m, to_h1->stream_id);
+ }
+ return status;
+}
+
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_to_h1__
+#define __mod_h2__h2_to_h1__
+
+struct h2_mplx;
+struct h2_task;
+typedef struct h2_to_h1 h2_to_h1;
+
+struct h2_to_h1 {
+ int stream_id;
+ apr_pool_t *pool;
+ h2_mplx *m;
+
+ const char *method;
+ const char *scheme;
+ const char *authority;
+ const char *path;
+
+ int chunked;
+ int eoh;
+ int eos;
+ int flushed;
+ int seen_host;
+
+ apr_off_t content_len;
+ apr_off_t remain_len;
+ apr_table_t *headers;
+ apr_bucket_brigade *bb;
+};
+
+/* Create a converter from a HTTP/2 request to a serialzation in
+ * HTTP/1.1 format. The serialized data will be written onto the
+ * given h2_mplx instance.
+ */
+h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool,
+ apr_bucket_alloc_t *bucket_alloc,
+ const char *method,
+ const char *scheme,
+ const char *authority,
+ const char *path,
+ struct h2_mplx *m);
+
+/* Destroy the converter and free resources. */
+void h2_to_h1_destroy(h2_to_h1 *to_h1);
+
+/* Add a header to the serialization. Only valid to call after start
+ * and before end_headers.
+ */
+apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen);
+
+apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers);
+
+/** End the request headers.
+ */
+apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1,
+ struct h2_task *task, int eos);
+
+/* Add request body data.
+ */
+apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
+ const char *data, size_t len);
+
+/* Flush the converted data onto the h2_mplx instance.
+ */
+apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1);
+
+/* Close the request, flushed automatically.
+ */
+apr_status_t h2_to_h1_close(h2_to_h1 *to_h1);
+
+#endif /* defined(__mod_h2__h2_to_h1__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_log.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2_private.h"
+#include "h2_util.h"
+
+size_t h2_util_hex_dump(char *buffer, size_t maxlen,
+ const char *data, size_t datalen)
+{
+ size_t offset = 0;
+ size_t maxoffset = (maxlen-4);
+ size_t i;
+ for (i = 0; i < datalen && offset < maxoffset; ++i) {
+ const char *sep = (i && i % 16 == 0)? "\n" : " ";
+ int n = apr_snprintf(buffer+offset, maxoffset-offset,
+ "%2x%s", ((unsigned int)data[i]&0xff), sep);
+ offset += n;
+ }
+ strcpy(buffer+offset, (i<datalen)? "..." : "");
+ return strlen(buffer);
+}
+
+size_t h2_util_header_print(char *buffer, size_t maxlen,
+ const char *name, size_t namelen,
+ const char *value, size_t valuelen)
+{
+ size_t offset = 0;
+ size_t i;
+ for (i = 0; i < namelen && offset < maxlen; ++i, ++offset) {
+ buffer[offset] = name[i];
+ }
+ for (i = 0; i < 2 && offset < maxlen; ++i, ++offset) {
+ buffer[offset] = ": "[i];
+ }
+ for (i = 0; i < valuelen && offset < maxlen; ++i, ++offset) {
+ buffer[offset] = value[i];
+ }
+ buffer[offset] = '\0';
+ return offset;
+}
+
+
+char *h2_strlwr(char *s)
+{
+ char *p;
+ for (p = s; *p; ++p) {
+ if (*p >= 'A' && *p <= 'Z') {
+ *p += 'a' - 'A';
+ }
+ }
+ return s;
+}
+
+void h2_util_camel_case_header(char *s, size_t len)
+{
+ size_t start = 1;
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ if (start) {
+ if (s[i] >= 'a' && s[i] <= 'z') {
+ s[i] -= 'a' - 'A';
+ }
+
+ start = 0;
+ }
+ else if (s[i] == '-') {
+ start = 1;
+ }
+ }
+}
+
+static const int BASE64URL_TABLE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1
+};
+
+apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
+ apr_pool_t *pool)
+{
+ const unsigned char *e = (const unsigned char *)encoded;
+ const unsigned char *p = e;
+ unsigned char *d;
+ int n;
+ apr_size_t len, mlen, remain, i;
+
+ while (*p && BASE64URL_TABLE[ *p ] == -1) {
+ ++p;
+ }
+ len = p - e;
+ mlen = (len/4)*4;
+ *decoded = apr_pcalloc(pool, len+1);
+
+ i = 0;
+ d = (unsigned char*)*decoded;
+ for (; i < mlen; i += 4) {
+ n = ((BASE64URL_TABLE[ e[i+0] ] << 18) +
+ (BASE64URL_TABLE[ e[i+1] ] << 12) +
+ (BASE64URL_TABLE[ e[i+2] ] << 6) +
+ BASE64URL_TABLE[ e[i+3] ]);
+ *d++ = n >> 16;
+ *d++ = n >> 8 & 0xffu;
+ *d++ = n & 0xffu;
+ }
+ remain = len - mlen;
+ switch (remain) {
+ case 2:
+ n = ((BASE64URL_TABLE[ e[mlen+0] ] << 18) +
+ (BASE64URL_TABLE[ e[mlen+1] ] << 12));
+ *d++ = n >> 16;
+ break;
+ case 3:
+ n = ((BASE64URL_TABLE[ e[mlen+0] ] << 18) +
+ (BASE64URL_TABLE[ e[mlen+1] ] << 12) +
+ (BASE64URL_TABLE[ e[mlen+2] ] << 6));
+ *d++ = n >> 16;
+ *d++ = n >> 8 & 0xffu;
+ break;
+ default: /* do nothing */
+ break;
+ }
+ return len;
+}
+
+int h2_util_contains_token(apr_pool_t *pool, const char *s, const char *token)
+{
+ char *c;
+ if (s) {
+ if (!apr_strnatcasecmp(s, token)) { /* the simple life */
+ return 1;
+ }
+
+ for (c = ap_get_token(pool, &s, 0); c && *c;
+ c = *s? ap_get_token(pool, &s, 0) : NULL) {
+ if (!apr_strnatcasecmp(c, token)) { /* seeing the token? */
+ return 1;
+ }
+ while (*s++ == ';') { /* skip parameters */
+ ap_get_token(pool, &s, 0);
+ }
+ if (*s++ != ',') { /* need comma separation */
+ return 0;
+ }
+ }
+ }
+ return 0;
+}
+
+const char *h2_util_first_token_match(apr_pool_t *pool, const char *s,
+ const char *tokens[], apr_size_t len)
+{
+ char *c;
+ apr_size_t i;
+ if (s && *s) {
+ for (c = ap_get_token(pool, &s, 0); c && *c;
+ c = *s? ap_get_token(pool, &s, 0) : NULL) {
+ for (i = 0; i < len; ++i) {
+ if (!apr_strnatcasecmp(c, tokens[i])) {
+ return tokens[i];
+ }
+ }
+ while (*s++ == ';') { /* skip parameters */
+ ap_get_token(pool, &s, 0);
+ }
+ if (*s++ != ',') { /* need comma separation */
+ return 0;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* DEEP_COPY==0 crashes under load. I think the setaside is fine,
+ * however buckets moved to another thread will still be
+ * free'd against the old bucket_alloc. *And* if the old
+ * pool gets destroyed too early, the bucket disappears while
+ * still needed.
+ */
+static const int DEEP_COPY = 1;
+static const int FILE_MOVE = 1;
+
+static apr_status_t last_not_included(apr_bucket_brigade *bb,
+ apr_size_t maxlen,
+ int same_alloc,
+ int *pfile_buckets_allowed,
+ apr_bucket **pend)
+{
+ apr_bucket *b;
+ apr_status_t status = APR_SUCCESS;
+ int files_allowed = pfile_buckets_allowed? *pfile_buckets_allowed : 0;
+
+ if (maxlen > 0) {
+ /* Find the bucket, up to which we reach maxlen/mem bytes */
+ for (b = APR_BRIGADE_FIRST(bb);
+ (b != APR_BRIGADE_SENTINEL(bb));
+ b = APR_BUCKET_NEXT(b)) {
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ /* included */
+ }
+ else {
+ if (maxlen == 0) {
+ *pend = b;
+ return status;
+ }
+
+ if (b->length == ((apr_size_t)-1)) {
+ const char *ign;
+ apr_size_t ilen;
+ status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ 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;
+ }
+ else if (maxlen < b->length) {
+ apr_bucket_split(b, maxlen);
+ maxlen = 0;
+ }
+ else {
+ maxlen -= b->length;
+ }
+ }
+ }
+ }
+ *pend = APR_BRIGADE_SENTINEL(bb);
+ return status;
+}
+
+#define LOG_BUCKETS 0
+#define LOG_LEVEL APLOG_INFO
+
+apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from,
+ apr_size_t maxlen, int *pfile_handles_allowed,
+ const char *msg)
+{
+ apr_status_t status = APR_SUCCESS;
+ int same_alloc;
+
+ AP_DEBUG_ASSERT(to);
+ AP_DEBUG_ASSERT(from);
+ same_alloc = (to->bucket_alloc == from->bucket_alloc);
+
+ if (!FILE_MOVE) {
+ pfile_handles_allowed = NULL;
+ }
+
+ if (!APR_BRIGADE_EMPTY(from)) {
+ apr_bucket *b, *end;
+
+ status = last_not_included(from, maxlen, same_alloc,
+ pfile_handles_allowed, &end);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ while (!APR_BRIGADE_EMPTY(from) && status == APR_SUCCESS) {
+ b = APR_BRIGADE_FIRST(from);
+ if (b == end) {
+ break;
+ }
+
+ if (same_alloc || (b->list == to->bucket_alloc)) {
+ /* both brigades use the same bucket_alloc and auto-cleanups
+ * have the same life time. It's therefore safe to just move
+ * directly. */
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(to, b);
+#if LOG_BUCKETS
+ ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+ "h2_util_move: %s, passed bucket(same bucket_alloc) "
+ "%ld-%ld, type=%s",
+ msg, (long)b->start, (long)b->length,
+ APR_BUCKET_IS_METADATA(b)?
+ (APR_BUCKET_IS_EOS(b)? "EOS":
+ (APR_BUCKET_IS_FLUSH(b)? "FLUSH" : "META")) :
+ (APR_BUCKET_IS_FILE(b)? "FILE" : "DATA"));
+#endif
+ }
+ else if (DEEP_COPY) {
+ /* we have not managed the magic of passing buckets from
+ * one thread to another. Any attempts result in
+ * cleanup of pools scrambling memory.
+ */
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ APR_BRIGADE_INSERT_TAIL(to, apr_bucket_eos_create(to->bucket_alloc));
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ APR_BRIGADE_INSERT_TAIL(to, apr_bucket_flush_create(to->bucket_alloc));
+ }
+ else {
+ /* ignore */
+ }
+ }
+ else if (pfile_handles_allowed
+ && *pfile_handles_allowed > 0
+ && APR_BUCKET_IS_FILE(b)) {
+ /* We do not want to read files when passing buckets, if
+ * we can avoid it. However, what we've come up so far
+ * is not working corrently, resulting either in crashes or
+ * too many open file descriptors.
+ */
+ apr_bucket_file *f = (apr_bucket_file *)b->data;
+ apr_file_t *fd = f->fd;
+ int setaside = (f->readpool != to->p);
+#if LOG_BUCKETS
+ ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+ "h2_util_move: %s, moving FILE bucket %ld-%ld "
+ "from=%lx(p=%lx) to=%lx(p=%lx), setaside=%d",
+ msg, (long)b->start, (long)b->length,
+ (long)from, (long)from->p,
+ (long)to, (long)to->p, setaside);
+#endif
+ if (setaside) {
+ status = apr_file_setaside(&fd, fd, to->p);
+ if (status != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, status, to->p,
+ APLOGNO(02947) "h2_util: %s, setaside FILE",
+ msg);
+ return status;
+ }
+ }
+ apr_brigade_insert_file(to, fd, b->start, b->length,
+ to->p);
+ --(*pfile_handles_allowed);
+ }
+ else {
+ const char *data;
+ apr_size_t len;
+ status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (status == APR_SUCCESS && len > 0) {
+ status = apr_brigade_write(to, NULL, NULL, data, len);
+#if LOG_BUCKETS
+ ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+ "h2_util_move: %s, copied bucket %ld-%ld "
+ "from=%lx(p=%lx) to=%lx(p=%lx)",
+ msg, (long)b->start, (long)b->length,
+ (long)from, (long)from->p,
+ (long)to, (long)to->p);
+#endif
+ }
+ }
+ apr_bucket_delete(b);
+ }
+ else {
+ apr_bucket_setaside(b, to->p);
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(to, b);
+#if LOG_BUCKETS
+ ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+ "h2_util_move: %s, passed setaside bucket %ld-%ld "
+ "from=%lx(p=%lx) to=%lx(p=%lx)",
+ msg, (long)b->start, (long)b->length,
+ (long)from, (long)from->p,
+ (long)to, (long)to->p);
+#endif
+ }
+ }
+ }
+
+ return status;
+}
+
+apr_status_t h2_util_copy(apr_bucket_brigade *to, apr_bucket_brigade *from,
+ apr_size_t maxlen, const char *msg)
+{
+ apr_status_t status = APR_SUCCESS;
+ int same_alloc;
+
+ (void)msg;
+ AP_DEBUG_ASSERT(to);
+ AP_DEBUG_ASSERT(from);
+ same_alloc = (to->bucket_alloc == from->bucket_alloc);
+
+ if (!APR_BRIGADE_EMPTY(from)) {
+ apr_bucket *b, *end, *cpy;
+
+ status = last_not_included(from, maxlen, 0, 0, &end);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ for (b = APR_BRIGADE_FIRST(from);
+ b != APR_BRIGADE_SENTINEL(from) && b != end;
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (same_alloc) {
+ status = apr_bucket_copy(b, &cpy);
+ if (status != APR_SUCCESS) {
+ break;
+ }
+ APR_BRIGADE_INSERT_TAIL(to, cpy);
+ }
+ else {
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ APR_BRIGADE_INSERT_TAIL(to, apr_bucket_eos_create(to->bucket_alloc));
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ APR_BRIGADE_INSERT_TAIL(to, apr_bucket_flush_create(to->bucket_alloc));
+ }
+ else {
+ /* ignore */
+ }
+ }
+ else {
+ const char *data;
+ apr_size_t len;
+ status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (status == APR_SUCCESS && len > 0) {
+ status = apr_brigade_write(to, NULL, NULL, data, len);
+#if LOG_BUCKETS
+ ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+ "h2_util_copy: %s, copied bucket %ld-%ld "
+ "from=%lx(p=%lx) to=%lx(p=%lx)",
+ msg, (long)b->start, (long)b->length,
+ (long)from, (long)from->p,
+ (long)to, (long)to->p);
+#endif
+ }
+ }
+ }
+ }
+ }
+ return status;
+}
+
+int h2_util_has_flush_or_eos(apr_bucket_brigade *bb) {
+ apr_bucket *b;
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int h2_util_has_eos(apr_bucket_brigade *bb, apr_size_t len)
+{
+ apr_bucket *b, *end;
+
+ apr_status_t status = last_not_included(bb, len, 0, 0, &end);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb) && b != end;
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (APR_BUCKET_IS_EOS(b)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int h2_util_bb_has_data(apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (!APR_BUCKET_IS_METADATA(b)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int h2_util_bb_has_data_or_eos(apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ return 1;
+ }
+ }
+ else {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
+ apr_size_t *plen, int *peos)
+{
+ apr_status_t status;
+ /* test read to determine available length */
+ apr_off_t blen = 0;
+ status = apr_brigade_length(bb, 0, &blen);
+ if (blen < (apr_off_t)*plen) {
+ *plen = blen;
+ }
+ *peos = h2_util_has_eos(bb, *plen);
+ return status;
+}
+
+apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb,
+ h2_util_pass_cb *cb, void *ctx,
+ apr_size_t *plen, int *peos)
+{
+ apr_status_t status = APR_SUCCESS;
+ int consume = (cb != NULL);
+ apr_size_t written = 0;
+ apr_size_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 = 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;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_util__
+#define __mod_h2__h2_util__
+
+struct nghttp2_frame;
+
+size_t h2_util_hex_dump(char *buffer, size_t maxlen,
+ const char *data, size_t datalen);
+
+size_t h2_util_header_print(char *buffer, size_t maxlen,
+ const char *name, size_t namelen,
+ const char *value, size_t valuelen);
+
+char *h2_strlwr(char *s);
+
+void h2_util_camel_case_header(char *s, size_t len);
+
+/**
+ * Return != 0 iff the string s contains the token, as specified in
+ * HTTP header syntax, rfc7230.
+ */
+int h2_util_contains_token(apr_pool_t *pool, const char *s, const char *token);
+
+const char *h2_util_first_token_match(apr_pool_t *pool, const char *s,
+ const char *tokens[], apr_size_t len);
+
+/**
+ * I always wanted to write my own base64url decoder...not. See
+ * https://tools.ietf.org/html/rfc4648#section-5 for description.
+ */
+apr_size_t h2_util_base64url_decode(const char **decoded,
+ const char *encoded,
+ apr_pool_t *pool);
+
+#define H2_HD_MATCH_LIT(l, name, nlen) \
+ ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+
+#define H2_HD_MATCH_LIT_CS(l, name) \
+ ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+
+#define H2_CREATE_NV_LIT_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME; \
+ nv->namelen = sizeof(NAME) - 1; \
+ nv->value = (uint8_t *)VALUE; \
+ nv->valuelen = strlen(VALUE)
+
+#define H2_CREATE_NV_CS_LIT(nv, NAME, VALUE) nv->name = (uint8_t *)NAME; \
+ nv->namelen = strlen(NAME); \
+ nv->value = (uint8_t *)VALUE; \
+ nv->valuelen = sizeof(VALUE) - 1
+
+#define H2_CREATE_NV_CS_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME; \
+ nv->namelen = strlen(NAME); \
+ nv->value = (uint8_t *)VALUE; \
+ nv->valuelen = strlen(VALUE)
+
+/**
+ * Moves data from one brigade into another. If maxlen > 0, it only
+ * moves up to maxlen bytes into the target brigade, making bucket splits
+ * if needed.
+ * @param to the brigade to move the data to
+ * @param from the brigade to get the data from
+ * @param maxlen of bytes to move, 0 for all
+ * @param pfile_buckets_allowed how many file buckets may be moved,
+ * may be 0 or NULL
+ * @param msg message for use in logging
+ */
+apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from,
+ apr_size_t maxlen, int *pfile_buckets_allowed,
+ const char *msg);
+
+/**
+ * Copies buckets from one brigade into another. If maxlen > 0, it only
+ * copies up to maxlen bytes into the target brigade, making bucket splits
+ * if needed.
+ * @param to the brigade to copy the data to
+ * @param from the brigade to get the data from
+ * @param maxlen of bytes to copy, 0 for all
+ * @param msg message for use in logging
+ */
+apr_status_t h2_util_copy(apr_bucket_brigade *to, apr_bucket_brigade *from,
+ apr_size_t maxlen, const char *msg);
+
+/**
+ * Return != 0 iff there is a FLUSH or EOS bucket in the brigade.
+ * @param bb the brigade to check on
+ * @return != 0 iff brigade holds FLUSH or EOS bucket (or both)
+ */
+int h2_util_has_flush_or_eos(apr_bucket_brigade *bb);
+int h2_util_has_eos(apr_bucket_brigade *bb, apr_size_t len);
+int h2_util_bb_has_data(apr_bucket_brigade *bb);
+int h2_util_bb_has_data_or_eos(apr_bucket_brigade *bb);
+
+/**
+ * Check how many bytes of the desired amount are available and if the
+ * end of stream is reached by that amount.
+ * @param bb the brigade to check
+ * @param plen the desired length and, on return, the available length
+ * @param on return, if eos has been reached
+ */
+apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
+ apr_size_t *plen, int *peos);
+
+typedef apr_status_t h2_util_pass_cb(void *ctx,
+ const char *data, apr_size_t len);
+
+apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb,
+ h2_util_pass_cb *cb, void *ctx,
+ apr_size_t *plen, int *peos);
+
+#endif /* defined(__mod_h2__h2_util__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+*
+* Licensed 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_version_h
+#define mod_h2_h2_version_h
+
+/**
+ * @macro
+ * Version number of the h2 module as c string
+ */
+#define MOD_H2_VERSION "0.9.7"
+
+/**
+ * @macro
+ * Numerical representation of the version number of the h2 module
+ * 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_H2_VERSION_NUM 0x000907
+
+
+#endif /* mod_h2_h2_version_h */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_thread_cond.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_mplx.h"
+#include "h2_task.h"
+#include "h2_worker.h"
+
+static void* APR_THREAD_FUNC execute(apr_thread_t *thread, void *wctx)
+{
+ h2_worker *worker = (h2_worker *)wctx;
+ apr_status_t status = APR_SUCCESS;
+ h2_mplx *m;
+ (void)thread;
+
+ /* Furthermore, other code might want to see the socket for
+ * this connection. Allocate one without further function...
+ */
+ status = apr_socket_create(&worker->socket,
+ APR_INET, SOCK_STREAM,
+ APR_PROTO_TCP, worker->pool);
+ if (status != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, status, worker->pool,
+ APLOGNO(02948) "h2_worker(%d): alloc socket",
+ worker->id);
+ worker->worker_done(worker, worker->ctx);
+ return NULL;
+ }
+
+ worker->task = NULL;
+ m = NULL;
+ while (!worker->aborted) {
+ status = worker->get_next(worker, &m, &worker->task, worker->ctx);
+
+ if (worker->task) {
+ h2_task_do(worker->task, worker);
+ worker->task = NULL;
+ apr_thread_cond_signal(h2_worker_get_cond(worker));
+ }
+ }
+
+ status = worker->get_next(worker, &m, NULL, worker->ctx);
+ m = NULL;
+
+ if (worker->socket) {
+ apr_socket_close(worker->socket);
+ worker->socket = NULL;
+ }
+
+ worker->worker_done(worker, worker->ctx);
+ return NULL;
+}
+
+h2_worker *h2_worker_create(int id,
+ apr_pool_t *parent_pool,
+ apr_threadattr_t *attr,
+ h2_worker_mplx_next_fn *get_next,
+ h2_worker_done_fn *worker_done,
+ void *ctx)
+{
+ apr_allocator_t *allocator = NULL;
+ apr_pool_t *pool = NULL;
+ h2_worker *w;
+
+ apr_status_t status = apr_allocator_create(&allocator);
+ if (status != APR_SUCCESS) {
+ return NULL;
+ }
+
+ status = apr_pool_create_ex(&pool, parent_pool, NULL, allocator);
+ if (status != APR_SUCCESS) {
+ return NULL;
+ }
+ apr_allocator_owner_set(allocator, pool);
+
+ w = apr_pcalloc(pool, sizeof(h2_worker));
+ if (w) {
+ APR_RING_ELEM_INIT(w, link);
+
+ w->id = id;
+ w->pool = pool;
+ w->bucket_alloc = apr_bucket_alloc_create(pool);
+
+ w->get_next = get_next;
+ w->worker_done = worker_done;
+ w->ctx = ctx;
+
+ status = apr_thread_cond_create(&w->io, w->pool);
+ if (status != APR_SUCCESS) {
+ return NULL;
+ }
+
+ apr_thread_create(&w->thread, attr, execute, w, pool);
+ }
+ return w;
+}
+
+apr_status_t h2_worker_destroy(h2_worker *worker)
+{
+ if (worker->io) {
+ apr_thread_cond_destroy(worker->io);
+ worker->io = NULL;
+ }
+ if (worker->pool) {
+ apr_pool_destroy(worker->pool);
+ /* worker is gone */
+ }
+ return APR_SUCCESS;
+}
+
+int h2_worker_get_id(h2_worker *worker)
+{
+ return worker->id;
+}
+
+void h2_worker_abort(h2_worker *worker)
+{
+ worker->aborted = 1;
+}
+
+int h2_worker_is_aborted(h2_worker *worker)
+{
+ return worker->aborted;
+}
+
+apr_thread_t *h2_worker_get_thread(h2_worker *worker)
+{
+ return worker->thread;
+}
+
+apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker)
+{
+ return worker->io;
+}
+
+apr_socket_t *h2_worker_get_socket(h2_worker *worker)
+{
+ return worker->socket;
+}
+
+apr_pool_t *h2_worker_get_pool(h2_worker *worker)
+{
+ return worker->pool;
+}
+
+apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker)
+{
+ return worker->bucket_alloc;
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_worker__
+#define __mod_h2__h2_worker__
+
+struct apr_thread_cond_t;
+struct h2_mplx;
+struct h2_task;
+
+/* h2_worker is a basically a apr_thread_t that reads fromt he h2_workers
+ * task queue and runs h2_tasks it is given.
+ */
+typedef struct h2_worker h2_worker;
+
+/* Invoked when the worker wants a new task to process. Will block
+ * until a h2_mplx becomes available or the worker itself
+ * gets aborted (idle timeout, for example). */
+typedef apr_status_t h2_worker_mplx_next_fn(h2_worker *worker,
+ struct h2_mplx **pm,
+ struct h2_task **ptask,
+ void *ctx);
+
+/* Invoked just before the worker thread exits. */
+typedef void h2_worker_done_fn(h2_worker *worker, void *ctx);
+
+
+struct h2_worker {
+ /** Links to the rest of the workers */
+ APR_RING_ENTRY(h2_worker) link;
+
+ int id;
+ apr_thread_t *thread;
+ apr_pool_t *pool;
+ apr_bucket_alloc_t *bucket_alloc;
+ struct apr_thread_cond_t *io;
+ apr_socket_t *socket;
+
+ h2_worker_mplx_next_fn *get_next;
+ h2_worker_done_fn *worker_done;
+ void *ctx;
+
+ int aborted;
+ struct h2_task *task;
+};
+
+/**
+ * The magic pointer value that indicates the head of a h2_worker list
+ * @param b The worker list
+ * @return The magic pointer value
+ */
+#define H2_WORKER_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_worker, link)
+
+/**
+ * Determine if the worker list is empty
+ * @param b The list to check
+ * @return true or false
+ */
+#define H2_WORKER_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_worker, link)
+
+/**
+ * Return the first worker in a list
+ * @param b The list to query
+ * @return The first worker in the list
+ */
+#define H2_WORKER_LIST_FIRST(b) APR_RING_FIRST(b)
+
+/**
+ * Return the last worker in a list
+ * @param b The list to query
+ * @return The last worker int he list
+ */
+#define H2_WORKER_LIST_LAST(b) APR_RING_LAST(b)
+
+/**
+ * Insert a single worker at the front of a list
+ * @param b The list to add to
+ * @param e The worker to insert
+ */
+#define H2_WORKER_LIST_INSERT_HEAD(b, e) do { \
+ h2_worker *ap__b = (e); \
+ APR_RING_INSERT_HEAD((b), ap__b, h2_worker, link); \
+ } while (0)
+
+/**
+ * Insert a single worker at the end of a list
+ * @param b The list to add to
+ * @param e The worker to insert
+ */
+#define H2_WORKER_LIST_INSERT_TAIL(b, e) do { \
+ h2_worker *ap__b = (e); \
+ APR_RING_INSERT_TAIL((b), ap__b, h2_worker, link); \
+ } while (0)
+
+/**
+ * Get the next worker in the list
+ * @param e The current worker
+ * @return The next worker
+ */
+#define H2_WORKER_NEXT(e) APR_RING_NEXT((e), link)
+/**
+ * Get the previous worker in the list
+ * @param e The current worker
+ * @return The previous worker
+ */
+#define H2_WORKER_PREV(e) APR_RING_PREV((e), link)
+
+/**
+ * Remove a worker from its list
+ * @param e The worker to remove
+ */
+#define H2_WORKER_REMOVE(e) APR_RING_REMOVE((e), link)
+
+
+/* Create a new worker with given id, pool and attributes, callbacks
+ * callback parameter.
+ */
+h2_worker *h2_worker_create(int id,
+ apr_pool_t *pool,
+ apr_threadattr_t *attr,
+ h2_worker_mplx_next_fn *get_next,
+ h2_worker_done_fn *worker_done,
+ void *ctx);
+
+apr_status_t h2_worker_destroy(h2_worker *worker);
+
+void h2_worker_abort(h2_worker *worker);
+
+int h2_worker_get_id(h2_worker *worker);
+
+int h2_worker_is_aborted(h2_worker *worker);
+
+apr_pool_t *h2_worker_get_pool(h2_worker *worker);
+
+apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker);
+
+apr_socket_t *h2_worker_get_socket(h2_worker *worker);
+
+apr_thread_t *h2_worker_get_thread(h2_worker *worker);
+
+struct apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker);
+
+#endif /* defined(__mod_h2__h2_worker__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_atomic.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_mplx.h"
+#include "h2_task.h"
+#include "h2_task_queue.h"
+#include "h2_worker.h"
+#include "h2_workers.h"
+
+static int in_list(h2_workers *workers, h2_mplx *m)
+{
+ h2_mplx *e;
+ for (e = H2_MPLX_LIST_FIRST(&workers->mplxs);
+ e != H2_MPLX_LIST_SENTINEL(&workers->mplxs);
+ e = H2_MPLX_NEXT(e)) {
+ if (e == m) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Get the next task for the given worker. Will block until a task arrives
+ * or the max_wait timer expires and more than min workers exist.
+ * The previous h2_mplx instance might be passed in and will be served
+ * with preference, since we can ask it for the next task without aquiring
+ * the h2_workers lock.
+ */
+static apr_status_t get_mplx_next(h2_worker *worker, h2_mplx **pm,
+ h2_task **ptask, void *ctx)
+{
+ apr_status_t status;
+ h2_mplx *m = NULL;
+ h2_task *task = NULL;
+ apr_time_t max_wait, start_wait;
+ int has_more = 0;
+ h2_workers *workers = (h2_workers *)ctx;
+
+ if (*pm && ptask != NULL) {
+ /* We have a h2_mplx instance and the worker wants the next task.
+ * Try to get one from the given mplx. */
+ *ptask = h2_mplx_pop_task(*pm, &has_more);
+ if (*ptask) {
+ return APR_SUCCESS;
+ }
+ }
+
+ if (*pm) {
+ /* Got a mplx handed in, but did not get or want a task from it.
+ * Release it, as the workers reference will be wiped.
+ */
+ h2_mplx_release(*pm);
+ *pm = NULL;
+ }
+
+ if (!ptask) {
+ /* the worker does not want a next task, we're done.
+ */
+ return APR_SUCCESS;
+ }
+
+ max_wait = apr_time_from_sec(apr_atomic_read32(&workers->max_idle_secs));
+ start_wait = apr_time_now();
+
+ status = apr_thread_mutex_lock(workers->lock);
+ if (status == APR_SUCCESS) {
+ ++workers->idle_worker_count;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_worker(%d): looking for work", h2_worker_get_id(worker));
+
+ while (!task && !h2_worker_is_aborted(worker) && !workers->aborted) {
+
+ /* Get the next h2_mplx to process that has a task to hand out.
+ * If it does, place it at the end of the queu and return the
+ * task to the worker.
+ * If it (currently) has no tasks, remove it so that it needs
+ * to register again for scheduling.
+ * If we run out of h2_mplx in the queue, we need to wait for
+ * new mplx to arrive. Depending on how many workers do exist,
+ * we do a timed wait or block indefinitely.
+ */
+ m = NULL;
+ while (!task && !H2_MPLX_LIST_EMPTY(&workers->mplxs)) {
+ m = H2_MPLX_LIST_FIRST(&workers->mplxs);
+ H2_MPLX_REMOVE(m);
+
+ task = h2_mplx_pop_task(m, &has_more);
+ if (task) {
+ if (has_more) {
+ H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
+ }
+ else {
+ has_more = !H2_MPLX_LIST_EMPTY(&workers->mplxs);
+ }
+ break;
+ }
+ }
+
+ if (!task) {
+ /* Need to wait for either a new mplx to arrive.
+ */
+ if (workers->worker_count > workers->min_size) {
+ apr_time_t now = apr_time_now();
+ if (now >= (start_wait + max_wait)) {
+ /* waited long enough without getting a task. */
+ status = APR_TIMEUP;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_worker(%d): waiting signal, "
+ "worker_count=%d", worker->id,
+ (int)workers->worker_count);
+ status = apr_thread_cond_timedwait(workers->mplx_added,
+ workers->lock, max_wait);
+ }
+
+ if (status == APR_TIMEUP) {
+ /* waited long enough */
+ if (workers->worker_count > workers->min_size) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0,
+ workers->s,
+ "h2_workers: aborting idle worker");
+ h2_worker_abort(worker);
+ break;
+ }
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_worker(%d): waiting signal (eternal), "
+ "worker_count=%d", worker->id,
+ (int)workers->worker_count);
+ apr_thread_cond_wait(workers->mplx_added, workers->lock);
+ }
+ }
+ }
+
+ /* Here, we either have gotten task and mplx for the worker or
+ * needed to give up with more than enough workers.
+ */
+ if (task) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_worker(%d): start task(%s)",
+ h2_worker_get_id(worker), task->id);
+ /* Since we hand out a reference to the worker, we increase
+ * its ref count.
+ */
+ h2_mplx_reference(m);
+ *pm = m;
+ *ptask = task;
+
+ if (has_more && workers->idle_worker_count > 1) {
+ apr_thread_cond_signal(workers->mplx_added);
+ }
+ status = APR_SUCCESS;
+ }
+ else {
+ status = APR_EOF;
+ }
+
+ --workers->idle_worker_count;
+ apr_thread_mutex_unlock(workers->lock);
+ }
+
+ return status;
+}
+
+static void worker_done(h2_worker *worker, void *ctx)
+{
+ h2_workers *workers = (h2_workers *)ctx;
+ apr_status_t status = apr_thread_mutex_lock(workers->lock);
+ if (status == APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_worker(%d): done", h2_worker_get_id(worker));
+ H2_WORKER_REMOVE(worker);
+ --workers->worker_count;
+ h2_worker_destroy(worker);
+
+ apr_thread_mutex_unlock(workers->lock);
+ }
+}
+
+
+static apr_status_t add_worker(h2_workers *workers)
+{
+ h2_worker *w = h2_worker_create(workers->next_worker_id++,
+ workers->pool, workers->thread_attr,
+ get_mplx_next, worker_done, workers);
+ if (!w) {
+ return APR_ENOMEM;
+ }
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
+ "h2_workers: adding worker(%d)", h2_worker_get_id(w));
+ ++workers->worker_count;
+ H2_WORKER_LIST_INSERT_TAIL(&workers->workers, w);
+ return APR_SUCCESS;
+}
+
+static apr_status_t h2_workers_start(h2_workers *workers) {
+ apr_status_t status = apr_thread_mutex_lock(workers->lock);
+ if (status == APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: starting");
+
+ while (workers->worker_count < workers->min_size
+ && status == APR_SUCCESS) {
+ status = add_worker(workers);
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ }
+ return status;
+}
+
+h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
+ int min_size, int max_size)
+{
+ apr_status_t status;
+ h2_workers *workers;
+ AP_DEBUG_ASSERT(s);
+ AP_DEBUG_ASSERT(pool);
+ status = APR_SUCCESS;
+
+ workers = apr_pcalloc(pool, sizeof(h2_workers));
+ if (workers) {
+ workers->s = s;
+ workers->pool = pool;
+ workers->min_size = min_size;
+ workers->max_size = max_size;
+ apr_atomic_set32(&workers->max_idle_secs, 10);
+
+ apr_threadattr_create(&workers->thread_attr, workers->pool);
+
+ APR_RING_INIT(&workers->workers, h2_worker, link);
+ APR_RING_INIT(&workers->mplxs, h2_mplx, link);
+
+ status = apr_thread_mutex_create(&workers->lock,
+ APR_THREAD_MUTEX_DEFAULT,
+ workers->pool);
+ if (status == APR_SUCCESS) {
+ status = apr_thread_cond_create(&workers->mplx_added, workers->pool);
+ }
+
+ if (status == APR_SUCCESS) {
+ status = h2_workers_start(workers);
+ }
+
+ if (status != APR_SUCCESS) {
+ h2_workers_destroy(workers);
+ workers = NULL;
+ }
+ }
+ return workers;
+}
+
+void h2_workers_destroy(h2_workers *workers)
+{
+ if (workers->mplx_added) {
+ apr_thread_cond_destroy(workers->mplx_added);
+ workers->mplx_added = NULL;
+ }
+ if (workers->lock) {
+ apr_thread_mutex_destroy(workers->lock);
+ workers->lock = NULL;
+ }
+ while (!H2_MPLX_LIST_EMPTY(&workers->mplxs)) {
+ h2_mplx *m = H2_MPLX_LIST_FIRST(&workers->mplxs);
+ H2_MPLX_REMOVE(m);
+ }
+ while (!H2_WORKER_LIST_EMPTY(&workers->workers)) {
+ h2_worker *w = H2_WORKER_LIST_FIRST(&workers->workers);
+ H2_WORKER_REMOVE(w);
+ }
+}
+
+apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
+{
+ apr_status_t status = apr_thread_mutex_lock(workers->lock);
+ if (status == APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, status, workers->s,
+ "h2_workers: register mplx(%ld)", m->id);
+ if (in_list(workers, m)) {
+ status = APR_EAGAIN;
+ }
+ else {
+ H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
+ status = APR_SUCCESS;
+ }
+
+ if (workers->idle_worker_count > 0) {
+ apr_thread_cond_signal(workers->mplx_added);
+ }
+ else if (workers->worker_count < workers->max_size) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_workers: got %d worker, adding 1",
+ workers->worker_count);
+ add_worker(workers);
+ }
+
+ apr_thread_mutex_unlock(workers->lock);
+ }
+ return status;
+}
+
+apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m)
+{
+ apr_status_t status = apr_thread_mutex_lock(workers->lock);
+ if (status == APR_SUCCESS) {
+ status = APR_EAGAIN;
+ if (in_list(workers, m)) {
+ H2_MPLX_REMOVE(m);
+ status = APR_SUCCESS;
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ }
+ return status;
+}
+
+void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs)
+{
+ if (idle_secs <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
+ APLOGNO(02962) "h2_workers: max_worker_idle_sec value of %d"
+ " is not valid, ignored.", idle_secs);
+ return;
+ }
+ apr_atomic_set32(&workers->max_idle_secs, idle_secs);
+}
+
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+
+ *
+ * Licensed 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_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.
+ *
+ */
+struct apr_thread_mutex_t;
+struct apr_thread_cond_t;
+struct h2_mplx;
+struct h2_task;
+struct h2_task_queue;
+
+typedef struct h2_workers h2_workers;
+
+struct h2_workers {
+ server_rec *s;
+ apr_pool_t *pool;
+ int aborted;
+
+ int next_worker_id;
+ int min_size;
+ int max_size;
+
+ apr_threadattr_t *thread_attr;
+
+ APR_RING_HEAD(h2_worker_list, h2_worker) workers;
+ APR_RING_HEAD(h2_mplx_list, h2_mplx) mplxs;
+
+ int worker_count;
+ volatile apr_uint32_t max_idle_secs;
+ volatile apr_uint32_t idle_worker_count;
+
+ struct apr_thread_mutex_t *lock;
+ struct apr_thread_cond_t *mplx_added;
+};
+
+
+/* Create a worker pool with the given minimum and maximum number of
+ * threads.
+ */
+h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
+ int min_size, int max_size);
+
+/* Destroy the worker pool and all its threads.
+ */
+void h2_workers_destroy(h2_workers *workers);
+
+/**
+ * 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.
+ */
+apr_status_t h2_workers_register(h2_workers *workers,
+ struct h2_mplx *m);
+
+/**
+ * Remove a h2_mplx from the worker registry.
+ */
+apr_status_t h2_workers_unregister(h2_workers *workers,
+ struct h2_mplx *m);
+
+/**
+ * Set the amount of seconds a h2_worker should wait for new tasks
+ * before shutting down (if there are more than the minimum number of
+ * workers).
+ */
+void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs);
+
+#endif /* defined(__mod_h2__h2_workers__) */
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_optional.h>
+#include <apr_optional_hooks.h>
+#include <apr_want.h>
+
+#include <httpd.h>
+#include <http_log.h>
+
+#include "mod_h2.h"
+
+#include <nghttp2/nghttp2.h>
+#include "h2_stream.h"
+#include "h2_alt_svc.h"
+#include "h2_conn.h"
+#include "h2_task.h"
+#include "h2_session.h"
+#include "h2_config.h"
+#include "h2_ctx.h"
+#include "h2_h2.h"
+#include "h2_switch.h"
+#include "h2_version.h"
+
+
+static void h2_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(h2) = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ h2_config_create_svr, /* func to create per server config */
+ h2_config_merge, /* func to merge per server config */
+ h2_cmds, /* command handlers */
+ h2_hooks
+};
+
+/* The module initialization. Called once as apache hook, before any multi
+ * processing (threaded or not) happens. It is typically at least called twice,
+ * see
+ * http://wiki.apache.org/httpd/ModuleLife
+ * Since the first run is just a "practise" run, we want to initialize for real
+ * only on the second try. This defeats the purpose of the first dry run a bit,
+ * since apache wants to verify that a new configuration actually will work.
+ * So if we have trouble with the configuration, this will only be detected
+ * when the server has already switched.
+ * On the other hand, when we initialize lib nghttp2, all possible crazy things
+ * might happen and this might even eat threads. So, better init on the real
+ * invocation, for now at least.
+ */
+static int h2_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ void *data = NULL;
+ const char *mod_h2_init_key = "mod_h2_init_counter";
+ nghttp2_info *ngh2;
+ apr_status_t status;
+ (void)plog;(void)ptemp;
+
+ apr_pool_userdata_get(&data, mod_h2_init_key, s->process->pool);
+ if ( data == NULL ) {
+ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s,
+ "initializing post config dry run");
+ apr_pool_userdata_set((const void *)1, mod_h2_init_key,
+ apr_pool_cleanup_null, s->process->pool);
+ return APR_SUCCESS;
+ }
+
+ ngh2 = nghttp2_version(0);
+ ap_log_error( APLOG_MARK, APLOG_INFO, 0, s,
+ "mod_h2 (v%s, nghttp2 %s), initializing...",
+ MOD_H2_VERSION, ngh2? ngh2->version_str : "unknown");
+
+ switch (h2_conn_mpm_type()) {
+ case H2_MPM_EVENT:
+ case H2_MPM_WORKER:
+ /* all fine, we know these ones */
+ break;
+ case H2_MPM_PREFORK:
+ /* ok, we now know how to handle that one */
+ break;
+ case H2_MPM_UNKNOWN:
+ /* ??? */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "post_config: mpm type unknown");
+ break;
+ }
+
+ status = h2_h2_init(p, s);
+ if (status == APR_SUCCESS) {
+ status = h2_switch_init(p, s);
+ }
+
+ return status;
+}
+
+/* Runs once per created child process. Perform any process
+ * related initionalization here.
+ */
+static void h2_child_init(apr_pool_t *pool, server_rec *s)
+{
+ /* Set up our connection processing */
+ apr_status_t status = h2_conn_child_init(pool, s);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
+ APLOGNO(02949) "initializing connection handling");
+ }
+}
+
+/* Install this module into the apache2 infrastructure.
+ */
+static void h2_hooks(apr_pool_t *pool)
+{
+ static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
+
+ ap_log_perror(APLOG_MARK, APLOG_INFO, 0, pool, "installing hooks");
+
+ /* Run once after configuration is set, but before mpm children initialize.
+ */
+ ap_hook_post_config(h2_post_config, mod_ssl, NULL, APR_HOOK_MIDDLE);
+
+ /* Run once after a child process has been created.
+ */
+ ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+
+ h2_h2_register_hooks();
+ h2_switch_register_hooks();
+ h2_task_register_hooks();
+
+ h2_alt_svc_register_hooks();
+
+}
+
+
--- /dev/null
+# Microsoft Developer Studio Project File - Name="mod_h2" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_h2 - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_h2.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_h2.mak" CFG="mod_h2 - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_h2 - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_h2 - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_h2 - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Release\mod_h2_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_h2.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_h2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /out:".\Release\mod_h2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_h2.so
+# ADD LINK32 kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Release\mod_h2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_h2.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_h2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_h2 - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Debug\mod_h2_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_h2.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_h2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Debug\mod_h2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_h2.so
+# ADD LINK32 kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Debug\mod_h2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_h2.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_h2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_h2 - Win32 Release"
+# Name "mod_h2 - Win32 Debug"
+# Begin Source File
+
+SOURCE=./h2_alt_svc.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_config.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_conn.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_from_h1.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_h2.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_io.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_io_set.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_mplx.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_request.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_response.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_session.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_stream.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_stream_set.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_switch.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_task.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_task_input.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_task_output.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_task_queue.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_to_h1.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_util.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_worker.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_workers.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_workers.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_h2.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_mod_h2_h
+#define mod_h2_mod_h2_h
+
+#endif
SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH);
SSL_CTX_set_info_callback(ctx, ssl_callback_Info);
+
+#ifdef HAVE_TLS_ALPN
+ SSL_CTX_set_alpn_select_cb(ctx, ssl_callback_alpn_select, NULL);
+#endif
}
static apr_status_t ssl_init_ctx_verify(server_rec *s,
apr_pool_t *pool;
char buffer[AP_IOBUFSIZE];
ssl_filter_ctx_t *filter_ctx;
+#ifdef HAVE_TLS_ALPN
+ int alpn_finished; /* 1 if ALPN has finished, 0 otherwise */
+#endif
} bio_filter_in_ctx_t;
/*
APR_BRIGADE_INSERT_TAIL(bb, bucket);
}
+#ifdef HAVE_TLS_ALPN
+ /* By this point, Application-Layer Protocol Negotiation (ALPN) should be
+ * completed (if our version of OpenSSL supports it). If we haven't already,
+ * find out which protocol was decided upon and inform other modules
+ * by calling alpn_proto_negotiated_hook.
+ */
+ if (!inctx->alpn_finished) {
+ SSLConnRec *sslconn = myConnConfig(f->c);
+ const unsigned char *next_proto = NULL;
+ unsigned next_proto_len = 0;
+ const char *protocol;
+ int n;
+
+ SSL_get0_alpn_selected(inctx->ssl, &next_proto, &next_proto_len);
+ if (next_proto && next_proto_len) {
+ protocol = apr_pstrmemdup(f->c->pool, (const char *)next_proto,
+ next_proto_len);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c,
+ APLOGNO(02836) "ALPN selected protocol: '%s'",
+ protocol);
+
+ if (strcmp(protocol, ap_get_protocol(f->c))) {
+ status = ap_switch_protocol(f->c, NULL, sslconn->server,
+ protocol);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, f->c,
+ APLOGNO(02908) "protocol switch to '%s' failed",
+ protocol);
+ return status;
+ }
+ }
+ }
+ inctx->alpn_finished = 1;
+ }
+#endif
+
return APR_SUCCESS;
}
inctx->block = APR_BLOCK_READ;
inctx->pool = c->pool;
inctx->filter_ctx = filter_ctx;
+#ifdef HAVE_TLS_ALPN
+ inctx->alpn_finished = 0;
+#endif
}
/* The request_rec pointer is passed in here only to ensure that the
if (rv != APR_SUCCESS || scope_id) {
return HTTP_BAD_REQUEST;
}
- if (strcasecmp(host, servername)) {
+ if (strcasecmp(host, servername)
+ || !sslconn->server
+ || !ssl_util_vhost_matches(host, sslconn->server)) {
+ /*
+ * We are really not in Kansas anymore...
+ * The request hostname does not match the SNI and does not
+ * select the virtual host that was selected by the SNI.
+ */
ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02032)
- "Hostname %s provided via SNI and hostname %s provided"
- " via HTTP are different", servername, host);
- return HTTP_BAD_REQUEST;
+ "Hostname %s provided via SNI and hostname %s provided"
+ " via HTTP are different", servername, host);
+ return HTTP_MISDIRECTED_REQUEST;
}
}
else if (((sc->strict_sni_vhost_check == SSL_ENABLED_TRUE)
#ifdef HAVE_TLSEXT
/*
- * This callback function is executed when OpenSSL encounters an extended
+ * This function sets the virtual host from an extended
* client hello with a server name indication extension ("SNI", cf. RFC 6066).
*/
-int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
+static apr_status_t init_vhost(conn_rec *c, SSL *ssl)
{
- const char *servername =
- SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
- conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
-
+ const char *servername;
+
if (c) {
+ SSLConnRec *sslcon = myConnConfig(c);
+
+ if (sslcon->server != c->base_server) {
+ /* already found the vhost */
+ return APR_SUCCESS;
+ }
+
+ servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (servername) {
if (ap_vhost_iterate_given_conn(c, ssl_find_vhost,
(void *)servername)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043)
"SSL virtual host for servername %s found",
servername);
- return SSL_TLSEXT_ERR_OK;
+ return APR_SUCCESS;
}
else {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044)
"(using default/first virtual host)");
}
}
+
+ return APR_NOTFOUND;
+}
- return SSL_TLSEXT_ERR_NOACK;
+/*
+ * This callback function is executed when OpenSSL encounters an extended
+ * client hello with a server name indication extension ("SNI", cf. RFC 6066).
+ */
+int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ apr_status_t status = init_vhost(c, ssl);
+
+ return (status == APR_SUCCESS)? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_NOACK;
}
/*
{
SSLSrvConfigRec *sc;
SSL *ssl;
- BOOL found = FALSE;
- apr_array_header_t *names;
- int i;
+ BOOL found;
SSLConnRec *sslcon;
- /* check ServerName */
- if (!strcasecmp(servername, s->server_hostname)) {
- found = TRUE;
- }
-
- /*
- * if not matched yet, check ServerAlias entries
- * (adapted from vhost.c:matches_aliases())
- */
- if (!found) {
- names = s->names;
- if (names) {
- char **name = (char **)names->elts;
- for (i = 0; i < names->nelts; ++i) {
- if (!name[i])
- continue;
- if (!strcasecmp(servername, name[i])) {
- found = TRUE;
- break;
- }
- }
- }
- }
-
- /* if still no match, check ServerAlias entries with wildcards */
- if (!found) {
- names = s->wild_names;
- if (names) {
- char **name = (char **)names->elts;
- for (i = 0; i < names->nelts; ++i) {
- if (!name[i])
- continue;
- if (!ap_strcasecmp_match(servername, name[i])) {
- found = TRUE;
- break;
- }
- }
- }
- }
+ found = ssl_util_vhost_matches(servername, s);
/* set SSL_CTX (if matched) */
sslcon = myConnConfig(c);
}
#endif /* HAVE_TLS_SESSION_TICKETS */
+
+#ifdef HAVE_TLS_ALPN
+/*
+ * This callback function is executed when the TLS Application-Layer
+ * Protocol Negotiation Extension (ALPN, RFC 7301) is triggered by the Client
+ * Hello, giving a list of desired protocol names (in descending preference)
+ * to the server.
+ * The callback has to select a protocol name or return an error if none of
+ * the clients preferences is supported.
+ * The selected protocol does not have to be on the client list, according
+ * to RFC 7301, so no checks are performed.
+ * The client protocol list is serialized as length byte followed by ASCII
+ * characters (not null-terminated), followed by the next protocol name.
+ */
+int ssl_callback_alpn_select(SSL *ssl,
+ const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ void *arg)
+{
+ conn_rec *c = (conn_rec*)SSL_get_app_data(ssl);
+ SSLConnRec *sslconn = myConnConfig(c);
+ apr_array_header_t *client_protos;
+ const char *proposed;
+ size_t len;
+ int i;
+
+ /* If the connection object is not available,
+ * then there's nothing for us to do. */
+ if (c == NULL) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ if (inlen == 0) {
+ // someone tries to trick us?
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02837)
+ "ALPN client protocol list empty");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ client_protos = apr_array_make(c->pool, 0, sizeof(char *));
+ for (i = 0; i < inlen; /**/) {
+ unsigned int plen = in[i++];
+ if (plen + i > inlen) {
+ // someone tries to trick us?
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02838)
+ "ALPN protocol identifier too long");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ APR_ARRAY_PUSH(client_protos, char *) =
+ apr_pstrndup(c->pool, (const char *)in+i, plen);
+ i += plen;
+ }
+
+ /* The order the callbacks are invoked from TLS extensions is, unfortunately
+ * not defined and older openssl versions do call ALPN selection before
+ * they callback the SNI. We need to make sure that we know which vhost
+ * we are dealing with so we respect the correct protocols.
+ */
+ init_vhost(c, ssl);
+
+ proposed = ap_select_protocol(c, NULL, sslconn->server, client_protos);
+ *out = (const unsigned char *)(proposed? proposed : ap_get_protocol(c));
+ len = strlen((const char*)*out);
+ if (len > 255) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02840)
+ "ALPN negotiated protocol name too long");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ *outlen = (unsigned char)len;
+
+ return SSL_TLSEXT_ERR_OK;
+}
+#endif /* HAVE_TLS_ALPN */
+
#ifdef HAVE_SRP
int ssl_callback_SRPServerParams(SSL *ssl, int *ad, void *arg)
#include <openssl/srp.h>
#endif
+/* ALPN Protocol Negotiation */
+#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
+#define HAVE_TLS_ALPN
+#endif
+
#endif /* !defined(OPENSSL_NO_TLSEXT) && defined(SSL_set_tlsext_host_name) */
/* mod_ssl headers */
EVP_CIPHER_CTX *, HMAC_CTX *, int);
#endif
+#ifdef HAVE_TLS_ALPN
+int ssl_callback_alpn_select(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg);
+#endif
+
/** Session Cache Support */
apr_status_t ssl_scache_init(server_rec *, apr_pool_t *);
void ssl_scache_status_register(apr_pool_t *p);
void ssl_util_thread_setup(apr_pool_t *);
int ssl_init_ssl_connection(conn_rec *c, request_rec *r);
+BOOL ssl_util_vhost_matches(const char *servername, server_rec *s);
+
/** Pass Phrase Support */
apr_status_t ssl_load_encrypted_pkey(server_rec *, apr_pool_t *, int,
const char *, apr_array_header_t **);
return id;
}
+/*
+ * Return TRUE iff the given servername matches the server record when
+ * selecting virtual hosts.
+ */
+BOOL ssl_util_vhost_matches(const char *servername, server_rec *s)
+{
+ apr_array_header_t *names;
+ int i;
+
+ /* check ServerName */
+ if (!strcasecmp(servername, s->server_hostname)) {
+ return TRUE;
+ }
+
+ /*
+ * if not matched yet, check ServerAlias entries
+ * (adapted from vhost.c:matches_aliases())
+ */
+ names = s->names;
+ if (names) {
+ char **name = (char **)names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if (!name[i])
+ continue;
+ if (!strcasecmp(servername, name[i])) {
+ return TRUE;
+ }
+ }
+ }
+
+ /* if still no match, check ServerAlias entries with wildcards */
+ names = s->wild_names;
+ if (names) {
+ char **name = (char **)names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if (!name[i])
+ continue;
+ if (!ap_strcasecmp_match(servername, name[i])) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
apr_file_t *ssl_util_ppopen(server_rec *s, apr_pool_t *p, const char *cmd,
const char * const *argv)
{
mod_optional_hook_export.so 0x70BD0000 0x00010000
mod_optional_hook_import.so 0x70BE0000 0x00010000
mod_authnz_fcgi.so 0x70BF0000 0x00020000
+mod_h2.so 0x70D00000 0x00020000
+>>>>>>> .merge-right.r1698023
static void *create_core_server_config(apr_pool_t *a, server_rec *s)
{
core_server_config *conf;
+ const char **np;
int is_virtual = s->is_virtual;
conf = (core_server_config *)apr_pcalloc(a, sizeof(core_server_config));
conf->trace_enable = AP_TRACE_UNSET;
+ conf->protocols = apr_array_make(a, 5, sizeof(const char *));
+ conf->protocols_honor_order = -1;
+
return (void *)conf;
}
? virt->merge_trailers
: base->merge_trailers;
+ conf->protocols = ((virt->protocols->nelts > 0)?
+ virt->protocols : base->protocols);
+ conf->protocols_honor_order = ((virt->protocols_honor_order < 0)?
+ base->protocols_honor_order :
+ virt->protocols_honor_order);
+
return conf;
}
return NULL;
}
+static const char *set_protocols(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(cmd->server->module_config);
+ const char **np;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
+
+ if (err) {
+ return err;
+ }
+
+ np = (const char **)apr_array_push(conf->protocols);
+ *np = arg;
+
+ return NULL;
+}
+
+static const char *set_protocols_honor_order(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(cmd->server->module_config);
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
+
+ if (err) {
+ return err;
+ }
+
+ if (strcasecmp(arg, "on") == 0) {
+ conf->protocols_honor_order = 1;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ conf->protocols_honor_order = 0;
+ }
+ else {
+ return "ProtocolsHonorOrder must be 'on' or 'off'";
+ }
+
+ return NULL;
+}
+
static apr_hash_t *errorlog_hash;
static int log_constant_item(const ap_errorlog_info *info, const char *arg,
"'on' (default), 'off' or 'extended' to trace request body content"),
AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF,
"merge request trailers into request headers or not"),
+AP_INIT_ITERATE("Protocols", set_protocols, NULL, RSRC_CONF,
+ "Controls which protocols are allowed"),
+AP_INIT_TAKE1("ProtocolsHonorOrder", set_protocols_honor_order, NULL, RSRC_CONF,
+ "'off' (default) or 'on' to respect given order of protocols, "
+ "by default the client specified order determines selection"),
{ NULL }
};
}
}
+static int core_upgrade_handler(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
+
+ if (upgrade && *upgrade) {
+ const char *conn = apr_table_get(r->headers_in, "Connection");
+ if (ap_find_token(r->pool, conn, "upgrade")) {
+ apr_array_header_t *offers = NULL;
+ const char *err;
+
+ err = ap_parse_token_list_strict(r->pool, upgrade, &offers, 0);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02910)
+ "parsing Upgrade header: %s", err);
+ return DECLINED;
+ }
+
+ if (offers && offers->nelts > 0) {
+ const char *protocol = ap_select_protocol(c, r, r->server,
+ offers);
+ if (protocol && strcmp(protocol, ap_get_protocol(c))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02909)
+ "Upgrade selects '%s'", protocol);
+ /* Let the client know what we are upgrading to. */
+ apr_table_clear(r->headers_out);
+ apr_table_setn(r->headers_out, "Upgrade", protocol);
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+
+ r->status = HTTP_SWITCHING_PROTOCOLS;
+ r->status_line = ap_get_status_line(r->status);
+ ap_send_interim_response(r, 1);
+
+ ap_switch_protocol(c, r, r->server, protocol);
+
+ /* make sure httpd closes the connection after this */
+ c->keepalive = AP_CONN_CLOSE;
+ return DONE;
+ }
+ }
+ }
+ }
+
+ return DECLINED;
+}
+
+static int core_upgrade_storage(request_rec *r)
+{
+ if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') &&
+ (r->uri[1] == '\0')) {
+ return core_upgrade_handler(r);
+ }
+ return DECLINED;
+}
+
static void register_hooks(apr_pool_t *p)
{
errorlog_hash = apr_hash_make(p);
ap_hook_check_config(core_check_config,NULL,NULL,APR_HOOK_FIRST);
ap_hook_test_config(core_dump_config,NULL,NULL,APR_HOOK_FIRST);
ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_map_to_storage(core_upgrade_storage,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_child_init(core_child_init,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_child_init(ap_logs_child_init,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_handler(core_upgrade_handler,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST);
/* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */
ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
APR_HOOK_LINK(http_scheme)
APR_HOOK_LINK(default_port)
APR_HOOK_LINK(note_auth_failure)
+ APR_HOOK_LINK(protocol_propose)
+ APR_HOOK_LINK(protocol_switch)
+ APR_HOOK_LINK(protocol_get)
)
AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL;
apr_brigade_destroy(x.bb);
}
+/*
+ * Compare two protocol identifier. Result is similar to strcmp():
+ * 0 gives same precedence, >0 means proto1 is preferred.
+ */
+static int protocol_cmp(const apr_array_header_t *preferences,
+ const char *proto1,
+ const char *proto2)
+{
+ if (preferences && preferences->nelts > 0) {
+ int index1 = ap_array_str_index(preferences, proto1, 0);
+ int index2 = ap_array_str_index(preferences, proto2, 0);
+ if (index2 > index1) {
+ return (index1 >= 0) ? 1 : -1;
+ }
+ else if (index1 > index2) {
+ return (index2 >= 0) ? -1 : 1;
+ }
+ }
+ /* both have the same index (mabye -1 or no pref configured) and we compare
+ * the names so that spdy3 gets precedence over spdy2. That makes
+ * the outcome at least deterministic. */
+ return strcmp(proto1, proto2);
+}
+
+AP_DECLARE(const char *) ap_get_protocol(conn_rec *c)
+{
+ const char *protocol = ap_run_protocol_get(c);
+ return protocol? protocol : AP_PROTOCOL_HTTP1;
+}
+
+AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *choices)
+{
+ apr_pool_t *pool = r? r->pool : c->pool;
+ core_server_config *conf = ap_get_core_module_config(s->module_config);
+ const char *protocol = NULL, *existing;
+ apr_array_header_t *proposals;
+
+ if (APLOGcdebug(c)) {
+ const char *p = apr_array_pstrcat(pool, conf->protocols, ',');
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "select protocol from %s, choices=%s for server %s",
+ p, apr_array_pstrcat(pool, choices, ','),
+ s->server_hostname);
+ }
+
+ if (conf->protocols->nelts <= 0) {
+ /* nothing configured, by default, we only allow http/1.1 here.
+ * For now...
+ */
+ if (ap_array_str_contains(choices, AP_PROTOCOL_HTTP1)) {
+ return AP_PROTOCOL_HTTP1;
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ proposals = apr_array_make(pool, choices->nelts + 1, sizeof(char *));
+ ap_run_protocol_propose(c, r, s, choices, proposals);
+
+ /* If the existing protocol has not been proposed, but is a choice,
+ * add it to the proposals implicitly.
+ */
+ existing = ap_get_protocol(c);
+ if (!ap_array_str_contains(proposals, existing)
+ && ap_array_str_contains(choices, existing)) {
+ APR_ARRAY_PUSH(proposals, const char*) = existing;
+ }
+
+ if (proposals->nelts > 0) {
+ int i;
+ const apr_array_header_t *prefs = NULL;
+
+ /* Default for protocols_honor_order is 'on' or != 0 */
+ if (conf->protocols_honor_order == 0 && choices->nelts > 0) {
+ prefs = choices;
+ }
+ else {
+ prefs = conf->protocols;
+ }
+
+ /* Select the most preferred protocol */
+ if (APLOGcdebug(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "select protocol, proposals=%s preferences=%s configured=%s",
+ apr_array_pstrcat(pool, proposals, ','),
+ apr_array_pstrcat(pool, prefs, ','),
+ apr_array_pstrcat(pool, conf->protocols, ','));
+ }
+ for (i = 0; i < proposals->nelts; ++i) {
+ const char *p = APR_ARRAY_IDX(proposals, i, const char *);
+ if (!ap_array_str_contains(conf->protocols, p)) {
+ /* not a configured protocol here */
+ continue;
+ }
+ else if (!protocol
+ || (protocol_cmp(prefs, protocol, p) < 0)) {
+ /* none selected yet or this one has preference */
+ protocol = p;
+ }
+ }
+ }
+ if (APLOGcdebug(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "selected protocol=%s",
+ protocol? protocol : "(none)");
+ }
+
+ return protocol;
+}
+
+AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const char *protocol)
+{
+ const char *current = ap_get_protocol(c);
+ int rc;
+
+ if (!strcmp(current, protocol)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02906)
+ "already at it, protocol_switch to %s",
+ protocol);
+ return APR_SUCCESS;
+ }
+
+ rc = ap_run_protocol_switch(c, r, s, protocol);
+ switch (rc) {
+ case DECLINED:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02907)
+ "no implementation for protocol_switch to %s",
+ protocol);
+ return APR_ENOTIMPL;
+ case OK:
+ case DONE:
+ return APR_SUCCESS;
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02905)
+ "unexpected return code %d from protocol_switch to %s"
+ , rc, protocol);
+ return APR_EOF;
+ }
+}
+
AP_IMPLEMENT_HOOK_VOID(pre_read_request,
(request_rec *r, conn_rec *c),
AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure,
(request_rec *r, const char *auth_type),
(r, auth_type), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int,protocol_propose,
+ (conn_rec *c, request_rec *r, server_rec *s,
+ const apr_array_header_t *offers,
+ apr_array_header_t *proposals),
+ (c, r, s, offers, proposals), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,protocol_switch,
+ (conn_rec *c, request_rec *r, server_rec *s,
+ const char *protocol),
+ (c, r, s, protocol), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,protocol_get,
+ (const conn_rec *c), (c), NULL)
return find_list_item(p, line, tok, AP_ETAG_WEAK);
}
+/* Grab a list of tokens of the format 1#token (from RFC7230) */
+AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p,
+ const char *str_in,
+ apr_array_header_t **tokens,
+ int skip_invalid)
+{
+ int in_leading_space = 1;
+ int in_trailing_space = 0;
+ int string_end = 0;
+ const char *tok_begin;
+ const char *cur;
+
+ if (!str_in) {
+ return NULL;
+ }
+
+ tok_begin = cur = str_in;
+
+ while (!string_end) {
+ const unsigned char c = (unsigned char)*cur;
+
+ if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP) && c != '\0') {
+ /* Non-separator character; we are finished with leading
+ * whitespace. We must never have encountered any trailing
+ * whitespace before the delimiter (comma) */
+ in_leading_space = 0;
+ if (in_trailing_space) {
+ return "Encountered illegal whitespace in token";
+ }
+ }
+ else if (c == ' ' || c == '\t') {
+ /* "Linear whitespace" only includes ASCII CRLF, space, and tab;
+ * we can't get a CRLF since headers are split on them already,
+ * so only look for a space or a tab */
+ if (in_leading_space) {
+ /* We're still in leading whitespace */
+ ++tok_begin;
+ }
+ else {
+ /* We must be in trailing whitespace */
+ ++in_trailing_space;
+ }
+ }
+ else if (c == ',' || c == '\0') {
+ if (!in_leading_space) {
+ /* If we're out of the leading space, we know we've read some
+ * characters of a token */
+ if (*tokens == NULL) {
+ *tokens = apr_array_make(p, 4, sizeof(char *));
+ }
+ APR_ARRAY_PUSH(*tokens, char *) =
+ apr_pstrmemdup((*tokens)->pool, tok_begin,
+ (cur - tok_begin) - in_trailing_space);
+ }
+ /* We're allowed to have null elements, just don't add them to the
+ * array */
+
+ tok_begin = cur + 1;
+ in_leading_space = 1;
+ in_trailing_space = 0;
+ string_end = (c == '\0');
+ }
+ else {
+ /* Encountered illegal separator char */
+ if (skip_invalid) {
+ /* Skip to the next separator */
+ const char *temp;
+ temp = ap_strchr_c(cur, ',');
+ if(!temp) {
+ temp = ap_strchr_c(cur, '\0');
+ }
+
+ /* Act like we haven't seen a token so we reset */
+ cur = temp - 1;
+ in_leading_space = 1;
+ in_trailing_space = 0;
+ }
+ else {
+ return apr_psprintf(p, "Encountered illegal separator "
+ "'\\x%.2x'", (unsigned int)c);
+ }
+ }
+
+ ++cur;
+ }
+
+ return NULL;
+}
+
/* Retrieve a token, spacing over it and returning a pointer to
* the first non-white byte afterwards. Note that these tokens
* are delimited by semis and commas; and can also be delimited
return apr_pstrndup(p, buf, k);
}
+
+AP_DECLARE(int) ap_array_str_index(const apr_array_header_t *array,
+ const char *s,
+ int start)
+{
+ if (start >= 0) {
+ int i;
+
+ for (i = start; i < array->nelts; i++) {
+ const char *p = APR_ARRAY_IDX(array, i, const char *);
+ if (!strcmp(p, s)) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+AP_DECLARE(int) ap_array_str_contains(const apr_array_header_t *array,
+ const char *s)
+{
+ return (ap_array_str_index(array, s, 0) >= 0);
+}
+