You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2023/06/20 12:01:09 UTC
svn commit: r1910507 [1/2] - in /httpd/httpd/trunk: ./ .github/workflows/ changes-entries/ docs/manual/mod/ include/ modules/http2/ modules/proxy/ server/ test/ test/clients/ test/modules/http2/ test/pyhttpd/
Author: icing
Date: Tue Jun 20 12:01:09 2023
New Revision: 1910507
URL: http://svn.apache.org/viewvc?rev=1910507&view=rev
Log:
*) mod_http2: added support for bootstrapping WebSockets via HTTP/2, as
described in RFC 8441. A new directive 'H2WebSockets on|off' has been
added. The feature is by default not enabled.
As also discussed in the manual, this feature should work for setups
using "ProxyPass backend-url upgrade=websocket" without further changes.
Special server modules for WebSockets will have to be adapted,
most likely, as the handling if IO events is different with HTTP/2.
HTTP/2 WebSockets are supported on platforms with native pipes. This
excludes Windows.
Added:
httpd/httpd/trunk/changes-entries/h2_websockets.txt
httpd/httpd/trunk/modules/http2/h2_ws.c
httpd/httpd/trunk/modules/http2/h2_ws.h
httpd/httpd/trunk/test/clients/ (with props)
httpd/httpd/trunk/test/clients/.gitignore
httpd/httpd/trunk/test/clients/Makefile.in
httpd/httpd/trunk/test/clients/h2ws.c
httpd/httpd/trunk/test/modules/http2/test_800_websockets.py
httpd/httpd/trunk/test/modules/http2/ws_server.py
httpd/httpd/trunk/test/pyhttpd/ws_util.py
Modified:
httpd/httpd/trunk/.github/workflows/linux.yml
httpd/httpd/trunk/CMakeLists.txt
httpd/httpd/trunk/configure.in
httpd/httpd/trunk/docs/manual/mod/mod_http2.xml
httpd/httpd/trunk/include/ap_mmn.h
httpd/httpd/trunk/include/http_core.h
httpd/httpd/trunk/modules/http2/config2.m4
httpd/httpd/trunk/modules/http2/h2.h
httpd/httpd/trunk/modules/http2/h2_bucket_beam.c
httpd/httpd/trunk/modules/http2/h2_bucket_beam.h
httpd/httpd/trunk/modules/http2/h2_c1_io.c
httpd/httpd/trunk/modules/http2/h2_c2.c
httpd/httpd/trunk/modules/http2/h2_c2_filter.c
httpd/httpd/trunk/modules/http2/h2_config.c
httpd/httpd/trunk/modules/http2/h2_config.h
httpd/httpd/trunk/modules/http2/h2_conn_ctx.h
httpd/httpd/trunk/modules/http2/h2_mplx.c
httpd/httpd/trunk/modules/http2/h2_proxy_util.c
httpd/httpd/trunk/modules/http2/h2_push.c
httpd/httpd/trunk/modules/http2/h2_request.c
httpd/httpd/trunk/modules/http2/h2_session.c
httpd/httpd/trunk/modules/http2/h2_stream.c
httpd/httpd/trunk/modules/http2/h2_util.c
httpd/httpd/trunk/modules/http2/h2_util.h
httpd/httpd/trunk/modules/http2/h2_version.h
httpd/httpd/trunk/modules/http2/mod_http2.c
httpd/httpd/trunk/modules/http2/mod_http2.dsp
httpd/httpd/trunk/modules/proxy/proxy_util.c
httpd/httpd/trunk/server/core.c
httpd/httpd/trunk/test/pyhttpd/config.ini.in
httpd/httpd/trunk/test/pyhttpd/env.py
httpd/httpd/trunk/test/travis_run_linux.sh
Modified: httpd/httpd/trunk/.github/workflows/linux.yml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/.github/workflows/linux.yml?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/.github/workflows/linux.yml (original)
+++ httpd/httpd/trunk/.github/workflows/linux.yml Tue Jun 20 12:01:09 2023
@@ -197,7 +197,7 @@ jobs:
# -------------------------------------------------------------------------
- name: HTTP/2 test suite
config: --enable-mods-shared=reallyall --with-mpm=event --enable-mpms-shared=all
- pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart
+ pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart python3-filelock python3-websockets
env: |
APR_VERSION=1.7.4
APU_VERSION=1.6.3
@@ -228,7 +228,7 @@ jobs:
### TODO: fix caching here.
- name: MOD_TLS test suite
config: --enable-mods-shared=reallyall --with-mpm=event --enable-mpms-shared=event
- pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart cargo cbindgen
+ pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart python3-filelock python3-websockets cargo cbindgen
env: |
APR_VERSION=1.7.4
APU_VERSION=1.6.3
Modified: httpd/httpd/trunk/CMakeLists.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CMakeLists.txt?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/CMakeLists.txt (original)
+++ httpd/httpd/trunk/CMakeLists.txt Tue Jun 20 12:01:09 2023
@@ -497,6 +497,7 @@ SET(mod_http2_extra_sources
modules/http2/h2_request.c modules/http2/h2_session.c
modules/http2/h2_stream.c modules/http2/h2_switch.c
modules/http2/h2_util.c modules/http2/h2_workers.c
+ modules/http2/h2_ws.c
)
SET(mod_ldap_extra_defines LDAP_DECLARE_EXPORT)
SET(mod_ldap_extra_libs wldap32)
Added: httpd/httpd/trunk/changes-entries/h2_websockets.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/changes-entries/h2_websockets.txt?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/changes-entries/h2_websockets.txt (added)
+++ httpd/httpd/trunk/changes-entries/h2_websockets.txt Tue Jun 20 12:01:09 2023
@@ -0,0 +1,10 @@
+ *) mod_http2: added support for bootstrapping WebSockets via HTTP/2, as
+ described in RFC 8441. A new directive 'H2WebSockets on|off' has been
+ added. The feature is by default not enabled.
+ As also discussed in the manual, this feature should work for setups
+ using "ProxyPass backend-url upgrade=websocket" without further changes.
+ Special server modules for WebSockets will have to be adapted,
+ most likely, as the handling if IO events is different with HTTP/2.
+ HTTP/2 WebSockets are supported on platforms with native pipes. This
+ excludes Windows.
+ [Stefan Eissing]
\ No newline at end of file
Modified: httpd/httpd/trunk/configure.in
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/configure.in?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/configure.in (original)
+++ httpd/httpd/trunk/configure.in Tue Jun 20 12:01:09 2023
@@ -999,6 +999,7 @@ APACHE_FAST_OUTPUT(support/Makefile)
if test -d ./test; then
APACHE_FAST_OUTPUT(test/Makefile)
AC_CONFIG_FILES([test/pyhttpd/config.ini])
+ APACHE_FAST_OUTPUT(test/clients/Makefile)
fi
dnl ## Finalize the variables
Modified: httpd/httpd/trunk/docs/manual/mod/mod_http2.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_http2.xml?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_http2.xml (original)
+++ httpd/httpd/trunk/docs/manual/mod/mod_http2.xml Tue Jun 20 12:01:09 2023
@@ -1082,4 +1082,42 @@ H2EarlyHint Link "</my.css>;rel=pr
</usage>
</directivesynopsis>
+ <directivesynopsis>
+ <name>H2WebSockets</name>
+ <description>En-/Disable WebSockets via HTTP/2</description>
+ <syntax>H2WebSockets on|off</syntax>
+ <default>H2WebSockets off</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <compatibility>Available in version 2.5.1 and later.</compatibility>
+
+ <usage>
+ <p>
+ Use <directive>H2WebSockets</directive> to enable or disable
+ bootstrapping of WebSockets via the HTTP/2 protocol. This
+ protocol extension is defined in RFC 8441.
+ </p><p>
+ Such requests come as a CONNECT with an extra ':protocol'
+ header. Such requests are transformed inside the module to
+ their HTTP/1.1 equivalents before passing it to internal
+ processing.
+ </p><p>
+ This means that HTTP/2 WebSockets can be used for a
+ <directive module="mod_proxy">ProxyPass</directive> with
+ 'upgrade=websocket' parameter without further changes.
+ </p><p>
+ For (3rd party) modules that handle WebSockets directly in the
+ server, the protocol bootstrapping itself will also work. However
+ the transfer of data does require extra support in case of HTTP/2.
+ The negotiated WebSocket will not be able to use the client connection
+ socket for polling IO related events.
+ </p><p>
+ Because enabling this feature might break backward compatibility
+ for such 3rd party modules, it is not enabled by default.
+ </p>
+ </usage>
+ </directivesynopsis>
+
</modulesynopsis>
Modified: httpd/httpd/trunk/include/ap_mmn.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/ap_mmn.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/include/ap_mmn.h (original)
+++ httpd/httpd/trunk/include/ap_mmn.h Tue Jun 20 12:01:09 2023
@@ -718,6 +718,7 @@
* 20211221.13 (2.5.1-dev) Add hook token_checker to check for authorization other
* than username / password. Add autht_provider structure.
* 20211221.14 (2.5.1-dev) Add request_rec->final_resp_passed bit
+ * 20211221.15 (2.5.1-dev) Add ap_get_pollfd_from_conn()
*/
#define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
@@ -725,7 +726,7 @@
#ifndef MODULE_MAGIC_NUMBER_MAJOR
#define MODULE_MAGIC_NUMBER_MAJOR 20211221
#endif
-#define MODULE_MAGIC_NUMBER_MINOR 14 /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 15 /* 0...n */
/**
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a
Modified: httpd/httpd/trunk/include/http_core.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/http_core.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/include/http_core.h (original)
+++ httpd/httpd/trunk/include/http_core.h Tue Jun 20 12:01:09 2023
@@ -31,6 +31,7 @@
#include "apr_optional.h"
#include "util_filter.h"
#include "ap_expr.h"
+#include "apr_poll.h"
#include "apr_tables.h"
#include "http_config.h"
@@ -1109,6 +1110,30 @@ AP_DECLARE(int) ap_state_query(int query
*/
AP_CORE_DECLARE(conn_rec *) ap_create_slave_connection(conn_rec *c);
+/** Get a apr_pollfd_t populated with descriptor and descriptor type
+ * and the timeout to use for it.
+ * @return APR_ENOTIMPL if not supported for a connection.
+ */
+AP_DECLARE_HOOK(apr_status_t, get_pollfd_from_conn,
+ (conn_rec *c, struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout))
+
+/**
+ * Pass in a `struct apr_pollfd_t*` and get `desc_type` and `desc`
+ * populated with a suitable value for polling connection input.
+ * For primary connection (c->master == NULL), this will be the connection
+ * socket. For secondary connections this may differ or not be available
+ * at all.
+ * Note that APR_NO_DESC may be set to indicate that the connection
+ * input is already closed.
+ *
+ * @param pfd the pollfd to set the descriptor in
+ * @param ptimeout != NULL to retrieve the timeout in effect
+ * @return ARP_SUCCESS when the information was assigned.
+ */
+AP_CORE_DECLARE(apr_status_t) ap_get_pollfd_from_conn(conn_rec *c,
+ struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout);
/** Macro to provide a default value if the pointer is not yet initialised
*/
Modified: httpd/httpd/trunk/modules/http2/config2.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/config2.m4?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/config2.m4 (original)
+++ httpd/httpd/trunk/modules/http2/config2.m4 Tue Jun 20 12:01:09 2023
@@ -37,6 +37,7 @@ h2_stream.lo dnl
h2_switch.lo dnl
h2_util.lo dnl
h2_workers.lo dnl
+h2_ws.lo dnl
"
dnl
Modified: httpd/httpd/trunk/modules/http2/h2.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2.h (original)
+++ httpd/httpd/trunk/modules/http2/h2.h Tue Jun 20 12:01:09 2023
@@ -62,6 +62,8 @@ extern const char *H2_MAGIC_TOKEN;
#define H2_HEADER_AUTH_LEN 10
#define H2_HEADER_PATH ":path"
#define H2_HEADER_PATH_LEN 5
+#define H2_HEADER_PROTO ":protocol"
+#define H2_HEADER_PROTO_LEN 9
#define H2_CRLF "\r\n"
/* Size of the frame header itself in HTTP/2 */
@@ -153,6 +155,7 @@ struct h2_request {
const char *scheme;
const char *authority;
const char *path;
+ const char *protocol;
apr_table_t *headers;
apr_time_t request_time;
Modified: httpd/httpd/trunk/modules/http2/h2_bucket_beam.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_bucket_beam.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_bucket_beam.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_bucket_beam.c Tue Jun 20 12:01:09 2023
@@ -268,6 +268,7 @@ static void beam_shutdown(h2_bucket_beam
if (how == APR_SHUTDOWN_READWRITE) {
beam->cons_io_cb = NULL;
beam->recv_cb = NULL;
+ beam->eagain_cb = NULL;
}
/* shutdown sender (or both)? */
@@ -747,6 +748,9 @@ transfer:
leave:
H2_BEAM_LOG(beam, to, APLOG_TRACE2, rv, "end receive", bb);
+ if (rv == APR_EAGAIN && beam->eagain_cb) {
+ beam->eagain_cb(beam->eagain_ctx, beam);
+ }
apr_thread_mutex_unlock(beam->lock);
return rv;
}
@@ -769,6 +773,15 @@ void h2_beam_on_received(h2_bucket_beam
apr_thread_mutex_unlock(beam->lock);
}
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+ h2_beam_ev_callback *eagain_cb, void *ctx)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->eagain_cb = eagain_cb;
+ beam->eagain_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
void h2_beam_on_send(h2_bucket_beam *beam,
h2_beam_ev_callback *send_cb, void *ctx)
{
@@ -846,3 +859,25 @@ int h2_beam_report_consumption(h2_bucket
apr_thread_mutex_unlock(beam->lock);
return rv;
}
+
+int h2_beam_is_complete(h2_bucket_beam *beam)
+{
+ int rv = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ if (beam->closed)
+ rv = 1;
+ else {
+ apr_bucket *b;
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ rv = 1;
+ break;
+ }
+ }
+ }
+ apr_thread_mutex_unlock(beam->lock);
+ return rv;
+}
Modified: httpd/httpd/trunk/modules/http2/h2_bucket_beam.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_bucket_beam.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_bucket_beam.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_bucket_beam.h Tue Jun 20 12:01:09 2023
@@ -67,6 +67,8 @@ struct h2_bucket_beam {
void *recv_ctx;
h2_beam_ev_callback *send_cb; /* event: buckets were added in h2_beam_send() */
void *send_ctx;
+ h2_beam_ev_callback *eagain_cb; /* event: a receive results in ARP_EAGAIN */
+ void *eagain_ctx;
apr_off_t recv_bytes; /* amount of bytes transferred in h2_beam_receive() */
apr_off_t recv_bytes_reported; /* amount of bytes reported as received via callback */
@@ -206,6 +208,16 @@ void h2_beam_on_received(h2_bucket_beam
h2_beam_ev_callback *recv_cb, void *ctx);
/**
+ * Register a callback to be invoked on the receiver side whenever
+ * APR_EAGAIN is being returned in h2_beam_receive().
+ * @param beam the beam to set the callback on
+ * @param egain_cb the callback or NULL, called before APR_EAGAIN is returned
+ * @param ctx the context to use in callback invocation
+ */
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+ h2_beam_ev_callback *eagain_cb, void *ctx);
+
+/**
* Register a call back from the sender side to be invoked when send
* has added buckets to the beam.
* Unregister by passing a NULL on_send_cb.
@@ -246,4 +258,10 @@ apr_off_t h2_beam_get_buffered(h2_bucket
*/
apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam);
+/**
+ * @return != 0 iff beam has been closed or has an EOS bucket buffered
+ * waiting to be received.
+ */
+int h2_beam_is_complete(h2_bucket_beam *beam);
+
#endif /* h2_bucket_beam_h */
Modified: httpd/httpd/trunk/modules/http2/h2_c1_io.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c1_io.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c1_io.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c1_io.c Tue Jun 20 12:01:09 2023
@@ -267,7 +267,7 @@ static apr_status_t pass_output(h2_c1_io
/* recursive call, may be triggered by an H2EOS bucket
* being destroyed and triggering sending more data? */
AP_DEBUG_ASSERT(0);
- ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(10456)
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10456)
"h2_c1_io(%ld): recursive call of h2_c1_io_pass. "
"Denied to prevent output corruption. This "
"points to a bug in the HTTP/2 implementation.",
Modified: httpd/httpd/trunk/modules/http2/h2_c2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c2.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c2.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c2.c Tue Jun 20 12:01:09 2023
@@ -48,6 +48,7 @@
#include "h2_headers.h"
#include "h2_session.h"
#include "h2_stream.h"
+#include "h2_ws.h"
#include "h2_c2.h"
#include "h2_util.h"
@@ -173,6 +174,7 @@ void h2_c2_abort(conn_rec *c2, conn_rec
typedef struct {
apr_bucket_brigade *bb; /* c2: data in holding area */
+ unsigned did_upgrade_eos:1; /* for Upgrade, we added an extra EOS */
} h2_c2_fctx_in_t;
static apr_status_t h2_c2_filter_in(ap_filter_t* f,
@@ -216,7 +218,17 @@ static apr_status_t h2_c2_filter_in(ap_f
APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
}
}
-
+
+ /* If this is a HTTP Upgrade, it means the request we process
+ * has not Content, although the stream is not necessarily closed.
+ * On first read, we insert an EOS to signal processing that it
+ * has the complete body. */
+ if (conn_ctx->is_upgrade && !fctx->did_upgrade_eos) {
+ b = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
+ fctx->did_upgrade_eos = 1;
+ }
+
while (APR_BRIGADE_EMPTY(fctx->bb)) {
/* Get more input data for our request. */
if (APLOGctrace2(f->c)) {
@@ -547,6 +559,31 @@ static int c2_hook_pre_connection(conn_r
return OK;
}
+static apr_status_t c2_get_pollfd_from_conn(conn_rec *c,
+ struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout)
+{
+ if (c->master) {
+ h2_conn_ctx_t *ctx = h2_conn_ctx_get(c);
+ if (ctx) {
+ if (ctx->beam_in && ctx->pipe_in[H2_PIPE_OUT]) {
+ pfd->desc_type = APR_POLL_FILE;
+ pfd->desc.f = ctx->pipe_in[H2_PIPE_OUT];
+ if (ptimeout)
+ *ptimeout = h2_beam_timeout_get(ctx->beam_in);
+ }
+ else {
+ /* no input */
+ pfd->desc_type = APR_NO_DESC;
+ if (ptimeout)
+ *ptimeout = -1;
+ }
+ return APR_SUCCESS;
+ }
+ }
+ return APR_ENOTIMPL;
+}
+
void h2_c2_register_hooks(void)
{
/* When the connection processing actually starts, we might
@@ -558,8 +595,14 @@ void h2_c2_register_hooks(void)
/* We need to manipulate the standard HTTP/1.1 protocol filters and
* install our own. This needs to be done very early. */
ap_hook_pre_read_request(c2_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_post_read_request(c2_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_post_read_request(c2_post_read_request, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 15)
+ ap_hook_get_pollfd_from_conn(c2_get_pollfd_from_conn, NULL, NULL,
+ APR_HOOK_MIDDLE);
+#endif
+
c2_net_in_filter_handle =
ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
@@ -668,11 +711,21 @@ static apr_status_t c2_process(h2_conn_c
{
const h2_request *req = conn_ctx->request;
conn_state_t *cs = c->cs;
- request_rec *r;
+ request_rec *r = NULL;
const char *tenc;
apr_time_t timeout;
+ apr_status_t rv = APR_SUCCESS;
+
+ if(req->protocol && !strcmp("websocket", req->protocol)) {
+ req = h2_ws_rewrite_request(req, c, conn_ctx->beam_in == NULL);
+ if (!req) {
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+ }
+
+ r = h2_create_request_rec(req, c, conn_ctx->beam_in == NULL);
- r = h2_create_request_rec(conn_ctx->request, c, conn_ctx->beam_in == NULL);
if (!r) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"h2_c2(%s-%d): create request_rec failed, r=NULL",
Modified: httpd/httpd/trunk/modules/http2/h2_c2_filter.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c2_filter.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c2_filter.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c2_filter.c Tue Jun 20 12:01:09 2023
@@ -39,6 +39,7 @@
#include "h2_c2.h"
#include "h2_mplx.h"
#include "h2_request.h"
+#include "h2_ws.h"
#include "h2_util.h"
@@ -108,15 +109,26 @@ apr_status_t h2_c2_filter_request_in(ap_
/* This filter is a one-time wonder */
ap_remove_input_filter(f);
- if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) && conn_ctx->stream_id) {
- if (conn_ctx->request->http_status != H2_HTTP_STATUS_UNSET) {
+ if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) &&
+ conn_ctx->stream_id) {
+ const h2_request *req = conn_ctx->request;
+
+ if (req->http_status == H2_HTTP_STATUS_UNSET &&
+ req->protocol && !strcmp("websocket", req->protocol)) {
+ req = h2_ws_rewrite_request(req, f->c, conn_ctx->beam_in == NULL);
+ if (!req)
+ return APR_EGENERAL;
+ }
+
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
/* error was encountered preparing this request */
- b = ap_bucket_error_create(conn_ctx->request->http_status, NULL, f->r->pool,
+ b = ap_bucket_error_create(req->http_status, NULL, f->r->pool,
f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
return APR_SUCCESS;
}
- b = h2_request_create_bucket(conn_ctx->request, f->r);
+
+ b = h2_request_create_bucket(req, f->r);
APR_BRIGADE_INSERT_TAIL(bb, b);
if (!conn_ctx->beam_in) {
b = apr_bucket_eos_create(f->c->bucket_alloc);
@@ -184,7 +196,7 @@ static int uniq_field_values(void *d, co
*/
for (i = 0, strpp = (char **) values->elts; i < values->nelts;
++i, ++strpp) {
- if (*strpp && apr_strnatcasecmp(*strpp, start) == 0) {
+ if (*strpp && ap_cstr_casecmp(*strpp, start) == 0) {
break;
}
}
@@ -292,7 +304,7 @@ static h2_headers *create_response(reque
while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
for (i = 0; i < r->content_languages->nelts; ++i) {
- if (!apr_strnatcasecmp(token, languages[i]))
+ if (!ap_cstr_casecmp(token, languages[i]))
break;
}
if (i == r->content_languages->nelts) {
@@ -636,9 +648,11 @@ apr_status_t h2_c2_filter_catch_h1_out(a
int result = ap_map_http_request_error(conn_ctx->last_err,
HTTP_INTERNAL_SERVER_ERROR);
request_rec *r = h2_create_request_rec(conn_ctx->request, f->c, 1);
- ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
- b = ap_bucket_eor_create(f->c->bucket_alloc, r);
- APR_BRIGADE_INSERT_TAIL(bb, b);
+ if (r) {
+ ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
+ b = ap_bucket_eor_create(f->c->bucket_alloc, r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
}
}
/* There are cases where we need to parse a serialized http/1.1 response.
Modified: httpd/httpd/trunk/modules/http2/h2_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.c Tue Jun 20 12:01:09 2023
@@ -77,6 +77,7 @@ typedef struct h2_config {
int output_buffered;
apr_interval_time_t stream_timeout;/* beam timeout */
int max_data_frame_len; /* max # bytes in a single h2 DATA frame */
+ int h2_websockets; /* if mod_h2 negotiating WebSockets */
} h2_config;
typedef struct h2_dir_config {
@@ -115,6 +116,7 @@ static h2_config defconf = {
1, /* stream output buffered */
-1, /* beam timeout */
0, /* max DATA frame len, 0 == no extra limit */
+ 0, /* WebSockets negotiation, enabled */
};
static h2_dir_config defdconf = {
@@ -161,6 +163,7 @@ void *h2_config_create_svr(apr_pool_t *p
conf->output_buffered = DEF_VAL;
conf->stream_timeout = DEF_VAL;
conf->max_data_frame_len = DEF_VAL;
+ conf->h2_websockets = DEF_VAL;
return conf;
}
@@ -210,6 +213,7 @@ static void *h2_config_merge(apr_pool_t
n->padding_always = H2_CONFIG_GET(add, base, padding_always);
n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
n->max_data_frame_len = H2_CONFIG_GET(add, base, max_data_frame_len);
+ n->h2_websockets = H2_CONFIG_GET(add, base, h2_websockets);
return n;
}
@@ -301,6 +305,8 @@ static apr_int64_t h2_srv_config_geti64(
return H2_CONFIG_GET(conf, &defconf, stream_timeout);
case H2_CONF_MAX_DATA_FRAME_LEN:
return H2_CONFIG_GET(conf, &defconf, max_data_frame_len);
+ case H2_CONF_WEBSOCKETS:
+ return H2_CONFIG_GET(conf, &defconf, h2_websockets);
default:
return DEF_VAL;
}
@@ -363,6 +369,9 @@ static void h2_srv_config_seti(h2_config
case H2_CONF_MAX_DATA_FRAME_LEN:
H2_CONFIG_SET(conf, max_data_frame_len, val);
break;
+ case H2_CONF_WEBSOCKETS:
+ H2_CONFIG_SET(conf, h2_websockets, val);
+ break;
default:
break;
}
@@ -681,6 +690,24 @@ static const char *h2_conf_set_push(cmd_
return "value must be On or Off";
}
+static const char *h2_conf_set_websockets(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+#if H2_USE_PIPES
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 1);
+ return NULL;
+#else
+ return "HTTP/2 WebSockets are not supported on this platform";
+#endif
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
const char *ctype, const char *sdependency,
const char *sweight)
@@ -1021,6 +1048,8 @@ const command_rec h2_cmds[] = {
RSRC_CONF, "maximum number of bytes in a single HTTP/2 DATA frame"),
AP_INIT_TAKE2("H2EarlyHint", h2_conf_add_early_hint, NULL,
OR_FILEINFO|OR_AUTHCFG, "add a a 'Link:' header for a 103 Early Hints response."),
+ AP_INIT_TAKE1("H2WebSockets", h2_conf_set_websockets, NULL,
+ RSRC_CONF, "off to disable WebSockets over HTTP/2"),
AP_END_CMD
};
Modified: httpd/httpd/trunk/modules/http2/h2_config.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.h Tue Jun 20 12:01:09 2023
@@ -44,6 +44,7 @@ typedef enum {
H2_CONF_OUTPUT_BUFFER,
H2_CONF_STREAM_TIMEOUT,
H2_CONF_MAX_DATA_FRAME_LEN,
+ H2_CONF_WEBSOCKETS,
} h2_config_var_t;
struct apr_hash_t;
Modified: httpd/httpd/trunk/modules/http2/h2_conn_ctx.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_conn_ctx.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_conn_ctx.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_conn_ctx.h Tue Jun 20 12:01:09 2023
@@ -53,7 +53,8 @@ struct h2_conn_ctx_t {
const struct h2_request *request; /* c2: the request to process */
struct h2_bucket_beam *beam_out; /* c2: data out, created from req_pool */
struct h2_bucket_beam *beam_in; /* c2: data in or NULL, borrowed from request stream */
- unsigned int input_chunked; /* c2: if input needs HTTP/1.1 chunking applied */
+ unsigned input_chunked:1; /* c2: if input needs HTTP/1.1 chunking applied */
+ unsigned is_upgrade:1; /* c2: if requst is a HTTP Upgrade */
apr_file_t *pipe_in[2]; /* c2: input produced notification pipe */
apr_pollfd_t pfd; /* c1: poll socket input, c2: NUL */
Modified: httpd/httpd/trunk/modules/http2/h2_mplx.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_mplx.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_mplx.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_mplx.c Tue Jun 20 12:01:09 2023
@@ -146,6 +146,7 @@ static void m_stream_cleanup(h2_mplx *m,
if (c2_ctx->beam_in) {
h2_beam_on_send(c2_ctx->beam_in, NULL, NULL);
h2_beam_on_received(c2_ctx->beam_in, NULL, NULL);
+ h2_beam_on_eagain(c2_ctx->beam_in, NULL, NULL);
h2_beam_on_consumed(c2_ctx->beam_in, NULL, NULL);
}
}
@@ -666,7 +667,9 @@ static apr_status_t c1_process_stream(h2
if (APLOGctrace1(m->c1)) {
const h2_request *r = stream->request;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
- H2_STRM_MSG(stream, "process %s %s%s%s%s"),
+ H2_STRM_MSG(stream, "process %s%s%s %s%s%s%s"),
+ r->protocol? r->protocol : "",
+ r->protocol? " " : "",
r->method, r->scheme? r->scheme : "",
r->scheme? "://" : "",
r->authority, r->path? r->path: "");
@@ -780,6 +783,19 @@ static void c2_beam_input_read_notify(vo
}
}
+static void c2_beam_input_read_eagain(void *ctx, h2_bucket_beam *beam)
+{
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+ /* installed in the input bucket beams when we use pipes.
+ * Drain the pipe just before the beam returns APR_EAGAIN.
+ * A clean state for allowing polling on the pipe to rest
+ * when the beam is empty */
+ if (conn_ctx && conn_ctx->pipe_in[H2_PIPE_OUT]) {
+ h2_util_drain_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]);
+ }
+}
+
static void c2_beam_output_write_notify(void *ctx, h2_bucket_beam *beam)
{
conn_rec *c = ctx;
@@ -824,6 +840,7 @@ static apr_status_t c2_setup_io(h2_mplx
c2->pool, c2->pool);
if (APR_SUCCESS != rv) goto cleanup;
#endif
+ h2_beam_on_eagain(stream->input, c2_beam_input_read_eagain, c2);
}
cleanup:
@@ -930,6 +947,15 @@ static void s_c2_done(h2_mplx *m, conn_r
"h2_c2(%s-%d): processing finished without final response",
conn_ctx->id, conn_ctx->stream_id);
c2->aborted = 1;
+ if (conn_ctx->beam_out)
+ h2_beam_abort(conn_ctx->beam_out, c2);
+ }
+ else if (!conn_ctx->beam_out || !h2_beam_is_complete(conn_ctx->beam_out)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, conn_ctx->last_err, c2,
+ "h2_c2(%s-%d): processing finished with incomplete output",
+ conn_ctx->id, conn_ctx->stream_id);
+ c2->aborted = 1;
+ h2_beam_abort(conn_ctx->beam_out, c2);
}
else if (!c2->aborted) {
s_mplx_be_happy(m, c2, conn_ctx);
Modified: httpd/httpd/trunk/modules/http2/h2_proxy_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_proxy_util.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_proxy_util.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_proxy_util.c Tue Jun 20 12:01:09 2023
@@ -381,7 +381,7 @@ static int iq_bubble_down(h2_proxy_iqueu
* h2_proxy_ngheader
******************************************************************************/
#define H2_HD_MATCH_LIT_CS(l, name) \
- ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+ ((strlen(name) == sizeof(l) - 1) && !ap_cstr_casecmp(l, name))
static int h2_util_ignore_header(const char *name)
{
@@ -500,7 +500,7 @@ static int ignore_header(const literal *
for (i = 0; i < llen; ++i) {
lit = &lits[i];
- if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) {
+ if (lit->len == nlen && !ap_cstr_casecmp(lit->name, name)) {
return 1;
}
}
@@ -542,7 +542,7 @@ void h2_proxy_util_camel_case_header(cha
/** Match a header value against a string constance, case insensitive */
#define H2_HD_MATCH_LIT(l, name, nlen) \
- ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+ ((nlen == sizeof(l) - 1) && !ap_cstr_casecmp(l, name))
static apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool,
const char *name, size_t nlen,
Modified: httpd/httpd/trunk/modules/http2/h2_push.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_push.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_push.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_push.c Tue Jun 20 12:01:09 2023
@@ -426,7 +426,7 @@ static void inspect_link(link_ctx *ctx,
static int head_iter(void *ctx, const char *key, const char *value)
{
- if (!apr_strnatcasecmp("link", key)) {
+ if (!ap_cstr_casecmp("link", key)) {
inspect_link(ctx, value, strlen(value));
}
return 1;
Modified: httpd/httpd/trunk/modules/http2/h2_request.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_request.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_request.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_request.c Tue Jun 20 12:01:09 2023
@@ -166,6 +166,10 @@ apr_status_t h2_request_add_header(h2_re
&& !strncmp(H2_HEADER_AUTH, name, nlen)) {
req->authority = apr_pstrndup(pool, value, vlen);
}
+ else if (H2_HEADER_PROTO_LEN == nlen
+ && !strncmp(H2_HEADER_PROTO, name, nlen)) {
+ req->protocol = apr_pstrndup(pool, value, vlen);
+ }
else {
char buffer[32];
memset(buffer, 0, 32);
@@ -214,6 +218,7 @@ h2_request *h2_request_clone(apr_pool_t
dst->scheme = apr_pstrdup(p, src->scheme);
dst->authority = apr_pstrdup(p, src->authority);
dst->path = apr_pstrdup(p, src->path);
+ dst->protocol = apr_pstrdup(p, src->protocol);
dst->headers = apr_table_clone(p, src->headers);
return dst;
}
@@ -299,13 +304,13 @@ apr_bucket *h2_request_create_bucket(con
#endif
static void assign_headers(request_rec *r, const h2_request *req,
- int no_body)
+ int no_body, int is_connect)
{
const char *cl;
r->headers_in = apr_table_clone(r->pool, req->headers);
- if (req->authority) {
+ if (req->authority && !is_connect) {
/* for internal handling, we have to simulate that :authority
* came in as Host:, RFC 9113 ch. says that mismatches between
* :authority and Host: SHOULD be rejected as malformed. However,
@@ -324,36 +329,40 @@ static void assign_headers(request_rec *
"set 'Host: %s' from :authority", req->authority);
}
- cl = apr_table_get(req->headers, "Content-Length");
- if (no_body) {
- if (!cl && apr_table_get(req->headers, "Content-Type")) {
- /* If we have a content-type, but already seen eos, no more
- * data will come. Signal a zero content length explicitly.
- */
- apr_table_setn(req->headers, "Content-Length", "0");
+ /* Unless we open a byte stream via CONNECT, apply content-length guards. */
+ if (!is_connect) {
+ cl = apr_table_get(req->headers, "Content-Length");
+ if (no_body) {
+ if (!cl && apr_table_get(req->headers, "Content-Type")) {
+ /* If we have a content-type, but already seen eos, no more
+ * data will come. Signal a zero content length explicitly.
+ */
+ apr_table_setn(req->headers, "Content-Length", "0");
+ }
}
- }
#if !AP_HAS_RESPONSE_BUCKETS
- else if (!cl) {
- /* there may be a body and we have internal HTTP/1.1 processing.
- * If the Content-Length is unspecified, we MUST simulate
- * chunked Transfer-Encoding.
- *
- * HTTP/2 does not need a Content-Length for framing. Ideally
- * all clients set the EOS flag on the header frame if they
- * do not intent to send a body. However, forwarding proxies
- * might just no know at the time and send an empty DATA
- * frame with EOS much later.
- */
- apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
- }
+ else if (!cl) {
+ /* there may be a body and we have internal HTTP/1.1 processing.
+ * If the Content-Length is unspecified, we MUST simulate
+ * chunked Transfer-Encoding.
+ *
+ * HTTP/2 does not need a Content-Length for framing. Ideally
+ * all clients set the EOS flag on the header frame if they
+ * do not intent to send a body. However, forwarding proxies
+ * might just no know at the time and send an empty DATA
+ * frame with EOS much later.
+ */
+ apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
+ }
#endif /* else AP_HAS_RESPONSE_BUCKETS */
+ }
}
request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
int no_body)
{
int access_status = HTTP_OK;
+ int is_connect = !ap_cstr_casecmp("CONNECT", req->method);
#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
request_rec *r = ap_create_request(c);
@@ -362,24 +371,43 @@ request_rec *h2_create_request_rec(const
#endif
#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
- assign_headers(r, req, no_body);
+ assign_headers(r, req, no_body, is_connect);
ap_run_pre_read_request(r, c);
/* Time to populate r with the data we have. */
r->request_time = req->request_time;
AP_DEBUG_ASSERT(req->authority);
- if (!apr_strnatcasecmp("CONNECT", req->method)) {
+ if (is_connect) {
/* CONNECT MUST NOT have scheme or path */
+ if (req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
+ "':scheme: %s' header present in CONNECT request",
+ req->scheme);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ if (req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
+ "':path: %s' header present in CONNECT request",
+ req->path);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
req->method, req->authority);
}
- else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
- && ap_cstr_casecmp(req->scheme, "https")) {
- /* FIXME: we also need to create absolute uris when we are
- * in a forward proxy configuration! But there is currently
- * no way to detect that. */
+ else if (req->protocol) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10460)
+ "':protocol: %s' header present in %s request",
+ req->protocol, req->method);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ else if (req->scheme &&
+ ap_cstr_casecmp(req->scheme, ap_ssl_conn_is_ssl(c->master? c->master : c)?
+ "https" : "http")) {
/* Client sent a ':scheme' pseudo header for something else
- * than what we handle by default. Make an absolute URI. */
+ * than what we have on this connection. Make an absolute URI. */
r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
req->method, req->scheme, req->authority,
req->path ? req->path : "");
@@ -420,7 +448,7 @@ request_rec *h2_create_request_rec(const
{
const char *s;
- assign_headers(r, req, no_body);
+ assign_headers(r, req, no_body, is_connect);
ap_run_pre_read_request(r, c);
/* Time to populate r with the data we have. */
Modified: httpd/httpd/trunk/modules/http2/h2_session.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_session.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_session.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_session.c Tue Jun 20 12:01:09 2023
@@ -621,9 +621,8 @@ static int on_invalid_header_cb(nghttp2_
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03456)
H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
- "invalid header '%s: %s'"),
- apr_pstrndup(session->pool, (const char *)name, namelen),
- apr_pstrndup(session->pool, (const char *)value, valuelen));
+ "invalid header '%.*s: %.*s'"),
+ (int)namelen, name, (int)valuelen, value);
stream = get_stream(session, frame->hd.stream_id);
if (stream) {
h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
@@ -1003,7 +1002,7 @@ apr_status_t h2_session_create(h2_sessio
static apr_status_t h2_session_start(h2_session *session, int *rv)
{
apr_status_t status = APR_SUCCESS;
- nghttp2_settings_entry settings[3];
+ nghttp2_settings_entry settings[4];
size_t slen;
int win_size;
@@ -1070,7 +1069,12 @@ static apr_status_t h2_session_start(h2_
settings[slen].value = win_size;
++slen;
}
-
+ if (h2_config_sgeti(session->s, H2_CONF_WEBSOCKETS)) {
+ settings[slen].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+ settings[slen].value = 1;
+ ++slen;
+ }
+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
H2_SSSN_LOG(APLOGNO(03201), session,
"start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"),
Modified: httpd/httpd/trunk/modules/http2/h2_stream.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_stream.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_stream.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_stream.c Tue Jun 20 12:01:09 2023
@@ -767,6 +767,9 @@ apr_status_t h2_stream_add_header(h2_str
status = h2_request_add_header(stream->rtmp, stream->pool,
name, nlen, value, vlen,
session->s->limit_req_fieldsize, &was_added);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
+ H2_STRM_MSG(stream, "add_header: '%.*s: %.*s"),
+ (int)nlen, name, (int)vlen, value);
if (was_added) ++stream->request_headers_added;
}
else if (H2_SS_OPEN == stream->state) {
@@ -897,7 +900,14 @@ apr_status_t h2_stream_end_headers(h2_st
* of CONNECT requests (see [RFC7230], Section 5.3)).
*/
if (!ap_cstr_casecmp(req->method, "CONNECT")) {
- if (req->scheme || req->path) {
+ if (req->protocol && !strcmp("websocket", req->protocol)) {
+ if (!req->scheme || !req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
+ "without :scheme or :path, sending 400 answer"));
+ }
+ }
+ else if (req->scheme || req->path) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(10384), stream, "Request to CONNECT "
"with :scheme or :path specified, sending 400 answer"));
@@ -1459,8 +1469,8 @@ static ssize_t stream_data_cb(nghttp2_se
* it is all fine. */
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_SSSN_STRM_MSG(session, stream_id, "rst stream"));
- h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
- return NGHTTP2_ERR_CALLBACK_FAILURE;
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ return NGHTTP2_ERR_DEFERRED;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_SSSN_STRM_MSG(session, stream_id,
@@ -1469,10 +1479,17 @@ static ssize_t stream_data_cb(nghttp2_se
eos = 1;
rv = APR_SUCCESS;
}
+ else if (APR_ECONNRESET == rv || APR_ECONNABORTED == rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(), stream, "data_cb, reading data"));
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ return NGHTTP2_ERR_DEFERRED;
+ }
else {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
H2_STRM_LOG(APLOGNO(02938), stream, "data_cb, reading data"));
- return NGHTTP2_ERR_CALLBACK_FAILURE;
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ return NGHTTP2_ERR_DEFERRED;
}
}
Modified: httpd/httpd/trunk/modules/http2/h2_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_util.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_util.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_util.c Tue Jun 20 12:01:09 2023
@@ -1281,8 +1281,8 @@ apr_size_t h2_util_bucket_print(char *bu
else if (bmax > off) {
off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
b->type->name,
- (long)(b->length == ((apr_size_t)-1)?
- -1 : b->length));
+ (b->length == ((apr_size_t)-1)?
+ -1 : (long)b->length));
}
return off;
}
@@ -1650,7 +1650,7 @@ static int contains_name(const literal *
for (i = 0; i < llen; ++i) {
lit = &lits[i];
if (lit->len == nv->namelen
- && !apr_strnatcasecmp(lit->name, (const char *)nv->name)) {
+ && !ap_cstr_casecmp(lit->name, (const char *)nv->name)) {
return 1;
}
}
@@ -1706,7 +1706,7 @@ static apr_status_t req_add_header(apr_t
return APR_SUCCESS;
}
else if (nv->namelen == sizeof("cookie")-1
- && !apr_strnatcasecmp("cookie", (const char *)nv->name)) {
+ && !ap_cstr_casecmp("cookie", (const char *)nv->name)) {
existing = apr_table_get(headers, "cookie");
if (existing) {
/* Cookie header come separately in HTTP/2, but need
@@ -1725,7 +1725,7 @@ static apr_status_t req_add_header(apr_t
}
}
else if (nv->namelen == sizeof("host")-1
- && !apr_strnatcasecmp("host", (const char *)nv->name)) {
+ && !ap_cstr_casecmp("host", (const char *)nv->name)) {
if (apr_table_get(headers, "Host")) {
return APR_SUCCESS; /* ignore duplicate */
}
@@ -1883,6 +1883,13 @@ void h2_util_drain_pipe(apr_file_t *pipe
{
char rb[512];
apr_size_t nr = sizeof(rb);
+ apr_interval_time_t timeout;
+ apr_status_t trv;
+
+ /* Make the pipe non-blocking if we can */
+ trv = apr_file_pipe_timeout_get(pipe, &timeout);
+ if (trv == APR_SUCCESS)
+ apr_file_pipe_timeout_set(pipe, 0);
while (apr_file_read(pipe, rb, &nr) == APR_SUCCESS) {
/* Although we write just one byte to the other end of the pipe
@@ -1893,6 +1900,8 @@ void h2_util_drain_pipe(apr_file_t *pipe
if (nr != sizeof(rb))
break;
}
+ if (trv == APR_SUCCESS)
+ apr_file_pipe_timeout_set(pipe, timeout);
}
apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe)
Modified: httpd/httpd/trunk/modules/http2/h2_util.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_util.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_util.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_util.h Tue Jun 20 12:01:09 2023
@@ -337,7 +337,7 @@ apr_size_t h2_util_table_bytes(apr_table
/** Match a header value against a string constance, case insensitive */
#define H2_HD_MATCH_LIT(l, name, nlen) \
- ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+ ((nlen == sizeof(l) - 1) && !ap_cstr_casecmp(l, name))
/*******************************************************************************
* HTTP/2 header helpers
Modified: httpd/httpd/trunk/modules/http2/h2_version.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_version.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_version.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_version.h Tue Jun 20 12:01:09 2023
@@ -27,7 +27,7 @@
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "2.0.19-git"
+#define MOD_HTTP2_VERSION "2.0.20-git"
/**
* @macro
@@ -35,7 +35,7 @@
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_HTTP2_VERSION_NUM 0x020013
+#define MOD_HTTP2_VERSION_NUM 0x020014
#endif /* mod_h2_h2_version_h */
Added: httpd/httpd/trunk/modules/http2/h2_ws.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_ws.c?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_ws.c (added)
+++ httpd/httpd/trunk/modules/http2/h2_ws.c Tue Jun 20 12:01:09 2023
@@ -0,0 +1,326 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_encode.h"
+#include "apr_sha1.h"
+#include "apr_strmatch.h"
+
+#include <ap_mmn.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_ssl.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mpm.h>
+
+#include "h2_private.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
+#include "h2_request.h"
+#include "h2_ws.h"
+
+static ap_filter_rec_t *c2_ws_out_filter_handle;
+
+struct ws_filter_ctx {
+ const char *ws_accept_base64;
+ int has_final_response;
+ int override_body;
+};
+
+/**
+ * Generate the "Sec-WebSocket-Accept" header field for the given key
+ * (base64 encoded) as defined in RFC 6455 ch. 4.2.2 step 5.3
+ */
+static const char *gen_ws_accept(conn_rec *c, const char *key_base64)
+{
+ apr_byte_t dgst[APR_SHA1_DIGESTSIZE];
+ const char ws_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ apr_sha1_ctx_t sha1_ctx;
+
+ apr_sha1_init(&sha1_ctx);
+ apr_sha1_update(&sha1_ctx, key_base64, (unsigned int)strlen(key_base64));
+ apr_sha1_update(&sha1_ctx, ws_guid, (unsigned int)strlen(ws_guid));
+ apr_sha1_final(dgst, &sha1_ctx);
+
+ return apr_pencode_base64_binary(c->pool, dgst, sizeof(dgst),
+ APR_ENCODE_NONE, NULL);
+}
+
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+ conn_rec *c2, int no_body)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+ h2_request *wsreq;
+ unsigned char key_raw[16];
+ const char *key_base64, *accept_base64;
+ struct ws_filter_ctx *ws_ctx;
+ apr_status_t rv;
+
+ if (!conn_ctx || !req->protocol || strcmp("websocket", req->protocol))
+ return req;
+
+ if (ap_cstr_casecmp("CONNECT", req->method)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket request with method %s",
+ conn_ctx->id, conn_ctx->stream_id, req->method);
+ return req;
+ }
+ if (!req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT without :scheme",
+ conn_ctx->id, conn_ctx->stream_id);
+ return req;
+ }
+ if (!req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT without :path",
+ conn_ctx->id, conn_ctx->stream_id);
+ return req;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT for %s",
+ conn_ctx->id, conn_ctx->stream_id, req->path);
+ /* Transform the HTTP/2 extended CONNECT to an internal GET using
+ * the HTTP/1.1 version of websocket connection setup. */
+ wsreq = h2_request_clone(c2->pool, req);
+ wsreq->method = "GET";
+ wsreq->protocol = NULL;
+ apr_table_set(wsreq->headers, "Upgrade", "websocket");
+ /* add Sec-WebSocket-Key header */
+ rv = apr_generate_random_bytes(key_raw, sizeof(key_raw));
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(10461)
+ "error generating secret");
+ return NULL;
+ }
+ key_base64 = apr_pencode_base64_binary(c2->pool, key_raw, sizeof(key_raw),
+ APR_ENCODE_NONE, NULL);
+ apr_table_set(wsreq->headers, "Sec-WebSocket-Key", key_base64);
+ /* This is now the request to process internally */
+
+ /* When this request gets processed and delivers a 101 response,
+ * we expect it to carry a "Sec-WebSocket-Accept" header with
+ * exactly the following value, as per RFC 6455. */
+ accept_base64 = gen_ws_accept(c2, key_base64);
+ /* Add an output filter that intercepts generated responses:
+ * - if a valid WebSocket negotiation happens, transform the
+ * 101 response to a 200
+ * - if a 2xx response happens, that does not pass the Accept test,
+ * return a 502 indicating that the URI seems not support the websocket
+ * protocol (RFC 8441 does not define this, but it seems the best
+ * choice)
+ * - if a 3xx, 4xx or 5xx response happens, forward this unchanged.
+ */
+ ws_ctx = apr_pcalloc(c2->pool, sizeof(*ws_ctx));
+ ws_ctx->ws_accept_base64 = accept_base64;
+ /* insert our filter just before the C2 core filter */
+ ap_remove_output_filter_byhandle(c2->output_filters, "H2_C2_NET_OUT");
+ ap_add_output_filter("H2_C2_WS_OUT", ws_ctx, NULL, c2);
+ ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
+ /* Mark the connection as being an Upgrade, with some special handling
+ * since the request needs an EOS, without the stream being closed */
+ conn_ctx->is_upgrade = 1;
+
+ return wsreq;
+}
+
+static apr_bucket *make_valid_resp(conn_rec *c2, int status,
+ apr_table_t *headers, apr_table_t *notes)
+{
+ apr_table_t *nheaders, *nnotes;
+
+ ap_assert(headers);
+ nheaders = apr_table_clone(c2->pool, headers);
+ apr_table_unset(nheaders, "Connection");
+ apr_table_unset(nheaders, "Upgrade");
+ apr_table_unset(nheaders, "Sec-WebSocket-Accept");
+ nnotes = notes? apr_table_clone(c2->pool, notes) :
+ apr_table_make(c2->pool, 10);
+#if AP_HAS_RESPONSE_BUCKETS
+ return ap_bucket_response_create(status, NULL, nheaders, nnotes,
+ c2->pool, c2->bucket_alloc);
+#else
+ return h2_bucket_headers_create(c2->bucket_alloc,
+ h2_headers_create(status, nheaders,
+ nnotes, 0, c2->pool));
+#endif
+}
+
+static apr_bucket *make_invalid_resp(conn_rec *c2, int status,
+ apr_table_t *notes)
+{
+ apr_table_t *nheaders, *nnotes;
+
+ nheaders = apr_table_make(c2->pool, 10);
+ apr_table_setn(nheaders, "Content-Length", "0");
+ nnotes = notes? apr_table_clone(c2->pool, notes) :
+ apr_table_make(c2->pool, 10);
+#if AP_HAS_RESPONSE_BUCKETS
+ return ap_bucket_response_create(status, NULL, nheaders, nnotes,
+ c2->pool, c2->bucket_alloc);
+#else
+ return h2_bucket_headers_create(c2->bucket_alloc,
+ h2_headers_create(status, nheaders,
+ nnotes, 0, c2->pool));
+#endif
+}
+
+static void ws_handle_resp(conn_rec *c2, h2_conn_ctx_t *conn_ctx,
+ struct ws_filter_ctx *ws_ctx, apr_bucket *b)
+{
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_response *resp = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+ h2_headers *resp = h2_bucket_headers_get(b);
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+ apr_bucket *b_override = NULL;
+ int is_final = 0;
+ int override_body = 0;
+
+ if (ws_ctx->has_final_response) {
+ /* already did, nop */
+ return;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_c2(%s-%d): H2_C2_WS_OUT inspecting response %d",
+ conn_ctx->id, conn_ctx->stream_id, resp->status);
+ if (resp->status == HTTP_SWITCHING_PROTOCOLS) {
+ /* The resource agreed to switch protocol. But this is only valid
+ * if it send back the correct Sec-WebSocket-Accept header value */
+ const char *hd = apr_table_get(resp->headers, "Sec-WebSocket-Accept");
+ if (hd && !strcmp(ws_ctx->ws_accept_base64, hd)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT, valid 101 Upgrade"
+ ", converting to 200 response",
+ conn_ctx->id, conn_ctx->stream_id);
+ b_override = make_valid_resp(c2, HTTP_OK, resp->headers, resp->notes);
+ is_final = 1;
+ }
+ else {
+ if (!hd) {
+ /* This points to someone being confused */
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2, APLOGNO(10462)
+ "h2_c2(%s-%d): websocket CONNECT, got 101 response "
+ "without Sec-WebSocket-Accept header",
+ conn_ctx->id, conn_ctx->stream_id);
+ }
+ else {
+ /* This points to a bug, either in our WebSockets negotiation
+ * or in the request processings implementation of WebSockets */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c2, APLOGNO(10463)
+ "h2_c2(%s-%d): websocket CONNECT, 101 response "
+ "without 'Sec-WebSocket-Accept: %s' but expected %s",
+ conn_ctx->id, conn_ctx->stream_id, hd,
+ ws_ctx->ws_accept_base64);
+ }
+ b_override = make_invalid_resp(c2, HTTP_BAD_GATEWAY, resp->notes);
+ override_body = is_final = 1;
+ }
+ }
+ else if (resp->status < 200) {
+ /* other intermediate response, pass through */
+ }
+ else if (resp->status < 300) {
+ /* Failure, we might be talking to a plain http resource */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT, invalid response %d",
+ conn_ctx->id, conn_ctx->stream_id, resp->status);
+ b_override = make_invalid_resp(c2, HTTP_BAD_GATEWAY, resp->notes);
+ override_body = is_final = 1;
+ }
+ else {
+ /* error response, pass through. */
+ ws_ctx->has_final_response = 1;
+ }
+
+ if (b_override) {
+ APR_BUCKET_INSERT_BEFORE(b, b_override);
+ apr_bucket_delete(b);
+ b = b_override;
+ }
+ if (override_body) {
+ APR_BUCKET_INSERT_AFTER(b, apr_bucket_eos_create(c2->bucket_alloc));
+ ws_ctx->override_body = 1;
+ }
+ if (is_final) {
+ ws_ctx->has_final_response = 1;
+ conn_ctx->has_final_response = 1;
+ }
+}
+
+static apr_status_t h2_c2_ws_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ struct ws_filter_ctx *ws_ctx = f->ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ apr_bucket *b, *bnext;
+
+ ap_assert(conn_ctx);
+ if (ws_ctx->override_body) {
+ /* We have overridden the original response and also its body.
+ * If this filter is called again, we signal a hard abort to
+ * allow processing to terminate at the earliest. */
+ f->c->aborted = 1;
+ return APR_ECONNABORTED;
+ }
+
+ /* Inspect the brigade, looking for RESPONSE/HEADER buckets.
+ * Remember, this filter is only active for client websocket CONNECT
+ * requests that we translated to an internal GET with websocket
+ * headers.
+ * We inspect the repsone to see if the internal resource actually
+ * agrees to talk websocket or is "just" a normal HTTP resource that
+ * ignored the websocket request headers. */
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = bnext)
+ {
+ bnext = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+#else
+ if (H2_BUCKET_IS_HEADERS(b)) {
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+ ws_handle_resp(f->c, conn_ctx, ws_ctx, b);
+ continue;
+ }
+ }
+ else if (ws_ctx->override_body) {
+ apr_bucket_delete(b);
+ }
+ }
+ return ap_pass_brigade(f->next, bb);
+}
+
+void h2_ws_register_hooks(void)
+{
+ c2_ws_out_filter_handle =
+ ap_register_output_filter("H2_C2_WS_OUT", h2_c2_ws_filter_out,
+ NULL, AP_FTYPE_NETWORK);
+}
Added: httpd/httpd/trunk/modules/http2/h2_ws.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_ws.h?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_ws.h (added)
+++ httpd/httpd/trunk/modules/http2/h2_ws.h Tue Jun 20 12:01:09 2023
@@ -0,0 +1,35 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_ws__
+#define __mod_h2__h2_ws__
+
+#include "h2.h"
+
+/**
+ * Rewrite a websocket request.
+ *
+ * @param req the h2 request to rewrite
+ * @param conn the connection to process the request on
+ * @param no_body != 0 iff the request is known to have no body
+ * @return the websocket request for internal submit
+ */
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+ conn_rec *c2, int no_body);
+
+void h2_ws_register_hooks(void);
+
+#endif /* defined(__mod_h2__h2_ws__) */
Modified: httpd/httpd/trunk/modules/http2/mod_http2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_http2.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_http2.c (original)
+++ httpd/httpd/trunk/modules/http2/mod_http2.c Tue Jun 20 12:01:09 2023
@@ -42,6 +42,7 @@
#include "h2_switch.h"
#include "h2_version.h"
#include "h2_bucket_beam.h"
+#include "h2_ws.h"
static void h2_hooks(apr_pool_t *pool);
@@ -199,6 +200,7 @@ static void h2_hooks(apr_pool_t *pool)
h2_c1_register_hooks();
h2_switch_register_hooks();
h2_c2_register_hooks();
+ h2_ws_register_hooks();
/* Setup subprocess env for certain variables
*/
Modified: httpd/httpd/trunk/modules/http2/mod_http2.dsp
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_http2.dsp?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_http2.dsp (original)
+++ httpd/httpd/trunk/modules/http2/mod_http2.dsp Tue Jun 20 12:01:09 2023
@@ -173,6 +173,10 @@ SOURCE=./h2_workers.c
# End Source File
# Begin Source File
+SOURCE=./h2_ws.c
+# End Source File
+# Begin Source File
+
SOURCE=./mod_http2.c
# End Source File
# Begin Source File
Modified: httpd/httpd/trunk/modules/proxy/proxy_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/proxy_util.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/proxy/proxy_util.c (original)
+++ httpd/httpd/trunk/modules/proxy/proxy_util.c Tue Jun 20 12:01:09 2023
@@ -21,6 +21,7 @@
#include "apr_version.h"
#include "apr_strings.h"
#include "apr_hash.h"
+#include "http_core.h"
#include "proxy_util.h"
#include "ajp.h"
#include "scgi.h"
@@ -4871,7 +4872,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
{
apr_status_t rv;
conn_rec *c_i = r->connection;
- apr_interval_time_t timeout = -1;
+ apr_interval_time_t client_timeout = -1, origin_timeout = -1;
proxy_tunnel_rec *tunnel;
*ptunnel = NULL;
@@ -4898,9 +4899,16 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
tunnel->client->bb = apr_brigade_create(c_i->pool, c_i->bucket_alloc);
tunnel->client->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
tunnel->client->pfd->p = r->pool;
- tunnel->client->pfd->desc_type = APR_POLL_SOCKET;
- tunnel->client->pfd->desc.s = ap_get_conn_socket(c_i);
+ tunnel->client->pfd->desc_type = APR_NO_DESC;
+ rv = ap_get_pollfd_from_conn(tunnel->client->c,
+ tunnel->client->pfd, &client_timeout);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
tunnel->client->pfd->client_data = tunnel->client;
+ if (tunnel->client->pfd->desc_type == APR_POLL_SOCKET) {
+ apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1);
+ }
tunnel->origin->c = c_o;
tunnel->origin->name = "origin";
@@ -4910,17 +4918,12 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
tunnel->origin->pfd->desc_type = APR_POLL_SOCKET;
tunnel->origin->pfd->desc.s = ap_get_conn_socket(c_o);
tunnel->origin->pfd->client_data = tunnel->origin;
+ apr_socket_timeout_get(tunnel->origin->pfd->desc.s, &origin_timeout);
+ apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1);
/* Defaults to the biggest timeout of both connections */
- apr_socket_timeout_get(tunnel->client->pfd->desc.s, &timeout);
- apr_socket_timeout_get(tunnel->origin->pfd->desc.s, &tunnel->timeout);
- if (timeout >= 0 && (tunnel->timeout < 0 || tunnel->timeout < timeout)) {
- tunnel->timeout = timeout;
- }
-
- /* We should be nonblocking from now on the sockets */
- apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1);
- apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1);
+ tunnel->timeout = (origin_timeout >= 0 && origin_timeout > client_timeout)?
+ origin_timeout : client_timeout;
/* Bidirectional non-HTTP stream will confuse mod_reqtimeoout */
ap_remove_input_filter_byhandle(c_i->input_filters, "reqtimeout");
@@ -4938,14 +4941,43 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
tunnel->nohalfclose = 1;
}
- /* Start with POLLOUT and let ap_proxy_tunnel_run() schedule both
- * directions when there are no output data pending (anymore).
- */
- tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
- tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
- if ((rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))
- || (rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
- return rv;
+ if (tunnel->client->pfd->desc_type == APR_POLL_SOCKET) {
+ /* Both ends are sockets, the poll strategy is:
+ * - poll both sides POLLOUT
+ * - when one side is writable, remove the POLLOUT
+ * and add POLLIN to the other side.
+ * - tunnel arriving data, remove POLLIN from the source
+ * again and add POLLOUT to the receiving side
+ * - on EOF on read, remove the POLLIN from that side
+ * Repeat until both sides are down */
+ tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+ tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd)) ||
+ (rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))) {
+ return rv;
+ }
+ }
+ else if (tunnel->client->pfd->desc_type == APR_POLL_FILE) {
+ /* Input is a PIPE fd, the poll strategy is:
+ * - always POLLIN on origin
+ * - use socket strategy described above for client only
+ * otherwise the same
+ */
+ tunnel->client->pfd->reqevents = 0;
+ tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP |
+ APR_POLLOUT | APR_POLLERR;
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
+ return rv;
+ }
+ }
+ else {
+ /* input is already closed, unsual, but we know nothing about
+ * the tunneled protocol. */
+ tunnel->client->down_in = 1;
+ tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP;
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
+ return rv;
+ }
}
*ptunnel = tunnel;
@@ -5054,7 +5086,23 @@ static int proxy_tunnel_transfer(proxy_t
}
del_pollset(tunnel->pollset, in->pfd, APR_POLLIN);
- add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
+ if (out->pfd->desc_type == APR_POLL_SOCKET) {
+ /* if the output is a SOCKET, we can stop polling the input
+ * until the output signals POLLOUT again. */
+ add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
+ }
+ else {
+ /* We can't use POLLOUT in this direction for the only
+ * APR_POLL_FILE case we have so far (mod_h2's "signal" pipe),
+ * we assume that the client's ouput filters chain will block/flush
+ * if necessary (i.e. no pending data), hence that the origin
+ * is EOF when reaching here. This direction is over. */
+ ap_assert(in->down_in && APR_STATUS_IS_EOF(rv));
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r,
+ "proxy: %s: %s write shutdown",
+ tunnel->scheme, out->name);
+ out->down_out = 1;
+ }
}
return OK;
Modified: httpd/httpd/trunk/server/core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/server/core.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/server/core.c (original)
+++ httpd/httpd/trunk/server/core.c Tue Jun 20 12:01:09 2023
@@ -92,6 +92,7 @@
APR_HOOK_STRUCT(
APR_HOOK_LINK(get_mgmt_items)
APR_HOOK_LINK(insert_network_bucket)
+ APR_HOOK_LINK(get_pollfd_from_conn)
)
AP_IMPLEMENT_HOOK_RUN_ALL(int, get_mgmt_items,
@@ -103,6 +104,11 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t
apr_socket_t *socket),
(c, bb, socket), AP_DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, get_pollfd_from_conn,
+ (conn_rec *c, struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout),
+ (c, pfd, ptimeout), APR_ENOTIMPL)
+
/* Server core module... This module provides support for really basic
* server operations, including options and commands which control the
* operation of other modules. Consider this the bureaucracy module.
@@ -5971,6 +5977,28 @@ static int core_upgrade_storage(request_
return DECLINED;
}
+static apr_status_t core_get_pollfd_from_conn(conn_rec *c,
+ struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout)
+{
+ if (c && !c->master) {
+ pfd->desc_type = APR_POLL_SOCKET;
+ pfd->desc.s = ap_get_conn_socket(c);
+ if (ptimeout) {
+ apr_socket_timeout_get(pfd->desc.s, ptimeout);
+ }
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+AP_CORE_DECLARE(apr_status_t) ap_get_pollfd_from_conn(conn_rec *c,
+ struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout)
+{
+ return ap_run_get_pollfd_from_conn(c, pfd, ptimeout);
+}
+
static void register_hooks(apr_pool_t *p)
{
errorlog_hash = apr_hash_make(p);
@@ -6016,6 +6044,8 @@ static void register_hooks(apr_pool_t *p
ap_hook_open_htaccess(ap_open_htaccess, NULL, NULL, APR_HOOK_REALLY_LAST);
ap_hook_optional_fn_retrieve(core_optional_fn_retrieve, NULL, NULL,
APR_HOOK_MIDDLE);
+ ap_hook_get_pollfd_from_conn(core_get_pollfd_from_conn, NULL, NULL,
+ APR_HOOK_REALLY_LAST);
ap_hook_input_pending(ap_filter_input_pending, NULL, NULL,
APR_HOOK_MIDDLE);
Propchange: httpd/httpd/trunk/test/clients/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Jun 20 12:01:09 2023
@@ -0,0 +1,3 @@
+.deps
+h2ws
+Makefile
Added: httpd/httpd/trunk/test/clients/.gitignore
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/clients/.gitignore?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/test/clients/.gitignore (added)
+++ httpd/httpd/trunk/test/clients/.gitignore Tue Jun 20 12:01:09 2023
@@ -0,0 +1 @@
+h2ws
\ No newline at end of file
Added: httpd/httpd/trunk/test/clients/Makefile.in
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/clients/Makefile.in?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/test/clients/Makefile.in (added)
+++ httpd/httpd/trunk/test/clients/Makefile.in Tue Jun 20 12:01:09 2023
@@ -0,0 +1,20 @@
+DISTCLEAN_TARGETS = h2ws
+
+CLEAN_TARGETS = h2ws
+
+bin_PROGRAMS = h2ws
+TARGETS = $(bin_PROGRAMS)
+
+PROGRAM_LDADD = $(UTIL_LDFLAGS) $(PROGRAM_DEPENDENCIES) $(EXTRA_LIBS) $(AP_LIBS)
+PROGRAM_DEPENDENCIES =
+
+include $(top_builddir)/build/rules.mk
+
+h2ws.lo: h2ws.c
+ $(LIBTOOL) --mode=compile $(CC) $(ab_CFLAGS) $(ALL_CFLAGS) $(ALL_CPPFLAGS) \
+ $(ALL_INCLUDES) $(PICFLAGS) $(LTCFLAGS) -c $< && touch $@
+h2ws_OBJECTS = h2ws.lo
+h2ws_LDADD = -lnghttp2
+h2ws: $(h2ws_OBJECTS)
+ $(LIBTOOL) --mode=link $(CC) $(ALL_CFLAGS) $(PILDFLAGS) \
+ $(LT_LDFLAGS) $(ALL_LDFLAGS) -o $@ $(h2ws_LTFLAGS) $(h2ws_OBJECTS) $(h2ws_LDADD)