]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
merge of protocols + http2 relevant changes
authorStefan Eissing <icing@apache.org>
Mon, 7 Sep 2015 17:37:19 +0000 (17:37 +0000)
committerStefan Eissing <icing@apache.org>
Mon, 7 Sep 2015 17:37:19 +0000 (17:37 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.17-protocols-http2@1701655 13f79535-47bb-0310-9956-ffa450edef68

93 files changed:
Apache-apr2.dsw
Apache.dsw
BuildBin.dsp
CHANGES
Makefile.win
build/NWGNUenvironment.inc
build/installwinconf.awk
docs/conf/extra/httpd-h2.conf.in [new file with mode: 0644]
docs/conf/httpd.conf.in
docs/manual/mod/allmodules.xml
docs/manual/mod/core.xml
docs/manual/mod/mod_h2.html [new file with mode: 0644]
docs/manual/mod/mod_h2.html.en [new file with mode: 0644]
docs/manual/mod/mod_h2.xml [new file with mode: 0644]
docs/manual/mod/mod_h2.xml.meta [new file with mode: 0644]
docs/manual/mod/mod_proxy.xml
docs/manual/mod/quickreference.html.en
include/ap_mmn.h
include/http_core.h
include/http_protocol.h
include/http_request.h
include/httpd.h
modules/NWGNUmakefile
modules/README
modules/http/http_protocol.c
modules/http/http_request.c
modules/http2/.gitignore [new file with mode: 0644]
modules/http2/Makefile.in [new file with mode: 0644]
modules/http2/NWGNUmakefile [new file with mode: 0644]
modules/http2/README.h2 [new file with mode: 0644]
modules/http2/config.m4 [new file with mode: 0644]
modules/http2/h2_alt_svc.c [new file with mode: 0644]
modules/http2/h2_alt_svc.h [new file with mode: 0644]
modules/http2/h2_config.c [new file with mode: 0644]
modules/http2/h2_config.h [new file with mode: 0644]
modules/http2/h2_conn.c [new file with mode: 0644]
modules/http2/h2_conn.h [new file with mode: 0644]
modules/http2/h2_conn_io.c [new file with mode: 0644]
modules/http2/h2_conn_io.h [new file with mode: 0644]
modules/http2/h2_ctx.c [new file with mode: 0644]
modules/http2/h2_ctx.h [new file with mode: 0644]
modules/http2/h2_from_h1.c [new file with mode: 0644]
modules/http2/h2_from_h1.h [new file with mode: 0644]
modules/http2/h2_h2.c [new file with mode: 0644]
modules/http2/h2_h2.h [new file with mode: 0644]
modules/http2/h2_io.c [new file with mode: 0644]
modules/http2/h2_io.h [new file with mode: 0644]
modules/http2/h2_io_set.c [new file with mode: 0644]
modules/http2/h2_io_set.h [new file with mode: 0644]
modules/http2/h2_mplx.c [new file with mode: 0644]
modules/http2/h2_mplx.h [new file with mode: 0644]
modules/http2/h2_private.h [new file with mode: 0644]
modules/http2/h2_request.c [new file with mode: 0644]
modules/http2/h2_request.h [new file with mode: 0644]
modules/http2/h2_response.c [new file with mode: 0644]
modules/http2/h2_response.h [new file with mode: 0644]
modules/http2/h2_session.c [new file with mode: 0644]
modules/http2/h2_session.h [new file with mode: 0644]
modules/http2/h2_stream.c [new file with mode: 0644]
modules/http2/h2_stream.h [new file with mode: 0644]
modules/http2/h2_stream_set.c [new file with mode: 0644]
modules/http2/h2_stream_set.h [new file with mode: 0644]
modules/http2/h2_switch.c [new file with mode: 0644]
modules/http2/h2_switch.h [new file with mode: 0644]
modules/http2/h2_task.c [new file with mode: 0644]
modules/http2/h2_task.h [new file with mode: 0644]
modules/http2/h2_task_input.c [new file with mode: 0644]
modules/http2/h2_task_input.h [new file with mode: 0644]
modules/http2/h2_task_output.c [new file with mode: 0644]
modules/http2/h2_task_output.h [new file with mode: 0644]
modules/http2/h2_task_queue.c [new file with mode: 0644]
modules/http2/h2_task_queue.h [new file with mode: 0644]
modules/http2/h2_to_h1.c [new file with mode: 0644]
modules/http2/h2_to_h1.h [new file with mode: 0644]
modules/http2/h2_util.c [new file with mode: 0644]
modules/http2/h2_util.h [new file with mode: 0644]
modules/http2/h2_version.h [new file with mode: 0644]
modules/http2/h2_worker.c [new file with mode: 0644]
modules/http2/h2_worker.h [new file with mode: 0644]
modules/http2/h2_workers.c [new file with mode: 0644]
modules/http2/h2_workers.h [new file with mode: 0644]
modules/http2/mod_h2.c [new file with mode: 0644]
modules/http2/mod_h2.dsp [new file with mode: 0644]
modules/http2/mod_h2.h [new file with mode: 0644]
modules/ssl/ssl_engine_init.c
modules/ssl/ssl_engine_io.c
modules/ssl/ssl_engine_kernel.c
modules/ssl/ssl_private.h
modules/ssl/ssl_util.c
os/win32/BaseAddr.ref
server/core.c
server/protocol.c
server/util.c

index 44b193e1d7b2a2423cea5a1a06ef73cb3b250c86..db8f9c455fc574fa38919d6d23b932136cfe0768 100644 (file)
@@ -1723,6 +1723,24 @@ Package=<4>
 
 ###############################################################################
 
+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>
index 6ac0075f81ef70b46dcd8820d94ded794062d2e6..bfbab3f584ad12089e3aa5bf517e9398ebd00e5b 100644 (file)
@@ -2041,6 +2041,27 @@ Package=<4>
 
 ###############################################################################
 
+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>
index 08d4762b6593aa3fc7900882774ea5e30895fc2a..84143ca124d16695f850b7b8350cf5e89ae6c3d7 100644 (file)
@@ -39,7 +39,7 @@ CFG=BuildBin - Win32 Debug
 # 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"
@@ -58,7 +58,7 @@ CFG=BuildBin - Win32 Debug
 # 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"
diff --git a/CHANGES b/CHANGES
index bbe52e8419e07ec05671b5760deea801c3db654c..709d46e8e5c6335d8de702ba41ed157cbb4735e4 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
                                                          -*- 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. 
index 35d9b8eab05c234326f3315174d0a4a151f01dff..2dade641e9c3b9d78b3f98b8795780b2c1f66e29 100644 (file)
@@ -215,6 +215,30 @@ _trylua:
 
 !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
@@ -754,6 +778,14 @@ _copybin:
        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
index 52295608fdaf9c9a75e0a84ff2b8adc3b12552f4..0e510c0b859ff4b6358c1ae12990af342fa8377b 100644 (file)
@@ -49,6 +49,12 @@ ifneq "$(wildcard $(LDAPSDK)/inc/ldap.h)" "$(LDAPSDK)/inc/ldap.h"
 $(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
index 33627341292f5a5acbcfefe3318337304c129f40..8e66679bec3f7d8223c73a09ffd57ae10f055c79 100644 (file)
@@ -59,6 +59,7 @@ BEGIN {
     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";
@@ -136,6 +137,7 @@ BEGIN {
           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;
diff --git a/docs/conf/extra/httpd-h2.conf.in b/docs/conf/extra/httpd-h2.conf.in
new file mode 100644 (file)
index 0000000..6d624f6
--- /dev/null
@@ -0,0 +1,12 @@
+#
+# 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>
index 6e4189137746df5089323aa8bbbfba01a0333f70..9fb2e9d1ab5ae798cc443f6b114c7d2bfa68b08a 100644 (file)
@@ -418,3 +418,6 @@ SSLRandomSeed connect builtin
 #RequestHeader unset DNT env=bad_DNT
 #</IfModule>
 
+# h2/h2c (HTTP/2) connections
+#Include @rel_sysconfdir@/extra/httpd-h2.conf
+
index 48e61762b67c26fd5ea57235cf23987b038f2365..fbc20266d1916daccbfe2ffeb478500ca07e5a78 100644 (file)
@@ -51,6 +51,7 @@
   <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>
index 2e7812cc8e12b0d56fd25e579d9762298ab59bc2..1620ccbbc1dbb119c2bcceca488e460a878da739 100644 (file)
@@ -3589,6 +3589,74 @@ On Windows, from Apache 2.3.3 and later.</compatibility>
 </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
diff --git a/docs/manual/mod/mod_h2.html b/docs/manual/mod/mod_h2.html
new file mode 100644 (file)
index 0000000..9346e29
--- /dev/null
@@ -0,0 +1,5 @@
+# GENERATED FROM XML -- DO NOT EDIT
+
+URI: mod_h2.html.en
+Content-Language: en
+Content-type: text/html; charset=ISO-8859-1
diff --git a/docs/manual/mod/mod_h2.html.en b/docs/manual/mod/mod_h2.html.en
new file mode 100644 (file)
index 0000000..90a8e0e
--- /dev/null
@@ -0,0 +1,409 @@
+<?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="&lt;-" alt="&lt;-" src="../images/left.gif" /></a></div>
+<div id="path">
+<a href="http://www.apache.org/">Apache</a> &gt; <a href="http://httpd.apache.org/">HTTP Server</a> &gt; <a href="http://httpd.apache.org/docs/">Documentation</a> &gt; <a href="../">Version 2.5</a> &gt; <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">&nbsp;en&nbsp;</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">&lt;VirtualHost&gt;</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">&lt;VirtualHost&gt;</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">&lt;VirtualHost _default_:443&gt;
+    H2Engine on
+    #...
+&lt;/VirtualHost&gt;</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">&nbsp;en&nbsp;</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&amp;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
diff --git a/docs/manual/mod/mod_h2.xml b/docs/manual/mod/mod_h2.xml
new file mode 100644 (file)
index 0000000..c5c77aa
--- /dev/null
@@ -0,0 +1,278 @@
+<?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>
diff --git a/docs/manual/mod/mod_h2.xml.meta b/docs/manual/mod/mod_h2.xml.meta
new file mode 100644 (file)
index 0000000..ff80d56
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
index a2ba3395919a990f53c077dad5fc38a47da44606..e4cdd50c1d755f68adfc368e6526ee5111766453 100644 (file)
@@ -1202,9 +1202,9 @@ ProxyPass "/mirror/foo" "http://backend.example.com"
         <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>
index ca697e8702cfe2c930c0ea01cfae2f4dccbcb114..1a5acbeb0f0f164f45f377fe0970dd00f76e9bb5 100644 (file)
@@ -467,7 +467,21 @@ media type in the HTTP Content-Type header field</td></tr>
 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>
index 7e609e3ea9d6efb16a2432d10438c93119a04236..72fe541e14fe7b774fedfdbbf266621eaca75862 100644 (file)
  * 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" */
index 8171823a083c2cc26faaec08eebe0dc11938e5a2..6ca53f76edb2d7b550c92f12d3a601dbf2cf0a13 100644 (file)
@@ -681,6 +681,10 @@ typedef struct {
 #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 */
index ee61b6876950e3bfc3df3cc3d6eb4b6257dda201..64ed01362cc3f234cc10a4338b8dc7ada260f685 100644 (file)
@@ -700,6 +700,139 @@ AP_DECLARE_HOOK(const char *,http_scheme,(const request_rec *r))
  */
 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;
 
index fabb4c843b748a3b34fe9fb44eff7220fc186e3a..3d0b143e786865a64c3d4c3607eea19960eb6abe 100644 (file)
@@ -315,7 +315,7 @@ AP_DECLARE(void) ap_allow_standard_methods(request_rec *r, int reset, ...);
  * 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)
index a9163c31a33dacddb7f0aa0ccbae06fbc2802e6f..331a4d0c3bad94727b67eecbfcc13ddb301de0b7 100644 (file)
@@ -518,6 +518,7 @@ AP_DECLARE(const char *) ap_get_server_built(void);
 #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
@@ -1548,6 +1549,23 @@ AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line, const char *t
  */
 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
@@ -2265,6 +2283,28 @@ AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p,
 
 #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
index 6e8113d4b93d485ab56d8ade0863a152097c0107..f625e7c2422b71371f27446babd2c7382a4bc4bd 100644 (file)
@@ -5,7 +5,8 @@
 #  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
 #
@@ -72,6 +73,13 @@ SUBDIRS += lua
 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
index f11ad42f21691aaab16fdc46526bf39e6bd69c66..2dee079566460f430fc2cec48f2251c421e67017 100644 (file)
@@ -41,6 +41,9 @@ generators/
 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.
 
index fc7ec6ccbe7ff79e9374b3f6c70c0de89b73d4e2..589611b593b2daef80458e06bbec498d325e7ccb 100644 (file)
@@ -135,7 +135,7 @@ static const char * const status_lines[RESPONSE_CODES] =
     NULL, /* 418 */
     NULL, /* 419 */
     NULL, /* 420 */
-    NULL, /* 421 */
+    "421 Misdirected Request",
     "422 Unprocessable Entity",
     "423 Locked",
     "424 Failed Dependency",
@@ -1293,6 +1293,11 @@ static const char *get_canned_error_string(int status,
     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
index 7b06def9405573da04ff7712b038c24ac67f7bc0..70bf2937c08dbd6cdbb346c50267ce25d5ab6146 100644 (file)
@@ -363,7 +363,7 @@ void ap_process_async_request(request_rec *r)
     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;
diff --git a/modules/http2/.gitignore b/modules/http2/.gitignore
new file mode 100644 (file)
index 0000000..ca49620
--- /dev/null
@@ -0,0 +1,35 @@
+*.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
diff --git a/modules/http2/Makefile.in b/modules/http2/Makefile.in
new file mode 100644 (file)
index 0000000..4395bc3
--- /dev/null
@@ -0,0 +1,20 @@
+# 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
diff --git a/modules/http2/NWGNUmakefile b/modules/http2/NWGNUmakefile
new file mode 100644 (file)
index 0000000..cc08702
--- /dev/null
@@ -0,0 +1,331 @@
+#
+# 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
+
+
diff --git a/modules/http2/README.h2 b/modules/http2/README.h2
new file mode 100644 (file)
index 0000000..803f787
--- /dev/null
@@ -0,0 +1,70 @@
+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
+
diff --git a/modules/http2/config.m4 b/modules/http2/config.m4
new file mode 100644 (file)
index 0000000..6bb231f
--- /dev/null
@@ -0,0 +1,186 @@
+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
+
diff --git a/modules/http2/h2_alt_svc.c b/modules/http2/h2_alt_svc.c
new file mode 100644 (file)
index 0000000..d18ae5f
--- /dev/null
@@ -0,0 +1,132 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_alt_svc.h b/modules/http2/h2_alt_svc.h
new file mode 100644 (file)
index 0000000..51f89d0
--- /dev/null
@@ -0,0 +1,39 @@
+/* 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__) */
diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c
new file mode 100644 (file)
index 0000000..883f273
--- /dev/null
@@ -0,0 +1,416 @@
+/* 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);
+}
+
diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h
new file mode 100644 (file)
index 0000000..931af59
--- /dev/null
@@ -0,0 +1,74 @@
+/* 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__ */
+
diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c
new file mode 100644 (file)
index 0000000..38e5c11
--- /dev/null
@@ -0,0 +1,532 @@
+/* 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);
+}
+
diff --git a/modules/http2/h2_conn.h b/modules/http2/h2_conn.h
new file mode 100644 (file)
index 0000000..49a70db
--- /dev/null
@@ -0,0 +1,60 @@
+/* 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__) */
diff --git a/modules/http2/h2_conn_io.c b/modules/http2/h2_conn_io.c
new file mode 100644 (file)
index 0000000..129456e
--- /dev/null
@@ -0,0 +1,321 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_conn_io.h b/modules/http2/h2_conn_io.h
new file mode 100644 (file)
index 0000000..084445e
--- /dev/null
@@ -0,0 +1,58 @@
+/* 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__) */
diff --git a/modules/http2/h2_ctx.c b/modules/http2/h2_ctx.c
new file mode 100644 (file)
index 0000000..0e19845
--- /dev/null
@@ -0,0 +1,84 @@
+/* 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;
+}
diff --git a/modules/http2/h2_ctx.h b/modules/http2/h2_ctx.h
new file mode 100644 (file)
index 0000000..f9bc827
--- /dev/null
@@ -0,0 +1,59 @@
+/* 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__) */
diff --git a/modules/http2/h2_from_h1.c b/modules/http2/h2_from_h1.c
new file mode 100644 (file)
index 0000000..be11f5c
--- /dev/null
@@ -0,0 +1,570 @@
+/* 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);
+}
diff --git a/modules/http2/h2_from_h1.h b/modules/http2/h2_from_h1.h
new file mode 100644 (file)
index 0000000..115a314
--- /dev/null
@@ -0,0 +1,82 @@
+/* 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__) */
diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c
new file mode 100644 (file)
index 0000000..221f511
--- /dev/null
@@ -0,0 +1,232 @@
+/* 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;
+}
+
+
diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h
new file mode 100644 (file)
index 0000000..9a1184d
--- /dev/null
@@ -0,0 +1,57 @@
+/* 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__) */
diff --git a/modules/http2/h2_io.c b/modules/http2/h2_io.c
new file mode 100644 (file)
index 0000000..24d2a9f
--- /dev/null
@@ -0,0 +1,165 @@
+/* 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;
+}
diff --git a/modules/http2/h2_io.h b/modules/http2/h2_io.h
new file mode 100644 (file)
index 0000000..946ee44
--- /dev/null
@@ -0,0 +1,126 @@
+/* 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__) */
diff --git a/modules/http2/h2_io_set.c b/modules/http2/h2_io_set.c
new file mode 100644 (file)
index 0000000..91afde8
--- /dev/null
@@ -0,0 +1,180 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_io_set.h b/modules/http2/h2_io_set.h
new file mode 100644 (file)
index 0000000..a9c6546
--- /dev/null
@@ -0,0 +1,47 @@
+/* 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__) */
diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
new file mode 100644 (file)
index 0000000..c04b05e
--- /dev/null
@@ -0,0 +1,798 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h
new file mode 100644 (file)
index 0000000..62977d6
--- /dev/null
@@ -0,0 +1,322 @@
+/* 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__) */
diff --git a/modules/http2/h2_private.h b/modules/http2/h2_private.h
new file mode 100644 (file)
index 0000000..6931278
--- /dev/null
@@ -0,0 +1,36 @@
+/* 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
diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c
new file mode 100644 (file)
index 0000000..ca9a362
--- /dev/null
@@ -0,0 +1,182 @@
+/* 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);
+}
+
diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h
new file mode 100644 (file)
index 0000000..aa5e0bc
--- /dev/null
@@ -0,0 +1,67 @@
+/* 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__) */
diff --git a/modules/http2/h2_response.c b/modules/http2/h2_response.c
new file mode 100644 (file)
index 0000000..9cedd85
--- /dev/null
@@ -0,0 +1,232 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_response.h b/modules/http2/h2_response.h
new file mode 100644 (file)
index 0000000..456d222
--- /dev/null
@@ -0,0 +1,47 @@
+/* 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__) */
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
new file mode 100644 (file)
index 0000000..8c8bced
--- /dev/null
@@ -0,0 +1,1257 @@
+/* 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);
+    }
+}
+
diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h
new file mode 100644 (file)
index 0000000..77dd440
--- /dev/null
@@ -0,0 +1,137 @@
+/* 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__) */
diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
new file mode 100644 (file)
index 0000000..08b8f5a
--- /dev/null
@@ -0,0 +1,273 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h
new file mode 100644 (file)
index 0000000..f6bd71a
--- /dev/null
@@ -0,0 +1,108 @@
+/* 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__) */
diff --git a/modules/http2/h2_stream_set.c b/modules/http2/h2_stream_set.c
new file mode 100644 (file)
index 0000000..dddd2e3
--- /dev/null
@@ -0,0 +1,164 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_stream_set.h b/modules/http2/h2_stream_set.h
new file mode 100644 (file)
index 0000000..5607583
--- /dev/null
@@ -0,0 +1,52 @@
+/* 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__) */
diff --git a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c
new file mode 100644 (file)
index 0000000..4cdc411
--- /dev/null
@@ -0,0 +1,180 @@
+/* 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);
+}
+
diff --git a/modules/http2/h2_switch.h b/modules/http2/h2_switch.h
new file mode 100644 (file)
index 0000000..3d9c628
--- /dev/null
@@ -0,0 +1,29 @@
+/* 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__) */
diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c
new file mode 100644 (file)
index 0000000..26b8c6e
--- /dev/null
@@ -0,0 +1,467 @@
+/* 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;
+}
+
+
+
+
diff --git a/modules/http2/h2_task.h b/modules/http2/h2_task.h
new file mode 100644 (file)
index 0000000..b66ce38
--- /dev/null
@@ -0,0 +1,187 @@
+/* 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__) */
diff --git a/modules/http2/h2_task_input.c b/modules/http2/h2_task_input.c
new file mode 100644 (file)
index 0000000..cc7d850
--- /dev/null
@@ -0,0 +1,219 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_task_input.h b/modules/http2/h2_task_input.h
new file mode 100644 (file)
index 0000000..32adc17
--- /dev/null
@@ -0,0 +1,46 @@
+/* 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__) */
diff --git a/modules/http2/h2_task_output.c b/modules/http2/h2_task_output.c
new file mode 100644 (file)
index 0000000..879cb5f
--- /dev/null
@@ -0,0 +1,132 @@
+/* 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);
+}
+
diff --git a/modules/http2/h2_task_output.h b/modules/http2/h2_task_output.h
new file mode 100644 (file)
index 0000000..86571a1
--- /dev/null
@@ -0,0 +1,56 @@
+/* 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__) */
diff --git a/modules/http2/h2_task_queue.c b/modules/http2/h2_task_queue.c
new file mode 100644 (file)
index 0000000..a81cc10
--- /dev/null
@@ -0,0 +1,88 @@
+/* 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;
+}
+
+
+
diff --git a/modules/http2/h2_task_queue.h b/modules/http2/h2_task_queue.h
new file mode 100644 (file)
index 0000000..d93d74a
--- /dev/null
@@ -0,0 +1,148 @@
+/* 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__) */
diff --git a/modules/http2/h2_to_h1.c b/modules/http2/h2_to_h1.c
new file mode 100644 (file)
index 0000000..8dacfe8
--- /dev/null
@@ -0,0 +1,305 @@
+/* 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;
+}
+
+
diff --git a/modules/http2/h2_to_h1.h b/modules/http2/h2_to_h1.h
new file mode 100644 (file)
index 0000000..74586e2
--- /dev/null
@@ -0,0 +1,87 @@
+/* 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__) */
diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c
new file mode 100644 (file)
index 0000000..c0c3837
--- /dev/null
@@ -0,0 +1,627 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h
new file mode 100644 (file)
index 0000000..9a1b5c6
--- /dev/null
@@ -0,0 +1,124 @@
+/* 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__) */
diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h
new file mode 100644 (file)
index 0000000..df4da02
--- /dev/null
@@ -0,0 +1,34 @@
+/* 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 */
diff --git a/modules/http2/h2_worker.c b/modules/http2/h2_worker.c
new file mode 100644 (file)
index 0000000..8145b7a
--- /dev/null
@@ -0,0 +1,170 @@
+/* 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;
+}
+
diff --git a/modules/http2/h2_worker.h b/modules/http2/h2_worker.h
new file mode 100644 (file)
index 0000000..9c69e6b
--- /dev/null
@@ -0,0 +1,155 @@
+/* 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__) */
diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c
new file mode 100644 (file)
index 0000000..cf30095
--- /dev/null
@@ -0,0 +1,352 @@
+/* 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);
+}
+
diff --git a/modules/http2/h2_workers.h b/modules/http2/h2_workers.h
new file mode 100644 (file)
index 0000000..50fd6b8
--- /dev/null
@@ -0,0 +1,87 @@
+/* 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__) */
diff --git a/modules/http2/mod_h2.c b/modules/http2/mod_h2.c
new file mode 100644 (file)
index 0000000..1c7cd2d
--- /dev/null
@@ -0,0 +1,146 @@
+/* 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();
+    
+}
+
+
diff --git a/modules/http2/mod_h2.dsp b/modules/http2/mod_h2.dsp
new file mode 100644 (file)
index 0000000..7147e2d
--- /dev/null
@@ -0,0 +1,211 @@
+# 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
diff --git a/modules/http2/mod_h2.h b/modules/http2/mod_h2.h
new file mode 100644 (file)
index 0000000..bb895dd
--- /dev/null
@@ -0,0 +1,19 @@
+/* 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
index 6baf2f514dc7f2fe3933608e8a773d76bcc28e5a..72b458e8754803502e92b9749806fd8668f5a75d 100644 (file)
@@ -625,6 +625,10 @@ static void ssl_init_ctx_callbacks(server_rec *s,
     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,
index ce8abe5d54bbd796708968cea54d2cbd857146dc..5ea76c6b6738ddcb3b0c611807479224ec881c74 100644 (file)
@@ -297,6 +297,9 @@ typedef struct {
     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;
 
 /*
@@ -1412,6 +1415,42 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f,
         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;
 }
 
@@ -1893,6 +1932,9 @@ static void ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c,
     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
index d0f9489a87b22b616f900c3f8eca7e6e3711e0c7..cad7cc9e6f22881aa40d81ef492be4b44fe1a428 100644 (file)
@@ -199,11 +199,18 @@ int ssl_hook_ReadReq(request_rec *r)
             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)
@@ -1903,23 +1910,29 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc)
 
 #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)
@@ -1949,8 +1962,20 @@ int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
                           "(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;
 }
 
 /*
@@ -1962,50 +1987,10 @@ static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s)
 {
     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);
@@ -2149,6 +2134,80 @@ int ssl_callback_SessionTicket(SSL *ssl,
 }
 #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)
index 8f889971c154c30941a28cdf6dfaa5d776475292..1c91ffdf8d195879ce9fec35152c4ef5e746df30 100644 (file)
 #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 */
@@ -798,6 +803,12 @@ int         ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *,
                                        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);
@@ -856,6 +867,8 @@ BOOL         ssl_util_path_check(ssl_pathcheck_t, const char *, apr_pool_t *);
 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 **);
index 476aa0b6d4baa584da2eb5c2075a965c49de5769..ddde3c74ef5731cafa21c277fcac77c476488bdb 100644 (file)
@@ -60,6 +60,52 @@ char *ssl_util_vhostid(apr_pool_t *p, server_rec *s)
     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)
 {
index 678e7be1d6ff8a08dd034e4b1018f93f93da9a95..1a221f87a14da65345a13490f01b30cd48b9e1a5 100644 (file)
@@ -127,3 +127,5 @@ mod_optional_fn_import.so   0x70BC0000    0x00010000
 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
index 802d8fdcdb8e73cd8b7390d738eb2d7f9386705c..ad777fadea0395888efa4584528e477521f0d125 100644 (file)
@@ -423,6 +423,7 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv)
 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));
@@ -468,6 +469,9 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s)
 
     conf->trace_enable = AP_TRACE_UNSET;
 
+    conf->protocols = apr_array_make(a, 5, sizeof(const char *));
+    conf->protocols_honor_order = -1;
+    
     return (void *)conf;
 }
 
@@ -518,6 +522,12 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
                            ? 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;
 }
 
@@ -3692,6 +3702,48 @@ static const char *set_trace_enable(cmd_parms *cmd, void *dummy,
     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,
@@ -4205,6 +4257,11 @@ AP_INIT_TAKE1("TraceEnable", set_trace_enable, NULL, RSRC_CONF,
               "'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 }
 };
 
@@ -4936,6 +4993,61 @@ static void core_dump_config(apr_pool_t *p, server_rec *s)
     }
 }
 
+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);
@@ -4958,10 +5070,12 @@ static void register_hooks(apr_pool_t *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);
index 8ebf4f41f6324b8ae1c52ba9d18aea10acc62b73..fc507fa08973186d11f26af08d1c79d36c657ad8 100644 (file)
@@ -67,6 +67,9 @@ APR_HOOK_STRUCT(
     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;
@@ -1790,6 +1793,150 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers)
     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),
@@ -1805,3 +1952,14 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(unsigned short,default_port,
 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)
index 464b07f4903469e9d47b5b119895e1f94b4cfaa0..916213c380172eacec2a0b224bf4c3b9bcbe5a03 100644 (file)
@@ -1451,6 +1451,95 @@ AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line,
     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
@@ -3005,3 +3094,28 @@ AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p,
 
     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);
+}
+