1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 #include <nghttp2/nghttp2.h>
19 #include <mod_proxy.h>
22 #include "mod_proxy_http2.h"
23 #include "h2_request.h"
25 #include "h2_version.h"
26 #include "h2_proxy_session.h"
28 static void register_hook(apr_pool_t *p);
30 AP_DECLARE_MODULE(proxy_http2) = {
31 STANDARD20_MODULE_STUFF,
32 NULL, /* create per-directory config structure */
33 NULL, /* merge per-directory config structures */
34 NULL, /* create per-server config structure */
35 NULL, /* merge per-server config structures */
36 NULL, /* command apr_table_t */
37 register_hook /* register hooks */
40 static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog,
41 apr_pool_t *ptemp, server_rec *s)
44 const char *init_key = "mod_proxy_http2_init_counter";
46 apr_status_t status = APR_SUCCESS;
47 (void)plog;(void)ptemp;
49 apr_pool_userdata_get(&data, init_key, s->process->pool);
51 apr_pool_userdata_set((const void *)1, init_key,
52 apr_pool_cleanup_null, s->process->pool);
56 ngh2 = nghttp2_version(0);
57 ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO()
58 "mod_proxy_http2 (v%s, nghttp2 %s), initializing...",
59 MOD_HTTP2_VERSION, ngh2? ngh2->version_str : "unknown");
65 * canonicalize the url into the request, if it is meant for us.
66 * slightly modified copy from mod_http
68 static int proxy_http2_canon(request_rec *r, char *url)
70 char *host, *path, sport[7];
74 const char *http_scheme;
75 apr_port_t port, def_port;
77 /* ap_port_of_scheme() */
78 if (ap_casecmpstrn(url, "h2c:", 4) == 0) {
83 else if (ap_casecmpstrn(url, "h2:", 3) == 0) {
86 http_scheme = "https";
91 port = def_port = ap_proxy_port_of_scheme(http_scheme);
93 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
94 "HTTP2: canonicalising URL %s", url);
97 * We break the URL into host, port, path, search
99 err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
101 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
102 "error parsing URL %s: %s", url, err);
103 return HTTP_BAD_REQUEST;
107 * now parse path/search args, according to rfc1738:
110 * In a reverse proxy, our URL has been processed, so canonicalise
111 * unless proxy-nocanon is set to say it's raw
112 * In a forward proxy, we have and MUST NOT MANGLE the original.
114 switch (r->proxyreq) {
115 default: /* wtf are we doing here? */
116 case PROXYREQ_REVERSE:
117 if (apr_table_get(r->notes, "proxy-nocanon")) {
118 path = url; /* this is the raw path */
121 path = ap_proxy_canonenc(r->pool, url, strlen(url),
122 enc_path, 0, r->proxyreq);
132 return HTTP_BAD_REQUEST;
135 if (port != def_port) {
136 apr_snprintf(sport, sizeof(sport), ":%d", port);
142 if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
143 host = apr_pstrcat(r->pool, "[", host, "]", NULL);
145 r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
146 "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
150 static apr_status_t proxy_http2_cleanup(const char *scheme, request_rec *r,
151 proxy_conn_rec *backend)
153 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "cleanup, releasing connection");
154 ap_proxy_release_connection(scheme, backend, r->server);
159 int proxy_http2_process_stream(apr_pool_t *p, const char *url, request_rec *r,
160 proxy_conn_rec **pp_conn, proxy_worker *worker,
161 proxy_server_conf *conf, char *server_portstr,
164 int rv = APR_ENOTIMPL;
165 proxy_conn_rec *p_conn = *pp_conn;
166 h2_proxy_session *session;
167 h2_proxy_stream *stream;
169 session = h2_proxy_session_setup(r, *pp_conn, conf);
171 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, p_conn->connection,
172 "session unavailable");
173 return HTTP_SERVICE_UNAVAILABLE;
177 * - enter http2 client processing loop:
178 * - send any input in datasource callback from r->input_filters
179 * - await response HEADERs
180 * - send any DATA to r->output_filters
181 * - on stream close, check for missing response
182 * - on certain errors, mark connection for close
184 rv = h2_proxy_session_open_stream(session, url, r, &stream);
186 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
187 "process stream(%d): %s %s%s, original: %s",
188 stream->id, stream->req->method,
189 stream->req->authority, stream->req->path,
191 rv = h2_proxy_stream_process(stream);
195 conn_rec *c = r->connection;
196 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO()
197 "pass request body failed to %pI (%s) from %s (%s)",
198 p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
199 c->client_ip, c->remote_host ? c->remote_host: "");
205 static int proxy_http2_handler(request_rec *r,
206 proxy_worker *worker,
207 proxy_server_conf *conf,
209 const char *proxyname,
210 apr_port_t proxyport)
212 const char *proxy_function;
213 proxy_conn_rec *backend;
214 char *locurl = url, *u;
219 char server_portstr[32];
220 conn_rec *c = r->connection;
221 apr_pool_t *p = r->pool;
222 apr_uri_t *uri = apr_palloc(p, sizeof(*uri));
225 /* find the scheme */
226 if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') {
229 u = strchr(url, ':');
230 if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0') {
236 proxy_function = "H2";
240 if (url[2] != 'c' && url[2] != 'C') {
243 proxy_function = "H2C";
249 if (apr_table_get(r->subprocess_env, "proxy-flushall")) {
253 /* scheme says, this is for us. */
254 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "H2: serving URL %s", url);
256 /* Get a proxy_conn_rec from the worker, might be a new one, might
257 * be one still open from another request, or it might fail if the
258 * worker is stopped or in error. */
259 if ((status = ap_proxy_acquire_connection(proxy_function, &backend,
260 worker, r->server)) != OK) {
264 backend->is_ssl = is_ssl;
266 /* If there is still some data on an existing ssl connection, now
267 * would be a good timne to get rid of it. */
268 ap_proxy_ssl_connection_cleanup(backend, r);
271 /* Step One: Determine the URL to connect to (might be a proxy),
272 * initialize the backend accordingly and determine the server
273 * port string we can expect in responses. */
274 if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend,
275 uri, &locurl, proxyname,
276 proxyport, server_portstr,
277 sizeof(server_portstr))) != OK) {
281 /* Step Two: Make the Connection (or check that an already existing
282 * socket is still usable). On success, we have a socket connected to
283 * backend->hostname. */
284 if (ap_proxy_connect_backend(proxy_function, backend, worker, r->server)) {
285 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
286 "H2: failed to make connection to backend: %s",
288 status = HTTP_SERVICE_UNAVAILABLE;
292 /* Step Three: Create conn_rec for the socket we have open now. */
293 backconn = backend->connection;
295 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO()
296 "setup new connection: is_ssl=%d %s %s %s",
298 backend->ssl_hostname, r->hostname, backend->hostname);
299 if ((status = ap_proxy_connection_create(proxy_function, backend,
300 c, r->server)) != OK) {
303 backconn = backend->connection;
306 * On SSL connections set a note on the connection what CN is
307 * requested, such that mod_ssl can check if it is requested to do
310 if (backend->ssl_hostname) {
311 apr_table_setn(backend->connection->notes,
312 "proxy-request-hostname", backend->ssl_hostname);
315 if (backend->is_ssl) {
316 apr_table_setn(backend->connection->notes,
317 "proxy-request-alpn-protos", "h2");
321 /* Step Four: Send the Request in a new HTTP/2 stream and
322 * loop until we got the response or encounter errors.
324 if ((status = proxy_http2_process_stream(p, url, r, &backend, worker,
325 conf, server_portstr,
327 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO()
328 "H2: failed to process request: %s",
332 /* clean up before return */
338 proxy_run_detach_backend(r, backend);
339 proxy_http2_cleanup(proxy_function, r, backend);
341 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r, "leaving handler");
345 static void register_hook(apr_pool_t *p)
347 ap_hook_post_config(h2_proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE);
349 proxy_hook_scheme_handler(proxy_http2_handler, NULL, NULL, APR_HOOK_FIRST);
350 proxy_hook_canon_handler(proxy_http2_canon, NULL, NULL, APR_HOOK_FIRST);