mod_proxy_fcgi.c revision 05aeb72ad4d3487297de9b6a27e9d61cbd7672a8
/* Copyright 2005 The Apache Software Foundation or its licensors, as
* applicable.
*
* Licensed 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
*
*
* 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"
/*
* 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_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->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];
}
/*
* 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.
*/
{
url += 5;
}
else {
return DECLINED;
}
"proxy: FCGI: canonicalising URL %s", url);
if (err) {
return HTTP_BAD_REQUEST;
}
/* if literal IPv6 address */
}
r->proxyreq);
return HTTP_BAD_REQUEST;
"proxy: FCGI: set r->filename to %s", r->filename);
"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.
*/
unsigned char type,
unsigned char padding_len)
{
}
/* Wrapper for apr_socket_sendv that handles updating the worker stats. */
int nvec,
int blocking)
{
int i, offset;
if (!blocking) {
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++) {
}
offset = 0;
while (to_write) {
apr_size_t n = 0;
if (rv != APR_SUCCESS) {
break;
}
if (n > 0) {
written += n;
break; /* short circuit out */
offset++;
} else {
break;
}
}
}
}
if (!blocking) {
return arv;
}
}
return rv;
}
/* Wrapper for apr_socket_recv that handles updating the worker stats. */
char *buffer,
{
if (rv == APR_SUCCESS) {
}
return rv;
}
{
unsigned char farray[FCGI_HEADER_LEN];
unsigned char abrb[FCGI_HEADER_LEN];
}
int request_id)
{
const apr_array_header_t *envarr;
const apr_table_entry_t *elts;
unsigned char farray[FCGI_HEADER_LEN];
int i, numenv;
ap_add_cgi_vars(r);
/* XXX are there any FastCGI specific env vars we need to send? */
* the TZ value specially. We could use that, but it would mean
* not to mention allocating a totally useless array in the first
* place, which would suck. */
continue;
}
if (keylen >> 7 == 0) {
envlen += 1;
}
else {
envlen += 4;
}
#ifdef FCGI_DUMP_ENV_VARS
"proxy: FCGI: sending env var '%s' value '%s'",
#endif
if (vallen >> 7 == 0) {
envlen += 1;
}
else {
envlen += 4;
}
if (envlen > FCGI_MAX_ENV_SIZE) {
"proxy: FCGI: truncating environment to %d bytes and %d elements",
(int)bodylen, i);
break;
}
}
numenv = i;
for (i = 0; i < numenv; ++i) {
continue;
}
if (keylen >> 7 == 0) {
itr += 1;
}
else {
itr += 4;
}
if (vallen >> 7 == 0) {
itr += 1;
}
else {
itr += 4;
}
}
if (rv) {
return rv;
}
}
enum {
};
/* 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,
{
conn_rec *c = r->connection;
while (*itr) {
if (*itr == '\r') {
switch (*state) {
case HDR_STATE_GOT_CRLF:
break;
default:
break;
}
}
else if (*itr == '\n') {
switch (*state) {
case HDR_STATE_GOT_LF:
break;
case HDR_STATE_GOT_CR:
break;
case HDR_STATE_GOT_CRLFCR:
break;
default:
break;
}
}
else {
}
if (*state == HDR_STATE_DONE_WITH_HEADERS)
break;
++itr;
}
if (*state == HDR_STATE_DONE_WITH_HEADERS) {
apr_bucket *b;
b = apr_bucket_eos_create(c->bucket_alloc);
"proxy: FCGI: Error parsing script headers");
return -1;
}
else {
return 1;
}
}
return 0;
}
{
#ifdef FCGI_DUMP_HEADERS
apr_size_t posn = 0;
char asc_line[20];
char hex_line[60];
int i = 0;
char hexval[3];
if (i >= 20) {
i = 0;
}
if (isprint(c)) {
asc_line[i] = c;
}
else {
asc_line[i] = '.';
}
if ((c >> 4) >= 10) {
}
else {
}
if ((c & 0x0F) >= 10) {
}
else {
}
i++;
posn++;
}
if (i != 1) {
}
#endif
}
int request_id)
{
int seen_end_of_headers = 0, done = 0;
conn_rec *c = r->connection;
unsigned char farray[FCGI_HEADER_LEN];
while (! done) {
int n;
/* We need SOME kind of timeout here, or virtually anything will
* cause timeout errors. */
}
if (rv != APR_SUCCESS) {
break;
}
char writebuf[AP_IOBUFSIZE];
int last_stdin = 0;
sizeof(writebuf));
if (rv != APR_SUCCESS) {
break;
}
last_stdin = 1;
}
writebuflen = sizeof(writebuf);
if (rv != APR_SUCCESS) {
break;
}
(apr_uint16_t) writebuflen, 0);
if (rv != APR_SUCCESS) {
break;
}
if (last_stdin) {
}
}
/* 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. */
apr_bucket *b;
char plen;
/* First, we grab the header... */
if (rv != APR_SUCCESS) {
break;
}
if (readbuflen != FCGI_HEADER_LEN) {
"proxy: FCGI: Failed to read entire header "
"got %d wanted %d",
rv = APR_EINVAL;
break;
}
"proxy: FCGI: Got bogus version %d",
rv = APR_EINVAL;
break;
}
if (rid != request_id) {
"proxy: FCGI: Got bogus rid %d, expected %d",
rid, request_id);
rv = APR_EINVAL;
break;
}
} 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) {
if (rv != APR_SUCCESS) {
break;
}
readbuf[readbuflen] = 0;
}
switch (type) {
case FCGI_STDOUT:
if (clen != 0) {
c->bucket_alloc);
if (! seen_end_of_headers) {
if (st == 1) {
seen_end_of_headers = 1;
if (rv != APR_SUCCESS) {
break;
}
}
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. */
}
} 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
*/
if (rv != APR_SUCCESS) {
break;
}
}
/* 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);
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) {
"proxy: FCGI: Got error '%s'", readbuf);
}
if (clen > readbuflen) {
clen -= readbuflen;
goto recv_again;
}
break;
case FCGI_END_REQUEST:
done = 1;
break;
default:
"proxy: FCGI: Got bogus record %d", type);
break;
}
if (plen) {
readbuflen = plen;
if (rv != APR_SUCCESS) {
break;
}
}
}
}
return rv;
}
/*
* process the request and write the response.
*/
char *url, char *server_portstr)
{
/* Request IDs are arbitrary numbers that we assign to a
* 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;
/* Step 1: Send FCGI_BEGIN_REQUEST */
if (rv != APR_SUCCESS) {
"proxy: FCGI: Failed Writing Request to %s:",
return HTTP_SERVICE_UNAVAILABLE;
}
/* Step 2: Send Enviroment via FCGI_PARAMS */
if (rv != APR_SUCCESS) {
"proxy: FCGI: Failed writing Environment to %s:",
return HTTP_SERVICE_UNAVAILABLE;
}
/* Step 3: Read records from the back end server and handle them. */
if (rv != APR_SUCCESS) {
"proxy: FCGI: Error dispatching request to %s:",
return HTTP_SERVICE_UNAVAILABLE;
}
return OK;
}
#define FCGI_SCHEME "FCGI"
/*
* This handles fcgi:(dest) URLs
*/
{
int status;
char server_portstr[32];
&proxy_module);
apr_pool_t *p = r->pool;
"proxy: FCGI: url: %s proxyname: %s proxyport: %d",
url += 5;
}
else {
"proxy: FCGI: declining URL %s", url);
return DECLINED;
}
"proxy: FCGI: serving URL %s", url);
/* Create space for state information */
if (! backend) {
r->server);
if (backend) {
}
return status;
}
}
/* XXX Setting close_on_recycle 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. */
/* Step One: Determine Who To Connect To */
sizeof(server_portstr));
goto cleanup;
}
/* Step Two: Make the Connection */
"proxy: FCGI: failed to make connection to backend: %s",
goto cleanup;
}
/* Step Three: Process the Request */
/* Do not close the socket */
return status;
}
static void register_hooks(apr_pool_t *p)
{
}
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 */
};