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.
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"
33 #include "http_config.h"
35 #include "http_protocol.h"
36 #include "http_request.h"
37 #include "util_script.h"
39 #include "mod_proxy.h"
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
;
56 scgi_internal_redirect
,
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";
70 int internal_redirect
;
75 * We create our own bucket type, which is actually derived (c&p) from the
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.
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
,
93 apr_read_type_e block
)
95 socket_ex_data
*data
= a
->data
;
96 apr_socket_t
*p
= data
->sock
;
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);
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
);
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
);
130 h
->alloc_len
= APR_BUCKET_BUFF_SIZE
; /* note the real buffer size */
132 APR_BUCKET_INSERT_AFTER(a
, bucket_socket_ex_create(data
, a
->list
));
135 apr_bucket_free(buf
);
136 a
= apr_bucket_immortal_make(a
, "", 0);
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);
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
);
166 b
->free
= apr_bucket_free
;
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)) {
184 url
+= sizeof(SCHEME
); /* Keep slashes */
186 err
= ap_proxy_canon_netloc(r
->pool
, &url
, NULL
, NULL
, &host
, &port
);
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,
202 return HTTP_BAD_REQUEST
;
205 r
->filename
= apr_pstrcat(r
->pool
, "proxy:" SCHEME
"://", host
, sport
, "/",
207 r
->path_info
= apr_pstrcat(r
->pool
, "/", path
, NULL
);
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
,
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
;
241 * Send SCGI header block
243 static int send_headers(request_rec
*r
, proxy_conn_rec
*conn
)
245 char *buf
, *cp
, *bodylen
;
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
)
252 + sizeof(SCGI_PROTOCOL_VERSION
);
254 ap_add_common_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
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
))) {
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
);
287 memcpy(cp
, CONTENT_LENGTH
, sizeof(CONTENT_LENGTH
));
288 cp
+= sizeof(CONTENT_LENGTH
);
289 memcpy(cp
, bodylen
, 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
))) {
302 len
= strlen(env
[j
].key
) + 1;
303 memcpy(cp
, env
[j
].key
, len
);
305 len
= strlen(env
[j
].val
) + 1;
306 memcpy(cp
, env
[j
].val
, len
);
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
);
325 readlen
= ap_get_client_block(r
, buf
, AP_IOBUFSIZE
);
326 while (readlen
> 0) {
327 status
= sendall(conn
, buf
, readlen
, r
);
329 return HTTP_SERVICE_UNAVAILABLE
;
331 readlen
= ap_get_client_block(r
, buf
, AP_IOBUFSIZE
);
334 ap_log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
335 "proxy: " PROXY_FUNCTION
": receiving request body "
337 return HTTP_INTERNAL_SERVER_ERROR
;
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
;
352 const char *location
;
354 socket_ex_data
*sock_data
;
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
);
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
);
377 conf
= ap_get_module_config(r
->per_dir_config
, &proxy_scgi_module
);
378 if (conf
->sendfile
&& conf
->sendfile
!= scgi_sendfile_off
) {
381 location
= apr_table_get(r
->err_headers_out
, conf
->sendfile
);
384 location
= apr_table_get(r
->headers_out
, conf
->sendfile
);
387 scgi_request_config
*req_conf
= apr_palloc(r
->pool
,
389 ap_log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
390 "proxy: " PROXY_FUNCTION
": Found %s: %s - "
391 "preparing subrequest.",
392 conf
->sendfile
, location
);
395 apr_table_unset(r
->err_headers_out
, conf
->sendfile
);
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
,
404 apr_brigade_destroy(bb
);
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
,
414 req_conf
->location
= location
;
415 req_conf
->type
= scgi_internal_redirect
;
416 ap_set_module_config(r
->request_config
, &proxy_scgi_module
,
418 apr_brigade_destroy(bb
);
423 /* XXX: What could we do with that return code? */
424 (void)ap_pass_brigade(r
->output_filters
, bb
);
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
;
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",
445 r
->status_line
= NULL
;
446 if (r
->method_number
!= M_GET
) {
447 /* keep HEAD, which is passed around as M_GET, too */
449 r
->method_number
= M_GET
;
451 apr_table_unset(r
->headers_in
, "Content-Length");
452 ap_internal_redirect_handler(req_conf
->location
, r
);
457 ap_log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
458 "proxy: " PROXY_FUNCTION
": File subrequest to %s",
463 rr
= ap_sub_req_lookup_file(req_conf
->location
, r
,
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.
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
,
479 *status
= HTTP_INTERNAL_SERVER_ERROR
;
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
)
501 proxy_conn_rec
*backend
= NULL
;
502 apr_pool_t
*p
= r
->pool
;
503 apr_uri_t
*uri
= apr_palloc(r
->pool
, sizeof(*uri
));
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
);
511 url
+= sizeof(SCHEME
); /* keep the slashes */
513 /* Create space for state information */
514 status
= ap_proxy_acquire_connection(PROXY_FUNCTION
, &backend
, worker
,
521 /* Step One: Determine Who To Connect To */
522 status
= ap_proxy_determine_connection(p
, r
, conf
, worker
, backend
,
523 uri
, &url
, proxyname
, proxyport
,
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
;
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
)) {
548 backend
->close
= 1; /* always close the socket */
549 ap_proxy_release_connection(PROXY_FUNCTION
, backend
, r
->server
);
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;
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
;
578 static const char *scgi_set_send_file(cmd_parms
*cmd
, void *mconfig
,
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
;
590 conf
->sendfile
= arg
;
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 "
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"),
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
,
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 */