switch to a 60 bit hash
[httpd-crcsyncproxy.git] / modules / proxy / mod_proxy_scgi.c
blobe152c277b8470dca8af11356b6b822addcb125d2
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * mod_proxy_scgi.c
19 * Proxy backend module for the SCGI protocol
20 * (http://python.ca/scgi/protocol.txt)
22 * André Malo (nd/perlig.de), August 2007
25 #define APR_WANT_MEMFUNC
26 #define APR_WANT_STRFUNC
27 #include "apr_strings.h"
28 #include "apr_hooks.h"
29 #include "apr_optional_hooks.h"
30 #include "apr_buckets.h"
32 #include "httpd.h"
33 #include "http_config.h"
34 #include "http_log.h"
35 #include "http_protocol.h"
36 #include "http_request.h"
37 #include "util_script.h"
39 #include "mod_proxy.h"
42 #define SCHEME "scgi"
43 #define PROXY_FUNCTION "SCGI"
44 #define SCGI_MAGIC "SCGI"
45 #define SCGI_PROTOCOL_VERSION "1"
46 #define SCGI_DEFAULT_PORT (4000)
48 /* just protect from typos */
49 #define CONTENT_LENGTH "CONTENT_LENGTH"
50 #define GATEWAY_INTERFACE "GATEWAY_INTERFACE"
52 module AP_MODULE_DECLARE_DATA proxy_scgi_module;
55 typedef enum {
56 scgi_internal_redirect,
57 scgi_sendfile
58 } scgi_request_type;
60 typedef struct {
61 const char *location; /* target URL */
62 scgi_request_type type; /* type of request */
63 } scgi_request_config;
65 const char *scgi_sendfile_off = "off";
66 const char *scgi_sendfile_on = "X-Sendfile";
68 typedef struct {
69 const char *sendfile;
70 int internal_redirect;
71 } scgi_config;
75 * We create our own bucket type, which is actually derived (c&p) from the
76 * socket bucket.
77 * Maybe some time this should be made more abstract (like passing an
78 * interception function to read or something) and go into the ap_ or
79 * even apr_ namespace.
82 typedef struct {
83 apr_socket_t *sock;
84 apr_off_t *counter;
85 } socket_ex_data;
87 static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
88 apr_bucket_alloc_t *list);
91 static apr_status_t bucket_socket_ex_read(apr_bucket *a, const char **str,
92 apr_size_t *len,
93 apr_read_type_e block)
95 socket_ex_data *data = a->data;
96 apr_socket_t *p = data->sock;
97 char *buf;
98 apr_status_t rv;
99 apr_interval_time_t timeout;
101 if (block == APR_NONBLOCK_READ) {
102 apr_socket_timeout_get(p, &timeout);
103 apr_socket_timeout_set(p, 0);
106 *str = NULL;
107 *len = APR_BUCKET_BUFF_SIZE;
108 buf = apr_bucket_alloc(*len, a->list);
110 rv = apr_socket_recv(p, buf, len);
112 if (block == APR_NONBLOCK_READ) {
113 apr_socket_timeout_set(p, timeout);
116 if (rv != APR_SUCCESS && rv != APR_EOF) {
117 apr_bucket_free(buf);
118 return rv;
121 if (*len > 0) {
122 apr_bucket_heap *h;
124 /* count for stats */
125 *data->counter += *len;
127 /* Change the current bucket to refer to what we read */
128 a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
129 h = a->data;
130 h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
131 *str = buf;
132 APR_BUCKET_INSERT_AFTER(a, bucket_socket_ex_create(data, a->list));
134 else {
135 apr_bucket_free(buf);
136 a = apr_bucket_immortal_make(a, "", 0);
137 *str = a->data;
139 return APR_SUCCESS;
142 static const apr_bucket_type_t bucket_type_socket_ex = {
143 "SOCKET_EX", 5, APR_BUCKET_DATA,
144 apr_bucket_destroy_noop,
145 bucket_socket_ex_read,
146 apr_bucket_setaside_notimpl,
147 apr_bucket_split_notimpl,
148 apr_bucket_copy_notimpl
151 static apr_bucket *bucket_socket_ex_make(apr_bucket *b, socket_ex_data *data)
153 b->type = &bucket_type_socket_ex;
154 b->length = (apr_size_t)(-1);
155 b->start = -1;
156 b->data = data;
157 return b;
160 static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
161 apr_bucket_alloc_t *list)
163 apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
165 APR_BUCKET_INIT(b);
166 b->free = apr_bucket_free;
167 b->list = list;
168 return bucket_socket_ex_make(b, data);
173 * Canonicalize scgi-like URLs.
175 static int scgi_canon(request_rec *r, char *url)
177 char *host, sport[sizeof(":65535")];
178 const char *err, *path;
179 apr_port_t port = SCGI_DEFAULT_PORT;
181 if (strncasecmp(url, SCHEME "://", sizeof(SCHEME) + 2)) {
182 return DECLINED;
184 url += sizeof(SCHEME); /* Keep slashes */
186 err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
187 if (err) {
188 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
189 "error parsing URL %s: %s", url, err);
190 return HTTP_BAD_REQUEST;
193 apr_snprintf(sport, sizeof(sport), ":%u", port);
195 if (ap_strchr(host, ':')) { /* if literal IPv6 address */
196 host = apr_pstrcat(r->pool, "[", host, "]", NULL);
199 path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
200 r->proxyreq);
201 if (!path) {
202 return HTTP_BAD_REQUEST;
205 r->filename = apr_pstrcat(r->pool, "proxy:" SCHEME "://", host, sport, "/",
206 path, NULL);
207 r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
208 return OK;
213 * Send a block of data, ensure, everything is sent
215 static int sendall(proxy_conn_rec *conn, const char *buf, apr_size_t length,
216 request_rec *r)
218 apr_status_t rv;
219 apr_size_t written;
221 while (length > 0) {
222 written = length;
223 if ((rv = apr_socket_send(conn->sock, buf, &written)) != APR_SUCCESS) {
224 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
225 "proxy: " PROXY_FUNCTION ": sending data to "
226 "%s:%u failed", conn->hostname, conn->port);
227 return HTTP_SERVICE_UNAVAILABLE;
230 /* count for stats */
231 conn->worker->s->transferred += written;
232 buf += written;
233 length -= written;
236 return OK;
241 * Send SCGI header block
243 static int send_headers(request_rec *r, proxy_conn_rec *conn)
245 char *buf, *cp, *bodylen;
246 const char *ns_len;
247 const apr_array_header_t *env_table;
248 const apr_table_entry_t *env;
249 apr_size_t j, len, bodylen_size;
250 apr_size_t headerlen = sizeof(CONTENT_LENGTH)
251 + sizeof(SCGI_MAGIC)
252 + sizeof(SCGI_PROTOCOL_VERSION);
254 ap_add_common_vars(r);
255 ap_add_cgi_vars(r);
258 * The header blob basically takes the environment and concatenates
259 * keys and values using 0 bytes. There are special treatments here:
260 * - GATEWAY_INTERFACE and SCGI_MAGIC are dropped
261 * - CONTENT_LENGTH is always set and must be sent as the very first
262 * variable
264 * Additionally it's wrapped into a so-called netstring (see SCGI spec)
266 env_table = apr_table_elts(r->subprocess_env);
267 env = (apr_table_entry_t *)env_table->elts;
268 for (j=0; j<env_table->nelts; ++j) {
269 if ( (!strcmp(env[j].key, GATEWAY_INTERFACE))
270 || (!strcmp(env[j].key, CONTENT_LENGTH))
271 || (!strcmp(env[j].key, SCGI_MAGIC))) {
272 continue;
274 headerlen += strlen(env[j].key) + strlen(env[j].val) + 2;
276 bodylen = apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->remaining);
277 bodylen_size = strlen(bodylen) + 1;
278 headerlen += bodylen_size;
280 ns_len = apr_psprintf(r->pool, "%" APR_SIZE_T_FMT ":", headerlen);
281 len = strlen(ns_len);
282 headerlen += len + 1; /* 1 == , */
283 cp = buf = apr_palloc(r->pool, headerlen);
284 memcpy(cp, ns_len, len);
285 cp += len;
287 memcpy(cp, CONTENT_LENGTH, sizeof(CONTENT_LENGTH));
288 cp += sizeof(CONTENT_LENGTH);
289 memcpy(cp, bodylen, bodylen_size);
290 cp += bodylen_size;
291 memcpy(cp, SCGI_MAGIC, sizeof(SCGI_MAGIC));
292 cp += sizeof(SCGI_MAGIC);
293 memcpy(cp, SCGI_PROTOCOL_VERSION, sizeof(SCGI_PROTOCOL_VERSION));
294 cp += sizeof(SCGI_PROTOCOL_VERSION);
296 for (j=0; j<env_table->nelts; ++j) {
297 if ( (!strcmp(env[j].key, GATEWAY_INTERFACE))
298 || (!strcmp(env[j].key, CONTENT_LENGTH))
299 || (!strcmp(env[j].key, SCGI_MAGIC))) {
300 continue;
302 len = strlen(env[j].key) + 1;
303 memcpy(cp, env[j].key, len);
304 cp += len;
305 len = strlen(env[j].val) + 1;
306 memcpy(cp, env[j].val, len);
307 cp += len;
309 *cp++ = ',';
311 return sendall(conn, buf, headerlen, r);
316 * Send request body (if any)
318 static int send_request_body(request_rec *r, proxy_conn_rec *conn)
320 if (ap_should_client_block(r)) {
321 char *buf = apr_palloc(r->pool, AP_IOBUFSIZE);
322 int status;
323 apr_size_t readlen;
325 readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
326 while (readlen > 0) {
327 status = sendall(conn, buf, readlen, r);
328 if (status != OK) {
329 return HTTP_SERVICE_UNAVAILABLE;
331 readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
333 if (readlen == -1) {
334 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
335 "proxy: " PROXY_FUNCTION ": receiving request body "
336 "failed");
337 return HTTP_INTERNAL_SERVER_ERROR;
341 return OK;
346 * Fetch response from backend and pass back to the front
348 static int pass_response(request_rec *r, proxy_conn_rec *conn)
350 apr_bucket_brigade *bb;
351 apr_bucket *b;
352 const char *location;
353 scgi_config *conf;
354 socket_ex_data *sock_data;
355 int status;
357 sock_data = apr_palloc(r->pool, sizeof(*sock_data));
358 sock_data->sock = conn->sock;
359 sock_data->counter = &conn->worker->s->read;
361 bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
362 b = bucket_socket_ex_create(sock_data, r->connection->bucket_alloc);
363 APR_BRIGADE_INSERT_TAIL(bb, b);
364 b = apr_bucket_eos_create(r->connection->bucket_alloc);
365 APR_BRIGADE_INSERT_TAIL(bb, b);
367 status = ap_scan_script_header_err_brigade(r, bb, NULL);
368 if (status != OK) {
369 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
370 "proxy: " PROXY_FUNCTION ": error reading response "
371 "headers from %s:%u", conn->hostname, conn->port);
372 r->status_line = NULL;
373 apr_brigade_destroy(bb);
374 return status;
377 conf = ap_get_module_config(r->per_dir_config, &proxy_scgi_module);
378 if (conf->sendfile && conf->sendfile != scgi_sendfile_off) {
379 short err = 1;
381 location = apr_table_get(r->err_headers_out, conf->sendfile);
382 if (!location) {
383 err = 0;
384 location = apr_table_get(r->headers_out, conf->sendfile);
386 if (location) {
387 scgi_request_config *req_conf = apr_palloc(r->pool,
388 sizeof(*req_conf));
389 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
390 "proxy: " PROXY_FUNCTION ": Found %s: %s - "
391 "preparing subrequest.",
392 conf->sendfile, location);
394 if (err) {
395 apr_table_unset(r->err_headers_out, conf->sendfile);
397 else {
398 apr_table_unset(r->headers_out, conf->sendfile);
400 req_conf->location = location;
401 req_conf->type = scgi_sendfile;
402 ap_set_module_config(r->request_config, &proxy_scgi_module,
403 req_conf);
404 apr_brigade_destroy(bb);
405 return OK;
409 if (conf->internal_redirect && r->status == HTTP_OK) {
410 location = apr_table_get(r->headers_out, "Location");
411 if (location && *location == '/') {
412 scgi_request_config *req_conf = apr_palloc(r->pool,
413 sizeof(*req_conf));
414 req_conf->location = location;
415 req_conf->type = scgi_internal_redirect;
416 ap_set_module_config(r->request_config, &proxy_scgi_module,
417 req_conf);
418 apr_brigade_destroy(bb);
419 return OK;
423 /* XXX: What could we do with that return code? */
424 (void)ap_pass_brigade(r->output_filters, bb);
426 return OK;
430 * Internal redirect / subrequest handler, working on request_status hook
432 static int scgi_request_status(int *status, request_rec *r)
434 scgi_request_config *req_conf;
436 if ( (*status == OK)
437 && (req_conf = ap_get_module_config(r->request_config,
438 &proxy_scgi_module))) {
439 switch (req_conf->type) {
440 case scgi_internal_redirect:
441 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
442 "proxy: " PROXY_FUNCTION ": Internal redirect to %s",
443 req_conf->location);
445 r->status_line = NULL;
446 if (r->method_number != M_GET) {
447 /* keep HEAD, which is passed around as M_GET, too */
448 r->method = "GET";
449 r->method_number = M_GET;
451 apr_table_unset(r->headers_in, "Content-Length");
452 ap_internal_redirect_handler(req_conf->location, r);
453 return OK;
454 /* break; */
456 case scgi_sendfile:
457 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
458 "proxy: " PROXY_FUNCTION ": File subrequest to %s",
459 req_conf->location);
460 do {
461 request_rec *rr;
463 rr = ap_sub_req_lookup_file(req_conf->location, r,
464 r->output_filters);
465 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
467 * We don't touch Content-Length here. It might be
468 * borked (there's plenty of room for a race condition).
469 * Either the backend sets it or it's gonna be chunked.
471 ap_run_sub_req(rr);
473 else {
474 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
475 "Subrequest to file '%s' not possible. "
476 "(rr->status=%d, rr->finfo.filetype=%d)",
477 req_conf->location, rr->status,
478 rr->finfo.filetype);
479 *status = HTTP_INTERNAL_SERVER_ERROR;
480 return *status;
482 } while(0);
484 return OK;
485 /* break; */
489 return DECLINED;
494 * This handles scgi:(dest) URLs
496 static int scgi_handler(request_rec *r, proxy_worker *worker,
497 proxy_server_conf *conf, char *url,
498 const char *proxyname, apr_port_t proxyport)
500 int status;
501 proxy_conn_rec *backend = NULL;
502 apr_pool_t *p = r->pool;
503 apr_uri_t *uri = apr_palloc(r->pool, sizeof(*uri));
504 char dummy;
506 if (strncasecmp(url, SCHEME "://", sizeof(SCHEME) + 2)) {
507 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
508 "proxy: " PROXY_FUNCTION ": declining URL %s", url);
509 return DECLINED;
511 url += sizeof(SCHEME); /* keep the slashes */
513 /* Create space for state information */
514 status = ap_proxy_acquire_connection(PROXY_FUNCTION, &backend, worker,
515 r->server);
516 if (status != OK) {
517 goto cleanup;
519 backend->is_ssl = 0;
521 /* Step One: Determine Who To Connect To */
522 status = ap_proxy_determine_connection(p, r, conf, worker, backend,
523 uri, &url, proxyname, proxyport,
524 &dummy, 1);
525 if (status != OK) {
526 goto cleanup;
529 /* Step Two: Make the Connection */
530 if (ap_proxy_connect_backend(PROXY_FUNCTION, backend, worker, r->server)) {
531 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
532 "proxy: " PROXY_FUNCTION ": failed to make connection "
533 "to backend: %s:%u", backend->hostname, backend->port);
534 status = HTTP_SERVICE_UNAVAILABLE;
535 goto cleanup;
538 /* Step Three: Process the Request */
539 if ( ((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
540 || ((status = send_headers(r, backend)) != OK)
541 || ((status = send_request_body(r, backend)) != OK)
542 || ((status = pass_response(r, backend)) != OK)) {
543 goto cleanup;
546 cleanup:
547 if (backend) {
548 backend->close = 1; /* always close the socket */
549 ap_proxy_release_connection(PROXY_FUNCTION, backend, r->server);
551 return status;
555 static void *create_scgi_config(apr_pool_t *p, char *dummy)
557 scgi_config *conf=apr_palloc(p, sizeof(*conf));
559 conf->sendfile = NULL;
560 conf->internal_redirect = -1;
562 return conf;
566 static void *merge_scgi_config(apr_pool_t *p, void *base_, void *add_)
568 scgi_config *base=base_, *add=add_, *conf=apr_palloc(p, sizeof(*conf));
570 conf->sendfile = add->sendfile ? add->sendfile: base->sendfile;
571 conf->internal_redirect = (add->internal_redirect != -1)
572 ? add->internal_redirect
573 : base->internal_redirect;
574 return conf;
578 static const char *scgi_set_send_file(cmd_parms *cmd, void *mconfig,
579 const char *arg)
581 scgi_config *conf=mconfig;
583 if (!strcasecmp(arg, "Off")) {
584 conf->sendfile = scgi_sendfile_off;
586 else if (!strcasecmp(arg, "On")) {
587 conf->sendfile = scgi_sendfile_on;
589 else {
590 conf->sendfile = arg;
592 return NULL;
596 static const command_rec scgi_cmds[] =
598 AP_INIT_TAKE1("ProxySCGISendfile", scgi_set_send_file, NULL,
599 RSRC_CONF|ACCESS_CONF,
600 "The name of the X-Sendfile peudo response header or "
601 "On or Off"),
602 AP_INIT_FLAG("ProxySCGIInternalRedirect", ap_set_flag_slot,
603 (void*)APR_OFFSETOF(scgi_config, internal_redirect),
604 RSRC_CONF|ACCESS_CONF,
605 "Off if internal redirect responses should not be accepted"),
606 {NULL}
610 static void register_hooks(apr_pool_t *p)
612 proxy_hook_scheme_handler(scgi_handler, NULL, NULL, APR_HOOK_FIRST);
613 proxy_hook_canon_handler(scgi_canon, NULL, NULL, APR_HOOK_FIRST);
614 APR_OPTIONAL_HOOK(proxy, request_status, scgi_request_status, NULL, NULL,
615 APR_HOOK_MIDDLE);
619 module AP_MODULE_DECLARE_DATA proxy_scgi_module = {
620 STANDARD20_MODULE_STUFF,
621 create_scgi_config, /* create per-directory config structure */
622 merge_scgi_config, /* merge per-directory config structures */
623 NULL, /* create per-server config structure */
624 NULL, /* merge per-server config structures */
625 scgi_cmds, /* command table */
626 register_hooks /* register hooks */