]> granicus.if.org Git - apache/blob - modules/http2/mod_proxy_http2.c
mod_proxy_http2: remove code working around issue fixed (hopefully) in 1729826.
[apache] / modules / http2 / mod_proxy_http2.c
1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
2  *
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
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  
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.
14  */
15
16 #include <nghttp2/nghttp2.h>
17
18 #include <httpd.h>
19 #include <mod_proxy.h>
20
21
22 #include "mod_proxy_http2.h"
23 #include "h2_request.h"
24 #include "h2_util.h"
25 #include "h2_version.h"
26 #include "h2_proxy_session.h"
27
28 static void register_hook(apr_pool_t *p);
29
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 */
38 };
39
40 static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog,
41                                 apr_pool_t *ptemp, server_rec *s)
42 {
43     void *data = NULL;
44     const char *init_key = "mod_proxy_http2_init_counter";
45     nghttp2_info *ngh2;
46     apr_status_t status = APR_SUCCESS;
47     (void)plog;(void)ptemp;
48     
49     apr_pool_userdata_get(&data, init_key, s->process->pool);
50     if ( data == NULL ) {
51         apr_pool_userdata_set((const void *)1, init_key,
52                               apr_pool_cleanup_null, s->process->pool);
53         return APR_SUCCESS;
54     }
55     
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");
60     
61     return status;
62 }
63
64 /**
65  * canonicalize the url into the request, if it is meant for us.
66  * slightly modified copy from mod_http
67  */
68 static int proxy_http2_canon(request_rec *r, char *url)
69 {
70     char *host, *path, sport[7];
71     char *search = NULL;
72     const char *err;
73     const char *scheme;
74     const char *http_scheme;
75     apr_port_t port, def_port;
76
77     /* ap_port_of_scheme() */
78     if (ap_casecmpstrn(url, "h2c:", 4) == 0) {
79         url += 4;
80         scheme = "h2c";
81         http_scheme = "http";
82     }
83     else if (ap_casecmpstrn(url, "h2:", 3) == 0) {
84         url += 3;
85         scheme = "h2";
86         http_scheme = "https";
87     }
88     else {
89         return DECLINED;
90     }
91     port = def_port = ap_proxy_port_of_scheme(http_scheme);
92
93     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
94                   "HTTP2: canonicalising URL %s", url);
95
96     /* do syntatic check.
97      * We break the URL into host, port, path, search
98      */
99     err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
100     if (err) {
101         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
102                       "error parsing URL %s: %s", url, err);
103         return HTTP_BAD_REQUEST;
104     }
105
106     /*
107      * now parse path/search args, according to rfc1738:
108      * process the path.
109      *
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.
113      */
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 */
119         }
120         else {
121             path = ap_proxy_canonenc(r->pool, url, strlen(url),
122                                      enc_path, 0, r->proxyreq);
123             search = r->args;
124         }
125         break;
126     case PROXYREQ_PROXY:
127         path = url;
128         break;
129     }
130
131     if (path == NULL) {
132         return HTTP_BAD_REQUEST;
133     }
134
135     if (port != def_port) {
136         apr_snprintf(sport, sizeof(sport), ":%d", port);
137     }
138     else {
139         sport[0] = '\0';
140     }
141
142     if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
143         host = apr_pstrcat(r->pool, "[", host, "]", NULL);
144     }
145     r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
146             "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
147     return OK;
148 }
149
150 static apr_status_t proxy_http2_cleanup(const char *scheme, request_rec *r,
151                                         proxy_conn_rec *backend)
152 {
153     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "cleanup, releasing connection");
154     ap_proxy_release_connection(scheme, backend, r->server);
155     return OK;
156 }
157
158 static
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,
162                                int flushall)
163 {
164     int rv = APR_ENOTIMPL;
165     proxy_conn_rec *p_conn = *pp_conn;
166     h2_proxy_session *session;
167     h2_proxy_stream *stream;
168     
169     session = h2_proxy_session_setup(r, *pp_conn, conf);
170     if (!session) {
171         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, p_conn->connection, 
172                       "session unavailable");
173         return HTTP_SERVICE_UNAVAILABLE;
174     }
175     
176     /* TODO
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
183      */ 
184     rv = h2_proxy_session_open_stream(session, url, r, &stream);
185     if (rv == OK) {
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, 
190                       r->the_request);
191         rv = h2_proxy_stream_process(stream);
192     }
193     
194     if (rv != OK) {
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: "");
200     }
201
202     return rv;
203 }
204
205 static int proxy_http2_handler(request_rec *r, 
206                                proxy_worker *worker,
207                                proxy_server_conf *conf,
208                                char *url, 
209                                const char *proxyname,
210                                apr_port_t proxyport)
211 {
212     const char *proxy_function;
213     proxy_conn_rec *backend;
214     char *locurl = url, *u;
215     apr_size_t slen;
216     int is_ssl = 0;
217     int flushall = 0;
218     int status;
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));
223     conn_rec *backconn;
224
225     /* find the scheme */
226     if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') {
227        return DECLINED;
228     }
229     u = strchr(url, ':');
230     if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0') {
231        return DECLINED;
232     }
233     slen = (u - url);
234     switch(slen) {
235         case 2:
236             proxy_function = "H2";
237             is_ssl = 1;
238             break;
239         case 3:
240             if (url[2] != 'c' && url[2] != 'C') {
241                 return DECLINED;
242             }
243             proxy_function = "H2C";
244             break;
245         default:
246             return DECLINED;
247     }
248
249     if (apr_table_get(r->subprocess_env, "proxy-flushall")) {
250         flushall = 1;
251     }
252
253     /* scheme says, this is for us. */
254     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "H2: serving URL %s", url);
255
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) {
261         goto cleanup;
262     }
263
264     backend->is_ssl = is_ssl;
265     if (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);
269     }
270
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) {
278         goto cleanup;
279     }
280     
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",
287                       backend->hostname);
288         status = HTTP_SERVICE_UNAVAILABLE;
289         goto cleanup;
290     }
291     
292     /* Step Three: Create conn_rec for the socket we have open now. */
293     backconn = backend->connection;
294     if (!backconn) {
295         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO()
296                       "setup new connection: is_ssl=%d %s %s %s", 
297                       backend->is_ssl, 
298                       backend->ssl_hostname, r->hostname, backend->hostname);
299         if ((status = ap_proxy_connection_create(proxy_function, backend,
300                                                  c, r->server)) != OK) {
301             goto cleanup;
302         }
303         backconn = backend->connection;
304         
305         /*
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
308          * so.
309          */
310         if (backend->ssl_hostname) {
311             apr_table_setn(backend->connection->notes,
312                            "proxy-request-hostname", backend->ssl_hostname);
313         }
314         
315         if (backend->is_ssl) {
316             apr_table_setn(backend->connection->notes,
317                            "proxy-request-alpn-protos", "h2");
318         }
319     }
320
321     /* Step Four: Send the Request in a new HTTP/2 stream and
322      * loop until we got the response or encounter errors.
323      */
324     if ((status = proxy_http2_process_stream(p, url, r, &backend, worker,
325                                              conf, server_portstr, 
326                                              flushall)) != OK) {
327         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO()
328                       "H2: failed to process request: %s",
329                       r->the_request);
330     }
331
332     /* clean up before return */
333 cleanup:
334     if (backend) {
335         if (status != OK) {
336             backend->close = 1;
337         }
338         proxy_run_detach_backend(r, backend);
339         proxy_http2_cleanup(proxy_function, r, backend);
340     }
341     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r, "leaving handler");
342     return status;
343 }
344
345 static void register_hook(apr_pool_t *p)
346 {
347     ap_hook_post_config(h2_proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE);
348
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);
351 }
352