the upcoming 2.0 version
https://redmine.lighttpd.net/projects/lighttpd2
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
894 lines
28 KiB
894 lines
28 KiB
#include "fastcgi_stream.h" |
|
#include <lighttpd/plugin_core.h> |
|
#include <lighttpd/stream_http_response.h> |
|
|
|
|
|
/**********************************************************************************/ |
|
/* fastcgi types */ |
|
|
|
#define FCGI_VERSION_1 1 |
|
#define FCGI_HEADER_LEN 8 |
|
|
|
enum FCGI_Type { |
|
FCGI_BEGIN_REQUEST = 1, /* web server -> backend */ |
|
FCGI_ABORT_REQUEST = 2, /* web server -> backend */ |
|
FCGI_END_REQUEST = 3, /* backend -> web server (status) */ |
|
FCGI_PARAMS = 4, /* web server -> backend (stream name-value pairs) */ |
|
FCGI_STDIN = 5, /* web server -> backend (stream request body) */ |
|
FCGI_STDOUT = 6, /* backend -> web server (stream response body) */ |
|
FCGI_STDERR = 7, /* backend -> web server (stream error messages) */ |
|
FCGI_DATA = 8, /* web server -> backend (stream additional data) */ |
|
FCGI_GET_VALUES = 9, /* web server -> backend (request names-value pairs with empty values) */ |
|
FCGI_GET_VALUES_RESULT = 10,/* backend -> web server (response name-value pairs) */ |
|
FCGI_UNKNOWN_TYPE = 11 |
|
}; |
|
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) |
|
|
|
enum FCGI_Flags { |
|
FCGI_KEEP_CONN = 1 |
|
}; |
|
|
|
enum FCGI_Role { |
|
FCGI_RESPONDER = 1, |
|
FCGI_AUTHORIZER = 2, |
|
FCGI_FILTER = 3 |
|
}; |
|
|
|
enum FCGI_ProtocolStatus { |
|
FCGI_REQUEST_COMPLETE = 0, |
|
FCGI_CANT_MPX_CONN = 1, |
|
FCGI_OVERLOADED = 2, |
|
FCGI_UNKNOWN_ROLE = 3 |
|
}; |
|
|
|
/**********************************************************************************/ |
|
|
|
typedef struct liFastCGIBackendContext liFastCGIBackendContext; |
|
typedef struct liFastCGIBackendConnection_p liFastCGIBackendConnection_p; |
|
typedef struct liFastCGIBackendPool_p liFastCGIBackendPool_p; |
|
|
|
struct liFastCGIBackendContext { |
|
gint refcount; |
|
liFastCGIBackendPool_p *pool; |
|
liBackendConnection *subcon; |
|
gboolean is_active; /* if is_active == FALSE iostream->io_watcher must not have a ref on the loop */ |
|
|
|
liWorker *wrk; |
|
liIOStream *iostream; |
|
|
|
liStream fcgi_out, fcgi_in; |
|
|
|
/* for now: no multiplexing, at most one connection */ |
|
liFastCGIBackendConnection_p *currentcon; |
|
gboolean stdin_closed, stdout_closed, stderr_closed, request_done; |
|
|
|
/* current record */ |
|
guint8 version; |
|
guint8 type; |
|
guint16 requestID; |
|
guint16 contentLength; |
|
guint8 paddingLength; |
|
gint remainingContent, remainingPadding; |
|
}; |
|
|
|
struct liFastCGIBackendConnection_p { |
|
liFastCGIBackendConnection public; |
|
liFastCGIBackendContext *ctx; |
|
|
|
liVRequest *vr; |
|
}; |
|
|
|
struct liFastCGIBackendPool_p { |
|
liFastCGIBackendPool public; |
|
const liFastCGIBackendCallbacks *callbacks; |
|
|
|
liBackendConfig config; |
|
}; |
|
|
|
/* debug */ |
|
#if 0 |
|
#define STRINGIFY(x) #x |
|
#define _STRINGIFY(x) STRINGIFY(x) |
|
#define fcgi_debug(...) fprintf(stderr, "fastcgi-stream.c:" _STRINGIFY(__LINE__) ": " __VA_ARGS__) |
|
#define FCGI_DEBUG |
|
|
|
static const gchar* fcgi_type_string(enum FCGI_Type type) { |
|
switch (type) { |
|
case FCGI_BEGIN_REQUEST: |
|
return "begin_request"; |
|
case FCGI_ABORT_REQUEST: |
|
return "abort_request"; |
|
case FCGI_END_REQUEST: |
|
return "end_request"; |
|
case FCGI_PARAMS: |
|
return "params"; |
|
case FCGI_STDIN: |
|
return "stdin"; |
|
case FCGI_STDOUT: |
|
return "stdout"; |
|
case FCGI_STDERR: |
|
return "stderr"; |
|
case FCGI_DATA: |
|
return "data"; |
|
case FCGI_GET_VALUES: |
|
return "get_values"; |
|
case FCGI_GET_VALUES_RESULT: |
|
return "get_values_result"; |
|
default: |
|
return "unknown_type"; |
|
} |
|
} |
|
#else |
|
#define fcgi_debug(...) do { } while (0) |
|
#endif |
|
|
|
static void fastcgi_stream_out(liStream *stream, liStreamEvent event); |
|
static void fastcgi_stream_in(liStream *stream, liStreamEvent event); |
|
|
|
static void backend_detach_thread(liBackendPool *bpool, liWorker *wrk, liBackendConnection *bcon) { |
|
liFastCGIBackendContext *ctx = bcon->data; |
|
UNUSED(bpool); |
|
|
|
assert(wrk == ctx->wrk); |
|
ctx->wrk = NULL; |
|
|
|
li_stream_disconnect(&ctx->fcgi_out); |
|
li_stream_disconnect_dest(&ctx->fcgi_in); |
|
|
|
assert(2 == ctx->fcgi_in.refcount); |
|
assert(2 == ctx->fcgi_out.refcount); |
|
|
|
li_iostream_detach(ctx->iostream); |
|
li_stream_detach(&ctx->fcgi_out); |
|
li_stream_detach(&ctx->fcgi_in); |
|
} |
|
|
|
static void backend_attach_thread(liBackendPool *bpool, liWorker *wrk, liBackendConnection *bcon) { |
|
liFastCGIBackendContext *ctx = bcon->data; |
|
UNUSED(bpool); |
|
|
|
ctx->wrk = wrk; |
|
li_iostream_attach(ctx->iostream, wrk); |
|
li_stream_attach(&ctx->fcgi_out, &wrk->loop); |
|
li_stream_attach(&ctx->fcgi_in, &wrk->loop); |
|
} |
|
|
|
static void backend_new(liBackendPool *bpool, liWorker *wrk, liBackendConnection *bcon) { |
|
liFastCGIBackendPool_p *pool = LI_CONTAINER_OF(bpool->config, liFastCGIBackendPool_p, config); |
|
liFastCGIBackendContext *ctx = g_slice_new0(liFastCGIBackendContext); |
|
|
|
fcgi_debug("backend_new\n"); |
|
|
|
ctx->refcount = 3; /* backend_close, fcgi_out, fcgi_in */ |
|
ctx->pool = pool; |
|
ctx->wrk = wrk; |
|
ctx->iostream = li_iostream_new(wrk, li_event_io_fd(&bcon->watcher), li_stream_simple_socket_io_cb, NULL); |
|
li_event_set_keep_loop_alive(&ctx->iostream->io_watcher, FALSE); |
|
|
|
li_stream_init(&ctx->fcgi_out, &wrk->loop, fastcgi_stream_out); |
|
li_stream_init(&ctx->fcgi_in, &wrk->loop, fastcgi_stream_in); |
|
|
|
li_stream_connect(&ctx->iostream->stream_in, &ctx->fcgi_in); |
|
li_stream_connect(&ctx->fcgi_out, &ctx->iostream->stream_out); |
|
|
|
ctx->subcon = bcon; |
|
bcon->data = ctx; |
|
} |
|
|
|
static void backend_ctx_unref(liFastCGIBackendContext *ctx) { |
|
assert(g_atomic_int_get(&ctx->refcount) > 0); |
|
if (g_atomic_int_dec_and_test(&ctx->refcount)) { |
|
g_slice_free(liFastCGIBackendContext, ctx); |
|
} |
|
} |
|
|
|
static void backend_close(liBackendPool *bpool, liWorker *wrk, liBackendConnection *bcon) { |
|
liFastCGIBackendContext *ctx = bcon->data; |
|
UNUSED(bpool); |
|
|
|
assert(NULL != ctx->pool); |
|
assert(wrk == ctx->wrk); |
|
|
|
ctx->pool = NULL; |
|
|
|
assert(NULL == ctx->currentcon); |
|
|
|
fcgi_debug("backend_close\n"); |
|
|
|
if (NULL != ctx->iostream) { |
|
int fd; |
|
li_stream_simple_socket_close(ctx->iostream, FALSE); |
|
fd = li_iostream_reset(ctx->iostream); |
|
assert(-1 == fd); |
|
ctx->iostream = NULL; |
|
} |
|
li_stream_reset(&ctx->fcgi_in); |
|
li_stream_reset(&ctx->fcgi_out); |
|
|
|
li_stream_release(&ctx->fcgi_in); |
|
li_stream_release(&ctx->fcgi_out); |
|
|
|
backend_ctx_unref(ctx); |
|
|
|
li_event_io_set_fd(&bcon->watcher, -1); |
|
} |
|
|
|
static void backend_free(liBackendPool *bpool) { |
|
liFastCGIBackendPool_p *pool = LI_CONTAINER_OF(bpool->config, liFastCGIBackendPool_p, config); |
|
|
|
li_sockaddr_clear(&pool->config.sock_addr); |
|
|
|
g_slice_free(liFastCGIBackendPool_p, pool); |
|
} |
|
|
|
static liBackendCallbacks backend_cbs = { |
|
backend_detach_thread, |
|
backend_attach_thread, |
|
backend_new, |
|
backend_close, |
|
backend_free |
|
}; |
|
|
|
static void fastcgi_check_put(liFastCGIBackendContext *ctx) { |
|
/* wait for li_fastcgi_backend_put() */ |
|
if (NULL != ctx->currentcon) return; |
|
/* already inactive */ |
|
if (!ctx->is_active) return; |
|
/* wait for vrequest streams to disconnect */ |
|
if (NULL != ctx->fcgi_in.dest || NULL != ctx->fcgi_out.source) return; |
|
|
|
li_stream_disconnect(&ctx->fcgi_out); |
|
li_stream_disconnect_dest(&ctx->fcgi_in); |
|
|
|
ctx->is_active = FALSE; |
|
|
|
li_stream_set_cqlimit(NULL, &ctx->fcgi_in, NULL); |
|
li_stream_set_cqlimit(&ctx->fcgi_out, NULL, NULL); |
|
|
|
if (NULL != ctx->iostream) { |
|
li_event_io_set_fd(&ctx->subcon->watcher, li_event_io_fd(&ctx->iostream->io_watcher)); |
|
li_event_set_keep_loop_alive(&ctx->iostream->io_watcher, FALSE); |
|
assert(NULL == ctx->iostream->stream_in.out->limit); |
|
assert(NULL == ctx->iostream->stream_out.out->limit); |
|
} else { |
|
li_event_io_set_fd(&ctx->subcon->watcher, -1); |
|
} |
|
|
|
assert(NULL == ctx->fcgi_in.out->limit); |
|
assert(NULL == ctx->fcgi_out.out->limit); |
|
|
|
fcgi_debug("li_backend_put\n"); |
|
li_backend_put(ctx->wrk, ctx->pool->public.subpool, ctx->subcon, TRUE); /* disable keep-alive for now */ |
|
} |
|
|
|
/* destroys ctx */ |
|
static void fastcgi_reset(liFastCGIBackendContext *ctx) { |
|
if (NULL == ctx->pool) return; |
|
fcgi_debug("fastcgi_reset\n"); |
|
|
|
if (!ctx->is_active) { |
|
li_backend_connection_closed(ctx->pool->public.subpool, ctx->subcon); |
|
} else { |
|
int fd; |
|
const liFastCGIBackendCallbacks *callbacks = ctx->pool->callbacks; |
|
liFastCGIBackendConnection_p *currentcon = ctx->currentcon; |
|
liIOStream *iostream = ctx->iostream; |
|
|
|
if (NULL == iostream) return; |
|
|
|
ctx->request_done = TRUE; |
|
ctx->iostream = NULL; |
|
li_stream_simple_socket_close(iostream, TRUE); |
|
fd = li_iostream_reset(iostream); |
|
assert(-1 == fd); |
|
|
|
li_stream_disconnect(&ctx->fcgi_out); |
|
li_stream_disconnect_dest(&ctx->fcgi_in); |
|
|
|
if (NULL != currentcon) { |
|
callbacks->reset_cb(currentcon->vr, &ctx->pool->public, ¤tcon->public); |
|
} |
|
} |
|
} |
|
|
|
/**********************************************************************************/ |
|
/* fastcgi stream send helper */ |
|
|
|
static const gchar __padding[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
|
|
static void append_padding(GByteArray *a, guint8 padlen) { |
|
g_byte_array_append(a, (guint8*) __padding, padlen); |
|
} |
|
|
|
static void l_byte_array_append_c(GByteArray *a, char c) { |
|
g_byte_array_append(a, (guint8*) &c, 1); |
|
} |
|
|
|
static gboolean _append_ba_len(GByteArray *a, size_t len) { |
|
if (len > G_MAXINT32) return FALSE; |
|
if (len > 127) { |
|
guint32 i = htonl(len | (1 << 31)); |
|
g_byte_array_append(a, (const guint8*) &i, sizeof(i)); |
|
} else { |
|
l_byte_array_append_c(a, (guint8) len); |
|
} |
|
return TRUE; |
|
} |
|
|
|
static gboolean append_key_value_pair(GByteArray *a, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) { |
|
if (!_append_ba_len(a, keylen) || !_append_ba_len(a, valuelen)) return FALSE; |
|
g_byte_array_append(a, (const guint8*) key, keylen); |
|
g_byte_array_append(a, (const guint8*) val, valuelen); |
|
return TRUE; |
|
} |
|
|
|
/* returns padding length */ |
|
static guint8 stream_build_fcgi_record(GByteArray *buf, guint8 type, guint16 requestid, guint16 datalen) { |
|
guint16 w; |
|
guint8 padlen = (8 - (datalen & 0x7)) % 8; /* padding must be < 8 */ |
|
|
|
g_byte_array_set_size(buf, FCGI_HEADER_LEN); |
|
g_byte_array_set_size(buf, 0); |
|
|
|
l_byte_array_append_c(buf, FCGI_VERSION_1); |
|
l_byte_array_append_c(buf, type); |
|
w = htons(requestid); |
|
g_byte_array_append(buf, (const guint8*) &w, sizeof(w)); |
|
w = htons(datalen); |
|
g_byte_array_append(buf, (const guint8*) &w, sizeof(w)); |
|
l_byte_array_append_c(buf, padlen); |
|
l_byte_array_append_c(buf, 0); |
|
return padlen; |
|
} |
|
|
|
/* returns padding length */ |
|
static guint8 stream_send_fcgi_record(liChunkQueue *out, guint8 type, guint16 requestid, guint16 datalen) { |
|
GByteArray *record = g_byte_array_sized_new(FCGI_HEADER_LEN); |
|
guint8 padlen = stream_build_fcgi_record(record, type, requestid, datalen); |
|
li_chunkqueue_append_bytearr(out, record); |
|
return padlen; |
|
} |
|
|
|
static void stream_send_data(liChunkQueue *out, guint8 type, guint16 requestid, const gchar *data, size_t datalen) { |
|
while (datalen > 0) { |
|
guint16 tosend = (datalen > G_MAXUINT16) ? G_MAXUINT16 : datalen; |
|
guint8 padlen = stream_send_fcgi_record(out, type, requestid, tosend); |
|
GByteArray *tmpa = g_byte_array_sized_new(tosend + padlen); |
|
g_byte_array_append(tmpa, (const guint8*) data, tosend); |
|
append_padding(tmpa, padlen); |
|
li_chunkqueue_append_bytearr(out, tmpa); |
|
data += tosend; |
|
datalen -= tosend; |
|
} |
|
} |
|
|
|
/* kills the data */ |
|
static void stream_send_bytearr(liChunkQueue *out, guint8 type, guint16 requestid, GByteArray *data) { |
|
if (data->len > G_MAXUINT16) { |
|
stream_send_data(out, type, requestid, (const gchar*) data->data, data->len); |
|
g_byte_array_free(data, TRUE); |
|
} else { |
|
guint8 padlen = stream_send_fcgi_record(out, type, requestid, data->len); |
|
append_padding(data, padlen); |
|
li_chunkqueue_append_bytearr(out, data); |
|
} |
|
} |
|
|
|
static void stream_send_chunks(liChunkQueue *out, guint8 type, guint16 requestid, liChunkQueue *in) { |
|
while (in->length > 0) { |
|
guint16 tosend = (in->length > G_MAXUINT16) ? G_MAXUINT16 : in->length; |
|
guint8 padlen = stream_send_fcgi_record(out, type, requestid, tosend); |
|
li_chunkqueue_steal_len(out, in, tosend); |
|
li_chunkqueue_append_mem(out, __padding, padlen); |
|
} |
|
} |
|
|
|
static void stream_send_begin(liChunkQueue *out, guint16 requestid) { |
|
GByteArray *buf = g_byte_array_sized_new(16); |
|
guint16 w; |
|
|
|
assert(1 == requestid); |
|
|
|
stream_build_fcgi_record(buf, FCGI_BEGIN_REQUEST, requestid, 8); |
|
w = htons(FCGI_RESPONDER); |
|
g_byte_array_append(buf, (const guint8*) &w, sizeof(w)); |
|
l_byte_array_append_c(buf, 0 /* FCGI_KEEP_CONN */); /* disabled keep-alive for now */ |
|
append_padding(buf, 5); |
|
li_chunkqueue_append_bytearr(out, buf); |
|
} |
|
|
|
/* end fastcgi stream send helpers */ |
|
/**********************************************************************************/ |
|
|
|
/**********************************************************************************/ |
|
/* fastcgi environment build helpers */ |
|
static void fastcgi_env_add(GByteArray *buf, liEnvironmentDup *envdup, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) { |
|
GString *sval; |
|
|
|
if (NULL != (sval = li_environment_dup_pop(envdup, key, keylen))) { |
|
append_key_value_pair(buf, key, keylen, GSTR_LEN(sval)); |
|
} else { |
|
append_key_value_pair(buf, key, keylen, val, valuelen); |
|
} |
|
} |
|
|
|
static void fastcgi_env_create(liVRequest *vr, liEnvironmentDup *envdup, GByteArray* buf) { |
|
liConInfo *coninfo = vr->coninfo; |
|
GString *tmp = vr->wrk->tmp_str; |
|
|
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SERVER_SOFTWARE"), GSTR_LEN(CORE_OPTIONPTR(LI_CORE_OPTION_SERVER_TAG).string)); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SERVER_NAME"), GSTR_LEN(vr->request.uri.host)); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")); |
|
{ |
|
guint port = 0; |
|
switch (coninfo->local_addr.addr->plain.sa_family) { |
|
case AF_INET: port = coninfo->local_addr.addr->ipv4.sin_port; break; |
|
#ifdef HAVE_IPV6 |
|
case AF_INET6: port = coninfo->local_addr.addr->ipv6.sin6_port; break; |
|
#endif |
|
} |
|
if (port) { |
|
g_string_printf(tmp, "%u", htons(port)); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SERVER_PORT"), GSTR_LEN(tmp)); |
|
} |
|
} |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SERVER_ADDR"), GSTR_LEN(coninfo->local_addr_str)); |
|
|
|
{ |
|
guint port = 0; |
|
switch (coninfo->remote_addr.addr->plain.sa_family) { |
|
case AF_INET: port = coninfo->remote_addr.addr->ipv4.sin_port; break; |
|
#ifdef HAVE_IPV6 |
|
case AF_INET6: port = coninfo->remote_addr.addr->ipv6.sin6_port; break; |
|
#endif |
|
} |
|
if (port) { |
|
g_string_printf(tmp, "%u", htons(port)); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("REMOTE_PORT"), GSTR_LEN(tmp)); |
|
} |
|
} |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("REMOTE_ADDR"), GSTR_LEN(coninfo->remote_addr_str)); |
|
|
|
if (vr->request.content_length > 0) { |
|
g_string_printf(tmp, "%" LI_GOFFSET_MODIFIER "i", vr->request.content_length); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("CONTENT_LENGTH"), GSTR_LEN(tmp)); |
|
} |
|
|
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SCRIPT_NAME"), GSTR_LEN(vr->request.uri.path)); |
|
|
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("PATH_INFO"), GSTR_LEN(vr->physical.pathinfo)); |
|
if (vr->physical.pathinfo->len) { |
|
g_string_truncate(tmp, 0); |
|
g_string_append_len(tmp, GSTR_LEN(vr->physical.doc_root)); /* TODO: perhaps an option for alternative doc-root? */ |
|
g_string_append_len(tmp, GSTR_LEN(vr->physical.pathinfo)); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("PATH_TRANSLATED"), GSTR_LEN(tmp)); |
|
} |
|
|
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SCRIPT_FILENAME"), GSTR_LEN(vr->physical.path)); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("DOCUMENT_ROOT"), GSTR_LEN(vr->physical.doc_root)); |
|
|
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("REQUEST_URI"), GSTR_LEN(vr->request.uri.raw_orig_path)); |
|
if (!g_string_equal(vr->request.uri.raw_orig_path, vr->request.uri.raw_path)) { |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("REDIRECT_URI"), GSTR_LEN(vr->request.uri.raw_path)); |
|
} |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("QUERY_STRING"), GSTR_LEN(vr->request.uri.query)); |
|
|
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("REQUEST_METHOD"), GSTR_LEN(vr->request.http_method_str)); |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); /* if php is compiled with --force-redirect */ |
|
switch (vr->request.http_version) { |
|
case LI_HTTP_VERSION_1_1: |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SERVER_PROTOCOL"), CONST_STR_LEN("HTTP/1.1")); |
|
break; |
|
case LI_HTTP_VERSION_1_0: |
|
default: |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("SERVER_PROTOCOL"), CONST_STR_LEN("HTTP/1.0")); |
|
break; |
|
} |
|
|
|
if (coninfo->is_ssl) { |
|
fastcgi_env_add(buf, envdup, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on")); |
|
} |
|
} |
|
|
|
static void fix_header_name(GString *str) { |
|
guint i, len = str->len; |
|
gchar *s = str->str; |
|
for (i = 0; i < len; i++) { |
|
if (g_ascii_isalpha(s[i])) { |
|
s[i] = g_ascii_toupper(s[i]); |
|
} else if (!g_ascii_isdigit(s[i])) { |
|
s[i] = '_'; |
|
} |
|
} |
|
} |
|
|
|
static void fastcgi_send_env(liVRequest *vr, liChunkQueue *out, int requestid) { |
|
GByteArray *buf = g_byte_array_sized_new(0); |
|
liEnvironmentDup *envdup; |
|
|
|
envdup = li_environment_make_dup(&vr->env); |
|
fastcgi_env_create(vr, envdup, buf); |
|
|
|
{ |
|
GList *i; |
|
GString *tmp = vr->wrk->tmp_str; |
|
|
|
for (i = vr->request.headers->entries.head; NULL != i; i = i->next) { |
|
liHttpHeader *h = (liHttpHeader*) i->data; |
|
const GString hkey = li_const_gstring(h->data->str, h->keylen); |
|
g_string_truncate(tmp, 0); |
|
if (!li_strncase_equal(&hkey, CONST_STR_LEN("CONTENT-TYPE"))) { |
|
g_string_append_len(tmp, CONST_STR_LEN("HTTP_")); |
|
} |
|
g_string_append_len(tmp, h->data->str, h->keylen); |
|
fix_header_name(tmp); |
|
|
|
fastcgi_env_add(buf, envdup, GSTR_LEN(tmp), h->data->str + h->keylen+2, h->data->len - (h->keylen+2)); |
|
} |
|
} |
|
|
|
{ |
|
GHashTableIter i; |
|
gpointer key, val; |
|
|
|
g_hash_table_iter_init(&i, envdup->table); |
|
while (g_hash_table_iter_next(&i, &key, &val)) { |
|
append_key_value_pair(buf, GSTR_LEN((GString*) key), GSTR_LEN((GString*) val)); |
|
} |
|
} |
|
|
|
li_environment_dup_free(envdup); |
|
|
|
if (buf->len > 0) stream_send_bytearr(out, FCGI_PARAMS, requestid, buf); |
|
stream_send_fcgi_record(out, FCGI_PARAMS, requestid, 0); |
|
} |
|
|
|
/* end fastcgi environment build helpers */ |
|
/**********************************************************************************/ |
|
|
|
|
|
/* request body -> fastcgi */ |
|
static void fastcgi_stream_out(liStream *stream, liStreamEvent event) { |
|
liFastCGIBackendContext *ctx = LI_CONTAINER_OF(stream, liFastCGIBackendContext, fcgi_out); |
|
fcgi_debug("fastcgi_stream_out event: %s\n", li_stream_event_string(event)); |
|
switch (event) { |
|
case LI_STREAM_NEW_DATA: |
|
if (NULL == stream->source) return; |
|
if (NULL == stream->dest || ctx->stdin_closed) { |
|
li_chunkqueue_skip_all(stream->source->out); |
|
return; |
|
} |
|
stream_send_chunks(stream->out, FCGI_STDIN, 1, stream->source->out); |
|
if (stream->source->out->is_closed && !ctx->stdin_closed) { |
|
fcgi_debug("fcgi_out: closing stdin\n"); |
|
ctx->stdin_closed = TRUE; |
|
stream_send_fcgi_record(stream->out, FCGI_STDIN, 1, 0); |
|
li_stream_disconnect(stream); |
|
} |
|
li_stream_notify(stream); |
|
break; |
|
case LI_STREAM_CONNECTED_SOURCE: |
|
/* support Connection: Upgrade by reopening stdin. not standard compliant, |
|
* but the backend asked for it :) */ |
|
ctx->stdin_closed = FALSE; |
|
break; |
|
case LI_STREAM_DISCONNECTED_SOURCE: |
|
if (!ctx->stdin_closed) { |
|
fcgi_debug("fcgi_out: lost request before request body was sent to FastCGI\n"); |
|
fastcgi_reset(ctx); |
|
} else { |
|
fastcgi_check_put(ctx); |
|
} |
|
break; |
|
case LI_STREAM_DISCONNECTED_DEST: |
|
if (stream->out->length > 0) { |
|
fcgi_debug("fcgi_out: lost iostream\n"); |
|
li_chunkqueue_skip_all(stream->out); |
|
} |
|
break; |
|
case LI_STREAM_DESTROY: |
|
backend_ctx_unref(ctx); |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
static void fastcgi_decode(liFastCGIBackendContext *ctx) { |
|
liChunkQueue *in = ctx->iostream->stream_in.out; |
|
liWorker *wrk = li_worker_from_iostream(ctx->iostream); |
|
|
|
while (NULL != ctx->iostream && 0 < in->length) { |
|
gboolean newdata = FALSE; |
|
|
|
if (0 == ctx->remainingContent && 0 == ctx->remainingPadding) { |
|
unsigned char header[FCGI_HEADER_LEN]; |
|
if (in->length < FCGI_HEADER_LEN) break; |
|
|
|
/* reading memory buffers can't fail */ |
|
if (!li_chunkqueue_extract_to_memory(in, FCGI_HEADER_LEN, header, NULL)) abort(); |
|
li_chunkqueue_skip(in, FCGI_HEADER_LEN); |
|
|
|
ctx->version = header[0]; |
|
ctx->type = header[1]; |
|
ctx->requestID = (header[2] << 8) + header[3]; |
|
ctx->contentLength = (header[4] << 8) + header[5]; |
|
ctx->paddingLength = header[6]; |
|
|
|
ctx->remainingContent = ctx->contentLength; |
|
ctx->remainingPadding = ctx->paddingLength; |
|
|
|
if (FCGI_VERSION_1 != ctx->version) { |
|
ERROR(wrk->srv, "(%s) Unknown fastcgi protocol version %i", |
|
li_sockaddr_to_string(ctx->pool->config.sock_addr, wrk->tmp_str, TRUE)->str, |
|
(gint) ctx->version); |
|
fastcgi_reset(ctx); |
|
return; |
|
} |
|
newdata = TRUE; |
|
fcgi_debug("fastcgi packet type %s (%i), payload %i\n", fcgi_type_string(ctx->type), ctx->type, (int) ctx->contentLength); |
|
} |
|
|
|
if (newdata || (ctx->remainingContent > 0 && in->length > 0)) { |
|
switch (ctx->type) { |
|
case FCGI_END_REQUEST: |
|
if (8 != ctx->contentLength) { |
|
ERROR(wrk->srv, "(%s) FastCGI end request message has unexpected length %i != 8", |
|
li_sockaddr_to_string(ctx->pool->config.sock_addr, wrk->tmp_str, TRUE)->str, |
|
(gint) ctx->contentLength); |
|
fastcgi_reset(ctx); |
|
return; |
|
} |
|
if (in->length < 8) return; /* wait for more */ |
|
|
|
{ |
|
unsigned char endreq[8]; |
|
guint8 protocolStatus; |
|
|
|
if (!li_chunkqueue_extract_to_memory(in, 8, endreq, NULL)) abort(); |
|
li_chunkqueue_skip(in, 8); |
|
ctx->remainingContent -= 8; |
|
|
|
protocolStatus = endreq[4]; |
|
if (FCGI_REQUEST_COMPLETE != protocolStatus) { |
|
fcgi_debug("fcgi_out: FCGI_END_REQUEST with protocolStatus %i != FCGI_REQUEST_COMPLETE\n", (int) protocolStatus); |
|
fastcgi_reset(ctx); |
|
return; |
|
} |
|
|
|
ctx->stdin_closed = TRUE; |
|
ctx->stdout_closed = TRUE; |
|
ctx->stderr_closed = TRUE; |
|
ctx->request_done = TRUE; |
|
ctx->fcgi_in.out->is_closed = TRUE; |
|
li_stream_notify_later(&ctx->fcgi_in); |
|
|
|
if (ctx->currentcon) { |
|
guint32 appStatus = (endreq[0] << 24) | (endreq[1] << 16) | (endreq[2] << 8) | endreq[3]; |
|
const liFastCGIBackendCallbacks *callbacks = ctx->pool->callbacks; |
|
|
|
fcgi_debug("fastcgi end request: %i\n", appStatus); |
|
callbacks->end_request_cb(ctx->currentcon->vr, &ctx->pool->public, &ctx->currentcon->public, appStatus); |
|
} |
|
} |
|
break; |
|
case FCGI_STDOUT: |
|
if (0 == ctx->contentLength) { |
|
fcgi_debug("fastcgi stdout eof\n"); |
|
ctx->stdout_closed = TRUE; |
|
} else if (ctx->stdout_closed) { |
|
fcgi_debug("fastcgi stdout data after eof\n"); |
|
fastcgi_reset(ctx); |
|
return; |
|
} else { |
|
int len = MIN(in->length, ctx->remainingContent); |
|
#ifdef FCGI_DEBUG |
|
GString *stdoutdata = g_string_new(0); |
|
li_chunkqueue_extract_to(in, len, stdoutdata, NULL); |
|
fcgi_debug("fastcgi stdout data: '%s'\n", stdoutdata->str); |
|
g_string_free(stdoutdata, TRUE); |
|
#endif |
|
li_chunkqueue_steal_len(ctx->fcgi_in.out, in, len); |
|
ctx->remainingContent -= len; |
|
} |
|
li_stream_notify_later(&ctx->fcgi_in); |
|
break; |
|
case FCGI_STDERR: |
|
if (0 == ctx->contentLength) { |
|
ctx->stderr_closed = TRUE; |
|
break; |
|
} |
|
if (ctx->stderr_closed) { |
|
fcgi_debug("fastcgi stderr data after stderr end-of-stream\n"); |
|
fastcgi_reset(ctx); |
|
return; |
|
} else { |
|
gint len = ctx->remainingContent > in->length ? in->length : ctx->remainingContent; |
|
GString *errormsg = g_string_new(0); |
|
li_chunkqueue_extract_to(in, len, errormsg, NULL); |
|
li_chunkqueue_skip(in, len); |
|
ctx->remainingContent -= len; |
|
|
|
fcgi_debug("fastcgi stderr data: '%s'\n", errormsg->str); |
|
|
|
if (NULL != ctx->currentcon) { |
|
const liFastCGIBackendCallbacks *callbacks = ctx->pool->callbacks; |
|
callbacks->fastcgi_stderr_cb(ctx->currentcon->vr, &ctx->pool->public, &ctx->currentcon->public, errormsg); |
|
} |
|
|
|
g_string_free(errormsg, TRUE); |
|
} |
|
break; |
|
default: |
|
if (newdata) { |
|
WARNING(wrk->srv, "(%s) Unhandled fastcgi record type %i", |
|
li_sockaddr_to_string(ctx->pool->config.sock_addr, wrk->tmp_str, TRUE)->str, |
|
(gint) ctx->type); |
|
} |
|
{ |
|
int len = li_chunkqueue_skip(in, ctx->remainingContent); |
|
ctx->remainingContent -= len; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
if (NULL == ctx->iostream || 0 == in->length || ctx->remainingContent > 0) break; |
|
|
|
if (ctx->remainingPadding > 0) { |
|
int len = li_chunkqueue_skip(in, ctx->remainingPadding); |
|
ctx->remainingPadding -= len; |
|
} |
|
} |
|
|
|
if (NULL != ctx->iostream && (in->is_closed && !ctx->request_done)) { |
|
if (0 != in->length || !ctx->stdout_closed) { |
|
fcgi_debug("unexpected eof, still have partial fastcgi record header\n"); |
|
fastcgi_reset(ctx); |
|
} else { |
|
ctx->stdin_closed = ctx->stdout_closed = ctx->stderr_closed = ctx->request_done = TRUE; |
|
ctx->fcgi_in.out->is_closed = TRUE; |
|
li_stream_simple_socket_close(ctx->iostream, FALSE); |
|
} |
|
} |
|
} |
|
|
|
/* fastcgi -> response body */ |
|
static void fastcgi_stream_in(liStream *stream, liStreamEvent event) { |
|
liFastCGIBackendContext *ctx = LI_CONTAINER_OF(stream, liFastCGIBackendContext, fcgi_in); |
|
fcgi_debug("fastcgi_stream_in event: %s\n", li_stream_event_string(event)); |
|
switch (event) { |
|
case LI_STREAM_NEW_DATA: |
|
fastcgi_decode(ctx); |
|
break; |
|
case LI_STREAM_DISCONNECTED_SOURCE: |
|
if (!ctx->request_done) { |
|
fcgi_debug("fastcgi backend closed connection before request was finished\n"); |
|
fastcgi_reset(ctx); |
|
} |
|
break; |
|
case LI_STREAM_DISCONNECTED_DEST: |
|
if (!ctx->stdout_closed) { |
|
fcgi_debug("request aborted (by client?) before request was finished\n"); |
|
fastcgi_reset(ctx); |
|
} else { |
|
fastcgi_check_put(ctx); |
|
} |
|
break; |
|
case LI_STREAM_DESTROY: |
|
backend_ctx_unref(ctx); |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
|
|
liFastCGIBackendPool* li_fastcgi_backend_pool_new(const liFastCGIBackendConfig *config) { |
|
liFastCGIBackendPool_p *pool = g_slice_new0(liFastCGIBackendPool_p); |
|
|
|
pool->config.callbacks = &backend_cbs; |
|
pool->config.sock_addr = li_sockaddr_dup(config->sock_addr); |
|
pool->config.max_connections = config->max_connections; |
|
pool->config.idle_timeout = config->idle_timeout; |
|
pool->config.connect_timeout = config->connect_timeout; |
|
pool->config.wait_timeout = config->wait_timeout; |
|
pool->config.disable_time = config->disable_time; |
|
pool->config.max_requests = config->max_requests; |
|
pool->config.watch_for_close = FALSE; |
|
|
|
pool->callbacks = config->callbacks; |
|
|
|
pool->public.subpool = li_backend_pool_new(&pool->config); |
|
|
|
return &pool->public; |
|
} |
|
|
|
void li_fastcgi_backend_pool_free(liFastCGIBackendPool *bpool) { |
|
li_backend_pool_free(bpool->subpool); |
|
} |
|
|
|
liBackendResult li_fastcgi_backend_get(liVRequest *vr, liFastCGIBackendPool *bpool, liFastCGIBackendConnection **pbcon, liFastCGIBackendWait **pbwait) { |
|
liFastCGIBackendPool_p *pool = LI_CONTAINER_OF(bpool, liFastCGIBackendPool_p, public); |
|
liBackendConnection *subcon = NULL; |
|
liBackendWait *subwait = (liBackendWait*) *pbwait; |
|
liBackendResult res; |
|
|
|
fcgi_debug("li_fastcgi_backend_get\n"); |
|
|
|
res = li_backend_get(vr, pool->public.subpool, &subcon, &subwait); |
|
*pbwait = (liFastCGIBackendWait*) subwait; |
|
|
|
if (subcon != NULL) { |
|
liFastCGIBackendConnection_p *con = g_slice_new0(liFastCGIBackendConnection_p); |
|
liFastCGIBackendContext *ctx = subcon->data; |
|
liStream *http_out; |
|
|
|
assert(NULL != ctx); |
|
assert(LI_BACKEND_SUCCESS == res); |
|
con->ctx = ctx; |
|
con->vr = vr; |
|
ctx->currentcon = con; |
|
ctx->is_active = TRUE; |
|
*pbcon = &con->public; |
|
|
|
fcgi_debug("li_fastcgi_backend_get: got backend\n"); |
|
|
|
assert(vr->wrk == li_worker_from_iostream(ctx->iostream)); |
|
assert(vr->wrk == li_worker_from_stream(&ctx->fcgi_in)); |
|
assert(vr->wrk == li_worker_from_stream(&ctx->fcgi_out)); |
|
|
|
assert(li_event_active(&ctx->iostream->io_watcher)); |
|
li_event_set_keep_loop_alive(&ctx->iostream->io_watcher, TRUE); |
|
|
|
assert(NULL == ctx->fcgi_in.dest); |
|
assert(NULL == ctx->fcgi_out.source); |
|
|
|
assert(NULL != ctx->iostream); |
|
assert(-1 != li_event_io_fd(&ctx->iostream->io_watcher)); |
|
|
|
assert(ctx->iostream->stream_in.dest == &ctx->fcgi_in); |
|
assert(ctx->iostream->stream_out.source == &ctx->fcgi_out); |
|
|
|
ctx->stdin_closed = ctx->stdout_closed = ctx->stderr_closed = ctx->request_done = FALSE; |
|
li_chunkqueue_reset(ctx->fcgi_in.out); |
|
|
|
stream_send_begin(ctx->fcgi_out.out, 1); |
|
fastcgi_send_env(vr, ctx->fcgi_out.out, 1); |
|
li_stream_notify_later(&ctx->fcgi_out); |
|
|
|
http_out = li_stream_http_response_handle(&ctx->fcgi_in, vr, TRUE, TRUE); |
|
|
|
li_vrequest_handle_indirect(vr, NULL); |
|
li_vrequest_indirect_connect(vr, &ctx->fcgi_out, http_out); |
|
|
|
li_stream_release(http_out); |
|
} else { |
|
*pbcon = NULL; |
|
assert(LI_BACKEND_SUCCESS != res); |
|
if (LI_BACKEND_WAIT == res) assert(NULL != subwait); |
|
|
|
fcgi_debug("li_fastcgi_backend_get: still waiting\n"); |
|
} |
|
|
|
return res; |
|
} |
|
|
|
void li_fastcgi_backend_wait_stop(liVRequest *vr, liFastCGIBackendPool *bpool, liFastCGIBackendWait **pbwait) { |
|
liBackendWait *subwait = (liBackendWait*) *pbwait; |
|
*pbwait = NULL; |
|
|
|
li_backend_wait_stop(vr, bpool->subpool, &subwait); |
|
} |
|
|
|
void li_fastcgi_backend_put(liFastCGIBackendConnection *bcon) { |
|
liFastCGIBackendConnection_p *con = LI_CONTAINER_OF(bcon, liFastCGIBackendConnection_p, public); |
|
liFastCGIBackendContext *ctx = con->ctx; |
|
|
|
assert(NULL != ctx && con == ctx->currentcon); |
|
ctx->currentcon = NULL; |
|
con->ctx = NULL; |
|
con->vr = NULL; |
|
|
|
g_slice_free(liFastCGIBackendConnection_p, con); |
|
|
|
fastcgi_check_put(ctx); |
|
}
|
|
|