diff options
Diffstat (limited to 'src/mod_proxy_core.c')
-rw-r--r-- | src/mod_proxy_core.c | 2624 |
1 files changed, 0 insertions, 2624 deletions
diff --git a/src/mod_proxy_core.c b/src/mod_proxy_core.c deleted file mode 100644 index 31f884a7..00000000 --- a/src/mod_proxy_core.c +++ /dev/null @@ -1,2624 +0,0 @@ -#include <string.h> -#include <stdlib.h> -#include <fcntl.h> -#include <errno.h> -#include <ctype.h> -#include <assert.h> -#include <fcntl.h> -#include "base.h" -#include "sys-strings.h" - -#include "buffer.h" -#include "array.h" -#include "log.h" - -#include "plugin.h" -#include "joblist.h" -#include "sys-files.h" -#include "inet_ntop_cache.h" -#include "crc32.h" -#include "configfile.h" -#include "stat_cache.h" -#include "buffer.h" -#include "array.h" -#include "log.h" -#include "status_counter.h" - -#include "mod_proxy_core.h" -#include "mod_proxy_core_protocol.h" - -#define PROXY_CORE "proxy-core" -#define CONFIG_PROXY_CORE_BALANCER PROXY_CORE ".balancer" -#define CONFIG_PROXY_CORE_PROTOCOL PROXY_CORE ".protocol" -#define CONFIG_PROXY_CORE_DEBUG PROXY_CORE ".debug" -#define CONFIG_PROXY_CORE_MAX_KEEP_ALIVE PROXY_CORE ".max-keep-alive-requests" -#define CONFIG_PROXY_CORE_BACKENDS PROXY_CORE ".backends" -#define CONFIG_PROXY_CORE_REWRITE_REQUEST PROXY_CORE ".rewrite-request" -#define CONFIG_PROXY_CORE_REWRITE_RESPONSE PROXY_CORE ".rewrite-response" -#define CONFIG_PROXY_CORE_ALLOW_X_SENDFILE PROXY_CORE ".allow-x-sendfile" -#define CONFIG_PROXY_CORE_ALLOW_X_REWRITE PROXY_CORE ".allow-x-rewrite" -#define CONFIG_PROXY_CORE_MAX_POOL_SIZE PROXY_CORE ".max-pool-size" -#define CONFIG_PROXY_CORE_CHECK_LOCAL PROXY_CORE ".check-local" -#define CONFIG_PROXY_CORE_SPLIT_HOSTNAMES PROXY_CORE ".split-hostnames" -#define CONFIG_PROXY_CORE_DISABLE_TIME PROXY_CORE ".disable-time" -#define CONFIG_PROXY_CORE_MAX_BACKLOG_SIZE PROXY_CORE ".max-backlog-size" - -static int mod_proxy_wakeup_connections(server *srv, plugin_data *p, plugin_config *p_conf); - -static int array_insert_int(array *a, const char *key, int val) { - data_integer *di; - - if (NULL == (di = (data_integer *)array_get_unused_element(a, TYPE_INTEGER))) { - di = data_integer_init(); - } - - buffer_copy_string(di->key, key); - di->value = val; - array_insert_unique(a, (data_unset *)di); - - return 0; -} - -static proxy_protocol *mod_proxy_core_register_protocol(const char *name) { - proxy_protocol *protocol = proxy_protocol_init(); - - protocol->name = buffer_init_string(name); - - proxy_protocols_register(protocol); - return protocol; -} - -INIT_FUNC(mod_proxy_core_init) { - plugin_data *p; - - UNUSED(srv); - - proxy_protocols_init(); - - p = calloc(1, sizeof(*p)); - - /* create some backends as long as we don't have the config-parser */ - - p->possible_balancers = array_init(); - array_insert_int(p->possible_balancers, "sqf", PROXY_BALANCE_SQF); - array_insert_int(p->possible_balancers, "carp", PROXY_BALANCE_CARP); - array_insert_int(p->possible_balancers, "round-robin", PROXY_BALANCE_RR); - array_insert_int(p->possible_balancers, "static", PROXY_BALANCE_STATIC); - - p->proxy_register_protocol = mod_proxy_core_register_protocol; - - /* statistics counters. */ - p->request_count = status_counter_get_counter(CONST_STR_LEN(PROXY_CORE ".requests")); - - p->balance_buf = buffer_init(); - p->protocol_buf = buffer_init(); - p->replace_buf = buffer_init(); - p->backends_arr = array_init(); - - p->tmp_buf = buffer_init(); - -#if 0 - /** - * create a small pool of session objects - * - * instead of creating new one each time, - * cleanup old ones and put them into the pool - * - * 8 clean items should be enough at a time, destroy the other ones - */ - p->session_pool = proxy_session_pool_init(); -#endif - - return p; -} - -FREE_FUNC(mod_proxy_core_free) { - plugin_data *p = p_d; - - if (!p) return HANDLER_GO_ON; - - if (p->config_storage) { - size_t i; - for (i = 0; i < srv->config_context->used; i++) { - plugin_config *s = p->config_storage[i]; - - if (!s) continue; - - proxy_backends_free(s->backends); - proxy_backlog_free(s->backlog); - - proxy_rewrites_free(s->request_rewrites); - proxy_rewrites_free(s->response_rewrites); - - free(s); - } - free(p->config_storage); - } - - array_free(p->possible_balancers); - array_free(p->backends_arr); - - buffer_free(p->balance_buf); - buffer_free(p->protocol_buf); - buffer_free(p->replace_buf); - buffer_free(p->tmp_buf); - -#if 0 - proxy_session_pool_free(p->session_pool); -#endif - - free(p); - - proxy_protocols_free(); - - return HANDLER_GO_ON; -} - -static handler_t mod_proxy_core_config_parse_rewrites(proxy_rewrites *dest, array *src, const char *config_key) { - data_unset *du; - size_t j; - - if (NULL != (du = array_get_element(src, config_key, strlen(config_key)))) { - data_array *keys = (data_array *)du; - - if (keys->type != TYPE_ARRAY) { - ERROR("%s = <...>", - config_key); - - return HANDLER_ERROR; - } - - /* - * proxy-core.rewrite-request = ( - * "_uri" => ( ... ) - * ) - */ - - for (j = 0; j < keys->value->used; j++) { - size_t k; - data_array *headers = (data_array *)keys->value->data[j]; - - /* keys->key should be "_uri" and the value a array of rewrite */ - if (headers->type != TYPE_ARRAY) { - ERROR("%s = ( %s => <...> ) has to a array", - config_key, - SAFE_BUF_STR(headers->key)); - - return HANDLER_ERROR; - } - - if (headers->value->used > 1) { - ERROR("%s = ( %s => <...> ) has to a array with only one element", - config_key, - SAFE_BUF_STR(headers->key)); - - return HANDLER_ERROR; - - } - - for (k = 0; k < headers->value->used; k++) { - data_string *rewrites = (data_string *)headers->value->data[k]; - proxy_rewrite *rw; - - /* keys->key should be "_uri" and the value a array of rewrite */ - if (rewrites->type != TYPE_STRING) { - ERROR("%s = ( \"%s\" => ( \"%s\" => <value> ) ) has to a string", - config_key, - SAFE_BUF_STR(headers->key), - SAFE_BUF_STR(rewrites->key)); - - return HANDLER_ERROR; - } - - rw = proxy_rewrite_init(); - - if (0 != proxy_rewrite_set_regex(rw, rewrites->key)) { - return HANDLER_ERROR; - } - buffer_copy_string_buffer(rw->replace, rewrites->value); - buffer_copy_string_buffer(rw->match, rewrites->key); - buffer_copy_string_buffer(rw->header, headers->key); - - proxy_rewrites_add(dest, rw); - } - } - } - - return HANDLER_GO_ON; -} - -static void mod_proxy_core_create_backend_stats(plugin_data *p, buffer *stat_basename, proxy_backend *backend) { -#define COUNTER_NAME(b, x) \ - buffer_copy_string_buffer(b, stat_basename); \ - buffer_append_string_len(b, CONST_STR_LEN("\"")); \ - buffer_append_string_buffer(b, backend->name); \ - buffer_append_string_len(b, CONST_STR_LEN("\"." x)); - - /* request count stat. */ - COUNTER_NAME(p->tmp_buf, "requests"); - backend->request_count = status_counter_get_counter(CONST_BUF_LEN(p->tmp_buf)); - - /* load */ - COUNTER_NAME(p->tmp_buf, "load"); - backend->load = status_counter_get_counter(CONST_BUF_LEN(p->tmp_buf)); - - /* pool size */ - COUNTER_NAME(p->tmp_buf, "pool_size"); - backend->pool_size = status_counter_get_counter(CONST_BUF_LEN(p->tmp_buf)); - - COUNTER_NAME(p->tmp_buf, "requests_failed"); - backend->requests_failed = status_counter_get_counter(CONST_BUF_LEN(p->tmp_buf)); -#undef COUNTER_NAME -} - -SETDEFAULTS_FUNC(mod_proxy_core_set_defaults) { - plugin_data *p = p_d; - buffer *stat_basename; - size_t i, j; - int proxy_counter = 0; - - config_values_t cv[] = { - { CONFIG_PROXY_CORE_BACKENDS, NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ - { CONFIG_PROXY_CORE_DEBUG, NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ - { CONFIG_PROXY_CORE_BALANCER, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ - { CONFIG_PROXY_CORE_PROTOCOL, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ - { CONFIG_PROXY_CORE_REWRITE_REQUEST, NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ - { CONFIG_PROXY_CORE_REWRITE_RESPONSE, NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ - { CONFIG_PROXY_CORE_ALLOW_X_SENDFILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */ - { CONFIG_PROXY_CORE_ALLOW_X_REWRITE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */ - { CONFIG_PROXY_CORE_MAX_POOL_SIZE, NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 */ - { CONFIG_PROXY_CORE_CHECK_LOCAL, "use $PHYSICAL[\"existing-path\"] =~ ... { ... } instead", - T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_CONNECTION }, /* 9 */ - { CONFIG_PROXY_CORE_MAX_KEEP_ALIVE, NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */ - { CONFIG_PROXY_CORE_SPLIT_HOSTNAMES, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 11 */ - { CONFIG_PROXY_CORE_DISABLE_TIME, NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ - { CONFIG_PROXY_CORE_MAX_BACKLOG_SIZE, NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ - { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } - }; - - stat_basename = buffer_init(); - - p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); - - for (i = 0; i < srv->config_context->used; i++) { - plugin_config *s; - array *ca; - proxy_backend *backend; - - array_reset(p->backends_arr); - buffer_reset(p->balance_buf); - buffer_reset(p->protocol_buf); - - s = calloc(1, sizeof(plugin_config)); - s->debug = 0; - s->balancer = PROXY_BALANCE_UNSET; - s->protocol = NULL; - s->backends = proxy_backends_init(); - s->backlog = proxy_backlog_init(); - s->response_rewrites = proxy_rewrites_init(); - s->request_rewrites = proxy_rewrites_init(); - s->check_local = 0; - s->split_hostnames = 1; - s->max_keep_alive_requests = 0; - s->disable_time = 1; - s->max_backlog_size = 4; - - cv[0].destination = p->backends_arr; - cv[1].destination = &(s->debug); - cv[2].destination = p->balance_buf; /* parse into a constant */ - cv[3].destination = p->protocol_buf; /* parse into a constant */ - cv[6].destination = &(s->allow_x_sendfile); - cv[7].destination = &(s->allow_x_rewrite); - cv[8].destination = &(s->max_pool_size); - cv[10].destination = &(s->max_keep_alive_requests); - cv[11].destination = &(s->split_hostnames); - cv[12].destination = &(s->disable_time); - cv[13].destination = &(s->max_backlog_size); - - buffer_reset(p->balance_buf); - - p->config_storage[i] = s; - ca = ((data_config *)srv->config_context->data[i])->value; - - if (0 != config_insert_values_global(srv, ca, cv)) { - return HANDLER_ERROR; - } - - if (!buffer_is_empty(p->balance_buf)) { - data_integer *di; - - if (NULL != (di = (data_integer *)array_get_element(p->possible_balancers, CONST_BUF_LEN(p->balance_buf)))) { - s->balancer = di->value; - } else { - ERROR("proxy.balance has to be one of 'round-robin', 'carp', 'sqf', 'static': got %s", SAFE_BUF_STR(p->balance_buf)); - return HANDLER_ERROR; - } - } - - if (!buffer_is_empty(p->protocol_buf)) { - proxy_protocol *protocol = NULL; - if (NULL == (protocol = proxy_get_protocol(p->protocol_buf))) { - ERROR("proxy.protocol has to be one of { %s } got %s, you might have to load 'mod_proxy_backend_%s'", - proxy_available_protocols(), - SAFE_BUF_STR(p->protocol_buf), - SAFE_BUF_STR(p->protocol_buf) - ); - return HANDLER_ERROR; - } - s->protocol = protocol; - } - - if (p->backends_arr->used) { - /* statistics base name. */ - buffer_copy_string_len(stat_basename, CONST_STR_LEN(PROXY_CORE ".")); - buffer_append_long(stat_basename, proxy_counter); - - /* backlog size stats */ - buffer_copy_string_buffer(p->tmp_buf, stat_basename); - buffer_append_string_len(p->tmp_buf, CONST_STR_LEN(".backlogged")); - s->backlog_size = status_counter_get_counter(CONST_BUF_LEN(p->tmp_buf)); - - /* backends stats base name. */ - buffer_append_string_len(stat_basename, CONST_STR_LEN(".backends.")); - - /* check if the backends have a valid host-name */ - for (j = 0; j < p->backends_arr->used; j++) { - data_string *ds = (data_string *)p->backends_arr->data[j]; - backend = proxy_backend_init(); - - /* save name of backend for config. */ - buffer_copy_string_buffer(backend->name, ds->value); - /* the values should be ips or hostnames */ - if (0 != proxy_address_pool_add_string(backend->address_pool, ds->value)) { - return HANDLER_ERROR; - } - - if (s->max_pool_size) { - backend->pool->max_size = s->max_pool_size; - } - - /* append backend to list of backends */ - proxy_backends_add(s->backends, backend); - - /* if there is more then one address in the pool, split them out into seperate backends */ - if (s->split_hostnames && backend->address_pool->used > 1) { - proxy_address_pool *pool = backend->address_pool; - - /* change current backend's name to name of the first ip address in the pool. */ - buffer_copy_string_buffer(backend->name, pool->ptr[0]->name); - - /* setup stats */ - mod_proxy_core_create_backend_stats(p, stat_basename, backend); - - /* move all addresses from the pool into new backends, except for the first one */ - while (pool->used > 1) { - /* remove last address from pool */ - proxy_address *address = pool->ptr[--(pool->used)]; - - /* create new backend for address */ - backend = proxy_backend_init(); - - /* set backend name to name of address */ - buffer_copy_string_buffer(backend->name, address->name); - - /* setup stats */ - mod_proxy_core_create_backend_stats(p, stat_basename, backend); - - /* add address to pool */ - proxy_address_pool_add(backend->address_pool, address); - - if (s->max_pool_size) { - backend->pool->max_size = s->max_pool_size; - } - - /* append backend to list of backends */ - proxy_backends_add(s->backends, backend); - } - } else { - /* setup stats */ - mod_proxy_core_create_backend_stats(p, stat_basename, backend); - } - } - /* counter number of "proxy-core.backends" groups */ - proxy_counter++; - } - - if (HANDLER_GO_ON != mod_proxy_core_config_parse_rewrites(s->request_rewrites, ca, CONFIG_PROXY_CORE_REWRITE_REQUEST)) { - return HANDLER_ERROR; - } - - if (HANDLER_GO_ON != mod_proxy_core_config_parse_rewrites(s->response_rewrites, ca, CONFIG_PROXY_CORE_REWRITE_RESPONSE)) { - return HANDLER_ERROR; - } - } - - buffer_free(stat_basename); - - return HANDLER_GO_ON; -} - - -static proxy_session *proxy_session_init(void) { - proxy_session *sess; - - sess = calloc(1, sizeof(*sess)); - - sess->state = PROXY_STATE_UNSET; - sess->request_uri = buffer_init(); - sess->request_headers = array_init(); - sess->env_headers = array_init(); - - sess->resp = http_response_init(); - - sess->recv = chunkqueue_init(); - - sess->is_chunked = 0; - sess->content_length = -1; - sess->send_response_content = 1; - sess->do_new_session = 0; - sess->do_x_rewrite_backend = 0; - sess->sticky_session = NULL; - - return sess; -} - -static void proxy_session_reset(proxy_session *sess) { - if (!sess) return; - - buffer_reset(sess->request_uri); - array_reset(sess->request_headers); - array_reset(sess->env_headers); - - http_response_reset(sess->resp); - sess->p = NULL; - - chunkqueue_reset(sess->recv); - - sess->state = PROXY_STATE_UNSET; - - sess->is_chunked = 0; - sess->send_response_content = 1; - - sess->bytes_read = 0; - sess->connect_start_ts = 0; - sess->content_length = -1; - sess->internal_redirect_count = 0; - sess->do_internal_redirect = 0; - sess->is_closing = 0; - sess->is_closed = 0; - sess->is_request_finished = 0; - sess->have_response_headers = 0; - - sess->do_new_session = 0; - sess->do_x_rewrite_backend = 0; - buffer_free(sess->sticky_session); - sess->sticky_session = NULL; - - sess->remote_con = NULL; - sess->proxy_con = NULL; - sess->proxy_backend = NULL; -} - -static void proxy_session_free(proxy_session *sess) { - if (!sess) return; - - buffer_free(sess->request_uri); - array_free(sess->request_headers); - array_free(sess->env_headers); - - http_response_free(sess->resp); - sess->p = NULL; - - chunkqueue_free(sess->recv); - - buffer_free(sess->sticky_session); - free(sess); -} - -/** - * Copy decoded response content to client connection. - */ -static int proxy_copy_response(server *srv, connection *con, proxy_session *sess) { - chunk *c; - int we_have = 0; - - UNUSED(srv); - - chunkqueue_remove_finished_chunks(sess->recv); - /* copy the content to the next cq */ - for (c = sess->recv->first; c; c = c->next) { - if (c->mem->used == 0) continue; - - we_have = c->mem->used - c->offset - 1; - sess->recv->bytes_out += we_have; - if (sess->send_response_content) { - con->send->bytes_in += we_have; - /* X-Sendfile ignores the content-body */ - chunkqueue_steal_chunk(con->send, c); - } else { - /* discard the data */ - chunk_set_done(c); - } - } - chunkqueue_remove_finished_chunks(sess->recv); - - if(sess->recv->is_closed && sess->send_response_content) { - con->send->is_closed = 1; - } - return 0; -} - -/** - * Initialize protocol stream. - * - */ -static int proxy_stream_init(server *srv, proxy_session *sess) { - proxy_protocol *protocol = (sess->proxy_backend) ? sess->proxy_backend->protocol : NULL; - if(protocol && protocol->proxy_stream_init) { - return (protocol->proxy_stream_init)(srv, sess->proxy_con); - } - return 1; -} - -/** - * Cleanup protocol state data. - * - */ -static int proxy_stream_cleanup(server *srv, proxy_session *sess) { - proxy_protocol *protocol = (sess->proxy_backend) ? sess->proxy_backend->protocol : NULL; - if(protocol && protocol->proxy_stream_cleanup) { - return (protocol->proxy_stream_cleanup)(srv, sess->proxy_con); - } - return 1; -} - -/** - * decode the content for the protocol - * - * http might have chunk-encoding - * fastcgi has the fastcgi wrapper code - * - * @param out chunkqueue for the plain content - */ - -static handler_t proxy_stream_decoder(server *srv, proxy_session *sess, chunkqueue *out) { - proxy_protocol *protocol = (sess->proxy_backend) ? sess->proxy_backend->protocol : NULL; - if(protocol && protocol->proxy_stream_decoder) { - return (protocol->proxy_stream_decoder)(srv, sess, out); - } - ERROR("protocol '%s' is not supported yet", SAFE_BUF_STR(protocol->name)); - return HANDLER_ERROR; -} - -/** - * encode the content for the protocol - * - * @param in chunkqueue with the content to (no encoding) - */ -static handler_t proxy_stream_encoder(server *srv, proxy_session *sess, chunkqueue *in) { - proxy_protocol *protocol = (sess->proxy_backend) ? sess->proxy_backend->protocol : NULL; - if(protocol && protocol->proxy_stream_encoder) { - return (protocol->proxy_stream_encoder)(srv, sess, in); - } - ERROR("protocol '%s' is not supported yet", SAFE_BUF_STR(protocol->name)); - return HANDLER_ERROR; -} - -/** - * encode the request for the protocol - * - * @param in chunkqueue with the content to (no encoding) - * @param out chunkqueue for the encoded, protocol specific data - */ -static handler_t proxy_encode_request_headers(server *srv, proxy_session *sess, chunkqueue *in) { - proxy_protocol *protocol = (sess->proxy_backend) ? sess->proxy_backend->protocol : NULL; - if(protocol && protocol->proxy_encode_request_headers) { - /* reset proxy connection queues before we encode a new request. - */ - chunkqueue_reset(sess->proxy_con->send); - chunkqueue_reset(sess->proxy_con->recv); - return (protocol->proxy_encode_request_headers)(srv, sess, in); - } - - if (protocol == NULL) { - ERROR("protocol is not set: %s", ""); - } else { - ERROR("protocol '%s' is not supported yet", SAFE_BUF_STR(protocol->name)); - } - return HANDLER_ERROR; -} - -static handler_t proxy_handle_response_headers(server *srv, connection *con, plugin_data *p, - proxy_session *sess, chunkqueue *out) { - int have_content_length = 0; - int do_x_rewrite = 0; - size_t i; - - /* finished parsing http response headers from backend, now prepare http response headers - * for client response. - */ - sess->content_length = -1; - con->http_status = sess->resp->status; - - /* copy the http-headers */ - for (i = 0; i < sess->resp->headers->used; i++) { - const char *ign[] = { "Status", NULL }; - size_t j, k; - data_string *ds; - - data_string *header = (data_string *)sess->resp->headers->data[i]; - - /* some headers are ignored by default */ - for (j = 0; ign[j]; j++) { - if (0 == strcasecmp(ign[j], header->key->ptr)) break; - } - if (ign[j]) continue; - - if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("Location"))) { - /* CGI/1.1 rev 03 - 7.2.1.2 */ - if (con->http_status == 0) con->http_status = 302; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("Content-Length"))) { - have_content_length = 1; - - sess->content_length = strtol(header->value->ptr, NULL, 10); - - if (sess->content_length < 0) { - return HANDLER_ERROR; - } - con->response.content_length = sess->content_length; - /* don't save this header, other modules might change the content length. */ - continue; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("X-Sendfile")) || - 0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("X-LIGHTTPD-send-file"))) { - if (p->conf.allow_x_sendfile) { - sess->send_response_content = 0; - sess->do_internal_redirect = 1; - - /* don't try to rewrite this request through mod_proxy_core again */ - sess->internal_redirect_count = MAX_INTERNAL_REDIRECTS; - - buffer_copy_string_buffer(con->physical.path, header->value); - - /* as we want to support ETag and friends we set the physical path for the file - * and hope mod_staticfile catches up */ - } - - continue; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("X-LIGHTTPD-send-tempfile"))) { - if (p->conf.allow_x_sendfile && !buffer_is_empty(header->value)) { - stat_cache_entry *sce = NULL; - - if (HANDLER_ERROR != stat_cache_get_entry(srv, con, header->value, &sce)) { - chunk *c; - sess->send_response_content = 0; - - if(sce->st.st_size > 0) { - chunkqueue_append_file(con->send, header->value, 0, sce->st.st_size); - con->send->bytes_in += sce->st.st_size; - c = con->send->last; - c->file.is_temp = 1; - } else { - if(unlink(BUF_STR(header->value)) < 0) { - ERROR("Failed to delete empty tempfile: file=%s, error: %s", SAFE_BUF_STR(header->value), strerror(errno)); - } - } - con->response.content_length = sce->st.st_size; - have_content_length = 1; - con->send->is_closed = 1; - } else { - ERROR("Failed to send tempfile: file=%s, error: %s", SAFE_BUF_STR(header->value), strerror(errno)); - } - } - - continue; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("X-Rewrite-URI"))) { - if (p->conf.allow_x_rewrite) { - do_x_rewrite = 1; - buffer_copy_string_buffer(con->request.uri, header->value); - } - - continue; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("X-Rewrite-Host"))) { - if (p->conf.allow_x_rewrite) { - do_x_rewrite = 1; - buffer_copy_string_buffer(con->request.http_host, header->value); - /* replace Host request header */ - if (NULL != (ds = (data_string *)array_get_element(con->request.headers, CONST_STR_LEN("Host")))) { - buffer_copy_string_buffer(ds->value, header->value); - } else { - /* insert Host request header */ - if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) { - ds = data_response_init(); - } - buffer_copy_string_len(ds->key, CONST_STR_LEN("Host")); - buffer_copy_string_buffer(ds->value, header->value); - array_insert_unique(con->request.headers, (data_unset *)ds); - } - } - - continue; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("X-Rewrite-Backend"))) { - if (p->conf.allow_x_rewrite) { - do_x_rewrite = 1; - if (!sess->sticky_session) sess->sticky_session = buffer_init(); - buffer_copy_string_buffer(sess->sticky_session, header->value); - sess->do_x_rewrite_backend = 1; - } - - continue; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("Transfer-Encoding"))) { - if (strstr(header->value->ptr, "chunked")) { - sess->is_chunked = 1; - } - /* ignore the header */ - continue; - } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("Connection"))) { - if (strstr(header->value->ptr, "close")) { - sess->is_closing = 1; - } - /* ignore the header */ - continue; - - } - - if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { - ds = data_response_init(); - } - - - buffer_copy_string_buffer(ds->key, header->key); - -#ifdef HAVE_PCRE_H - for (k = 0; k < p->conf.response_rewrites->used; k++) { - proxy_rewrite *rw = p->conf.response_rewrites->ptr[k]; - - if (buffer_is_equal(rw->header, header->key)) { - int ret; - - if ((ret = pcre_replace(rw->regex, rw->replace, header->value, p->replace_buf)) < 0) { - switch (ret) { - case PCRE_ERROR_NOMATCH: - /* hmm, ok. no problem */ - buffer_append_string_buffer(ds->value, header->value); - break; - default: - TRACE("oops, pcre_replace failed with: %d", ret); - break; - } - } else { - buffer_append_string_buffer(ds->value, p->replace_buf); - } - - break; - } - } - - if (k == p->conf.response_rewrites->used) { - buffer_copy_string_buffer(ds->value, header->value); - } -#else - buffer_copy_string_buffer(ds->value, header->value); -#endif - - array_insert_unique(con->response.headers, (data_unset *)ds); - } - - if (do_x_rewrite) { - sess->send_response_content = 0; - sess->do_internal_redirect = 1; - sess->do_new_session = 1; - sess->is_chunked = 0; - con->http_status = 0; - sess->content_length = -1; - con->response.content_length = -1; - - /* we are restarting the whole request, reset all the response headers */ - array_reset(con->response.headers); - - buffer_reset(con->physical.path); - - config_cond_cache_reset(srv, con); - } - - /* we are finished decoding the response headers. */ - if(!out->is_closed) { - /* We don't have all the response content try to enable chunked encoding. */ - /* does the client allow us to send chunked encoding ? */ - if (con->request.http_version == HTTP_VERSION_1_1 && - !have_content_length) { - con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; - } - } - - /* we might have part of the response content too */ - proxy_copy_response(srv, con, sess); - - return HANDLER_FINISHED; /* we have a full header */ -} - -/* - * if there is data in the send queue enable FDEVENT_OUT & FDEVENT_IN. - * else just enable FDEVENT_IN. - */ -static int proxy_connection_enable_events(server *srv, proxy_connection *proxy_con) { - int events = FDEVENT_IN; - if (proxy_con->send->bytes_out < proxy_con->send->bytes_in) events |= FDEVENT_OUT; - return fdevent_event_add(srv->ev, proxy_con->sock, events); -} - -/** - * encode/decode stream data from backend connection. - * - */ -static handler_t proxy_stream_encode_decode(server *srv, proxy_session *sess) { - //proxy_connection *proxy_con = sess->proxy_con; - connection *con = sess->remote_con; - - if (!sess->recv->is_closed) { - /* call stream-decoder (HTTP-chunked, FastCGI, ... ) */ - switch (proxy_stream_decoder(srv, sess, sess->recv)) { - case HANDLER_FINISHED: - /* finished decoding reponse. */ - /* we are done, close the response content queue */ - sess->recv->is_closed = 1; - break; - case HANDLER_GO_ON: - break; - case HANDLER_ERROR: - ERROR("%s", "stream decoder failed."); - /* error */ - return HANDLER_ERROR; - default: - TRACE("stream-decoder: %s", "foo"); - break; - } - } - - /* encode request content. */ - switch(proxy_stream_encoder(srv, sess, con->recv)) { - case HANDLER_FINISHED: - /* finished encoding request content. */ - break; - case HANDLER_GO_ON: - break; - case HANDLER_ERROR: - ERROR("%s", "stream encoder failed."); - /* error */ - return HANDLER_ERROR; - default: - TRACE("stream-encoder: %s", "foo"); - break; - } - chunkqueue_remove_finished_chunks(con->recv); - - if (!sess->is_closed) { - /* enable FDEVENT_OUT if there is data to send. */ - proxy_connection_enable_events(srv, sess->proxy_con); - } - - return HANDLER_GO_ON; -} - -static handler_t proxy_connection_connect(proxy_connection *con) { - int fd; -#ifdef _WIN32 - int io_ctl = 1; - int win_err = 0; -#endif - - if (-1 == (fd = socket(con->address->addr.plain.sa_family, SOCK_STREAM, 0))) { - switch (errno) { - case EMFILE: - return HANDLER_WAIT_FOR_FD; - default: - ERROR("socket failed: %s (%d)", strerror(errno), errno); - return HANDLER_ERROR; - } - } - -#ifdef O_NONBLOCK - fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR); -#elif defined _WIN32 - ioctlsocket(fd, FIONBIO, &io_ctl); -#endif - - con->sock->fd = fd; - con->sock->fde_ndx = -1; - con->sock->type = IOSOCKET_TYPE_SOCKET; - - if (-1 == connect(fd, &(con->address->addr.plain), con->address->addrlen)) { - switch(light_sock_errno()) { - case EINPROGRESS: - case EALREADY: - case EINTR: -#ifdef _WIN32 - case EWOULDBLOCK: -#endif - return HANDLER_WAIT_FOR_EVENT; - default: - closesocket(fd); - con->sock->fd = -1; - - ERROR("connect(%s) failed: %s (%d)", - SAFE_BUF_STR(con->address->name), - strerror(errno), errno); - return HANDLER_ERROR; - } - } - - return HANDLER_GO_ON; -} - -/** - * event-handler for idling connections - * - * unused (idling) keep-alive connections are not bound to a session - * and need their own event-handler - * - * if the connection closes (we get a FDEVENT_IN), close our side too and - * let the trigger-func handle the cleanup - * - * @see proxy_trigger - */ - - -static handler_t proxy_handle_fdevent_idle(void *s, void *ctx, int revents) { - server *srv = (server *)s; - proxy_connection *proxy_con = ctx; - char buf[4096]; - - if (revents & FDEVENT_IN) { - switch (proxy_con->state) { - case PROXY_CONNECTION_STATE_IDLE: - proxy_con->state = PROXY_CONNECTION_STATE_CLOSED; - - /* close + unregister have to be in the same call, - * otherwise we get a events for a re-opened fd */ - - sockread(proxy_con->sock->fd, buf, sizeof(buf)); - - fdevent_event_del(srv->ev, proxy_con->sock); - - /* we have to notify the pool, that this connection is free now */ - - break; - case PROXY_CONNECTION_STATE_CLOSED: - /* poll() is state-driven, we will get events as long as it isn't disabled - * the close() above should disable the events too */ - ERROR("%s", "hurry up buddy, I got another event for a closed idle-connection"); - break; - default: - ERROR("invalid connection state: %d, should be idle", proxy_con->state); - break; - } - } - - return HANDLER_GO_ON; -} - - -/* don't call any proxy functions directly */ -static handler_t proxy_handle_fdevent(void *s, void *ctx, int revents) { - server *srv = (server *)s; - proxy_session *sess = ctx; - proxy_connection *proxy_con; - connection *con; - int call_append = 1; - - /** - * we might receive a event for a connection that should be closed already - */ - - if (sess == NULL) { - /** - * race - * the other fd of the connection might have been closed already - * mod_proxy_connection_close_callback() frees the - */ - - ERROR("the session ctx is NULL: %p, expect a crash", (void*) sess); - // FIXME: do not dereference an known NULL ??? - } - - proxy_con = sess->proxy_con; - con = sess->remote_con; - - if (revents & FDEVENT_IN) { - chunkqueue_remove_finished_chunks(proxy_con->recv); - switch (srv->network_backend_read(srv, con, proxy_con->sock, proxy_con->recv)) { - case NETWORK_STATUS_CONNECTION_CLOSE: - /* a close here means we can't read/write any more data. */ - sess->is_closed = 1; - proxy_con->send->is_closed = 1; - proxy_con->recv->is_closed = 1; - break; - case NETWORK_STATUS_SUCCESS: - case NETWORK_STATUS_WAIT_FOR_EVENT: - break; - default: - ERROR("%s", "oops, we failed to read"); - break; - } - } - - if (revents & FDEVENT_OUT) { - fdevent_event_add(srv->ev, proxy_con->sock, FDEVENT_IN); - - switch (srv->network_backend_write(srv, con, proxy_con->sock, proxy_con->send)) { - case NETWORK_STATUS_SUCCESS: - break; - case NETWORK_STATUS_WAIT_FOR_AIO_EVENT: - call_append = 0; /* let the joblist-queue-handler call the connection again */ - break; - case NETWORK_STATUS_WAIT_FOR_EVENT: - fdevent_event_add(srv->ev, proxy_con->sock, FDEVENT_IN | FDEVENT_OUT); - break; - case NETWORK_STATUS_CONNECTION_CLOSE: - /* done mark the connection closed here only the send queue, - * since there might be more data to read. - */ - proxy_con->send->is_closed = 1; - break; - default: - ERROR("%s", "oops, we failed to write"); - break; - } - chunkqueue_remove_finished_chunks(proxy_con->send); - } - - if (revents & FDEVENT_HUP) { - if (!(revents & FDEVENT_IN)) { - /* if we only received the FDEVENT_HUP event, then there is no more data to read. */ - sess->is_closed = 1; - proxy_con->recv->is_closed = 1; - } - /* can't write on a closed socket, so close the send queue. */ - proxy_con->send->is_closed = 1; - } - - if (sess->is_closed) { - sess->recv->is_closed = 1; - fdevent_event_del(srv->ev, sess->proxy_con->sock); - } - - /** - * on NETWORK_STATUS_WAIT_FOR_AIO_EVENT we are not allowed to call the state-engine - * the connection has to sleep until our disk-read is finished - * - * the joblist_append() will be called by the joblist_append_queue_handler in server.c - */ - if (call_append) joblist_append(srv, con); - - return HANDLER_GO_ON; -} - -/** - * Cleanup backend proxy connection. - */ -static int proxy_remove_backend_connection(server *srv, proxy_session *sess) { - - if(!sess->proxy_con) return -1; - - /* cleanup protocol stream */ - proxy_stream_cleanup(srv, sess); - - /* remove closed connection from pool. */ - proxy_connection_pool_remove_connection(sess->proxy_backend->pool, sess->proxy_con); - - /* update stats. */ - COUNTER_SET(sess->proxy_backend->pool_size, sess->proxy_backend->pool->used); - COUNTER_DEC(sess->proxy_backend->load); - - /* the backend might have been disabled by a full connection pool, re-enable - * if there is at least one active address. - */ - if (sess->proxy_backend->disabled_addresses <= sess->proxy_backend->address_pool->used) { - sess->proxy_backend->state = PROXY_BACKEND_STATE_ACTIVE; - } - - if (sess->proxy_con->sock->fd != -1) { - /* if we fail in connect() might not have a FD yet */ - fdevent_event_del(srv->ev, sess->proxy_con->sock); - fdevent_unregister(srv->ev, sess->proxy_con->sock); - } - - proxy_connection_free(sess->proxy_con); - sess->proxy_con = NULL; - - return 0; -} - -/** - * Recycle backend proxy connection. - * - * 1. close the connection if keep-alive is disable - * 2. or set the connection idling and wake up a backlogged request. - * - */ -static int proxy_recycle_backend_connection(server *srv, plugin_data *p, proxy_session *sess) { - proxy_request *req; - int reuse = 1; - - if (!sess) return HANDLER_GO_ON; - - if (sess->proxy_con) { - COUNTER_INC(p->request_count); - COUNTER_INC(sess->proxy_backend->request_count); - COUNTER_DEC(sess->proxy_backend->load); - switch (sess->proxy_con->state) { - case PROXY_CONNECTION_STATE_CONNECTED: - /* - * Set the connection to idling if: - * - * 1. keep-alive was not disabled (sess->is_closing) - * 2. backend connection is already closed (sess->is_closed) - * 3. backend protocol finished parsing all data for this request. (sess->recv->is_closed) - * 4. keep-alive request count hasn't reached max-keep-alive-requests - */ - if (sess->is_closing || sess->is_closed) { - reuse = 0; - } - - sess->proxy_con->request_count++; - if (p->conf.debug) TRACE("request_count=%d", sess->proxy_con->request_count); - if (sess->proxy_con->request_count >= p->conf.max_keep_alive_requests) { - reuse = 0; - } - if (reuse && sess->recv->is_closed) { - sess->proxy_con->state = PROXY_CONNECTION_STATE_IDLE; - - /* make sure backend is active since we have a free connection. */ - sess->proxy_backend->state = PROXY_BACKEND_STATE_ACTIVE; - - /* don't ignore events as the FD is idle - * we might get a HUP as the remote connection might close */ - fdevent_event_del(srv->ev, sess->proxy_con->sock); - fdevent_unregister(srv->ev, sess->proxy_con->sock); - - fdevent_register(srv->ev, sess->proxy_con->sock, proxy_handle_fdevent_idle, sess->proxy_con); - fdevent_event_add(srv->ev, sess->proxy_con->sock, FDEVENT_IN); - - break; - } - - /* fall-through for non-keep-alive or response parsing didn't finish */ - - case PROXY_CONNECTION_STATE_CLOSED: - /* user terminated connection and proxy connection is still in CONNECTING mode. - * this happens frequently when backends are slow - */ - case PROXY_CONNECTION_STATE_CONNECTING: - proxy_remove_backend_connection(srv, sess); - case PROXY_CONNECTION_STATE_IDLE: - default: - break; - } - sess->proxy_con = NULL; - } - - /* wake up a connection from the backlog */ - if ((req = proxy_backlog_shift(p->conf.backlog))) { - connection *next_con = req->con; - - if (p->conf.debug) TRACE("wakeup a connection from backlog: con=%d", next_con->sock->fd); - joblist_append(srv, next_con); - - COUNTER_DEC(p->conf.backlog_size); - proxy_request_free(req); - } - - return HANDLER_GO_ON; -} - -/** - * push the session into the backlog - * - * @returns HANDLER_ERROR in case we reach the max-connect-retry limit - */ -static handler_t mod_proxy_core_backlog_connection(server *srv, connection *con, plugin_data *p, proxy_session *sess) { - proxy_request *req; - - if (sess->sent_to_backlog >= p->conf.max_backlog_size) { - return HANDLER_ERROR; - } - - /* connection pool is full, queue the request for now */ - req = proxy_request_init(); - req->added_ts = srv->cur_ts; - req->con = con; - - proxy_backlog_push(p->conf.backlog, req); - - COUNTER_INC(p->conf.backlog_size); - sess->sent_to_backlog++; - - return HANDLER_GO_ON; -} - -/** - * build the request-header array and call the backend specific request formater - * to fill the chunkqueue - */ -static int proxy_get_request_header(server *srv, connection *con, plugin_data *p, proxy_session *sess) { - /* request line */ - const char *remote_ip; - size_t i; - - remote_ip = inet_ntop_cache_get_ip(srv, &(con->dst_addr)); - array_append_key_value(sess->request_headers, CONST_STR_LEN("X-Forwarded-For"), remote_ip, strlen(remote_ip)); - - /* http_host is NOT is just a pointer to a buffer - * which is NULL if it is not set */ - if (con->request.http_host && - !buffer_is_empty(con->request.http_host)) { - array_set_key_value(sess->request_headers, CONST_STR_LEN("X-Host"), CONST_BUF_LEN(con->request.http_host)); - } - if (con->conf.is_ssl) { - array_set_key_value(sess->request_headers, CONST_STR_LEN("X-Forwarded-Proto"), CONST_STR_LEN("https")); - } else { - array_set_key_value(sess->request_headers, CONST_STR_LEN("X-Forwarded-Proto"), CONST_STR_LEN("http")); - } - - /* request header */ - for (i = 0; i < con->request.headers->used; i++) { - data_string *ds; - size_t k; - - ds = (data_string *)con->request.headers->data[i]; - - if (buffer_is_empty(ds->value) || buffer_is_empty(ds->key)) continue; - - if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Connection"))) continue; - if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Keep-Alive"))) continue; - if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Expect"))) continue; -#ifdef HAVE_PCRE_H - for (k = 0; k < p->conf.request_rewrites->used; k++) { - proxy_rewrite *rw = p->conf.request_rewrites->ptr[k]; - - if (buffer_is_equal(rw->header, ds->key)) { - int ret; - - if ((ret = pcre_replace(rw->regex, rw->replace, ds->value, p->replace_buf)) < 0) { - switch (ret) { - case PCRE_ERROR_NOMATCH: - /* hmm, ok. no problem */ - array_set_key_value(sess->request_headers, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value)); - break; - default: - TRACE("oops, pcre_replace failed with: %d", ret); - break; - } - } else { - array_set_key_value(sess->request_headers, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(p->replace_buf)); - } - - break; - } - } - - if (k == p->conf.request_rewrites->used) { - array_set_key_value(sess->request_headers, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value)); - } -#else - array_set_key_value(sess->request_headers, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value)); -#endif - } - - /* populate sess->request_uri with the actually requested path - * (con->request.uri). if we have pcre and there is a _uri request - * rewrite, it will be overwritten later - */ - buffer_copy_string_buffer(sess->request_uri, con->request.uri); - - /* check if we want to rewrite the uri */ -#ifdef HAVE_PCRE_H - for (i = 0; i < p->conf.request_rewrites->used; i++) { - proxy_rewrite *rw = p->conf.request_rewrites->ptr[i]; - - if (buffer_is_equal_string(rw->header, CONST_STR_LEN("_uri"))) { - int ret; - - if ((ret = pcre_replace(rw->regex, rw->replace, con->request.uri, p->replace_buf)) < 0) { - switch (ret) { - case PCRE_ERROR_NOMATCH: - /* hmm, ok. no problem */ - buffer_copy_string_buffer(sess->request_uri, con->request.uri); - break; - default: - TRACE("oops, pcre_replace failed with: %d", ret); - break; - } - } else { - buffer_copy_string_buffer(sess->request_uri, p->replace_buf); - } - } else if (buffer_is_equal_string(rw->header, CONST_STR_LEN("_docroot"))) { - int ret; - - if ((ret = pcre_replace(rw->regex, rw->replace, con->physical.doc_root, p->replace_buf)) < 0) { - switch (ret) { - case PCRE_ERROR_NOMATCH: - /* hmm, ok. no problem */ - break; - default: - TRACE("oops, pcre_replace failed with: %d", ret); - break; - } - } else { - /* adjust DOCUMENT_ROOT */ - buffer_copy_string_buffer(con->physical.doc_root, p->replace_buf); - - /* adjust SCRIPT_FILENAME */ - buffer_copy_string_buffer(con->physical.path, p->replace_buf); - buffer_append_string_buffer(con->physical.path, con->physical.rel_path); - } - } else if (buffer_is_equal_string(rw->header, CONST_STR_LEN("_pathinfo"))) { - int ret; - - if ((ret = pcre_replace(rw->regex, rw->replace, con->uri.path, p->replace_buf)) < 0) { - switch (ret) { - case PCRE_ERROR_NOMATCH: - /* hmm, ok. no problem */ - break; - default: - TRACE("oops, pcre_replace failed with: %d", ret); - break; - } - } else { - /* we matched, cool. */ - buffer_copy_string_buffer(con->request.pathinfo, p->replace_buf); - } - } else if (buffer_is_equal_string(rw->header, CONST_STR_LEN("_scriptname"))) { - int ret; - - if ((ret = pcre_replace(rw->regex, rw->replace, con->uri.path, p->replace_buf)) < 0) { - switch (ret) { - case PCRE_ERROR_NOMATCH: - /* hmm, ok. no problem */ - break; - default: - TRACE("oops, pcre_replace failed with: %d", ret); - break; - } - } else { - /* we matched, cool. */ - buffer_copy_string_buffer(con->uri.path, p->replace_buf); - } - - } - } -#endif - - proxy_encode_request_headers(srv, sess, con->recv); - - return 0; -} - -/* we are event-driven - * - * the first entry is connect() call, if the doesn't need a event - * - * a bit boring - * - connect (+ delayed connect) - * - write header + content - * - read header + content - * - * as soon as have read the response header we switch con->file_started and return HANDLER_GO_ON to - * tell the core we are ready to stream out the content. - * */ -static handler_t proxy_state_engine(server *srv, connection *con, plugin_data *p, proxy_session *sess) { - /* do we have a connection ? */ - - if (p->conf.debug > 0) - TRACE("proxy_state_engine: state=%d", sess->state); - - switch (sess->state) { - case PROXY_STATE_UNSET: - /* we are not started yet */ - sess->connect_start_ts = srv->cur_ts; - switch(proxy_connection_connect(sess->proxy_con)) { - case HANDLER_WAIT_FOR_EVENT: - /* waiting on the connect call */ - - fdevent_register(srv->ev, sess->proxy_con->sock, proxy_handle_fdevent, sess); - fdevent_event_add(srv->ev, sess->proxy_con->sock, FDEVENT_OUT); - - sess->state = PROXY_STATE_CONNECTING; - sess->proxy_con->state = PROXY_CONNECTION_STATE_CONNECTING; - /** - * if we are in - * - * connect(...) = -1 EINPROGRESS - * - * it might take ages until we get a response - */ - sess->proxy_con->state_ts = srv->cur_ts; - sess->proxy_con->proxy_sess = sess; - - /* if the client connection closes its end get notified */ - fdevent_event_add(srv->ev, con->sock, FDEVENT_HUP); - - return HANDLER_WAIT_FOR_EVENT; - case HANDLER_GO_ON: - /* we are connected */ - sess->state = PROXY_STATE_CONNECTED; - sess->proxy_con->state = PROXY_CONNECTION_STATE_CONNECTED; - - /* initialize stream. */ - proxy_stream_init(srv, sess); - - fdevent_register(srv->ev, sess->proxy_con->sock, proxy_handle_fdevent, sess); - - break; - case HANDLER_WAIT_FOR_FD: - /* we have to come back later when we have a fd */ - return HANDLER_WAIT_FOR_FD; - case HANDLER_ERROR: - /* there is no-one on the other side */ - sess->proxy_con->address->disabled_until = srv->cur_ts + p->conf.disable_time; - - TRACE("connecting to address %s (%p) failed, disabling for %u sec", - SAFE_BUF_STR(sess->proxy_con->address->name), - (void*) sess->proxy_con->address, - (unsigned int) p->conf.disable_time); - COUNTER_INC(sess->proxy_backend->requests_failed); - - sess->proxy_con->address->state = PROXY_ADDRESS_STATE_DISABLED; - sess->proxy_backend->disabled_addresses++; - /* if all addresses in address_pool are disabled, then disable this backend. */ - - if (sess->proxy_backend->disabled_addresses == sess->proxy_backend->address_pool->used) { - sess->proxy_backend->state = PROXY_BACKEND_STATE_DISABLED; - } - /* try another backend instead */ - return HANDLER_COMEBACK; - default: - /* not good, something failed */ - return HANDLER_ERROR; - - } - - /* fall through */ - case PROXY_STATE_CONNECTING: - /* skip if already connected */ - if (sess->state == PROXY_STATE_CONNECTING) { - int socket_error; - socklen_t socket_error_len = sizeof(socket_error); - - fdevent_event_del(srv->ev, sess->proxy_con->sock); - - switch (sess->proxy_con->state) { - case PROXY_CONNECTION_STATE_CONNECTING: - if (0 != getsockopt(sess->proxy_con->sock->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) { - ERROR("getsockopt failed: %s", strerror(errno)); - - return HANDLER_ERROR; - } - if (socket_error != 0) { - switch (socket_error) { - case ECONNREFUSED: - /* there is no-one on the other side */ - sess->proxy_con->address->disabled_until = srv->cur_ts + p->conf.disable_time; - - TRACE("address %s refused us, disabling for %u sec", sess->proxy_con->address->name->ptr, (unsigned int) p->conf.disable_time); - COUNTER_INC(sess->proxy_backend->requests_failed); - - break; - case EHOSTUNREACH: - /* there is no-one on the other side */ - sess->proxy_con->address->disabled_until = srv->cur_ts + p->conf.disable_time; - - TRACE("host %s is unreachable, disabling for %u sec", sess->proxy_con->address->name->ptr, (unsigned int) p->conf.disable_time); - break; - default: - sess->proxy_con->address->disabled_until = srv->cur_ts + p->conf.disable_time; - - TRACE("connected finally failed: %s (%d)", strerror(socket_error), socket_error); - - TRACE("connect to address %s failed and I don't know why, disabling for %u sec", sess->proxy_con->address->name->ptr, (unsigned int) p->conf.disable_time); - - break; - } - - sess->proxy_con->address->state = PROXY_ADDRESS_STATE_DISABLED; - sess->proxy_backend->disabled_addresses++; - /* if all addresses in address_pool are disabled, then disable this backend. */ - if (sess->proxy_backend->disabled_addresses == sess->proxy_backend->address_pool->used) { - sess->proxy_backend->state = PROXY_BACKEND_STATE_DISABLED; - } - return HANDLER_COMEBACK; - } - - sess->state = PROXY_STATE_CONNECTED; - sess->proxy_con->state = PROXY_CONNECTION_STATE_CONNECTED; - - /* initialize stream. */ - proxy_stream_init(srv, sess); - - break; - case PROXY_CONNECTION_STATE_CLOSED: - /* looks like the connect() timed out */ - sess->proxy_con->address->disabled_until = srv->cur_ts + p->conf.disable_time; - sess->proxy_con->address->state = PROXY_ADDRESS_STATE_DISABLED; - - /* the connection */ - sess->proxy_con->state = PROXY_CONNECTION_STATE_CLOSED; - - /* if all addresses in address_pool are disabled, then disable this backend. */ - sess->proxy_backend->disabled_addresses++; - - if (sess->proxy_backend->disabled_addresses == sess->proxy_backend->address_pool->used) { - sess->proxy_backend->state = PROXY_BACKEND_STATE_DISABLED; - } - TRACE("connect(%s) to failed: trying another backed", - SAFE_BUF_STR(sess->proxy_con->address->name)); - return HANDLER_COMEBACK; - default: - ERROR("invalid connection-state: %d", sess->proxy_con->state); - break; - } - } - - /* fall through */ - case PROXY_STATE_CONNECTED: - - sess->state = PROXY_STATE_WRITE_REQUEST_HEADER; - - /* fall through */ - case PROXY_STATE_WRITE_REQUEST_HEADER: - /* build the header */ - proxy_get_request_header(srv, con, p, sess); - - /* send the header together with the body */ - sess->state = PROXY_STATE_WRITE_REQUEST_BODY; - - /* fall through */ - case PROXY_STATE_WRITE_REQUEST_BODY: - /* do we have a content-body to send up to the backend ? */ - - switch (proxy_stream_encode_decode(srv, sess)) { - case HANDLER_FINISHED: - case HANDLER_GO_ON: - /* some backends will send a response before all the request content has been written. */ - if (sess->is_closed || sess->have_response_headers) { - chunk *c; - if (sess->is_closed && !sess->have_response_headers) { - if (sess->p->conf.debug) TRACE("%s", "connection to backend closed when sending request headers/content."); - } - if (con->recv->bytes_out < con->recv->bytes_in) { - /* we have to consume all the request content data. */ - for (c = con->recv->first; c; c = c->next) { - switch(c->type) { - case MEM_CHUNK: - c->offset = c->mem->used - 1; - break; - case FILE_CHUNK: - c->offset = c->file.length; - break; - default: - break; - } - } - con->recv->bytes_out = con->recv->bytes_in; - con->recv->is_closed = 1; - } - break; - } - return HANDLER_WAIT_FOR_EVENT; - case HANDLER_ERROR: - /* error */ - return HANDLER_ERROR; - default: - TRACE("stream-decoder: %s", "foo"); - break; - } - - sess->state = PROXY_STATE_READ_RESPONSE_HEADER; - /* fall through */ - case PROXY_STATE_READ_RESPONSE_HEADER: - - /* decode/encode stream. */ - switch (proxy_stream_encode_decode(srv, sess)) { - case HANDLER_FINISHED: - case HANDLER_GO_ON: - if (!sess->proxy_con->recv->is_closed && !sess->have_response_headers) { - return HANDLER_WAIT_FOR_EVENT; - } - break; - case HANDLER_ERROR: - /* error */ - return HANDLER_ERROR; - default: - TRACE("stream-decoder: %s", "foo"); - return HANDLER_ERROR; - } - - if (sess->have_response_headers) { - /* handle the parsed response headers. */ - switch (proxy_handle_response_headers(srv, con, p, sess, sess->recv)) { - case HANDLER_FINISHED: - case HANDLER_GO_ON: - break; - case HANDLER_ERROR: - /* bad gateway */ - con->http_status = 502; - return HANDLER_FINISHED; - default: - ERROR("%s", "++ oops, something went wrong while parsing response headers"); - con->http_status = 500; /* Internal Server Error */ - return HANDLER_ERROR; - } - } - - if (!sess->is_closed && !sess->have_response_headers) { - if (sess->proxy_con->recv->bytes_in == 0) { - /* the connection went away before we got something back */ - if (p->conf.debug) TRACE("%s", "connection closed while reading the response headers"); - - if (con->request.content_length <= 0) { - /** - * we might run into a 'race-condition' - * - * 1. proxy-con is keep-alive, idling and just being closed (FDEVENT_IN) [fd=27] - * 2. new connection comes in, we use the idling connection [fd=14] - * 3. we write(), successful [to fd=27] - * 3. we read() ... and finally receive the close-event for the connection - */ - - return HANDLER_COMEBACK; - } else { - ERROR("%s", "request content length > 0 can't restart request."); - } - } else { - ERROR("%s", "connection closed after reading part of the response headers."); - } - - con->http_status = 500; /* Internal Server Error */ - return HANDLER_ERROR; - } else if (sess->do_internal_redirect) { - /* no more response data to process. do redirect now. */ - if (sess->recv->is_closed) { - sess->state = PROXY_STATE_FINISHED; - /* now it becomes tricky - * - * mod_staticfile should handle this file for us - * con->mode = DIRECT is taking us out of the loop */ - con->mode = DIRECT; - con->http_status = 0; - - return HANDLER_COMEBACK; - } else { - /* finish processing response data, so we can re-use backend connection. */ - sess->state = PROXY_STATE_READ_RESPONSE_BODY; - } - } else { - con->file_started = 1; - /* if Status: ... is not set, 200 is our default status-code */ - if (con->http_status == 0) con->http_status = 200; - - /* check if all the response content has been read/decoded. */ - if (sess->bytes_read == sess->content_length) { - sess->is_request_finished = 1; - /* close response content chunkqueue */ - sess->recv->is_closed = 1; - } - /* copy any response content we might have. */ - proxy_copy_response(srv, con, sess); - - sess->state = PROXY_STATE_READ_RESPONSE_BODY; - - /** - * set the event to pass the content through to the server - * - * this triggers the event-handler - * @see proxy_handle_fdevent - */ - - return HANDLER_GO_ON; /* tell http_response_prepare that we are done with the header */ - } - - if (sess->state != PROXY_STATE_READ_RESPONSE_BODY) break; - case PROXY_STATE_READ_RESPONSE_BODY: - - switch (proxy_stream_encode_decode(srv, sess)) { - case HANDLER_FINISHED: - case HANDLER_GO_ON: - break; - case HANDLER_ERROR: - /* error */ - return HANDLER_ERROR; - default: - TRACE("stream-decoder: %s", "foo"); - break; - } - - proxy_copy_response(srv, con, sess); - - if (!sess->proxy_con->recv->is_closed && !sess->is_request_finished) { - return HANDLER_WAIT_FOR_EVENT; - } - - if(sess->is_request_finished) { - sess->recv->is_closed = 1; - con->send->is_closed = 1; - /* recycle proxy connection. */ - proxy_recycle_backend_connection(srv, p, sess); - - sess->state = PROXY_STATE_FINISHED; - - if (sess->do_internal_redirect) { - /* now it becomes tricky - * - * mod_staticfile should handle this file for us - * con->mode = DIRECT is taking us out of the loop */ - con->send->is_closed = 0; - con->mode = DIRECT; - con->http_status = 0; - - return HANDLER_COMEBACK; - } - } - - /* we wrote something into the the send-buffers, - * call the connection-handler to push it to the client */ - joblist_append(srv, con); - - break; - default: - break; - } - - return HANDLER_GO_ON; -} - -static proxy_backend *proxy_find_backend(server *srv, connection *con, plugin_data *p, buffer *name) { - size_t i; - - UNUSED(srv); - UNUSED(con); - - for (i = 0; i < p->conf.backends->used; i++) { - proxy_backend *backend = p->conf.backends->ptr[i]; - - if (buffer_is_equal(backend->name, name)) { - return backend; - } - } - - return NULL; -} - -/** - * choose an available backend - * - */ -static proxy_backend *proxy_backend_balancer(server *srv, connection *con, proxy_session *sess) { - size_t i; - plugin_data *p = sess->p; - proxy_backends *backends = p->conf.backends; - unsigned long last_max; /* for the HASH balancer */ - proxy_backend *backend = NULL, *cur_backend = NULL; - int active_backends = 0, rand_ndx; - size_t min_used; - - UNUSED(srv); - - /* if we only have one backend just return it. */ - if (backends->used == 1) { - backend = backends->ptr[0]; - - return backend->state == PROXY_BACKEND_STATE_ACTIVE ? backend : NULL; - } - - /* frist try to select backend based on sticky session. */ - if (sess->sticky_session) { - /* find backend */ - backend = proxy_find_backend(srv, con, p, sess->sticky_session); - if (NULL != backend) return backend; - } - - /* apply balancer algorithm to select backend. */ - switch(p->conf.balancer) { - case PROXY_BALANCE_CARP: - /* hash balancing */ - - for (i = 0, last_max = ULONG_MAX; i < backends->used; i++) { - unsigned long cur_max; - - cur_backend = backends->ptr[i]; - - if (cur_backend->state != PROXY_BACKEND_STATE_ACTIVE) continue; - - cur_max = generate_crc32c(CONST_BUF_LEN(con->uri.path)) + - generate_crc32c(CONST_BUF_LEN(cur_backend->name)) + /* we can cache this */ - generate_crc32c(CONST_BUF_LEN(con->uri.authority)); -#if 0 - TRACE("hash-election: %s - %s - %s: %ld", - con->uri.path->ptr, - cur_backend->name->ptr, - con->uri.authority->ptr, - cur_max); -#endif - if (backend == NULL || (cur_max > last_max)) { - last_max = cur_max; - - backend = cur_backend; - } - } - - break; - case PROXY_BALANCE_STATIC: - /* static (only fail-over) */ - - for (i = 0; i < backends->used; i++) { - cur_backend = backends->ptr[i]; - - if (cur_backend->state != PROXY_BACKEND_STATE_ACTIVE) continue; - - backend = cur_backend; - break; - } - - break; - case PROXY_BALANCE_SQF: - /* shortest-queue-first balancing */ - - for (i = 0, min_used = SIZE_MAX; i < backends->used; i++) { - cur_backend = backends->ptr[i]; - - if (cur_backend->state != PROXY_BACKEND_STATE_ACTIVE) continue; - - /* the backend is up, use it */ - if (cur_backend->pool->used < min_used ) { - backend = cur_backend; - min_used = cur_backend->pool->used; - } - } - - break; - case PROXY_BALANCE_UNSET: /* if not set, use round-robin as default */ - case PROXY_BALANCE_RR: - /* round robin */ - - /** - * instead of real RoundRobin we just do a RandomSelect - * - * it is state-less and has the same distribution - */ - - active_backends = 0; - - for (i = 0; i < backends->used; i++) { - cur_backend = backends->ptr[i]; - - if (cur_backend->state != PROXY_BACKEND_STATE_ACTIVE) continue; - - active_backends++; - } - - rand_ndx = (int) (1.0 * active_backends * rand()/(RAND_MAX)); - - active_backends = 0; - for (i = 0; i < backends->used; i++) { - cur_backend = backends->ptr[i]; - - if (cur_backend->state != PROXY_BACKEND_STATE_ACTIVE) continue; - - backend = cur_backend; - - if (rand_ndx == active_backends++) break; - } - - break; - } - - return backend; -} - -/** - * choose an available address from the address-pool - * - * the backend has different balancers - */ -static proxy_address *proxy_address_balancer(server *srv, connection *con, proxy_session *sess) { - size_t i; - proxy_backend *backend = sess->proxy_backend; - proxy_address_pool *address_pool = backend->address_pool; - unsigned long last_max; /* for the HASH balancer */ - proxy_address *address = NULL, *cur_address = NULL; - int active_addresses = 0, rand_ndx; - size_t min_used; - - UNUSED(srv); - - /* if we only have one address just return it. */ - if (address_pool->used == 1) { - address = address_pool->ptr[0]; - - return address->state == PROXY_ADDRESS_STATE_ACTIVE ? address : NULL; - } - - /* apply balancer algorithm to select address. */ - switch(backend->balancer) { - case PROXY_BALANCE_CARP: - /* hash balancing */ - - for (i = 0, last_max = ULONG_MAX; i < address_pool->used; i++) { - unsigned long cur_max; - - cur_address = address_pool->ptr[i]; - - if (cur_address->state != PROXY_ADDRESS_STATE_ACTIVE) continue; - - cur_max = generate_crc32c(CONST_BUF_LEN(con->uri.path)) + - generate_crc32c(CONST_BUF_LEN(cur_address->name)) + /* we can cache this */ - generate_crc32c(CONST_BUF_LEN(con->uri.authority)); -#if 0 - TRACE("hash-election: %s - %s - %s: %ld", - con->uri.path->ptr, - cur_address->name->ptr, - con->uri.authority->ptr, - cur_max); -#endif - if (address == NULL || (cur_max > last_max)) { - last_max = cur_max; - - address = cur_address; - } - } - - break; - case PROXY_BALANCE_STATIC: - /* static (only fail-over) */ - - for (i = 0; i < address_pool->used; i++) { - cur_address = address_pool->ptr[i]; - - if (cur_address->state != PROXY_ADDRESS_STATE_ACTIVE) continue; - - address = cur_address; - break; - } - - break; - case PROXY_BALANCE_SQF: - /* shortest-queue-first balancing */ - - for (i = 0, min_used = SIZE_MAX; i < address_pool->used; i++) { - cur_address = address_pool->ptr[i]; - - if (cur_address->state != PROXY_ADDRESS_STATE_ACTIVE) continue; - - /* the address is up, use it */ - if (cur_address->used < min_used ) { - address = cur_address; - min_used = cur_address->used; - } - } - - break; - case PROXY_BALANCE_UNSET: /* if not set, use round-robin as default */ - case PROXY_BALANCE_RR: - /* round robin */ - - /** - * instead of real RoundRobin we just do a RandomSelect - * - * it is state-less and has the same distribution - */ - - active_addresses = 0; - - for (i = 0; i < address_pool->used; i++) { - cur_address = address_pool->ptr[i]; - - if (cur_address->state != PROXY_ADDRESS_STATE_ACTIVE) continue; - - active_addresses++; - } - - rand_ndx = (int) (1.0 * active_addresses * rand()/(RAND_MAX)); - - active_addresses = 0; - for (i = 0; i < address_pool->used; i++) { - cur_address = address_pool->ptr[i]; - - if (cur_address->state != PROXY_ADDRESS_STATE_ACTIVE) continue; - - address = cur_address; - - if (rand_ndx == active_addresses++) break; - } - - break; - } - - return address; -} - -static int mod_proxy_core_patch_connection(server *srv, connection *con, plugin_data *p) { - size_t i, j; - plugin_config *s = p->config_storage[0]; - - /* global defaults */ - PATCH_OPTION(balancer); - PATCH_OPTION(debug); - PATCH_OPTION(backends); - PATCH_OPTION(backlog); - PATCH_OPTION(backlog_size); - PATCH_OPTION(protocol); - PATCH_OPTION(request_rewrites); - PATCH_OPTION(response_rewrites); - PATCH_OPTION(allow_x_sendfile); - PATCH_OPTION(allow_x_rewrite); - PATCH_OPTION(max_pool_size); - PATCH_OPTION(check_local); - PATCH_OPTION(split_hostnames); - PATCH_OPTION(max_keep_alive_requests); - PATCH_OPTION(disable_time); - PATCH_OPTION(max_backlog_size); - - /* skip the first, the global context */ - for (i = 1; i < srv->config_context->used; i++) { - data_config *dc = (data_config *)srv->config_context->data[i]; - s = p->config_storage[i]; - - /* condition didn't match */ - if (!config_check_cond(srv, con, dc)) continue; - - /* merge config */ - for (j = 0; j < dc->value->used; j++) { - data_unset *du = dc->value->data[j]; - - if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_BACKENDS))) { - PATCH_OPTION(backends); - PATCH_OPTION(backlog); - PATCH_OPTION(backlog_size); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_DEBUG))) { - PATCH_OPTION(debug); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_BALANCER))) { - PATCH_OPTION(balancer); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_PROTOCOL))) { - PATCH_OPTION(protocol); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_REWRITE_REQUEST))) { - PATCH_OPTION(request_rewrites); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_REWRITE_RESPONSE))) { - PATCH_OPTION(response_rewrites); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_ALLOW_X_SENDFILE))) { - PATCH_OPTION(allow_x_sendfile); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_ALLOW_X_REWRITE))) { - PATCH_OPTION(allow_x_rewrite); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_MAX_POOL_SIZE))) { - PATCH_OPTION(max_pool_size); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_CHECK_LOCAL))) { - PATCH_OPTION(check_local); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_SPLIT_HOSTNAMES))) { - PATCH_OPTION(split_hostnames); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_MAX_KEEP_ALIVE))) { - PATCH_OPTION(max_keep_alive_requests); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_DISABLE_TIME))) { - PATCH_OPTION(disable_time); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_PROXY_CORE_MAX_BACKLOG_SIZE))) { - PATCH_OPTION(max_backlog_size); - } - } - } - - return 0; -} - -static int mod_proxy_core_check_match(server *srv, connection *con, plugin_data *p, int file_match) { - proxy_session *sess = con->plugin_ctx[p->id]; - buffer *path; - - if (sess && !sess->do_new_session) { - /* if this is the second round, sess is already prepared */ - return HANDLER_GO_ON; - } - - /* check if we have a matching conditional for this request */ - mod_proxy_core_patch_connection(srv, con, p); - - /* no proxy backends to handle this request. */ - if (p->conf.backends->used == 0) return HANDLER_GO_ON; -#if 0 - /* if check_local is enabled, then wait for file match. */ - if (file_match != p->conf.check_local) return HANDLER_GO_ON; -#endif - path = file_match ? con->physical.path : con->uri.path; -#if 0 - if (buffer_is_empty(path)) return HANDLER_GO_ON; -#endif - if (sess && sess->do_x_rewrite_backend) { - proxy_backend *backend; - buffer *sticky_session = sess->sticky_session; - - sess->sticky_session = NULL; - /* clear old session state */ - proxy_session_reset(sess); - - /* find backend */ - backend = proxy_find_backend(srv, con, p, sticky_session); - if (backend == NULL) { - backend = proxy_backend_init(); - - backend->balancer = p->conf.balancer; - backend->protocol = p->conf.protocol; - - buffer_copy_string_buffer(backend->name, sticky_session); - /* check if the new backend has a valid backend-address */ - if (0 == proxy_address_pool_add_string(backend->address_pool, backend->name)) { - if (p->conf.max_pool_size) { - backend->pool->max_size = p->conf.max_pool_size; - } - - proxy_backends_add(p->conf.backends, backend); - } else { - proxy_backend_free(backend); - backend = NULL; - } - } - /* no backend available. */ - if (NULL == backend) { - buffer_free(sticky_session); - return HANDLER_GO_ON; - } - sess->proxy_backend = backend; - sess->sticky_session = sticky_session; - } else if (sess) { - proxy_session_reset(sess); - } - - /* make sure we have a protocol. */ - if (p->conf.protocol == NULL) { - con->http_status = 500; /* internal error */ - con->send->is_closed = 1; - - TRACE("proxy-core.backends is set, but proxy-core.protocol is not, can't handle '%s' for host '%s'", - SAFE_BUF_STR(con->uri.path), - SAFE_BUF_STR(con->uri.authority)); - return HANDLER_FINISHED; - } - - if (!sess) { - /* a session lives for a single request */ - sess = proxy_session_init(); - } - /* make sure the state is correct. */ - sess->state = PROXY_STATE_UNSET; - - con->plugin_ctx[p->id] = sess; - con->mode = p->id; - - if (con->conf.log_request_handling) { - TRACE("handling it in mod_proxy_core: %s.path=%s", - file_match ? "physical" : "uri", SAFE_BUF_STR(path)); - } - sess->p = p; - sess->remote_con = con; - - return HANDLER_FINISHED; -} - -SUBREQUEST_FUNC(mod_proxy_core_match_url) { - return mod_proxy_core_check_match(srv, con, p_d, 0); -} - -SUBREQUEST_FUNC(mod_proxy_core_match_local_file) { - return mod_proxy_core_check_match(srv, con, p_d, 1); -} - -/** - * end of a request - */ -REQUESTDONE_FUNC(mod_proxy_connection_close_callback) { - plugin_data *p = p_d; - proxy_session *sess = con->plugin_ctx[p->id]; - - if (!sess) return HANDLER_GO_ON; - - /* TODO: copy p->conf into proxy_session when request starts. */ - /* re-patch p->conf */ - mod_proxy_core_patch_connection(srv, con, p); - - if (p->conf.debug) TRACE("proxy_connection_reset (%d)", con->sock->fd); - - if (sess->proxy_con) { - proxy_recycle_backend_connection(srv, p, sess); - } else { - /* if we have the connection in the backlog, remove it */ - if (0 == proxy_backlog_remove_connection(p->conf.backlog, con)) { - COUNTER_DEC(p->conf.backlog_size); - } - } - - /* cleanup proxy session */ - proxy_session_free(sess); - - con->plugin_ctx[p->id] = NULL; - - return HANDLER_GO_ON; -} - -CONNECTION_FUNC(mod_proxy_core_start_backend) { - plugin_data *p = p_d; - proxy_session *sess = con->plugin_ctx[p->id]; - - if (p->id != con->mode) return HANDLER_GO_ON; - if (!sess) return HANDLER_GO_ON; - - /* TODO: copy p->conf into proxy_session when request starts. */ - /* re-patch p->conf */ - mod_proxy_core_patch_connection(srv, con, p); - - /* - * 0. build session - * 1. get a proxy connection - * 2. create the http-request header - * 3. stream the content to the backend - * 4. wait for http-response header - * 5. decode the response + parse the response - * 6. stream the response-content to the client - * 7. session finished wait for request close - * 8. kill session - * */ - - if (sess->do_internal_redirect) { - if (sess->internal_redirect_count > MAX_INTERNAL_REDIRECTS) { - /* we already handled this request and sent it to the static file handling */ - - return HANDLER_GO_ON; - } - } - - switch (sess->state) { - case PROXY_STATE_FINISHED: - return HANDLER_GO_ON; - case PROXY_STATE_CONNECTING: - /* this connections has waited 10 seconds to connect to the backend - * and didn't got a successful connection yet, sending timeout */ - if (srv->cur_ts - sess->connect_start_ts > 10) { - con->http_status = 504; /* gateway timeout */ - con->send->is_closed = 1; - - if (sess->proxy_con) { - TRACE("connect to backend timed out: %s", SAFE_BUF_STR(sess->proxy_con->address->name)); - /* if we are waiting for a proxy-connection right now, close it */ - proxy_remove_backend_connection(srv, sess); - } else { - TRACE("%s", "timed out when trying to connect to backend and don't have a connection."); - } - - return HANDLER_FINISHED; - } - default: - /* handle-request-timeout, */ -#if 0 - if (srv->cur_ts - con->request_start > 60) { - TRACE("request runs longer than 60sec: current state: %d", sess->state); - } -#endif - break; - } - - - /* if the WRITE fails from the start, restart the connection */ - while (1) { - if (sess->proxy_con == NULL) { - proxy_address *address = NULL; - - /** - * ask the balancer for the next address and - * check the connection pool if we have a connection open - * for that address - */ - if (NULL == (sess->proxy_backend = proxy_backend_balancer(srv, con, sess))) { - if (p->conf.debug) TRACE("backlog: all backends are full or down, putting %s (%d) into the backlog, retry = %d", - SAFE_BUF_STR(con->uri.path), con->sock->fd, sess->sent_to_backlog + 1); - - /* no backends available right now. */ - if (HANDLER_ERROR == mod_proxy_core_backlog_connection(srv, con, p, sess)) { - con->http_status = 504; /* gateway timeout */ - con->send->is_closed = 1; - - TRACE("connecting backends timed out, retry limit reached: %d", sess->sent_to_backlog); - return HANDLER_FINISHED; - } - - /* no, not really an event, - * we just want to block the outer loop from stepping forward - * - * the trigger will bring this connection back into the game - */ - return HANDLER_WAIT_FOR_EVENT; - } - - sess->proxy_backend->protocol = p->conf.protocol; - sess->proxy_backend->balancer = p->conf.balancer; - - if (p->conf.debug && sess->proxy_backend) { - TRACE("selected backend: %s, state: %d", SAFE_BUF_STR(sess->proxy_backend->name), sess->proxy_backend->state); - } - - /** - * ask the balancer for the next address and - * check the connection pool if we have a connection open - * for that address - */ - if (NULL == (address = proxy_address_balancer(srv, con, sess))) { - /* no addresses available for this backend right now. */ - sess->proxy_backend->state = PROXY_BACKEND_STATE_DISABLED; /* disable backend */ - TRACE("backlog: all addresses are down, putting %s (%d) into the backlog, retry = %d", - SAFE_BUF_STR(con->uri.path), con->sock->fd, sess->sent_to_backlog + 1); - - if (HANDLER_ERROR == mod_proxy_core_backlog_connection(srv, con, p, sess)) { - con->http_status = 504; /* gateway timeout */ - con->send->is_closed = 1; - - TRACE("connecting backends timed out, retry limit reached: %d", sess->sent_to_backlog); - return HANDLER_FINISHED; - } - - /* no, not really an event, - * we just want to block the outer loop from stepping forward - * - * the trigger will bring this connection back into the game - */ - - return HANDLER_WAIT_FOR_EVENT; - } - - if (PROXY_CONNECTIONPOOL_FULL == proxy_connection_pool_get_connection( - sess->proxy_backend->pool, address, &(sess->proxy_con))) { - /* all connections are busy. */ - sess->proxy_backend->state = PROXY_BACKEND_STATE_FULL; - - if (p->conf.debug) TRACE("backlog: the con-pool is full, putting %s (%d) into the backlog", SAFE_BUF_STR(con->uri.path), con->sock->fd); - if (HANDLER_ERROR == mod_proxy_core_backlog_connection(srv, con, p, sess)) { - con->http_status = 504; /* gateway timeout */ - con->send->is_closed = 1; - - TRACE("connecting backends timed out, retry limit reached: %d", sess->sent_to_backlog); - return HANDLER_FINISHED; - } - - /* no, not really an event, - * we just want to block the outer loop from stepping forward - * - * the trigger will bring this connection back into the game - */ - return HANDLER_WAIT_FOR_EVENT; - } - COUNTER_SET(sess->proxy_backend->pool_size, sess->proxy_backend->pool->used); - COUNTER_INC(sess->proxy_backend->load); - - /* need to reset flags. */ - sess->is_closing = 0; - sess->is_closed = 0; - /* a fresh connection, we need address for it */ - if (sess->proxy_con->state == PROXY_CONNECTION_STATE_CONNECTING) { - sess->state = PROXY_STATE_UNSET; - sess->bytes_read = 0; - } else { - /* we are already connected */ - sess->state = PROXY_STATE_CONNECTED; - - /* the connection was idling and using the fdevent_idle-handler - * switch it back to the normal proxy-event-handler */ - fdevent_event_del(srv->ev, sess->proxy_con->sock); - fdevent_unregister(srv->ev, sess->proxy_con->sock); - - fdevent_register(srv->ev, sess->proxy_con->sock, proxy_handle_fdevent, sess); - fdevent_event_add(srv->ev, sess->proxy_con->sock, FDEVENT_IN); - } - } - - - switch (proxy_state_engine(srv, con, p, sess)) { - case HANDLER_WAIT_FOR_EVENT: - return HANDLER_WAIT_FOR_EVENT; - case HANDLER_COMEBACK: -#if 0 - TRACE("%s", "setting PROXY_STATE_FINISHED"); - sess->state = PROXY_STATE_FINISHED; -#endif - /* request finished do redirect. */ - if (sess->do_internal_redirect) { - /* recycle proxy connection. */ - proxy_recycle_backend_connection(srv, p, sess); - return HANDLER_COMEBACK; - } - /* restart the connection to the backend */ - if (p->conf.debug) TRACE("%s", "write failed, restarting request"); - proxy_remove_backend_connection(srv, sess); - break; - case HANDLER_WAIT_FOR_FD: - return HANDLER_WAIT_FOR_FD; - case HANDLER_GO_ON: - return HANDLER_GO_ON; - default: - TRACE("state: %d (error)", sess->state); - proxy_remove_backend_connection(srv, sess); - /* only set 500 if not another error code is already set (like 502) */ - if (con->http_status < 500 || con->http_status > 599) { - con->http_status = 500; /* Internal Server Error */ - } - con->mode = DIRECT; - return HANDLER_GO_ON; - } - } - - /* should not be reached */ - return HANDLER_ERROR; -} - -CONNECTION_FUNC(mod_proxy_send_request_content) { - plugin_data *p = p_d; - - if (p->id != con->mode) return HANDLER_GO_ON; - - /* read all the content before we start our backend */ - if (!con->recv->is_closed) { - return HANDLER_GO_ON; - } - - /* copy the chunks to our queue and call the state-engine to send it out */ - return mod_proxy_core_start_backend(srv, con, p_d); -} - -/** - * cleanup dead connections once a second - * - * the idling event-handler can't cleanup connections itself and has to wait until the - * trigger cleans up - */ -static int mod_proxy_wakeup_connections(server *srv, plugin_data *p, plugin_config *p_conf) { - size_t i, j; - proxy_request *req; - int total_conns_available = 0, backends_available = 0; - int woken_up; - - for (i = 0; i < p_conf->backends->used; i++) { - proxy_backend *backend = p_conf->backends->ptr[i]; - proxy_connection_pool *pool = backend->pool; - proxy_address_pool *address_pool = backend->address_pool; - unsigned int conns_available = 0, addrs_disabled = 0; - proxy_session *sess = NULL; - - conns_available = (pool->max_size - pool->used); - for (j = 0; j < pool->used; ) { - proxy_connection *proxy_con = pool->ptr[j]; - - /* remove-con is removing the current con and moves the good connections to the left - * no need to increment i */ - switch (proxy_con->state) { - case PROXY_CONNECTION_STATE_CLOSED: - proxy_connection_pool_remove_connection(backend->pool, proxy_con); - COUNTER_SET(backend->pool_size, backend->pool->used); - - fdevent_event_del(srv->ev, proxy_con->sock); - fdevent_unregister(srv->ev, proxy_con->sock); - - proxy_connection_free(proxy_con); - - conns_available++; - break; - case PROXY_CONNECTION_STATE_CONNECTING: - /* how long are we in this state already ? - * - * if the connect() failed with EINPROGRESS we have to wait until we get a POLLOUT - * if for some reason we don't get that in 4-5 seconds we have to kill the attempt - * - * */ - - if (srv->cur_ts - proxy_con->state_ts < 5) { - j++; - break; - } - - TRACE("connect(%s) timed out, closing backend connection", - SAFE_BUF_STR(proxy_con->address->name)); - - /** timed out - * - * we have to tell the proxy connection to try to connect another backend - */ - - proxy_con->state = PROXY_CONNECTION_STATE_CLOSED; - - sess = proxy_con->proxy_sess; - joblist_append(srv, sess->remote_con); - - j++; - break; - case PROXY_CONNECTION_STATE_IDLE: - conns_available++; - default: - j++; - } - } - - /* active the disabled addresses again */ - for (j = 0; j < address_pool->used; j++) { - proxy_address *address = address_pool->ptr[j]; - - if (address->state != PROXY_ADDRESS_STATE_DISABLED) continue; - - if (srv->cur_ts > address->disabled_until) { - address->disabled_until = 0; - address->state = PROXY_ADDRESS_STATE_ACTIVE; - } else { - addrs_disabled++; - } - } - - total_conns_available += conns_available; - backend->disabled_addresses = addrs_disabled; - /* update backend's state */ - if (conns_available == 0) { - /* connection pool is full and there are no idle connections. */ - backend->state = PROXY_BACKEND_STATE_FULL; - } else if (addrs_disabled == address_pool->used) { - /* all addresses are disabled. */ - backend->state = PROXY_BACKEND_STATE_DISABLED; - } else { - backend->state = PROXY_BACKEND_STATE_ACTIVE; - backends_available++; - } - } - - /* no backends available can't wake any connections. */ - if (backends_available == 0) { - /* all backends are full or disabled. */ - return 0; - } - - /* wake up the connections from the backlog */ - for (woken_up = 0; woken_up < total_conns_available && (req = proxy_backlog_shift(p_conf->backlog)); woken_up++) { - connection *con = req->con; - - if (p_conf->debug) TRACE("wakeup a connection from backlog: con=%d", con->sock->fd); - joblist_append(srv, con); - - COUNTER_DEC(p->conf.backlog_size); - proxy_request_free(req); - } - - return woken_up; -} - -TRIGGER_FUNC(mod_proxy_trigger) { - plugin_data *p = p_d; - size_t i; - - /** - * walk through all the different address pools and check if they are still alive - * - * in case of connect() = -1 -> EINPROGRESS we might have trigger the state-engine - */ - for (i = 0; i < srv->config_context->used; i++) { - mod_proxy_wakeup_connections(srv, p, p->config_storage[i]); - } - - return HANDLER_GO_ON; -} - -LI_EXPORT int mod_proxy_core_plugin_init(plugin *p); -LI_EXPORT int mod_proxy_core_plugin_init(plugin *p) { - p->version = LIGHTTPD_VERSION_ID; - p->name = buffer_init_string("mod_proxy_core"); - - p->init = mod_proxy_core_init; - p->cleanup = mod_proxy_core_free; - p->set_defaults = mod_proxy_core_set_defaults; - p->handle_physical = mod_proxy_core_match_url; - p->handle_start_backend = mod_proxy_core_match_local_file; - p->handle_send_request_content = mod_proxy_send_request_content; - p->handle_read_response_content = mod_proxy_core_start_backend; - p->connection_reset = mod_proxy_connection_close_callback; - p->handle_connection_close = mod_proxy_connection_close_callback; - p->handle_trigger = mod_proxy_trigger; - - p->data = NULL; - - return 0; -} |