| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "mod_proxy.h" |
| #include "fcgi_protocol.h" |
| #include "util_script.h" |
| |
| module AP_MODULE_DECLARE_DATA proxy_fcgi_module; |
| |
| /* |
| * The below 3 functions serve to map the FCGI structs |
| * back and forth between an 8 byte array. We do this to avoid |
| * any potential padding issues when we send or read these |
| * structures. |
| * |
| * NOTE: These have specific internal knowledge of the |
| * layout of the fcgi_header and fcgi_begin_request_body |
| * structs! |
| */ |
| static void fcgi_header_to_array(fcgi_header *h, unsigned char a[]) |
| { |
| a[FCGI_HDR_VERSION_OFFSET] = h->version; |
| a[FCGI_HDR_TYPE_OFFSET] = h->type; |
| a[FCGI_HDR_REQUEST_ID_B1_OFFSET] = h->requestIdB1; |
| a[FCGI_HDR_REQUEST_ID_B0_OFFSET] = h->requestIdB0; |
| a[FCGI_HDR_CONTENT_LEN_B1_OFFSET] = h->contentLengthB1; |
| a[FCGI_HDR_CONTENT_LEN_B0_OFFSET] = h->contentLengthB0; |
| a[FCGI_HDR_PADDING_LEN_OFFSET] = h->paddingLength; |
| a[FCGI_HDR_RESERVED_OFFSET] = h->reserved; |
| } |
| |
| static void fcgi_header_from_array(fcgi_header *h, unsigned char a[]) |
| { |
| h->version = a[FCGI_HDR_VERSION_OFFSET]; |
| h->type = a[FCGI_HDR_TYPE_OFFSET]; |
| h->requestIdB1 = a[FCGI_HDR_REQUEST_ID_B1_OFFSET]; |
| h->requestIdB0 = a[FCGI_HDR_REQUEST_ID_B0_OFFSET]; |
| h->contentLengthB1 = a[FCGI_HDR_CONTENT_LEN_B1_OFFSET]; |
| h->contentLengthB0 = a[FCGI_HDR_CONTENT_LEN_B0_OFFSET]; |
| h->paddingLength = a[FCGI_HDR_PADDING_LEN_OFFSET]; |
| h->reserved = a[FCGI_HDR_RESERVED_OFFSET]; |
| } |
| |
| static void fcgi_begin_request_body_to_array(fcgi_begin_request_body *h, |
| unsigned char a[]) |
| { |
| a[FCGI_BRB_ROLEB1_OFFSET] = h->roleB1; |
| a[FCGI_BRB_ROLEB0_OFFSET] = h->roleB0; |
| a[FCGI_BRB_FLAGS_OFFSET] = h->flags; |
| a[FCGI_BRB_RESERVED0_OFFSET] = h->reserved[0]; |
| a[FCGI_BRB_RESERVED1_OFFSET] = h->reserved[1]; |
| a[FCGI_BRB_RESERVED2_OFFSET] = h->reserved[2]; |
| a[FCGI_BRB_RESERVED3_OFFSET] = h->reserved[3]; |
| a[FCGI_BRB_RESERVED4_OFFSET] = h->reserved[4]; |
| } |
| |
| /* |
| * Canonicalise http-like URLs. |
| * scheme is the scheme for the URL |
| * url is the URL starting with the first '/' |
| * def_port is the default port for this scheme. |
| */ |
| static int proxy_fcgi_canon(request_rec *r, char *url) |
| { |
| char *host, sport[7]; |
| const char *err, *path; |
| apr_port_t port = 8000; |
| |
| if (strncasecmp(url, "fcgi://", 7) == 0) { |
| url += 5; |
| } |
| else { |
| return DECLINED; |
| } |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "proxy: FCGI: canonicalising URL %s", url); |
| |
| err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port); |
| if (err) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
| "error parsing URL %s: %s", url, err); |
| return HTTP_BAD_REQUEST; |
| } |
| |
| apr_snprintf(sport, sizeof(sport), ":%d", port); |
| |
| if (ap_strchr_c(host, ':')) { |
| /* if literal IPv6 address */ |
| host = apr_pstrcat(r->pool, "[", host, "]", NULL); |
| } |
| |
| path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, |
| r->proxyreq); |
| if (path == NULL) |
| return HTTP_BAD_REQUEST; |
| |
| r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/", |
| path, NULL); |
| |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, |
| "proxy: FCGI: set r->filename to %s", r->filename); |
| |
| r->path_info = apr_pstrcat(r->pool, "/", path, NULL); |
| |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, |
| "proxy: FCGI: set r->path_info to %s", r->path_info); |
| |
| return OK; |
| } |
| |
| /* |
| * Fill in a fastcgi request header with the following type, request id, |
| * content length, and padding length. |
| * |
| * The header array must be at least FCGI_HEADER_LEN bytes long. |
| */ |
| static void fill_in_header(fcgi_header *header, |
| unsigned char type, |
| apr_uint16_t request_id, |
| apr_uint16_t content_len, |
| unsigned char padding_len) |
| { |
| header->version = 1; |
| |
| header->type = type; |
| |
| header->requestIdB1 = ((request_id >> 8) & 0xff); |
| header->requestIdB0 = ((request_id) & 0xff); |
| |
| header->contentLengthB1 = ((content_len >> 8) & 0xff); |
| header->contentLengthB0 = ((content_len) & 0xff); |
| |
| header->paddingLength = padding_len; |
| |
| header->reserved = 0; |
| } |
| |
| /* Wrapper for apr_socket_sendv that handles updating the worker stats. */ |
| static apr_status_t send_data(proxy_conn_rec *conn, |
| struct iovec *vec, |
| int nvec, |
| apr_size_t *len, |
| int blocking) |
| { |
| apr_status_t rv = APR_SUCCESS, arv; |
| apr_size_t written = 0, to_write = 0; |
| int i, offset; |
| apr_interval_time_t old_timeout; |
| apr_socket_t *s = conn->sock; |
| |
| if (!blocking) { |
| arv = apr_socket_timeout_get(s, &old_timeout); |
| if (arv != APR_SUCCESS) { |
| return arv; |
| } |
| arv = apr_socket_timeout_set(s, 0); |
| if (arv != APR_SUCCESS) { |
| return arv; |
| } |
| } |
| |
| for (i = 0; i < nvec; i++) { |
| to_write += vec[i].iov_len; |
| } |
| |
| offset = 0; |
| while (to_write) { |
| apr_size_t n = 0; |
| rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| if (n > 0) { |
| written += n; |
| if (written >= to_write) |
| break; /* short circuit out */ |
| for (i = offset; i < nvec; ) { |
| if (n >= vec[i].iov_len) { |
| offset++; |
| n -= vec[i++].iov_len; |
| } else { |
| vec[i].iov_len -= n; |
| vec[i].iov_base += n; |
| break; |
| } |
| } |
| } |
| } |
| |
| conn->worker->s->transferred += written; |
| *len = written; |
| |
| if (!blocking) { |
| arv = apr_socket_timeout_set(s, old_timeout); |
| if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { |
| return arv; |
| } |
| } |
| return rv; |
| } |
| |
| /* Wrapper for apr_socket_recv that handles updating the worker stats. */ |
| static apr_status_t get_data(proxy_conn_rec *conn, |
| char *buffer, |
| apr_size_t *buflen) |
| { |
| apr_status_t rv = apr_socket_recv(conn->sock, buffer, buflen); |
| |
| if (rv == APR_SUCCESS) { |
| conn->worker->s->read += *buflen; |
| } |
| |
| return rv; |
| } |
| |
| static apr_status_t send_begin_request(proxy_conn_rec *conn, int request_id) |
| { |
| struct iovec vec[2]; |
| fcgi_header header; |
| unsigned char farray[FCGI_HEADER_LEN]; |
| fcgi_begin_request_body brb; |
| unsigned char abrb[FCGI_HEADER_LEN]; |
| apr_size_t len; |
| |
| fill_in_header(&header, FCGI_BEGIN_REQUEST, request_id, sizeof(abrb), 0); |
| |
| brb.roleB1 = ((FCGI_RESPONDER >> 8) & 0xff); |
| brb.roleB0 = ((FCGI_RESPONDER) & 0xff); |
| brb.flags = FCGI_KEEP_CONN; |
| brb.reserved[0] = 0; |
| brb.reserved[1] = 0; |
| brb.reserved[2] = 0; |
| brb.reserved[3] = 0; |
| brb.reserved[4] = 0; |
| |
| fcgi_header_to_array(&header, farray); |
| fcgi_begin_request_body_to_array(&brb, abrb); |
| |
| vec[0].iov_base = farray; |
| vec[0].iov_len = sizeof(farray); |
| vec[1].iov_base = abrb; |
| vec[1].iov_len = sizeof(abrb); |
| |
| return send_data(conn, vec, 2, &len, 1); |
| } |
| |
| static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r, |
| int request_id) |
| { |
| const apr_array_header_t *envarr; |
| const apr_table_entry_t *elts; |
| struct iovec vec[2]; |
| fcgi_header header; |
| unsigned char farray[FCGI_HEADER_LEN]; |
| apr_size_t bodylen, envlen; |
| char *body, *itr; |
| apr_status_t rv; |
| apr_size_t len; |
| int i, numenv; |
| |
| ap_add_common_vars(r); |
| ap_add_cgi_vars(r); |
| |
| /* XXX are there any FastCGI specific env vars we need to send? */ |
| |
| bodylen = envlen = 0; |
| |
| /* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in |
| * the TZ value specially. We could use that, but it would mean |
| * parsing the key/value pairs back OUT of the allocated env array, |
| * not to mention allocating a totally useless array in the first |
| * place, which would suck. */ |
| |
| envarr = apr_table_elts(r->subprocess_env); |
| |
| elts = (const apr_table_entry_t *) envarr->elts; |
| |
| for (i = 0; i < envarr->nelts; ++i) { |
| apr_size_t keylen, vallen; |
| |
| if (! elts[i].key) { |
| continue; |
| } |
| |
| keylen = strlen(elts[i].key); |
| |
| if (keylen >> 7 == 0) { |
| envlen += 1; |
| } |
| else { |
| envlen += 4; |
| } |
| |
| envlen += keylen; |
| |
| vallen = strlen(elts[i].val); |
| |
| #ifdef FCGI_DUMP_ENV_VARS |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, |
| "proxy: FCGI: sending env var '%s' value '%s'", |
| elts[i].key, elts[i].val); |
| #endif |
| |
| if (vallen >> 7 == 0) { |
| envlen += 1; |
| } |
| else { |
| envlen += 4; |
| } |
| |
| envlen += vallen; |
| |
| if (envlen > FCGI_MAX_ENV_SIZE) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, |
| "proxy: FCGI: truncating environment to %d bytes and %d elements", |
| (int)bodylen, i); |
| break; |
| } |
| |
| bodylen = envlen; |
| } |
| |
| numenv = i; |
| |
| body = apr_pcalloc(r->pool, bodylen); |
| |
| itr = body; |
| |
| for (i = 0; i < numenv; ++i) { |
| apr_size_t keylen, vallen; |
| |
| if (! elts[i].key) { |
| continue; |
| } |
| |
| keylen = strlen(elts[i].key); |
| |
| if (keylen >> 7 == 0) { |
| itr[0] = keylen & 0xff; |
| itr += 1; |
| } |
| else { |
| itr[0] = ((keylen >> 24) & 0xff) | 0x80; |
| itr[1] = ((keylen >> 16) & 0xff); |
| itr[2] = ((keylen >> 8) & 0xff); |
| itr[3] = ((keylen) & 0xff); |
| itr += 4; |
| } |
| |
| vallen = strlen(elts[i].val); |
| |
| if (vallen >> 7 == 0) { |
| itr[0] = vallen & 0xff; |
| itr += 1; |
| } |
| else { |
| itr[0] = ((vallen >> 24) & 0xff) | 0x80; |
| itr[1] = ((vallen >> 16) & 0xff); |
| itr[2] = ((vallen >> 8) & 0xff); |
| itr[3] = ((vallen) & 0xff); |
| itr += 4; |
| } |
| |
| memcpy(itr, elts[i].key, keylen); |
| itr += keylen; |
| |
| memcpy(itr, elts[i].val, vallen); |
| itr += vallen; |
| } |
| |
| fill_in_header(&header, FCGI_PARAMS, request_id, bodylen, 0); |
| fcgi_header_to_array(&header, farray); |
| |
| vec[0].iov_base = farray; |
| vec[0].iov_len = sizeof(farray); |
| vec[1].iov_base = body; |
| vec[1].iov_len = bodylen; |
| |
| rv = send_data(conn, vec, 2, &len, 1); |
| if (rv) { |
| return rv; |
| } |
| |
| fill_in_header(&header, FCGI_PARAMS, request_id, 0, 0); |
| fcgi_header_to_array(&header, farray); |
| |
| vec[0].iov_base = farray; |
| vec[0].iov_len = sizeof(farray); |
| |
| return send_data(conn, vec, 1, &len, 1); |
| } |
| |
| enum { |
| HDR_STATE_READING_HEADERS, |
| HDR_STATE_GOT_CR, |
| HDR_STATE_GOT_CRLF, |
| HDR_STATE_GOT_CRLFCR, |
| HDR_STATE_GOT_LF, |
| HDR_STATE_DONE_WITH_HEADERS |
| }; |
| |
| /* Try to parse the script headers in the response from the back end fastcgi |
| * server. Assumes that the contents of READBUF have already been added to |
| * the end of OB. STATE holds the current header parsing state for this |
| * request. |
| * |
| * Returns -1 on error, 0 if it can't find the end of the headers, and 1 if |
| * it found the end of the headers and scans them successfully. */ |
| static int handle_headers(request_rec *r, |
| int *state, |
| char *readbuf, |
| apr_bucket_brigade *ob) |
| { |
| conn_rec *c = r->connection; |
| const char *itr = readbuf; |
| |
| while (*itr) { |
| if (*itr == '\r') { |
| switch (*state) { |
| case HDR_STATE_GOT_CRLF: |
| *state = HDR_STATE_GOT_CRLFCR; |
| break; |
| |
| default: |
| *state = HDR_STATE_GOT_CR; |
| break; |
| } |
| } |
| else if (*itr == '\n') { |
| switch (*state) { |
| case HDR_STATE_GOT_LF: |
| *state = HDR_STATE_DONE_WITH_HEADERS; |
| break; |
| |
| case HDR_STATE_GOT_CR: |
| *state = HDR_STATE_GOT_CRLF; |
| break; |
| |
| case HDR_STATE_GOT_CRLFCR: |
| *state = HDR_STATE_DONE_WITH_HEADERS; |
| break; |
| |
| default: |
| *state = HDR_STATE_GOT_LF; |
| break; |
| } |
| } |
| else { |
| *state = HDR_STATE_READING_HEADERS; |
| } |
| |
| if (*state == HDR_STATE_DONE_WITH_HEADERS) |
| break; |
| |
| ++itr; |
| } |
| |
| if (*state == HDR_STATE_DONE_WITH_HEADERS) { |
| int status = ap_scan_script_header_err_brigade(r, ob, NULL); |
| if (status != OK) { |
| apr_bucket *b; |
| |
| r->status = status; |
| |
| apr_brigade_cleanup(ob); |
| |
| b = apr_bucket_eos_create(c->bucket_alloc); |
| |
| APR_BRIGADE_INSERT_TAIL(ob, b); |
| |
| ap_pass_brigade(r->output_filters, ob); |
| |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: FCGI: Error parsing script headers"); |
| |
| return -1; |
| } |
| else { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void dump_header_to_log(request_rec *r, unsigned char fheader[], |
| apr_size_t length) |
| { |
| #ifdef FCGI_DUMP_HEADERS |
| apr_size_t posn = 0; |
| char asc_line[20]; |
| char hex_line[60]; |
| int i = 0; |
| |
| memset(asc_line, 0, sizeof(asc_line)); |
| memset(hex_line, 0, sizeof(hex_line)); |
| |
| while (posn < length) { |
| unsigned char c = fheader[posn]; |
| char hexval[3]; |
| |
| if (i >= 20) { |
| i = 0; |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "HEADER: %s %s", asc_line, hex_line); |
| |
| memset(asc_line, 0, sizeof(asc_line)); |
| memset(hex_line, 0, sizeof(hex_line)); |
| } |
| |
| if (isprint(c)) { |
| asc_line[i] = c; |
| } |
| else { |
| asc_line[i] = '.'; |
| } |
| |
| if ((c >> 4) >= 10) { |
| hex_line[i * 3] = 'a' + ((c >> 4) - 10); |
| } |
| else { |
| hex_line[i * 3] = '0' + (c >> 4); |
| } |
| |
| if ((c & 0x0F) >= 10) { |
| hex_line[i * 3 + 1] = 'a' + ((c & 0x0F) - 10); |
| } |
| else { |
| hex_line[i * 3 + 1] = '0' + (c & 0xF); |
| } |
| |
| hex_line[i * 3 + 2] = ' '; |
| |
| i++; |
| posn++; |
| } |
| |
| if (i != 1) { |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "HEADER: %s %s", |
| asc_line, hex_line); |
| } |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "HEADER: -EOH-"); |
| #endif |
| } |
| |
| static apr_status_t dispatch(proxy_conn_rec *conn, request_rec *r, |
| int request_id) |
| { |
| apr_bucket_brigade *ib, *ob; |
| int seen_end_of_headers = 0, done = 0; |
| apr_status_t rv = APR_SUCCESS; |
| conn_rec *c = r->connection; |
| struct iovec vec[2]; |
| fcgi_header header; |
| unsigned char farray[FCGI_HEADER_LEN]; |
| apr_pollfd_t pfd; |
| int header_state = HDR_STATE_READING_HEADERS; |
| apr_pool_t *setaside_pool; |
| |
| apr_pool_create(&setaside_pool, r->pool); |
| |
| pfd.desc_type = APR_POLL_SOCKET; |
| pfd.desc.s = conn->sock; |
| pfd.p = r->pool; |
| pfd.reqevents = APR_POLLIN | APR_POLLOUT; |
| |
| ib = apr_brigade_create(r->pool, c->bucket_alloc); |
| ob = apr_brigade_create(r->pool, c->bucket_alloc); |
| |
| while (! done) { |
| apr_interval_time_t timeout = conn->worker->timeout; |
| apr_size_t len; |
| int n; |
| |
| /* We need SOME kind of timeout here, or virtually anything will |
| * cause timeout errors. */ |
| if (! conn->worker->timeout_set) { |
| timeout = apr_time_from_sec(30); |
| } |
| |
| rv = apr_poll(&pfd, 1, &n, timeout); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| |
| if (pfd.rtnevents & APR_POLLOUT) { |
| char writebuf[AP_IOBUFSIZE]; |
| apr_size_t writebuflen; |
| int last_stdin = 0; |
| |
| rv = ap_get_brigade(r->input_filters, ib, |
| AP_MODE_READBYTES, APR_BLOCK_READ, |
| sizeof(writebuf)); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| |
| if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(ib))) { |
| last_stdin = 1; |
| } |
| |
| writebuflen = sizeof(writebuf); |
| |
| rv = apr_brigade_flatten(ib, writebuf, &writebuflen); |
| |
| apr_brigade_cleanup(ib); |
| |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| |
| fill_in_header(&header, FCGI_STDIN, request_id, |
| (apr_uint16_t) writebuflen, 0); |
| fcgi_header_to_array(&header, farray); |
| |
| vec[0].iov_base = farray; |
| vec[0].iov_len = sizeof(farray); |
| vec[1].iov_base = writebuf; |
| vec[1].iov_len = writebuflen; |
| |
| rv = send_data(conn, vec, 2, &len, 0); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| |
| if (last_stdin) { |
| pfd.reqevents = APR_POLLIN; /* Done with input data */ |
| |
| fill_in_header(&header, FCGI_STDIN, request_id, 0, 0); |
| fcgi_header_to_array(&header, farray); |
| |
| vec[0].iov_base = farray; |
| vec[0].iov_len = sizeof(farray); |
| |
| rv = send_data(conn, vec, 1, &len, 1); |
| } |
| } |
| |
| if (pfd.rtnevents & APR_POLLIN) { |
| /* readbuf has one byte on the end that is always 0, so it's |
| * able to work with a strstr when we search for the end of |
| * the headers, even if we fill the entire length in the recv. */ |
| char readbuf[AP_IOBUFSIZE + 1]; |
| apr_size_t readbuflen; |
| apr_size_t clen; |
| int rid, type; |
| apr_bucket *b; |
| char plen; |
| |
| memset(readbuf, 0, sizeof(readbuf)); |
| memset(farray, 0, sizeof(farray)); |
| |
| /* First, we grab the header... */ |
| readbuflen = FCGI_HEADER_LEN; |
| |
| rv = get_data(conn, (char *) farray, &readbuflen); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| |
| dump_header_to_log(r, farray, readbuflen); |
| |
| if (readbuflen != FCGI_HEADER_LEN) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: FCGI: Failed to read entire header " |
| "got %" APR_SIZE_T_FMT " wanted %d", |
| readbuflen, FCGI_HEADER_LEN); |
| rv = APR_EINVAL; |
| break; |
| } |
| |
| fcgi_header_from_array(&header, farray); |
| |
| if (header.version != FCGI_VERSION) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: FCGI: Got bogus version %d", |
| (int) header.version); |
| rv = APR_EINVAL; |
| break; |
| } |
| |
| type = header.type; |
| |
| rid = header.requestIdB1 << 8; |
| rid |= header.requestIdB0; |
| |
| if (rid != request_id) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: FCGI: Got bogus rid %d, expected %d", |
| rid, request_id); |
| rv = APR_EINVAL; |
| break; |
| } |
| |
| clen = header.contentLengthB1 << 8; |
| clen |= header.contentLengthB0; |
| |
| plen = header.paddingLength; |
| |
| recv_again: |
| if (clen > sizeof(readbuf) - 1) { |
| readbuflen = sizeof(readbuf) - 1; |
| } else { |
| readbuflen = clen; |
| } |
| |
| /* Now get the actual data. Yes it sucks to do this in a second |
| * recv call, this will eventually change when we move to real |
| * nonblocking recv calls. */ |
| if (readbuflen != 0) { |
| rv = get_data(conn, readbuf, &readbuflen); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| readbuf[readbuflen] = 0; |
| } |
| |
| switch (type) { |
| case FCGI_STDOUT: |
| if (clen != 0) { |
| b = apr_bucket_transient_create(readbuf, |
| readbuflen, |
| c->bucket_alloc); |
| |
| APR_BRIGADE_INSERT_TAIL(ob, b); |
| |
| if (! seen_end_of_headers) { |
| int st = handle_headers(r, &header_state, readbuf, ob); |
| |
| if (st == 1) { |
| seen_end_of_headers = 1; |
| |
| rv = ap_pass_brigade(r->output_filters, ob); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| |
| apr_brigade_cleanup(ob); |
| |
| apr_pool_clear(setaside_pool); |
| } |
| else if (st == -1) { |
| rv = APR_EINVAL; |
| break; |
| } |
| else { |
| /* We're still looking for the end of the |
| * headers, so this part of the data will need |
| * to persist. */ |
| apr_bucket_setaside(b, setaside_pool); |
| } |
| } else { |
| /* we've already passed along the headers, so now pass |
| * through the content. we could simply continue to |
| * setaside the content and not pass until we see the |
| * 0 content-length (below, where we append the EOS), |
| * but that could be a huge amount of data; so we pass |
| * along smaller chunks |
| */ |
| rv = ap_pass_brigade(r->output_filters, ob); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| apr_brigade_cleanup(ob); |
| } |
| |
| /* If we didn't read all the data go back and get the |
| * rest of it. */ |
| if (clen > readbuflen) { |
| clen -= readbuflen; |
| goto recv_again; |
| } |
| } else { |
| /* XXX what if we haven't seen end of the headers yet? */ |
| |
| b = apr_bucket_eos_create(c->bucket_alloc); |
| |
| APR_BRIGADE_INSERT_TAIL(ob, b); |
| |
| rv = ap_pass_brigade(r->output_filters, ob); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| |
| /* XXX Why don't we cleanup here? (logic from AJP) */ |
| } |
| break; |
| |
| case FCGI_STDERR: |
| /* TODO: Should probably clean up this logging a bit... */ |
| if (clen) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: FCGI: Got error '%s'", readbuf); |
| } |
| |
| if (clen > readbuflen) { |
| clen -= readbuflen; |
| goto recv_again; |
| } |
| break; |
| |
| case FCGI_END_REQUEST: |
| done = 1; |
| break; |
| |
| default: |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: FCGI: Got bogus record %d", type); |
| break; |
| } |
| |
| if (plen) { |
| readbuflen = plen; |
| |
| rv = get_data(conn, readbuf, &readbuflen); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| } |
| } |
| } |
| |
| apr_brigade_destroy(ib); |
| apr_brigade_destroy(ob); |
| |
| return rv; |
| } |
| |
| /* |
| * process the request and write the response. |
| */ |
| static int fcgi_do_request(apr_pool_t *p, request_rec *r, |
| proxy_conn_rec *conn, |
| conn_rec *origin, |
| proxy_dir_conf *conf, |
| apr_uri_t *uri, |
| char *url, char *server_portstr) |
| { |
| /* Request IDs are arbitrary numbers that we assign to a |
| * single request. This would allow multiplex/pipelinig of |
| * multiple requests to the same FastCGI connection, but |
| * we don't support that, and always use a value of '1' to |
| * keep things simple. */ |
| int request_id = 1; |
| apr_status_t rv; |
| |
| /* Step 1: Send FCGI_BEGIN_REQUEST */ |
| rv = send_begin_request(conn, request_id); |
| if (rv != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, |
| "proxy: FCGI: Failed Writing Request to %s:", |
| server_portstr); |
| conn->close = 1; |
| return HTTP_SERVICE_UNAVAILABLE; |
| } |
| |
| /* Step 2: Send Enviroment via FCGI_PARAMS */ |
| rv = send_environment(conn, r, request_id); |
| if (rv != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, |
| "proxy: FCGI: Failed writing Environment to %s:", |
| server_portstr); |
| conn->close = 1; |
| return HTTP_SERVICE_UNAVAILABLE; |
| } |
| |
| /* Step 3: Read records from the back end server and handle them. */ |
| rv = dispatch(conn, r, request_id); |
| if (rv != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, |
| "proxy: FCGI: Error dispatching request to %s:", |
| server_portstr); |
| conn->close = 1; |
| return HTTP_SERVICE_UNAVAILABLE; |
| } |
| |
| return OK; |
| } |
| |
| #define FCGI_SCHEME "FCGI" |
| |
| /* |
| * This handles fcgi:(dest) URLs |
| */ |
| static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker, |
| proxy_server_conf *conf, |
| char *url, const char *proxyname, |
| apr_port_t proxyport) |
| { |
| int status; |
| char server_portstr[32]; |
| conn_rec *origin = NULL; |
| proxy_conn_rec *backend = NULL; |
| |
| proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, |
| &proxy_module); |
| |
| apr_pool_t *p = r->pool; |
| |
| apr_uri_t *uri = apr_palloc(r->pool, sizeof(*uri)); |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "proxy: FCGI: url: %s proxyname: %s proxyport: %d", |
| url, proxyname, proxyport); |
| |
| if (strncasecmp(url, "fcgi://", 7) == 0) { |
| url += 5; |
| } |
| else { |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "proxy: FCGI: declining URL %s", url); |
| return DECLINED; |
| } |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "proxy: FCGI: serving URL %s", url); |
| |
| /* Create space for state information */ |
| if (! backend) { |
| status = ap_proxy_acquire_connection(FCGI_SCHEME, &backend, worker, |
| r->server); |
| if (status != OK) { |
| if (backend) { |
| backend->close = 1; |
| ap_proxy_release_connection(FCGI_SCHEME, backend, r->server); |
| } |
| return status; |
| } |
| } |
| |
| backend->is_ssl = 0; |
| |
| /* XXX Setting close to 0 is a great way to end up with |
| * timeouts at this point, since we lack good ways to manage the |
| * back end fastcgi processes. This should be revisited when we |
| * have a better story on that part of things. */ |
| |
| backend->close = 1; |
| |
| /* Step One: Determine Who To Connect To */ |
| status = ap_proxy_determine_connection(p, r, conf, worker, backend, |
| uri, &url, proxyname, proxyport, |
| server_portstr, |
| sizeof(server_portstr)); |
| if (status != OK) { |
| goto cleanup; |
| } |
| |
| /* Step Two: Make the Connection */ |
| if (ap_proxy_connect_backend(FCGI_SCHEME, backend, worker, r->server)) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: FCGI: failed to make connection to backend: %s", |
| backend->hostname); |
| status = HTTP_SERVICE_UNAVAILABLE; |
| goto cleanup; |
| } |
| |
| /* Step Three: Process the Request */ |
| status = fcgi_do_request(p, r, backend, origin, dconf, uri, url, |
| server_portstr); |
| |
| cleanup: |
| /* Do not close the socket */ |
| ap_proxy_release_connection(FCGI_SCHEME, backend, r->server); |
| return status; |
| } |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| proxy_hook_scheme_handler(proxy_fcgi_handler, NULL, NULL, APR_HOOK_FIRST); |
| proxy_hook_canon_handler(proxy_fcgi_canon, NULL, NULL, APR_HOOK_FIRST); |
| } |
| |
| module AP_MODULE_DECLARE_DATA proxy_fcgi_module = { |
| STANDARD20_MODULE_STUFF, |
| NULL, /* create per-directory config structure */ |
| NULL, /* merge per-directory config structures */ |
| NULL, /* create per-server config structure */ |
| NULL, /* merge per-server config structures */ |
| NULL, /* command apr_table_t */ |
| register_hooks /* register hooks */ |
| }; |