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.
17 #include "mod_proxy.h"
18 #include "util_fcgi.h"
19 #include "util_script.h"
22 module AP_MODULE_DECLARE_DATA proxy_fcgi_module;
26 ap_expr_info_t *subst;
34 /* We will assume FPM, but still differentiate */
36 BACKEND_DEFAULT_UNKNOWN = 0,
42 #define FCGI_MAY_BE_FPM(dconf) \
44 ((dconf->backend_type == BACKEND_DEFAULT_UNKNOWN) || \
45 (dconf->backend_type == BACKEND_FPM)))
48 fcgi_backend_t backend_type;
49 apr_array_header_t *env_fixups;
53 * Canonicalise http-like URLs.
54 * scheme is the scheme for the URL
55 * url is the URL starting with the first '/'
56 * def_port is the default port for this scheme.
58 static int proxy_fcgi_canon(request_rec *r, char *url)
63 apr_port_t port, def_port;
64 fcgi_req_config_t *rconf = NULL;
65 const char *pathinfo_type = NULL;
67 if (ap_cstr_casecmpn(url, "fcgi:", 5) == 0) {
74 port = def_port = ap_proxy_port_of_scheme("fcgi");
76 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
77 "canonicalising URL %s", url);
78 err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
80 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01059)
81 "error parsing URL %s: %s", url, err);
82 return HTTP_BAD_REQUEST;
86 apr_snprintf(sport, sizeof(sport), ":%d", port);
90 if (ap_strchr_c(host, ':')) {
91 /* if literal IPv6 address */
92 host = apr_pstrcat(r->pool, "[", host, "]", NULL);
95 if (apr_table_get(r->notes, "proxy-nocanon")) {
96 path = url; /* this is the raw path */
99 path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
103 return HTTP_BAD_REQUEST;
105 r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/",
108 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01060)
109 "set r->filename to %s", r->filename);
111 rconf = ap_get_module_config(r->request_config, &proxy_fcgi_module);
113 rconf = apr_pcalloc(r->pool, sizeof(fcgi_req_config_t));
114 ap_set_module_config(r->request_config, &proxy_fcgi_module, rconf);
117 if (NULL != (pathinfo_type = apr_table_get(r->subprocess_env, "proxy-fcgi-pathinfo"))) {
118 /* It has to be on disk for this to work */
119 if (!strcasecmp(pathinfo_type, "full")) {
120 rconf->need_dirwalk = 1;
121 ap_unescape_url_keep2f(path, 0);
123 else if (!strcasecmp(pathinfo_type, "first-dot")) {
124 char *split = ap_strchr(path, '.');
126 char *slash = ap_strchr(split, '/');
128 r->path_info = apr_pstrdup(r->pool, slash);
129 ap_unescape_url_keep2f(r->path_info, 0);
130 *slash = '\0'; /* truncate path */
134 else if (!strcasecmp(pathinfo_type, "last-dot")) {
135 char *split = ap_strrchr(path, '.');
137 char *slash = ap_strchr(split, '/');
139 r->path_info = apr_pstrdup(r->pool, slash);
140 ap_unescape_url_keep2f(r->path_info, 0);
141 *slash = '\0'; /* truncate path */
146 /* before proxy-fcgi-pathinfo had multi-values. This requires the
147 * the FCGI server to fixup PATH_INFO because it's the entire path
149 r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
150 if (!strcasecmp(pathinfo_type, "unescape")) {
151 ap_unescape_url_keep2f(r->path_info, 0);
153 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01061)
154 "set r->path_info to %s", r->path_info);
163 ProxyFCGISetEnvIf "reqenv('PATH_INFO') =~ m#/foo(\d+)\.php$#" COVENV1 "$1"
164 ProxyFCGISetEnvIf "reqenv('PATH_INFO') =~ m#/foo(\d+)\.php$#" PATH_INFO "/foo.php"
165 ProxyFCGISetEnvIf "reqenv('PATH_TRANSLATED') =~ m#(/.*foo)(\d+)(.*)#" PATH_TRANSLATED "$1$3"
167 static void fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf)
170 const char *err, *src;
172 ap_regmatch_t regm[AP_MAX_REG_MATCH];
174 entries = (sei_entry *) dconf->env_fixups->elts;
175 for (i = 0; i < dconf->env_fixups->nelts; i++) {
176 sei_entry *entry = &entries[i];
178 if (entry->envname[0] == '!') {
179 apr_table_unset(r->subprocess_env, entry->envname+1);
181 else if (0 < (rc = ap_expr_exec_re(r, entry->cond, AP_MAX_REG_MATCH, regm, &src, &err))) {
182 const char *val = ap_expr_str_exec_re(r, entry->subst, AP_MAX_REG_MATCH, regm, &src, &err);
184 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03514)
185 "Error evaluating expression for replacement of %s: '%s'",
186 entry->envname, err);
189 if (APLOGrtrace4(r)) {
190 const char *oldval = apr_table_get(r->subprocess_env, entry->envname);
191 ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
192 "fix_cgivars: override %s from '%s' to '%s'",
193 entry->envname, oldval, val);
196 apr_table_setn(r->subprocess_env, entry->envname, val);
199 ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, "fix_cgivars: Condition returned %d", rc);
204 /* Wrapper for apr_socket_sendv that handles updating the worker stats. */
205 static apr_status_t send_data(proxy_conn_rec *conn,
210 apr_status_t rv = APR_SUCCESS;
211 apr_size_t written = 0, to_write = 0;
213 apr_socket_t *s = conn->sock;
215 for (i = 0; i < nvec; i++) {
216 to_write += vec[i].iov_len;
222 rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
223 if (rv != APR_SUCCESS) {
228 if (written >= to_write)
229 break; /* short circuit out */
230 for (i = offset; i < nvec; ) {
231 if (n >= vec[i].iov_len) {
233 n -= vec[i++].iov_len;
236 vec[i].iov_base = (char *) vec[i].iov_base + n;
243 conn->worker->s->transferred += written;
249 /* Wrapper for apr_socket_recv that handles updating the worker stats. */
250 static apr_status_t get_data(proxy_conn_rec *conn,
254 apr_status_t rv = apr_socket_recv(conn->sock, buffer, buflen);
256 if (rv == APR_SUCCESS) {
257 conn->worker->s->read += *buflen;
263 static apr_status_t get_data_full(proxy_conn_rec *conn,
268 apr_size_t cumulative_len = 0;
272 readlen = buflen - cumulative_len;
273 rv = get_data(conn, buffer + cumulative_len, &readlen);
274 if (rv != APR_SUCCESS) {
277 cumulative_len += readlen;
278 } while (cumulative_len < buflen);
283 static apr_status_t send_begin_request(proxy_conn_rec *conn,
284 apr_uint16_t request_id)
287 ap_fcgi_header header;
288 unsigned char farray[AP_FCGI_HEADER_LEN];
289 ap_fcgi_begin_request_body brb;
290 unsigned char abrb[AP_FCGI_HEADER_LEN];
293 ap_fcgi_fill_in_header(&header, AP_FCGI_BEGIN_REQUEST, request_id,
296 ap_fcgi_fill_in_request_body(&brb, AP_FCGI_RESPONDER,
297 ap_proxy_connection_reusable(conn)
298 ? AP_FCGI_KEEP_CONN : 0);
300 ap_fcgi_header_to_array(&header, farray);
301 ap_fcgi_begin_request_body_to_array(&brb, abrb);
303 vec[0].iov_base = (void *)farray;
304 vec[0].iov_len = sizeof(farray);
305 vec[1].iov_base = (void *)abrb;
306 vec[1].iov_len = sizeof(abrb);
308 return send_data(conn, vec, 2, &len);
311 static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r,
312 apr_pool_t *temp_pool,
313 apr_uint16_t request_id)
315 const apr_array_header_t *envarr;
316 const apr_table_entry_t *elts;
318 ap_fcgi_header header;
319 unsigned char farray[AP_FCGI_HEADER_LEN];
322 apr_size_t avail_len, len, required_len;
323 int next_elem, starting_elem;
325 fcgi_req_config_t *rconf = ap_get_module_config(r->request_config, &proxy_fcgi_module);
326 fcgi_dirconf_t *dconf = ap_get_module_config(r->per_dir_config, &proxy_fcgi_module);
329 if (rconf->need_dirwalk) {
330 ap_directory_walk(r);
334 /* Strip proxy: prefixes */
336 char *newfname = NULL;
338 if (!strncmp(r->filename, "proxy:balancer://", 17)) {
339 newfname = apr_pstrdup(r->pool, r->filename+17);
342 if (!FCGI_MAY_BE_FPM(dconf)) {
343 if (!strncmp(r->filename, "proxy:fcgi://", 13)) {
344 /* If we strip this under FPM, and any internal redirect occurs
345 * on PATH_INFO, FPM may use PATH_TRANSLATED instead of
346 * SCRIPT_FILENAME (a la mod_fastcgi + Action).
348 newfname = apr_pstrdup(r->pool, r->filename+13);
350 /* Query string in environment only */
351 if (newfname && r->args && *r->args) {
352 char *qs = strrchr(newfname, '?');
353 if (qs && !strcmp(qs+1, r->args)) {
362 newfname = ap_strchr(newfname, '/');
363 r->filename = newfname;
368 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(09999)
369 "r->filename: %s", (r->filename ? r->filename : "nil"));
370 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(09999)
371 "r->uri: %s", (r->uri ? r->uri : "nil"));
372 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(09999)
373 "r->path_info: %s", (r->path_info ? r->path_info : "nil"));
376 ap_add_common_vars(r);
379 if (fpm || apr_table_get(r->notes, "virtual_script")) {
381 * Adjust SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED for PHP-FPM
382 * TODO: Right now, PATH_INFO and PATH_TRANSLATED look OK...
385 const char *script_name = apr_table_get(r->subprocess_env, "SCRIPT_NAME");
386 pend = script_name + strlen(script_name);
387 if (r->path_info && *r->path_info) {
388 pend = script_name + ap_find_path_info(script_name, r->path_info) - 1;
390 while (pend != script_name && *pend != '/') {
393 apr_table_setn(r->subprocess_env, "SCRIPT_NAME", pend);
394 ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
395 "fpm:virtual_script: Modified SCRIPT_NAME to: %s",
399 /* XXX are there any FastCGI specific env vars we need to send? */
401 /* Give admins final option to fine-tune env vars */
402 fix_cgivars(r, dconf);
404 /* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in
405 * the TZ value specially. We could use that, but it would mean
406 * parsing the key/value pairs back OUT of the allocated env array,
407 * not to mention allocating a totally useless array in the first
408 * place, which would suck. */
410 envarr = apr_table_elts(r->subprocess_env);
411 elts = (const apr_table_entry_t *) envarr->elts;
413 if (APLOGrtrace8(r)) {
416 for (i = 0; i < envarr->nelts; ++i) {
417 ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(01062)
418 "sending env var '%s' value '%s'",
419 elts[i].key, elts[i].val);
423 /* Send envvars over in as many FastCGI records as it takes, */
424 next_elem = 0; /* starting with the first one */
426 avail_len = 16 * 1024; /* our limit per record, which could have been up
427 * to AP_FCGI_MAX_CONTENT_LEN
430 while (next_elem < envarr->nelts) {
431 starting_elem = next_elem;
432 required_len = ap_fcgi_encoded_env_len(r->subprocess_env,
437 if (next_elem < envarr->nelts) {
438 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
439 APLOGNO(02536) "couldn't encode envvar '%s' in %"
440 APR_SIZE_T_FMT " bytes",
441 elts[next_elem].key, avail_len);
442 /* skip this envvar and continue */
446 /* only an unused element at the end of the array */
450 body = apr_palloc(temp_pool, required_len);
451 rv = ap_fcgi_encode_env(r, r->subprocess_env, body, required_len,
453 /* we pre-compute, so we can't run out of space */
454 ap_assert(rv == APR_SUCCESS);
455 /* compute and encode must be in sync */
456 ap_assert(starting_elem == next_elem);
458 ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id,
459 (apr_uint16_t)required_len, 0);
460 ap_fcgi_header_to_array(&header, farray);
462 vec[0].iov_base = (void *)farray;
463 vec[0].iov_len = sizeof(farray);
464 vec[1].iov_base = body;
465 vec[1].iov_len = required_len;
467 rv = send_data(conn, vec, 2, &len);
468 apr_pool_clear(temp_pool);
475 /* Envvars sent, so say we're done */
476 ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id, 0, 0);
477 ap_fcgi_header_to_array(&header, farray);
479 vec[0].iov_base = (void *)farray;
480 vec[0].iov_len = sizeof(farray);
482 return send_data(conn, vec, 1, &len);
486 HDR_STATE_READING_HEADERS,
489 HDR_STATE_GOT_CRLFCR,
491 HDR_STATE_DONE_WITH_HEADERS
494 /* Try to find the end of the script headers in the response from the back
495 * end fastcgi server. STATE holds the current header parsing state for this
498 * Returns 0 if it can't find the end of the headers, and 1 if it found the
499 * end of the headers. */
500 static int handle_headers(request_rec *r, int *state,
501 const char *readbuf, apr_size_t readlen)
503 const char *itr = readbuf;
508 case HDR_STATE_GOT_CRLF:
509 *state = HDR_STATE_GOT_CRLFCR;
513 *state = HDR_STATE_GOT_CR;
517 else if (*itr == '\n') {
519 case HDR_STATE_GOT_LF:
520 *state = HDR_STATE_DONE_WITH_HEADERS;
523 case HDR_STATE_GOT_CR:
524 *state = HDR_STATE_GOT_CRLF;
527 case HDR_STATE_GOT_CRLFCR:
528 *state = HDR_STATE_DONE_WITH_HEADERS;
532 *state = HDR_STATE_GOT_LF;
537 *state = HDR_STATE_READING_HEADERS;
540 if (*state == HDR_STATE_DONE_WITH_HEADERS)
546 if (*state == HDR_STATE_DONE_WITH_HEADERS) {
553 static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
554 request_rec *r, apr_pool_t *setaside_pool,
555 apr_uint16_t request_id, const char **err,
556 int *bad_request, int *has_responded)
558 apr_bucket_brigade *ib, *ob;
559 int seen_end_of_headers = 0, done = 0, ignore_body = 0;
560 apr_status_t rv = APR_SUCCESS;
561 int script_error_status = HTTP_OK;
562 conn_rec *c = r->connection;
564 ap_fcgi_header header;
565 unsigned char farray[AP_FCGI_HEADER_LEN];
567 int header_state = HDR_STATE_READING_HEADERS;
568 char stack_iobuf[AP_IOBUFSIZE];
569 apr_size_t iobuf_size = AP_IOBUFSIZE;
570 char *iobuf = stack_iobuf;
573 if (conn->worker->s->io_buffer_size_set) {
574 iobuf_size = conn->worker->s->io_buffer_size;
575 iobuf = apr_palloc(r->pool, iobuf_size);
578 pfd.desc_type = APR_POLL_SOCKET;
579 pfd.desc.s = conn->sock;
581 pfd.reqevents = APR_POLLIN | APR_POLLOUT;
583 ib = apr_brigade_create(r->pool, c->bucket_alloc);
584 ob = apr_brigade_create(r->pool, c->bucket_alloc);
587 apr_interval_time_t timeout;
591 /* We need SOME kind of timeout here, or virtually anything will
592 * cause timeout errors. */
593 apr_socket_timeout_get(conn->sock, &timeout);
595 rv = apr_poll(&pfd, 1, &n, timeout);
596 if (rv != APR_SUCCESS) {
597 if (APR_STATUS_IS_EINTR(rv)) {
604 if (pfd.rtnevents & APR_POLLOUT) {
605 apr_size_t to_send, writebuflen;
609 rv = ap_get_brigade(r->input_filters, ib,
610 AP_MODE_READBYTES, APR_BLOCK_READ,
612 if (rv != APR_SUCCESS) {
613 *err = "reading input brigade";
618 if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(ib))) {
622 writebuflen = iobuf_size;
624 rv = apr_brigade_flatten(ib, iobuf, &writebuflen);
626 apr_brigade_cleanup(ib);
628 if (rv != APR_SUCCESS) {
629 *err = "flattening brigade";
633 to_send = writebuflen;
634 iobuf_cursor = iobuf;
635 while (to_send > 0) {
637 apr_size_t write_this_time;
640 to_send < AP_FCGI_MAX_CONTENT_LEN ? to_send : AP_FCGI_MAX_CONTENT_LEN;
642 ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id,
643 (apr_uint16_t)write_this_time, 0);
644 ap_fcgi_header_to_array(&header, farray);
646 vec[nvec].iov_base = (void *)farray;
647 vec[nvec].iov_len = sizeof(farray);
650 vec[nvec].iov_base = iobuf_cursor;
651 vec[nvec].iov_len = write_this_time;
655 rv = send_data(conn, vec, nvec, &len);
656 if (rv != APR_SUCCESS) {
657 *err = "sending stdin";
661 to_send -= write_this_time;
662 iobuf_cursor += write_this_time;
664 if (rv != APR_SUCCESS) {
669 pfd.reqevents = APR_POLLIN; /* Done with input data */
671 /* signal EOF (empty FCGI_STDIN) */
672 ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id,
674 ap_fcgi_header_to_array(&header, farray);
676 vec[0].iov_base = (void *)farray;
677 vec[0].iov_len = sizeof(farray);
679 rv = send_data(conn, vec, 1, &len);
680 if (rv != APR_SUCCESS) {
681 *err = "sending empty stdin";
687 if (pfd.rtnevents & APR_POLLIN) {
688 apr_size_t readbuflen;
689 apr_uint16_t clen, rid;
692 unsigned char type, version;
694 /* First, we grab the header... */
695 rv = get_data_full(conn, (char *) farray, AP_FCGI_HEADER_LEN);
696 if (rv != APR_SUCCESS) {
697 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01067)
698 "Failed to read FastCGI header");
702 ap_log_rdata(APLOG_MARK, APLOG_TRACE8, r, "FastCGI header",
703 farray, AP_FCGI_HEADER_LEN, 0);
705 ap_fcgi_header_fields_from_array(&version, &type, &rid,
706 &clen, &plen, farray);
708 if (version != AP_FCGI_VERSION_1) {
709 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01068)
710 "Got bogus version %d", (int)version);
715 if (rid != request_id) {
716 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01069)
717 "Got bogus rid %d, expected %d",
724 if (clen > iobuf_size) {
725 readbuflen = iobuf_size;
730 /* Now get the actual data. Yes it sucks to do this in a second
731 * recv call, this will eventually change when we move to real
732 * nonblocking recv calls. */
733 if (readbuflen != 0) {
734 rv = get_data(conn, iobuf, &readbuflen);
735 if (rv != APR_SUCCESS) {
736 *err = "reading response body";
744 b = apr_bucket_transient_create(iobuf,
748 APR_BRIGADE_INSERT_TAIL(ob, b);
750 if (! seen_end_of_headers) {
751 int st = handle_headers(r, &header_state,
756 seen_end_of_headers = 1;
758 status = ap_scan_script_header_err_brigade_ex(r, ob,
759 NULL, APLOG_MODULE_INDEX);
760 /* suck in all the rest */
763 apr_brigade_cleanup(ob);
764 tmp_b = apr_bucket_eos_create(c->bucket_alloc);
765 APR_BRIGADE_INSERT_TAIL(ob, tmp_b);
769 rv = ap_pass_brigade(r->output_filters, ob);
770 if (rv != APR_SUCCESS) {
771 *err = "passing headers brigade to output filters";
774 else if (status == HTTP_NOT_MODIFIED
775 || status == HTTP_PRECONDITION_FAILED) {
776 /* Special 'status' cases handled:
777 * 1) HTTP 304 response MUST NOT contain
778 * a message-body, ignore it.
779 * 2) HTTP 412 response.
780 * The break is not added since there might
781 * be more bytes to read from the FCGI
782 * connection. Even if the message-body is
783 * ignored (and the EOS bucket has already
784 * been sent) we want to avoid subsequent
789 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01070)
790 "Error parsing script headers");
796 if (conf->error_override
797 && ap_is_HTTP_ERROR(r->status) && ap_is_initial_req(r)) {
799 * set script_error_status to discard
800 * everything after the headers
802 script_error_status = r->status;
804 * prevent ap_die() from treating this as a
805 * recursive error, initially:
810 if (script_error_status == HTTP_OK
811 && !APR_BRIGADE_EMPTY(ob) && !ignore_body) {
812 /* Send the part of the body that we read while
813 * reading the headers.
816 rv = ap_pass_brigade(r->output_filters, ob);
817 if (rv != APR_SUCCESS) {
818 *err = "passing brigade to output filters";
822 apr_brigade_cleanup(ob);
824 apr_pool_clear(setaside_pool);
827 /* We're still looking for the end of the
828 * headers, so this part of the data will need
830 apr_bucket_setaside(b, setaside_pool);
833 /* we've already passed along the headers, so now pass
834 * through the content. we could simply continue to
835 * setaside the content and not pass until we see the
836 * 0 content-length (below, where we append the EOS),
837 * but that could be a huge amount of data; so we pass
838 * along smaller chunks
840 if (script_error_status == HTTP_OK && !ignore_body) {
842 rv = ap_pass_brigade(r->output_filters, ob);
843 if (rv != APR_SUCCESS) {
844 *err = "passing brigade to output filters";
848 apr_brigade_cleanup(ob);
851 /* If we didn't read all the data, go back and get the
853 if (clen > readbuflen) {
858 /* XXX what if we haven't seen end of the headers yet? */
860 if (script_error_status == HTTP_OK) {
861 b = apr_bucket_eos_create(c->bucket_alloc);
862 APR_BRIGADE_INSERT_TAIL(ob, b);
865 rv = ap_pass_brigade(r->output_filters, ob);
866 if (rv != APR_SUCCESS) {
867 *err = "passing brigade to output filters";
872 /* XXX Why don't we cleanup here? (logic from AJP) */
877 /* TODO: Should probably clean up this logging a bit... */
879 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01071)
880 "Got error '%.*s'", (int)readbuflen, iobuf);
883 if (clen > readbuflen) {
889 case AP_FCGI_END_REQUEST:
894 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01072)
895 "Got bogus record %d", type);
898 /* Leave on above switch's inner error. */
899 if (rv != APR_SUCCESS) {
904 rv = get_data_full(conn, iobuf, plen);
905 if (rv != APR_SUCCESS) {
906 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02537)
907 "Error occurred reading padding");
914 apr_brigade_destroy(ib);
915 apr_brigade_destroy(ob);
917 if (script_error_status != HTTP_OK) {
918 ap_die(script_error_status, r); /* send ErrorDocument */
926 * process the request and write the response.
928 static int fcgi_do_request(apr_pool_t *p, request_rec *r,
929 proxy_conn_rec *conn,
931 proxy_dir_conf *conf,
933 char *url, char *server_portstr)
935 /* Request IDs are arbitrary numbers that we assign to a
936 * single request. This would allow multiplex/pipelining of
937 * multiple requests to the same FastCGI connection, but
938 * we don't support that, and always use a value of '1' to
939 * keep things simple. */
940 apr_uint16_t request_id = 1;
942 apr_pool_t *temp_pool;
947 /* Step 1: Send AP_FCGI_BEGIN_REQUEST */
948 rv = send_begin_request(conn, request_id);
949 if (rv != APR_SUCCESS) {
950 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01073)
951 "Failed Writing Request to %s:", server_portstr);
953 return HTTP_SERVICE_UNAVAILABLE;
956 apr_pool_create(&temp_pool, r->pool);
958 /* Step 2: Send Environment via FCGI_PARAMS */
959 rv = send_environment(conn, r, temp_pool, request_id);
960 if (rv != APR_SUCCESS) {
961 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01074)
962 "Failed writing Environment to %s:", server_portstr);
964 return HTTP_SERVICE_UNAVAILABLE;
967 /* Step 3: Read records from the back end server and handle them. */
968 rv = dispatch(conn, conf, r, temp_pool, request_id,
969 &err, &bad_request, &has_responded);
970 if (rv != APR_SUCCESS) {
971 /* If the client aborted the connection during retrieval or (partially)
972 * sending the response, don't return a HTTP_SERVICE_UNAVAILABLE, since
973 * this is not a backend problem. */
974 if (r->connection->aborted) {
975 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
976 "The client aborted the connection.");
981 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01075)
982 "Error dispatching request to %s: %s%s%s",
989 return AP_FILTER_ERROR;
992 return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
994 if (APR_STATUS_IS_TIMEUP(rv)) {
995 return HTTP_GATEWAY_TIME_OUT;
997 return HTTP_SERVICE_UNAVAILABLE;
1003 #define FCGI_SCHEME "FCGI"
1006 * This handles fcgi:(dest) URLs
1008 static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
1009 proxy_server_conf *conf,
1010 char *url, const char *proxyname,
1011 apr_port_t proxyport)
1014 char server_portstr[32];
1015 conn_rec *origin = NULL;
1016 proxy_conn_rec *backend = NULL;
1019 proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
1022 apr_pool_t *p = r->pool;
1025 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01076)
1026 "url: %s proxyname: %s proxyport: %d",
1027 url, proxyname, proxyport);
1029 if (ap_cstr_casecmpn(url, "fcgi:", 5) != 0) {
1030 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01077) "declining URL %s", url);
1034 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01078) "serving URL %s", url);
1036 /* Create space for state information */
1037 status = ap_proxy_acquire_connection(FCGI_SCHEME, &backend, worker,
1042 ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
1047 backend->is_ssl = 0;
1049 /* Step One: Determine Who To Connect To */
1050 uri = apr_palloc(p, sizeof(*uri));
1051 status = ap_proxy_determine_connection(p, r, conf, worker, backend,
1052 uri, &url, proxyname, proxyport,
1054 sizeof(server_portstr));
1059 /* This scheme handler does not reuse connections by default, to
1060 * avoid tying up a fastcgi that isn't expecting to work on
1061 * parallel requests. But if the user went out of their way to
1062 * type the default value of disablereuse=off, we'll allow it.
1065 if (worker->s->disablereuse_set && !worker->s->disablereuse) {
1069 /* Step Two: Make the Connection */
1070 if (ap_proxy_check_connection(FCGI_SCHEME, backend, r->server, 0,
1071 PROXY_CHECK_CONN_EMPTY)
1072 && ap_proxy_connect_backend(FCGI_SCHEME, backend, worker,
1074 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01079)
1075 "failed to make connection to backend: %s",
1077 status = HTTP_SERVICE_UNAVAILABLE;
1081 /* Step Three: Process the Request */
1082 status = fcgi_do_request(p, r, backend, origin, dconf, uri, url,
1086 ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
1090 static void *fcgi_create_dconf(apr_pool_t *p, char *path)
1094 a = (fcgi_dirconf_t *)apr_pcalloc(p, sizeof(fcgi_dirconf_t));
1095 a->backend_type = BACKEND_DEFAULT_UNKNOWN;
1096 a->env_fixups = apr_array_make(p, 20, sizeof(sei_entry));
1101 static void *fcgi_merge_dconf(apr_pool_t *p, void *basev, void *overridesv)
1103 fcgi_dirconf_t *a, *base, *over;
1105 a = (fcgi_dirconf_t *)apr_pcalloc(p, sizeof(fcgi_dirconf_t));
1106 base = (fcgi_dirconf_t *)basev;
1107 over = (fcgi_dirconf_t *)overridesv;
1109 a->backend_type = (over->backend_type != BACKEND_DEFAULT_UNKNOWN)
1110 ? over->backend_type
1111 : base->backend_type;
1112 a->env_fixups = apr_array_append(p, base->env_fixups, over->env_fixups);
1116 static const char *cmd_servertype(cmd_parms *cmd, void *in_dconf,
1119 fcgi_dirconf_t *dconf = in_dconf;
1121 if (!strcasecmp(val, "GENERIC")) {
1122 dconf->backend_type = BACKEND_GENERIC;
1124 else if (!strcasecmp(val, "FPM")) {
1125 dconf->backend_type = BACKEND_FPM;
1128 return "ProxyFCGIBackendType requires one of the following arguments: "
1136 static const char *cmd_setenv(cmd_parms *cmd, void *in_dconf,
1137 const char *arg1, const char *arg2,
1140 fcgi_dirconf_t *dconf = in_dconf;
1143 const char *envvar = arg2;
1145 new = apr_array_push(dconf->env_fixups);
1146 new->cond = ap_expr_parse_cmd(cmd, arg1, 0, &err, NULL);
1148 return apr_psprintf(cmd->pool, "Could not parse expression \"%s\": %s",
1152 if (envvar[0] == '!') {
1155 return apr_psprintf(cmd->pool, "Third argument (\"%s\") is not "
1156 "allowed when using ProxyFCGISetEnvIf's unset "
1157 "mode (%s)", arg3, envvar);
1159 else if (!envvar[1]) {
1160 /* i.e. someone tried to give us a name of just "!" */
1161 return "ProxyFCGISetEnvIf: \"!\" is not a valid variable name";
1169 /* A missing expr-value should be treated as empty. */
1173 new->subst = ap_expr_parse_cmd(cmd, arg3, AP_EXPR_FLAG_STRING_RESULT, &err, NULL);
1175 return apr_psprintf(cmd->pool, "Could not parse expression \"%s\": %s",
1180 new->envname = envvar;
1184 static void register_hooks(apr_pool_t *p)
1186 proxy_hook_scheme_handler(proxy_fcgi_handler, NULL, NULL, APR_HOOK_FIRST);
1187 proxy_hook_canon_handler(proxy_fcgi_canon, NULL, NULL, APR_HOOK_FIRST);
1190 static const command_rec command_table[] = {
1191 AP_INIT_TAKE1("ProxyFCGIBackendType", cmd_servertype, NULL, OR_FILEINFO,
1192 "Specify the type of FastCGI server: 'Generic', 'FPM'"),
1193 AP_INIT_TAKE23("ProxyFCGISetEnvIf", cmd_setenv, NULL, OR_FILEINFO,
1194 "expr-condition env-name expr-value"),
1198 AP_DECLARE_MODULE(proxy_fcgi) = {
1199 STANDARD20_MODULE_STUFF,
1200 fcgi_create_dconf, /* create per-directory config structure */
1201 fcgi_merge_dconf, /* merge per-directory config structures */
1202 NULL, /* create per-server config structure */
1203 NULL, /* merge per-server config structures */
1204 command_table, /* command apr_table_t */
1205 register_hooks /* register hooks */