]> granicus.if.org Git - apache/blob - modules/http2/mod_proxy_http2.c
mod_http2: fix for partial file buckets in master connection output, flushing of...
[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 #include "mod_http2.h"
21
22
23 #include "mod_proxy_http2.h"
24 #include "h2.h"
25 #include "h2_util.h"
26 #include "h2_version.h"
27 #include "h2_proxy_session.h"
28
29 static void register_hook(apr_pool_t *p);
30
31 AP_DECLARE_MODULE(proxy_http2) = {
32     STANDARD20_MODULE_STUFF,
33     NULL,              /* create per-directory config structure */
34     NULL,              /* merge per-directory config structures */
35     NULL,              /* create per-server config structure */
36     NULL,              /* merge per-server config structures */
37     NULL,              /* command apr_table_t */
38     register_hook      /* register hooks */
39 };
40
41 /* Optional functions from mod_http2 */
42 static int (*is_h2)(conn_rec *c);
43 static apr_status_t (*req_engine_push)(const char *name, request_rec *r, 
44                                        http2_req_engine_init *einit);
45 static apr_status_t (*req_engine_pull)(h2_req_engine *engine, 
46                                        apr_read_type_e block, 
47                                        apr_uint32_t capacity, 
48                                        request_rec **pr);
49 static void (*req_engine_done)(h2_req_engine *engine, conn_rec *r_conn);
50                                        
51 typedef struct h2_proxy_ctx {
52     conn_rec *owner;
53     apr_pool_t *pool;
54     request_rec *rbase;
55     server_rec *server;
56     const char *proxy_func;
57     char server_portstr[32];
58     proxy_conn_rec *p_conn;
59     proxy_worker *worker;
60     proxy_server_conf *conf;
61     
62     h2_req_engine *engine;
63     const char *engine_id;
64     const char *engine_type;
65     apr_pool_t *engine_pool;    
66     apr_uint32_t req_buffer_size;
67     request_rec *next;
68     apr_size_t capacity;
69     
70     unsigned standalone : 1;
71     unsigned is_ssl : 1;
72     unsigned flushall : 1;
73     
74     apr_status_t r_status;     /* status of our first request work */
75     h2_proxy_session *session; /* current http2 session against backend */
76 } h2_proxy_ctx;
77
78 static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog,
79                                 apr_pool_t *ptemp, server_rec *s)
80 {
81     void *data = NULL;
82     const char *init_key = "mod_proxy_http2_init_counter";
83     nghttp2_info *ngh2;
84     apr_status_t status = APR_SUCCESS;
85     (void)plog;(void)ptemp;
86     
87     apr_pool_userdata_get(&data, init_key, s->process->pool);
88     if ( data == NULL ) {
89         apr_pool_userdata_set((const void *)1, init_key,
90                               apr_pool_cleanup_null, s->process->pool);
91         return APR_SUCCESS;
92     }
93     
94     ngh2 = nghttp2_version(0);
95     ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03349)
96                  "mod_proxy_http2 (v%s, nghttp2 %s), initializing...",
97                  MOD_HTTP2_VERSION, ngh2? ngh2->version_str : "unknown");
98     
99     is_h2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2);
100     req_engine_push = APR_RETRIEVE_OPTIONAL_FN(http2_req_engine_push);
101     req_engine_pull = APR_RETRIEVE_OPTIONAL_FN(http2_req_engine_pull);
102     req_engine_done = APR_RETRIEVE_OPTIONAL_FN(http2_req_engine_done);
103     
104     /* we need all of them */
105     if (!req_engine_push || !req_engine_pull || !req_engine_done) {
106         req_engine_push = NULL;
107         req_engine_pull = NULL;
108         req_engine_done = NULL;
109     }
110     
111     return status;
112 }
113
114 /**
115  * canonicalize the url into the request, if it is meant for us.
116  * slightly modified copy from mod_http
117  */
118 static int proxy_http2_canon(request_rec *r, char *url)
119 {
120     char *host, *path, sport[7];
121     char *search = NULL;
122     const char *err;
123     const char *scheme;
124     const char *http_scheme;
125     apr_port_t port, def_port;
126
127     /* ap_port_of_scheme() */
128     if (h2_casecmpstrn(url, "h2c:", 4) == 0) {
129         url += 4;
130         scheme = "h2c";
131         http_scheme = "http";
132     }
133     else if (h2_casecmpstrn(url, "h2:", 3) == 0) {
134         url += 3;
135         scheme = "h2";
136         http_scheme = "https";
137     }
138     else {
139         return DECLINED;
140     }
141     port = def_port = ap_proxy_port_of_scheme(http_scheme);
142
143     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
144                   "HTTP2: canonicalising URL %s", url);
145
146     /* do syntatic check.
147      * We break the URL into host, port, path, search
148      */
149     err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
150     if (err) {
151         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03350)
152                       "error parsing URL %s: %s", url, err);
153         return HTTP_BAD_REQUEST;
154     }
155
156     /*
157      * now parse path/search args, according to rfc1738:
158      * process the path.
159      *
160      * In a reverse proxy, our URL has been processed, so canonicalise
161      * unless proxy-nocanon is set to say it's raw
162      * In a forward proxy, we have and MUST NOT MANGLE the original.
163      */
164     switch (r->proxyreq) {
165     default: /* wtf are we doing here? */
166     case PROXYREQ_REVERSE:
167         if (apr_table_get(r->notes, "proxy-nocanon")) {
168             path = url;   /* this is the raw path */
169         }
170         else {
171             path = ap_proxy_canonenc(r->pool, url, strlen(url),
172                                      enc_path, 0, r->proxyreq);
173             search = r->args;
174         }
175         break;
176     case PROXYREQ_PROXY:
177         path = url;
178         break;
179     }
180
181     if (path == NULL) {
182         return HTTP_BAD_REQUEST;
183     }
184
185     if (port != def_port) {
186         apr_snprintf(sport, sizeof(sport), ":%d", port);
187     }
188     else {
189         sport[0] = '\0';
190     }
191
192     if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
193         host = apr_pstrcat(r->pool, "[", host, "]", NULL);
194     }
195     r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
196             "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
197     return OK;
198 }
199
200 static void out_consumed(void *baton, conn_rec *c, apr_off_t bytes)
201 {
202     h2_proxy_ctx *ctx = baton;
203     
204     if (ctx->session) {
205         h2_proxy_session_update_window(ctx->session, c, bytes);
206     }
207 }
208
209 static apr_status_t proxy_engine_init(h2_req_engine *engine, 
210                                         const char *id, 
211                                         const char *type,
212                                         apr_pool_t *pool, 
213                                         apr_uint32_t req_buffer_size,
214                                         request_rec *r,
215                                         http2_output_consumed **pconsumed,
216                                         void **pctx)
217 {
218     h2_proxy_ctx *ctx = ap_get_module_config(r->connection->conn_config, 
219                                              &proxy_http2_module);
220     if (ctx) {
221         conn_rec *c = ctx->owner;
222         h2_proxy_ctx *nctx;
223         
224         /* we need another lifetime for this. If we do not host
225          * an engine, the context lives in r->pool. Since we expect
226          * to server more than r, we need to live longer */
227         nctx = apr_pcalloc(pool, sizeof(*nctx));
228         if (nctx == NULL) {
229             return APR_ENOMEM;
230         }
231         memcpy(nctx, ctx, sizeof(*nctx));
232         ctx = nctx;
233         ctx->pool = pool;
234         ctx->engine = engine;
235         ctx->engine_id = id;
236         ctx->engine_type = type;
237         ctx->engine_pool = pool;
238         ctx->req_buffer_size = req_buffer_size;
239         ctx->capacity = 100;
240
241         ap_set_module_config(c->conn_config, &proxy_http2_module, ctx);
242
243         *pconsumed = out_consumed;
244         *pctx = ctx;
245         return APR_SUCCESS;
246     }
247     ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03368)
248                   "h2_proxy_session, engine init, no ctx found");
249     return APR_ENOTIMPL;
250 }
251
252 static apr_status_t add_request(h2_proxy_session *session, request_rec *r)
253 {
254     h2_proxy_ctx *ctx = session->user_data;
255     const char *url;
256     apr_status_t status;
257
258     url = apr_table_get(r->notes, H2_PROXY_REQ_URL_NOTE);
259     apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
260                    ctx->p_conn->connection->local_addr->port));
261     status = h2_proxy_session_submit(session, url, r);
262     if (status != OK) {
263         ap_log_cerror(APLOG_MARK, APLOG_ERR, status, r->connection, APLOGNO(03351)
264                       "pass request body failed to %pI (%s) from %s (%s)",
265                       ctx->p_conn->addr, ctx->p_conn->hostname ? 
266                       ctx->p_conn->hostname: "", session->c->client_ip, 
267                       session->c->remote_host ? session->c->remote_host: "");
268     }
269     return status;
270 }
271
272 static void request_done(h2_proxy_session *session, request_rec *r,
273                          int complete, int touched)
274 {   
275     h2_proxy_ctx *ctx = session->user_data;
276     const char *task_id = apr_table_get(r->connection->notes, H2_TASK_ID_NOTE);
277     
278     if (!complete && !touched) {
279         /* untouched request, need rescheduling */
280         if (req_engine_push && is_h2 && is_h2(ctx->owner)) {
281             if (req_engine_push(ctx->engine_type, r, NULL) == APR_SUCCESS) {
282                 /* push to engine */
283                 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection, 
284                               APLOGNO(03369)
285                               "h2_proxy_session(%s): rescheduled request %s",
286                               ctx->engine_id, task_id);
287                 return;
288             }
289         }
290     }
291     
292     if (r == ctx->rbase && complete) {
293         ctx->r_status = APR_SUCCESS;
294     }
295     
296     if (complete) {
297         if (req_engine_done && ctx->engine) {
298             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection, 
299                           APLOGNO(03370)
300                           "h2_proxy_session(%s): finished request %s",
301                           ctx->engine_id, task_id);
302             req_engine_done(ctx->engine, r->connection);
303         }
304     }
305     else {
306         if (req_engine_done && ctx->engine) {
307             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection, 
308                           APLOGNO(03371)
309                           "h2_proxy_session(%s): failed request %s",
310                           ctx->engine_id, task_id);
311             req_engine_done(ctx->engine, r->connection);
312         }
313     }
314 }    
315
316 static apr_status_t next_request(h2_proxy_ctx *ctx, int before_leave)
317 {
318     if (ctx->next) {
319         return APR_SUCCESS;
320     }
321     else if (req_engine_pull && ctx->engine) {
322         apr_status_t status;
323         status = req_engine_pull(ctx->engine, before_leave? 
324                                  APR_BLOCK_READ: APR_NONBLOCK_READ, 
325                                  ctx->capacity, &ctx->next);
326         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, ctx->owner, 
327                       "h2_proxy_engine(%s): pulled request %s", 
328                       ctx->engine_id, 
329                       (ctx->next? ctx->next->the_request : "NULL"));
330         return APR_STATUS_IS_EAGAIN(status)? APR_SUCCESS : status;
331     }
332     return APR_EOF;
333 }
334
335 static apr_status_t proxy_engine_run(h2_proxy_ctx *ctx) {
336     apr_status_t status = OK;
337     
338     /* Step Four: Send the Request in a new HTTP/2 stream and
339      * loop until we got the response or encounter errors.
340      */
341     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->owner, 
342                   "eng(%s): setup session", ctx->engine_id);
343     ctx->session = h2_proxy_session_setup(ctx->engine_id, ctx->p_conn, ctx->conf, 
344                                           30, h2_log2(ctx->req_buffer_size), 
345                                           request_done);
346     if (!ctx->session) {
347         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, 
348                       APLOGNO(03372) "session unavailable");
349         return HTTP_SERVICE_UNAVAILABLE;
350     }
351     
352     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03373)
353                   "eng(%s): run session %s", ctx->engine_id, ctx->session->id);
354     ctx->session->user_data = ctx;
355     
356     while (1) {
357         if (ctx->next) {
358             add_request(ctx->session, ctx->next);
359             ctx->next = NULL;
360         }
361         
362         status = h2_proxy_session_process(ctx->session);
363         
364         if (status == APR_SUCCESS) {
365             apr_status_t s2;
366             /* ongoing processing, call again */
367             if (ctx->session->remote_max_concurrent > 0
368                 && ctx->session->remote_max_concurrent != ctx->capacity) {
369                 ctx->capacity = ctx->session->remote_max_concurrent;
370             }
371             s2 = next_request(ctx, 0);
372             if (s2 == APR_ECONNABORTED) {
373                 /* master connection gone */
374                 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, s2, ctx->owner, 
375                               APLOGNO(03374) "eng(%s): pull request", 
376                               ctx->engine_id);
377                 status = s2;
378                 break;
379             }
380             if (!ctx->next && h2_ihash_empty(ctx->session->streams)) {
381                 break;
382             }
383         }
384         else {
385             /* end of processing, maybe error */
386             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, 
387                           APLOGNO(03375) "eng(%s): end of session run", 
388                           ctx->engine_id);
389             /*
390              * Any open stream of that session needs to
391              * a) be reopened on the new session iff safe to do so
392              * b) reported as done (failed) otherwise
393              */
394             h2_proxy_session_cleanup(ctx->session, request_done);
395             break;
396         }
397     }
398     
399     ctx->session->user_data = NULL;
400     ctx->session = NULL;
401     
402     return status;
403 }
404
405 static h2_proxy_ctx *push_request_somewhere(h2_proxy_ctx *ctx)
406 {
407     conn_rec *c = ctx->owner;
408     const char *engine_type, *hostname;
409     
410     hostname = (ctx->p_conn->ssl_hostname? 
411                 ctx->p_conn->ssl_hostname : ctx->p_conn->hostname);
412     engine_type = apr_psprintf(ctx->pool, "proxy_http2 %s%s", hostname, 
413                                ctx->server_portstr);
414     
415     if (c->master && req_engine_push && ctx->next && is_h2 && is_h2(c)) {
416         /* If we are have req_engine capabilities, push the handling of this
417          * request (e.g. slave connection) to a proxy_http2 engine which 
418          * uses the same backend. We may be called to create an engine 
419          * ourself. */
420         if (req_engine_push(engine_type, ctx->next, proxy_engine_init)
421             == APR_SUCCESS) {
422             /* to renew the lifetime, we might have set a new ctx */
423             ctx = ap_get_module_config(c->conn_config, &proxy_http2_module);
424             if (ctx->engine == NULL) {
425                 /* Another engine instance has taken over processing of this
426                  * request. */
427                 ctx->r_status = SUSPENDED;
428                 ctx->next = NULL;
429                 return ctx;
430             }
431         }
432     }
433     
434     if (!ctx->engine) {
435         /* No engine was available or has been initialized, handle this
436          * request just by ourself. */
437         ctx->engine_id = apr_psprintf(ctx->pool, "eng-proxy-%ld", c->id);
438         ctx->engine_type = engine_type;
439         ctx->engine_pool = ctx->pool;
440         ctx->req_buffer_size = (32*1024);
441         ctx->standalone = 1;
442         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
443                       "h2_proxy_http2(%ld): setup standalone engine for type %s", 
444                       c->id, engine_type);
445     }
446     else {
447         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
448                       "H2: hosting engine %s", ctx->engine_id);
449     }
450     return ctx;
451 }
452
453 static int proxy_http2_handler(request_rec *r, 
454                                proxy_worker *worker,
455                                proxy_server_conf *conf,
456                                char *url, 
457                                const char *proxyname,
458                                apr_port_t proxyport)
459 {
460     const char *proxy_func;
461     char *locurl = url, *u;
462     apr_size_t slen;
463     int is_ssl = 0;
464     apr_status_t status;
465     h2_proxy_ctx *ctx;
466     apr_uri_t uri;
467     int reconnected = 0;
468     
469     /* find the scheme */
470     if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') {
471        return DECLINED;
472     }
473     u = strchr(url, ':');
474     if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0') {
475        return DECLINED;
476     }
477     slen = (u - url);
478     switch(slen) {
479         case 2:
480             proxy_func = "H2";
481             is_ssl = 1;
482             break;
483         case 3:
484             if (url[2] != 'c' && url[2] != 'C') {
485                 return DECLINED;
486             }
487             proxy_func = "H2C";
488             break;
489         default:
490             return DECLINED;
491     }
492     ctx = apr_pcalloc(r->pool, sizeof(*ctx));
493     ctx->owner      = r->connection;
494     ctx->pool       = r->pool;
495     ctx->rbase      = r;
496     ctx->server     = r->server;
497     ctx->proxy_func = proxy_func;
498     ctx->is_ssl     = is_ssl;
499     ctx->worker     = worker;
500     ctx->conf       = conf;
501     ctx->flushall   = apr_table_get(r->subprocess_env, "proxy-flushall")? 1 : 0;
502     ctx->r_status   = HTTP_SERVICE_UNAVAILABLE;
503     ctx->next       = r;
504     r = NULL;
505     ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, ctx);
506
507     /* scheme says, this is for us. */
508     apr_table_setn(ctx->rbase->notes, H2_PROXY_REQ_URL_NOTE, url);
509     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->rbase, 
510                   "H2: serving URL %s", url);
511     
512 run_connect:    
513     /* Get a proxy_conn_rec from the worker, might be a new one, might
514      * be one still open from another request, or it might fail if the
515      * worker is stopped or in error. */
516     if ((status = ap_proxy_acquire_connection(ctx->proxy_func, &ctx->p_conn,
517                                               ctx->worker, ctx->server)) != OK) {
518         goto cleanup;
519     }
520
521     ctx->p_conn->is_ssl = ctx->is_ssl;
522     if (ctx->is_ssl) {
523         /* If there is still some data on an existing ssl connection, now
524          * would be a good timne to get rid of it. */
525         ap_proxy_ssl_connection_cleanup(ctx->p_conn, ctx->rbase);
526     }
527
528     /* Step One: Determine the URL to connect to (might be a proxy),
529      * initialize the backend accordingly and determine the server 
530      * port string we can expect in responses. */
531     if ((status = ap_proxy_determine_connection(ctx->pool, ctx->rbase, conf, worker, 
532                                                 ctx->p_conn, &uri, &locurl, 
533                                                 proxyname, proxyport, 
534                                                 ctx->server_portstr,
535                                                 sizeof(ctx->server_portstr))) != OK) {
536         goto cleanup;
537     }
538     
539     /* If we are not already hosting an engine, try to push the request 
540      * to an already existing engine or host a new engine here. */
541     if (!ctx->engine) {
542         ctx = push_request_somewhere(ctx);
543         if (ctx->r_status == SUSPENDED) {
544             /* request was pushed to another engine */
545             goto cleanup;
546         }
547     }
548     
549     /* Step Two: Make the Connection (or check that an already existing
550      * socket is still usable). On success, we have a socket connected to
551      * backend->hostname. */
552     if (ap_proxy_connect_backend(ctx->proxy_func, ctx->p_conn, ctx->worker, 
553                                  ctx->server)) {
554         ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctx->owner, APLOGNO(03352)
555                       "H2: failed to make connection to backend: %s",
556                       ctx->p_conn->hostname);
557         goto cleanup;
558     }
559     
560     /* Step Three: Create conn_rec for the socket we have open now. */
561     if (!ctx->p_conn->connection) {
562         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
563                       "setup new connection: is_ssl=%d %s %s %s", 
564                       ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname, 
565                       locurl, ctx->p_conn->hostname);
566         status = ap_proxy_connection_create_ex(ctx->proxy_func,
567                                                ctx->p_conn, ctx->rbase);
568         if (status != OK) {
569             goto cleanup;
570         }
571         
572         /*
573          * On SSL connections set a note on the connection what CN is
574          * requested, such that mod_ssl can check if it is requested to do
575          * so.
576          */
577         if (ctx->p_conn->ssl_hostname) {
578             apr_table_setn(ctx->p_conn->connection->notes,
579                            "proxy-request-hostname", ctx->p_conn->ssl_hostname);
580         }
581         
582         if (ctx->is_ssl) {
583             apr_table_setn(ctx->p_conn->connection->notes,
584                            "proxy-request-alpn-protos", "h2");
585         }
586     }
587
588 run_session:
589     status = proxy_engine_run(ctx);
590     if (status == APR_SUCCESS) {
591         /* session and connection still ok */
592         if (next_request(ctx, 1) == APR_SUCCESS) {
593             /* more requests, run again */
594             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03376)
595                           "run_session, again");
596             goto run_session;
597         }
598         /* done */
599         ctx->engine = NULL;
600     }
601
602 cleanup:
603     if (!reconnected && ctx->engine && next_request(ctx, 1) == APR_SUCCESS) {
604         /* Still more to do, tear down old conn and start over */
605         if (ctx->p_conn) {
606             ctx->p_conn->close = 1;
607             proxy_run_detach_backend(r, ctx->p_conn);
608             ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
609             ctx->p_conn = NULL;
610         }
611         reconnected = 1; /* we do this only once, then fail */
612         goto run_connect;
613     }
614     
615     if (ctx->p_conn) {
616         if (status != APR_SUCCESS) {
617             /* close socket when errors happened or session shut down (EOF) */
618             ctx->p_conn->close = 1;
619         }
620         proxy_run_detach_backend(ctx->rbase, ctx->p_conn);
621         ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
622         ctx->p_conn = NULL;
623     }
624
625     ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, NULL);
626     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, 
627                   APLOGNO(03377) "leaving handler");
628     return ctx->r_status;
629 }
630
631 static void register_hook(apr_pool_t *p)
632 {
633     ap_hook_post_config(h2_proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE);
634
635     proxy_hook_scheme_handler(proxy_http2_handler, NULL, NULL, APR_HOOK_FIRST);
636     proxy_hook_canon_handler(proxy_http2_canon, NULL, NULL, APR_HOOK_FIRST);
637 }
638