Mercurial > hg > nginx
view src/http/modules/proxy/ngx_http_proxy_upstream.c @ 485:4ebe09b07e30 release-0.1.17
nginx-0.1.17-RELEASE import
*) Change: the ngx_http_rewrite_module was rewritten from the scratch.
Now it is possible to redirect, to return the error codes, to check
the variables and referrers. The directives can be used inside
locations. The redirect directive was canceled.
*) Feature: the ngx_http_geo_module.
*) Feature: the proxy_set_x_var and fastcgi_set_var directives.
*) Bugfix: the location configuration with "=" modifier may be used in
another location.
*) Bugfix: the correct content type was set only for requests that use
small caps letters in extension.
*) Bugfix: if the proxy_pass or fastcgi_pass directives were set in the
location, and access was denied, and the error was redirected to a
static page, then the segmentation fault occurred.
*) Bugfix: if in a proxied "Location" header was a relative URL, then a
host name and a slash were added to them; the bug had appeared in
0.1.14.
*) Bugfix: the system error message was not logged on Linux.
author | Igor Sysoev <igor@sysoev.ru> |
---|---|
date | Thu, 03 Feb 2005 19:33:37 +0000 |
parents | c52408583801 |
children | 31ff3e943e16 |
line wrap: on
line source
/* * Copyright (C) Igor Sysoev */ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_event.h> #include <ngx_event_connect.h> #include <ngx_event_pipe.h> #include <ngx_http.h> #include <ngx_http_proxy_handler.h> static ngx_chain_t *ngx_http_proxy_create_request(ngx_http_proxy_ctx_t *p); static void ngx_http_proxy_init_upstream(ngx_http_request_t *r); static void ngx_http_proxy_reinit_upstream(ngx_http_proxy_ctx_t *p); static void ngx_http_proxy_connect(ngx_http_proxy_ctx_t *p); static void ngx_http_proxy_send_request(ngx_http_proxy_ctx_t *p); static void ngx_http_proxy_send_request_handler(ngx_event_t *wev); static void ngx_http_proxy_dummy_handler(ngx_event_t *wev); static void ngx_http_proxy_process_upstream_status_line(ngx_event_t *rev); static void ngx_http_proxy_process_upstream_headers(ngx_event_t *rev); static ssize_t ngx_http_proxy_read_upstream_header(ngx_http_proxy_ctx_t *); static void ngx_http_proxy_send_response(ngx_http_proxy_ctx_t *p); static void ngx_http_proxy_process_body(ngx_event_t *ev); static void ngx_http_proxy_next_upstream(ngx_http_proxy_ctx_t *p, ngx_uint_t ft_type); static ngx_str_t http_methods[] = { ngx_string("GET "), ngx_string("HEAD "), ngx_string("POST ") }; static char *upstream_header_errors[] = { "upstream sent invalid header", "upstream sent too long header line" }; static char http_version[] = " HTTP/1.0" CRLF; static char host_header[] = "Host: "; static char x_url_header[] = "X-URL: http"; static char x_real_ip_header[] = "X-Real-IP: "; static char x_forwarded_for_header[] = "X-Forwarded-For: "; static char connection_close_header[] = "Connection: close" CRLF; int ngx_http_proxy_request_upstream(ngx_http_proxy_ctx_t *p) { int rc; ngx_http_request_t *r; ngx_http_proxy_upstream_t *u; r = p->request; if (!(u = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_upstream_t)))) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } p->upstream = u; u->peer.log_error = NGX_ERROR_ERR; u->peer.peers = p->lcf->peers; u->peer.tries = p->lcf->peers->number; #if (NGX_THREADS) u->peer.lock = &r->connection->lock; #endif u->method = r->method; rc = ngx_http_read_client_request_body(r, ngx_http_proxy_init_upstream); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_chain_t *ngx_http_proxy_create_request(ngx_http_proxy_ctx_t *p) { size_t len; ngx_uint_t i, escape, *index; ngx_buf_t *b; ngx_chain_t *chain; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_http_request_t *r; ngx_http_variable_t *var; ngx_http_variable_value_t *value; ngx_http_core_main_conf_t *cmcf; ngx_http_proxy_upstream_conf_t *uc; r = p->request; uc = p->lcf->upstream; if (p->upstream->method) { len = http_methods[p->upstream->method - 1].len; } else { len = r->method_name.len; } if (r->quoted_uri) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + uc->location->len, r->uri.len - uc->location->len, NGX_ESCAPE_URI); } else { escape = 0; } len += uc->uri.len + r->uri.len - uc->location->len + escape + sizeof("?") - 1 + r->args.len + sizeof(http_version) - 1 + sizeof(connection_close_header) - 1 + sizeof(CRLF) - 1; if (p->lcf->set_x_url) { len += sizeof(x_url_header) - 1 + sizeof("s://") - 1 + r->port_text->len + r->unparsed_uri.len + sizeof(CRLF) - 1; if (r->headers_in.host) { len += r->headers_in.host_name_len; } else { len += r->server_name.len; } } if (p->lcf->preserve_host && r->headers_in.host) { len += sizeof(host_header) - 1 + r->headers_in.host_name_len + sizeof(":") - 1 + uc->port_text.len + sizeof(CRLF) - 1; } else { len += sizeof(host_header) - 1 + uc->host_header.len + sizeof(CRLF) - 1; } if (p->lcf->set_x_real_ip) { len += sizeof(x_real_ip_header) - 1 + INET_ADDRSTRLEN - 1 + sizeof(CRLF) - 1; } if (p->lcf->add_x_forwarded_for) { if (r->headers_in.x_forwarded_for) { len += sizeof(x_forwarded_for_header) - 1 + r->headers_in.x_forwarded_for->value.len + sizeof(", ") - 1 + INET_ADDRSTRLEN - 1 + sizeof(CRLF) - 1; } else { len += sizeof(x_forwarded_for_header) - 1 + INET_ADDRSTRLEN - 1 + sizeof(CRLF) - 1; } } cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); var = cmcf->variables.elts; index = p->lcf->x_vars.elts; for (i = 0; i < p->lcf->x_vars.nelts; i++) { if (!(value = ngx_http_get_variable(r, index[i]))) { continue; } if (value->text.len) { len += sizeof("X-") - 1 + var[index[i]].name.len + sizeof(": ") - 1 + value->text.len + sizeof(CRLF) - 1; } } part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (&header[i] == r->headers_in.host) { continue; } if (&header[i] == r->headers_in.connection) { continue; } len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len + sizeof(CRLF) - 1; } #if (NGX_DEBUG) len++; #endif if (!(b = ngx_create_temp_buf(r->pool, len))) { return NULL; } if (!(chain = ngx_alloc_chain_link(r->pool))) { return NULL; } chain->buf = b; chain->next = NULL; /* the request line */ if (p->upstream->method) { b->last = ngx_cpymem(b->last, http_methods[p->upstream->method - 1].data, http_methods[p->upstream->method - 1].len); } else { b->last = ngx_cpymem(b->last, r->method_name.data, r->method_name.len); } b->last = ngx_cpymem(b->last, uc->uri.data, uc->uri.len); if (escape) { ngx_escape_uri(b->last, r->uri.data + uc->location->len, r->uri.len - uc->location->len, NGX_ESCAPE_URI); b->last += r->uri.len - uc->location->len + escape; } else { b->last = ngx_cpymem(b->last, r->uri.data + uc->location->len, r->uri.len - uc->location->len); } if (r->args.len > 0) { *b->last++ = '?'; b->last = ngx_cpymem(b->last, r->args.data, r->args.len); } b->last = ngx_cpymem(b->last, http_version, sizeof(http_version) - 1); /* the "Connection: close" header */ b->last = ngx_cpymem(b->last, connection_close_header, sizeof(connection_close_header) - 1); /* the "Host" header */ b->last = ngx_cpymem(b->last, host_header, sizeof(host_header) - 1); if (p->lcf->preserve_host && r->headers_in.host) { b->last = ngx_cpymem(b->last, r->headers_in.host->value.data, r->headers_in.host_name_len); if (!uc->default_port) { *b->last++ = ':'; b->last = ngx_cpymem(b->last, uc->port_text.data, uc->port_text.len); } } else { b->last = ngx_cpymem(b->last, uc->host_header.data, uc->host_header.len); } *b->last++ = CR; *b->last++ = LF; /* the "X-URL" header */ if (p->lcf->set_x_url) { b->last = ngx_cpymem(b->last, x_url_header, sizeof(x_url_header) - 1); #if (NGX_OPENSSL) if (r->connection->ssl) { *b->last++ = 's'; } #endif *b->last++ = ':'; *b->last++ = '/'; *b->last++ = '/'; if (r->headers_in.host) { b->last = ngx_cpymem(b->last, r->headers_in.host->value.data, r->headers_in.host_name_len); } else { b->last = ngx_cpymem(b->last, r->server_name.data, r->server_name.len); } b->last = ngx_cpymem(b->last, r->port_text->data, r->port_text->len); b->last = ngx_cpymem(b->last, r->unparsed_uri.data, r->unparsed_uri.len); *b->last++ = CR; *b->last++ = LF; } /* the "X-Real-IP" header */ if (p->lcf->set_x_real_ip) { b->last = ngx_cpymem(b->last, x_real_ip_header, sizeof(x_real_ip_header) - 1); b->last = ngx_cpymem(b->last, r->connection->addr_text.data, r->connection->addr_text.len); *b->last++ = CR; *b->last++ = LF; } /* the "X-Forwarded-For" header */ if (p->lcf->add_x_forwarded_for) { if (r->headers_in.x_forwarded_for) { b->last = ngx_cpymem(b->last, x_forwarded_for_header, sizeof(x_forwarded_for_header) - 1); b->last = ngx_cpymem(b->last, r->headers_in.x_forwarded_for->value.data, r->headers_in.x_forwarded_for->value.len); *b->last++ = ','; *b->last++ = ' '; } else { b->last = ngx_cpymem(b->last, x_forwarded_for_header, sizeof(x_forwarded_for_header) - 1); } b->last = ngx_cpymem(b->last, r->connection->addr_text.data, r->connection->addr_text.len); *b->last++ = CR; *b->last++ = LF; } for (i = 0; i < p->lcf->x_vars.nelts; i++) { if (!(value = ngx_http_get_variable(r, index[i]))) { continue; } if (value->text.len == 0) { continue; } *b->last++ = 'X'; *b->last++ = '-'; b->last = ngx_cpymem(b->last, var[index[i]].name.data, var[index[i]].name.len); *b->last++ = ':'; *b->last++ = ' '; b->last = ngx_cpymem(b->last, value->text.data, value->text.len); *b->last++ = CR; *b->last++ = LF; } part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (&header[i] == r->headers_in.host) { continue; } if (&header[i] == r->headers_in.connection) { continue; } if (&header[i] == r->headers_in.keep_alive) { continue; } if (&header[i] == r->headers_in.x_forwarded_for && p->lcf->add_x_forwarded_for) { continue; } if (&header[i] == r->headers_in.x_real_ip && p->lcf->set_x_real_ip) { continue; } if (&header[i] == r->headers_in.x_url && p->lcf->set_x_url) { continue; } b->last = ngx_cpymem(b->last, header[i].key.data, header[i].key.len); *b->last++ = ':'; *b->last++ = ' '; b->last = ngx_cpymem(b->last, header[i].value.data, header[i].value.len); *b->last++ = CR; *b->last++ = LF; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header: \"%V: %V\"", &header[i].key, &header[i].value); } /* add "\r\n" at the header end */ *b->last++ = CR; *b->last++ = LF; #if (NGX_DEBUG) *b->last = '\0'; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header:\n\"%s\"", b->pos); #endif return chain; } static void ngx_http_proxy_init_upstream(ngx_http_request_t *r) { ngx_chain_t *cl; ngx_http_proxy_ctx_t *p; ngx_output_chain_ctx_t *output; ngx_chain_writer_ctx_t *writer; ngx_http_proxy_log_ctx_t *ctx; p = ngx_http_get_module_ctx(r, ngx_http_proxy_module); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy init upstream, client timer: %d", r->connection->read->timer_set); if (r->connection->read->timer_set) { ngx_del_timer(r->connection->read); } r->connection->read->event_handler = ngx_http_proxy_check_broken_connection; if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { r->connection->write->event_handler = ngx_http_proxy_check_broken_connection; if (!r->connection->write->active) { if (ngx_add_event(r->connection->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT) == NGX_ERROR) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } } if (!(cl = ngx_http_proxy_create_request(p))) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (r->request_body->bufs) { cl->next = r->request_body->bufs; } r->request_body->bufs = cl; if (!(ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_log_ctx_t)))) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ctx->connection = r->connection->number; ctx->proxy = p; p->upstream->peer.log = r->connection->log; p->saved_ctx = r->connection->log->data; p->saved_handler = r->connection->log->handler; r->connection->log->data = ctx; r->connection->log->handler = ngx_http_proxy_log_error; p->action = "connecting to upstream"; if (!(output = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t)))) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } p->upstream->output_chain_ctx = output; output->sendfile = r->connection->sendfile; output->pool = r->pool; output->bufs.num = 1; output->tag = (ngx_buf_tag_t) &ngx_http_proxy_module; output->output_filter = ngx_chain_writer; if (!(writer = ngx_palloc(r->pool, sizeof(ngx_chain_writer_ctx_t)))) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } output->filter_ctx = writer; writer->pool = r->pool; #if 0 if (p->lcf->busy_lock && p->busy_lock == NULL) { #else if (p->lcf->busy_lock && !p->busy_locked) { #endif ngx_http_proxy_upstream_busy_lock(p); } else { ngx_http_proxy_connect(p); } } static void ngx_http_proxy_reinit_upstream(ngx_http_proxy_ctx_t *p) { ngx_chain_t *cl; ngx_output_chain_ctx_t *output; ngx_http_proxy_state_e state; /* reinit the request chain */ for (cl = p->request->request_body->bufs; cl; cl = cl->next) { cl->buf->pos = cl->buf->start; cl->buf->file_pos = 0; } /* reinit the ngx_output_chain() context */ output = p->upstream->output_chain_ctx; output->buf = NULL; output->in = NULL; output->free = NULL; output->busy = NULL; /* reinit r->header_in buffer */ if (p->header_in) { if (p->cache) { p->header_in->pos = p->header_in->start + p->cache->ctx.header_size; p->header_in->last = p->header_in->pos; } else { p->header_in->pos = p->header_in->start; p->header_in->last = p->header_in->start; } } /* add one more state */ state = p->state->cache_state; if (!(p->state = ngx_push_array(&p->states))) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_memzero(p->state, sizeof(ngx_http_proxy_state_t)); p->state->cache_state = state; p->status = 0; p->status_count = 0; } #if 0 void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p) { ngx_int_t rc; rc = ngx_event_busy_lock(p->lcf->busy_lock, p->busy_lock); if (rc == NGX_AGAIN) { return; } if (rc == NGX_OK) { ngx_http_proxy_connect(p); return; } if (rc == NGX_ERROR) { p->state->status = NGX_HTTP_INTERNAL_SERVER_ERROR; ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* rc == NGX_BUSY */ #if (NGX_HTTP_CACHE) if (p->busy_lock->timer) { ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING; } else { ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK; } if (p->stale && (p->lcf->use_stale & ft_type)) { ngx_http_proxy_finalize_request(p, ngx_http_proxy_send_cached_response(p)); return; } #endif p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE; ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE); } #endif #if 1 void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p) { ngx_int_t rc; #if (NGX_HTTP_CACHE) ngx_int_t ft_type; #endif if (p->busy_lock.time == 0) { p->busy_lock.event = p->request->connection->read; p->busy_lock.event_handler = ngx_http_proxy_busy_lock_handler; } rc = ngx_http_busy_lock(p->lcf->busy_lock, &p->busy_lock); if (rc == NGX_AGAIN) { return; } if (rc == NGX_OK) { ngx_http_proxy_connect(p); return; } ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock); #if (NGX_HTTP_CACHE) if (rc == NGX_DONE) { ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK; } else { /* rc == NGX_ERROR */ ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING; } if (p->stale && (p->lcf->use_stale & ft_type)) { ngx_http_proxy_finalize_request(p, ngx_http_proxy_send_cached_response(p)); return; } #endif p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE; ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE); } #endif static void ngx_http_proxy_connect(ngx_http_proxy_ctx_t *p) { ngx_int_t rc; ngx_connection_t *c; ngx_http_request_t *r; ngx_output_chain_ctx_t *output; ngx_chain_writer_ctx_t *writer; p->action = "connecting to upstream"; p->request->connection->single_connection = 0; rc = ngx_event_connect_peer(&p->upstream->peer); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0, "http proxy connect: %i", rc); if (rc == NGX_ERROR) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } p->state->peer = &p->upstream->peer.peers->peer[p->upstream->peer.cur_peer].name; if (rc == NGX_CONNECT_ERROR) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR); return; } r = p->request; c = p->upstream->peer.connection; c->data = p; c->write->event_handler = ngx_http_proxy_send_request_handler; c->read->event_handler = ngx_http_proxy_process_upstream_status_line; c->sendfile = r->connection->sendfile; c->pool = r->pool; c->read->log = c->write->log = c->log = r->connection->log; /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */ output = p->upstream->output_chain_ctx; writer = output->filter_ctx; writer->out = NULL; writer->last = &writer->out; writer->connection = c; writer->limit = 0; if (p->request_sent) { ngx_http_proxy_reinit_upstream(p); } if (r->request_body->buf) { if (r->request_body->temp_file) { if (!(output->free = ngx_alloc_chain_link(r->pool))) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } output->free->buf = r->request_body->buf; output->free->next = NULL; output->allocated = 1; r->request_body->buf->pos = r->request_body->buf->start; r->request_body->buf->last = r->request_body->buf->start; r->request_body->buf->tag = (ngx_buf_tag_t) &ngx_http_proxy_module; } else { r->request_body->buf->pos = r->request_body->buf->start; } } p->request_sent = 0; if (rc == NGX_AGAIN) { ngx_add_timer(c->write, p->lcf->connect_timeout); return; } /* rc == NGX_OK */ #if 0 /* test only, see below about "post aio operation" */ if (c->read->ready) { /* post aio operation */ ngx_http_proxy_process_upstream_status_line(c->read); #if 0 return; #endif } #endif ngx_http_proxy_send_request(p); } static void ngx_http_proxy_send_request(ngx_http_proxy_ctx_t *p) { int rc; ngx_connection_t *c; c = p->upstream->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http proxy send request"); #if (NGX_HAVE_KQUEUE) if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && !p->request_sent && c->write->pending_eof) { ngx_log_error(NGX_LOG_ERR, c->log, c->write->kq_errno, "connect() failed"); ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR); return; } #endif p->action = "sending request to upstream"; rc = ngx_output_chain(p->upstream->output_chain_ctx, p->request_sent ? NULL: p->request->request_body->bufs); p->request_sent = 1; if (rc == NGX_ERROR) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR); return; } if (c->write->timer_set) { ngx_del_timer(c->write); } if (rc == NGX_AGAIN) { ngx_add_timer(c->write, p->lcf->send_timeout); if (ngx_handle_write_event(c->write, p->lcf->send_lowat) == NGX_ERROR) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } /* rc == NGX_OK */ if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { if (ngx_tcp_push(c->fd) == NGX_ERROR) { ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, ngx_tcp_push_n " failed"); ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; return; } ngx_add_timer(c->read, p->lcf->read_timeout); #if 1 if (c->read->ready) { /* post aio operation */ /* * although we can post aio operation just in the end * of ngx_http_proxy_connect() CHECK IT !!! * it's better to do here because we postpone header buffer allocation */ ngx_http_proxy_process_upstream_status_line(c->read); return; } #endif c->write->event_handler = ngx_http_proxy_dummy_handler; if (ngx_handle_level_write_event(c->write) == NGX_ERROR) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } static void ngx_http_proxy_send_request_handler(ngx_event_t *wev) { ngx_connection_t *c; ngx_http_proxy_ctx_t *p; c = wev->data; p = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http proxy send request handler"); if (wev->timedout) { p->action = "sending request to upstream"; ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT); return; } if (p->request->connection->write->eof && (!p->cachable || !p->request_sent)) { ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST); return; } ngx_http_proxy_send_request(p); } static void ngx_http_proxy_dummy_handler(ngx_event_t *wev) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http proxy dummy handler"); } static void ngx_http_proxy_process_upstream_status_line(ngx_event_t *rev) { int rc; ssize_t n; ngx_connection_t *c; ngx_http_proxy_ctx_t *p; c = rev->data; p = c->data; p->action = "reading upstream status line"; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http proxy process status line"); if (rev->timedout) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT); return; } if (p->header_in == NULL) { p->header_in = ngx_create_temp_buf(p->request->pool, p->lcf->header_buffer_size); if (p->header_in == NULL) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } p->header_in->tag = (ngx_buf_tag_t) &ngx_http_proxy_module; if (p->cache) { p->header_in->pos += p->cache->ctx.header_size; p->header_in->last = p->header_in->pos; } } n = ngx_http_proxy_read_upstream_header(p); if (n == NGX_AGAIN) { return; } if (n == 0) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "upstream prematurely closed connection"); } if (n == NGX_ERROR || n == 0) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR); return; } p->valid_header_in = 0; p->upstream->peer.cached = 0; rc = ngx_http_proxy_parse_status_line(p); if (rc == NGX_AGAIN) { if (p->header_in->pos == p->header_in->last) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "upstream sent too long status line"); ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER); } return; } if (rc == NGX_HTTP_PROXY_PARSE_NO_HEADER) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "upstream sent no valid HTTP/1.0 header"); if (p->accel) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER); } else { p->request->http_version = NGX_HTTP_VERSION_9; p->upstream->status = NGX_HTTP_OK; ngx_http_proxy_send_response(p); } return; } /* rc == NGX_OK */ p->upstream->status = p->status; p->state->status = p->status; if (p->status == NGX_HTTP_INTERNAL_SERVER_ERROR) { if (p->upstream->peer.tries > 1 && (p->lcf->next_upstream & NGX_HTTP_PROXY_FT_HTTP_500)) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_HTTP_500); return; } #if (NGX_HTTP_CACHE) if (p->upstream->peer.tries == 0 && p->stale && (p->lcf->use_stale & NGX_HTTP_PROXY_FT_HTTP_500)) { ngx_http_proxy_finalize_request(p, ngx_http_proxy_send_cached_response(p)); return; } #endif } if (p->status == NGX_HTTP_NOT_FOUND && p->upstream->peer.tries > 1 && p->lcf->next_upstream & NGX_HTTP_PROXY_FT_HTTP_404) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_HTTP_404); return; } /* TODO: "proxy_error_page" */ p->upstream->status_line.len = p->status_end - p->status_start; p->upstream->status_line.data = ngx_palloc(p->request->pool, p->upstream->status_line.len + 1); if (p->upstream->status_line.data == NULL) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_cpystrn(p->upstream->status_line.data, p->status_start, p->upstream->status_line.len + 1); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http proxy status %ui \"%V\"", p->upstream->status, &p->upstream->status_line); /* init or reinit the p->upstream->headers_in.headers table */ if (p->upstream->headers_in.headers.part.elts) { p->upstream->headers_in.headers.part.nelts = 0; p->upstream->headers_in.headers.part.next = NULL; p->upstream->headers_in.headers.last = &p->upstream->headers_in.headers.part; ngx_memzero(&p->upstream->headers_in.date, sizeof(ngx_http_proxy_headers_in_t) - sizeof(ngx_list_t)); } else { if (ngx_list_init(&p->upstream->headers_in.headers, p->request->pool, 20, sizeof(ngx_table_elt_t)) == NGX_ERROR) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } c->read->event_handler = ngx_http_proxy_process_upstream_headers; ngx_http_proxy_process_upstream_headers(rev); } static void ngx_http_proxy_process_upstream_headers(ngx_event_t *rev) { int i, rc; ssize_t n; ngx_table_elt_t *h; ngx_connection_t *c; ngx_http_request_t *r; ngx_http_proxy_ctx_t *p; c = rev->data; p = c->data; r = p->request; p->action = "reading upstream headers"; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http proxy process header line"); if (rev->timedout) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT); return; } rc = NGX_AGAIN; for ( ;; ) { if (rc == NGX_AGAIN) { n = ngx_http_proxy_read_upstream_header(p); if (n == 0) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "upstream prematurely closed connection"); } if (n == NGX_ERROR || n == 0) { ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR); return; } if (n == NGX_AGAIN) { return; } } rc = ngx_http_parse_header_line(p->request, p->header_in); if (rc == NGX_OK) { /* a header line has been parsed successfully */ if (!(h = ngx_list_push(&p->upstream->headers_in.headers))) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_palloc(p->request->pool, h->key.len + 1 + h->value.len + 1); if (h->key.data == NULL) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } h->value.data = h->key.data + h->key.len + 1; ngx_cpystrn(h->key.data, r->header_name_start, h->key.len + 1); ngx_cpystrn(h->value.data, r->header_start, h->value.len + 1); for (i = 0; ngx_http_proxy_headers_in[i].name.len != 0; i++) { if (ngx_http_proxy_headers_in[i].name.len != h->key.len) { continue; } if (ngx_strcasecmp(ngx_http_proxy_headers_in[i].name.data, h->key.data) == 0) { *((ngx_table_elt_t **) ((char *) &p->upstream->headers_in + ngx_http_proxy_headers_in[i].offset)) = h; break; } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http proxy header: \"%V: %V\"", &h->key, &h->value); continue; } else if (rc == NGX_HTTP_PARSE_HEADER_DONE) { /* a whole header has been parsed successfully */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http proxy header done"); /* TODO: hook to process the upstream header */ #if (NGX_HTTP_CACHE) if (p->cachable) { p->cachable = ngx_http_proxy_is_cachable(p); } #endif ngx_http_proxy_send_response(p); return; } else if (rc != NGX_AGAIN) { /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_ERR, rev->log, 0, upstream_header_errors[rc - NGX_HTTP_PARSE_HEADER_ERROR]); ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER); return; } /* rc == NGX_AGAIN: a header line parsing is still not complete */ if (p->header_in->last == p->header_in->end) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "upstream sent too big header"); ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER); return; } } } static ssize_t ngx_http_proxy_read_upstream_header(ngx_http_proxy_ctx_t *p) { ssize_t n; ngx_event_t *rev; rev = p->upstream->peer.connection->read; n = p->header_in->last - p->header_in->pos; if (n > 0) { return n; } n = ngx_recv(p->upstream->peer.connection, p->header_in->last, p->header_in->end - p->header_in->last); if (n == NGX_AGAIN) { #if 0 ngx_add_timer(rev, p->lcf->read_timeout); #endif if (ngx_handle_read_event(rev, 0) == NGX_ERROR) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } return NGX_AGAIN; } if (n == 0) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "upstream closed prematurely connection"); } if (n == 0 || n == NGX_ERROR) { return NGX_ERROR; } p->header_in->last += n; return n; } static void ngx_http_proxy_send_response(ngx_http_proxy_ctx_t *p) { int rc; ngx_event_pipe_t *ep; ngx_http_request_t *r; ngx_http_cache_header_t *header; ngx_http_core_loc_conf_t *clcf; r = p->request; r->headers_out.status = p->upstream->status; r->headers_out.status_line = p->upstream->status_line; #if 0 r->headers_out.content_length_n = -1; r->headers_out.content_length = NULL; #endif /* copy an upstream header to r->headers_out */ if (ngx_http_proxy_copy_header(p, &p->upstream->headers_in) == NGX_ERROR) { ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* TODO: preallocate event_pipe bufs, look "Content-Length" */ rc = ngx_http_send_header(r); p->header_sent = 1; if (p->cache && p->cache->ctx.file.fd != NGX_INVALID_FILE) { if (ngx_close_file(p->cache->ctx.file.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, ngx_close_file_n " \"%s\" failed", p->cache->ctx.file.name.data); } } if (p->cachable) { header = (ngx_http_cache_header_t *) p->header_in->start; header->expires = p->cache->ctx.expires; header->last_modified = p->cache->ctx.last_modified; header->date = p->cache->ctx.date; header->length = r->headers_out.content_length_n; p->cache->ctx.length = r->headers_out.content_length_n; header->key_len = p->cache->ctx.key0.len; ngx_memcpy(&header->key, p->cache->ctx.key0.data, header->key_len); header->key[header->key_len] = LF; } ep = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); if (ep == NULL) { ngx_http_proxy_finalize_request(p, 0); return; } p->upstream->event_pipe = ep; ep->input_filter = ngx_event_pipe_copy_input_filter; ep->output_filter = (ngx_event_pipe_output_filter_pt) ngx_http_output_filter; ep->output_ctx = r; ep->tag = (ngx_buf_tag_t) &ngx_http_proxy_module; ep->bufs = p->lcf->bufs; ep->busy_size = p->lcf->busy_buffers_size; ep->upstream = p->upstream->peer.connection; ep->downstream = r->connection; ep->pool = r->pool; ep->log = r->connection->log; ep->cachable = p->cachable; if (!(ep->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)))) { ngx_http_proxy_finalize_request(p, 0); return; } ep->temp_file->file.fd = NGX_INVALID_FILE; ep->temp_file->file.log = r->connection->log; ep->temp_file->path = p->lcf->temp_path; ep->temp_file->pool = r->pool; if (p->cachable) { ep->temp_file->persistent = 1; } else { ep->temp_file->warn = "an upstream response is buffered " "to a temporary file"; } ep->max_temp_file_size = p->lcf->max_temp_file_size; ep->temp_file_write_size = p->lcf->temp_file_write_size; if (!(ep->preread_bufs = ngx_alloc_chain_link(r->pool))) { ngx_http_proxy_finalize_request(p, 0); return; } ep->preread_bufs->buf = p->header_in; ep->preread_bufs->next = NULL; p->header_in->recycled = 1; ep->preread_size = p->header_in->last - p->header_in->pos; if (p->cachable) { ep->buf_to_file = ngx_calloc_buf(r->pool); if (ep->buf_to_file == NULL) { ngx_http_proxy_finalize_request(p, 0); return; } ep->buf_to_file->pos = p->header_in->start; ep->buf_to_file->last = p->header_in->pos; ep->buf_to_file->temporary = 1; } if (ngx_event_flags & NGX_USE_AIO_EVENT) { /* the posted aio operation can currupt a shadow buffer */ ep->single_buf = 1; } /* TODO: ep->free_bufs = 0 if use ngx_create_chain_of_bufs() */ ep->free_bufs = 1; /* * event_pipe would do p->header_in->last += ep->preread_size * as though these bytes were read. */ p->header_in->last = p->header_in->pos; if (p->lcf->cyclic_temp_file) { /* * we need to disable the use of sendfile() if we use cyclic temp file * because the writing a new data can interfere with sendfile() * that uses the same kernel file pages (at least on FreeBSD) */ ep->cyclic_temp_file = 1; r->connection->sendfile = 0; } else { ep->cyclic_temp_file = 0; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ep->read_timeout = p->lcf->read_timeout; ep->send_timeout = clcf->send_timeout; ep->send_lowat = clcf->send_lowat; p->upstream->peer.connection->read->event_handler = ngx_http_proxy_process_body; r->connection->write->event_handler = ngx_http_proxy_process_body; ngx_http_proxy_process_body(p->upstream->peer.connection->read); return; } static void ngx_http_proxy_process_body(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; ngx_http_proxy_ctx_t *p; ngx_event_pipe_t *ep; c = ev->data; if (ev->write) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http proxy process downstream"); r = c->data; p = ngx_http_get_module_ctx(r, ngx_http_proxy_module); p->action = "sending to client"; } else { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http proxy process upstream"); p = c->data; r = p->request; p->action = "reading upstream body"; } ep = p->upstream->event_pipe; if (ev->timedout) { if (ev->write) { ep->downstream_error = 1; ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT, "client timed out"); } else { ep->upstream_error = 1; ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT, "upstream timed out"); } } else { if (ngx_event_pipe(ep, ev->write) == NGX_ABORT) { ngx_http_proxy_finalize_request(p, 0); return; } } if (p->upstream->peer.connection) { #if (NGX_HTTP_FILE_CACHE) if (ep->upstream_done && p->cachable) { if (ngx_http_proxy_update_cache(p) == NGX_ERROR) { ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock); ngx_http_proxy_finalize_request(p, 0); return; } } else if (ep->upstream_eof && p->cachable) { /* TODO: check length & update cache */ if (ngx_http_proxy_update_cache(p) == NGX_ERROR) { ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock); ngx_http_proxy_finalize_request(p, 0); return; } } #endif if (ep->upstream_done || ep->upstream_eof || ep->upstream_error) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http proxy upstream exit: %p", ep->out); ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock); ngx_http_proxy_finalize_request(p, 0); return; } } if (ep->downstream_error) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http proxy downstream error"); if (!p->cachable && p->upstream->peer.connection) { ngx_http_proxy_finalize_request(p, 0); } } } static void ngx_http_proxy_next_upstream(ngx_http_proxy_ctx_t *p, ngx_uint_t ft_type) { ngx_uint_t status; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0, "http proxy next upstream: %ui", ft_type); ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock); if (ft_type != NGX_HTTP_PROXY_FT_HTTP_404) { ngx_event_connect_peer_failed(&p->upstream->peer); } if (ft_type == NGX_HTTP_PROXY_FT_TIMEOUT) { ngx_log_error(NGX_LOG_ERR, p->request->connection->log, NGX_ETIMEDOUT, "upstream timed out"); } if (p->upstream->peer.cached && ft_type == NGX_HTTP_PROXY_FT_ERROR) { status = 0; } else { switch(ft_type) { case NGX_HTTP_PROXY_FT_TIMEOUT: status = NGX_HTTP_GATEWAY_TIME_OUT; break; case NGX_HTTP_PROXY_FT_HTTP_500: status = NGX_HTTP_INTERNAL_SERVER_ERROR; break; case NGX_HTTP_PROXY_FT_HTTP_404: status = NGX_HTTP_NOT_FOUND; break; /* * NGX_HTTP_PROXY_FT_BUSY_LOCK and NGX_HTTP_PROXY_FT_MAX_WAITING * never reach here */ default: status = NGX_HTTP_BAD_GATEWAY; } } if (p->upstream->peer.connection) { ngx_http_proxy_close_connection(p); } if (p->request->connection->write->eof) { ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST); return; } if (status) { p->state->status = status; if (p->upstream->peer.tries == 0 || !(p->lcf->next_upstream & ft_type)) { #if (NGX_HTTP_CACHE) if (p->stale && (p->lcf->use_stale & ft_type)) { ngx_http_proxy_finalize_request(p, ngx_http_proxy_send_cached_response(p)); return; } #endif ngx_http_proxy_finalize_request(p, status); return; } } if (p->lcf->busy_lock && !p->busy_locked) { ngx_http_proxy_upstream_busy_lock(p); } else { ngx_http_proxy_connect(p); } }