in modules/proxy/mod_proxy.c [1231:1570]
static int proxy_handler(request_rec *r)
{
char *uri, *scheme, *p;
const char *p2;
void *sconf = r->server->module_config;
proxy_server_conf *conf = (proxy_server_conf *)
ap_get_module_config(sconf, &proxy_module);
apr_array_header_t *proxies = conf->proxies;
struct proxy_remote *ents = (struct proxy_remote *) proxies->elts;
int rc = DECLINED, access_status, i;
int direct_connect = 0;
const char *str;
apr_int64_t maxfwd;
proxy_balancer *balancer = NULL;
proxy_worker *worker = NULL;
int attempts = 0, max_attempts = 0;
struct dirconn_entry *list = (struct dirconn_entry *)conf->dirconn->elts;
int saved_status;
/* is this for us? */
if (!r->filename) {
return DECLINED;
}
/* We may have forced the proxy handler via config or .htaccess */
if (!r->proxyreq && r->handler && strncmp(r->handler, "proxy:", 6) == 0) {
char *old_filename = r->filename;
r->proxyreq = PROXYREQ_REVERSE;
r->filename = apr_pstrcat(r->pool, r->handler, r->filename, NULL);
apr_table_setn(r->notes, "proxy-sethandler", "1");
/* Still need to canonicalize r->filename */
rc = ap_proxy_canon_url(r);
if (rc != OK) {
r->filename = old_filename;
r->proxyreq = 0;
}
}
else if (r->proxyreq && strncmp(r->filename, "proxy:", 6) == 0) {
apr_table_unset(r->notes, "proxy-sethandler");
rc = OK;
}
if (rc != OK) {
return rc;
}
uri = r->filename + 6;
p = strchr(uri, ':');
if (p == NULL) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01141)
"proxy_handler no URL in %s", r->filename);
return HTTP_BAD_REQUEST;
}
scheme = apr_pstrmemdup(r->pool, uri, p - uri);
/* handle max-forwards / OPTIONS / TRACE */
if ((str = apr_table_get(r->headers_in, "Max-Forwards"))) {
char *end;
maxfwd = apr_strtoi64(str, &end, 10);
if (maxfwd < 0 || maxfwd == APR_INT64_MAX || *end) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10188)
"Max-Forwards value '%s' could not be parsed", str);
return ap_proxyerror(r, HTTP_BAD_REQUEST,
"Max-Forwards request header could not be parsed");
}
else if (maxfwd == 0) {
switch (r->method_number) {
case M_TRACE: {
int access_status;
r->proxyreq = PROXYREQ_NONE;
access_status = ap_send_http_trace(r);
ap_die(access_status, r);
return OK;
}
case M_OPTIONS: {
int access_status;
r->proxyreq = PROXYREQ_NONE;
access_status = ap_send_http_options(r);
ap_die(access_status, r);
return OK;
}
default: {
return ap_proxyerror(r, HTTP_BAD_REQUEST,
"Max-Forwards has reached zero - proxy loop?");
}
}
}
maxfwd = (maxfwd > 0) ? maxfwd - 1 : 0;
}
else {
/* set configured max-forwards */
maxfwd = conf->maxfwd;
}
if (maxfwd >= 0) {
apr_table_setn(r->headers_in, "Max-Forwards",
apr_psprintf(r->pool, "%" APR_INT64_T_FMT, maxfwd));
}
if (r->method_number == M_TRACE) {
core_server_config *coreconf = (core_server_config *)
ap_get_core_module_config(sconf);
if (coreconf->trace_enable == AP_TRACE_DISABLE)
{
/* Allow "error-notes" string to be printed by ap_send_error_response()
* Note; this goes nowhere, canned error response need an overhaul.
*/
apr_table_setn(r->notes, "error-notes",
"TRACE forbidden by server configuration");
apr_table_setn(r->notes, "verbose-error-to", "*");
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01139)
"TRACE forbidden by server configuration");
return HTTP_METHOD_NOT_ALLOWED;
}
/* Can't test ap_should_client_block, we aren't ready to send
* the client a 100 Continue response till the connection has
* been established
*/
if (coreconf->trace_enable != AP_TRACE_EXTENDED
&& (r->read_length || r->read_chunked || r->remaining))
{
/* Allow "error-notes" string to be printed by ap_send_error_response()
* Note; this goes nowhere, canned error response need an overhaul.
*/
apr_table_setn(r->notes, "error-notes",
"TRACE with request body is not allowed");
apr_table_setn(r->notes, "verbose-error-to", "*");
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01140)
"TRACE with request body is not allowed");
return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
}
/* If the host doesn't have a domain name, add one and redirect. */
if (conf->domain != NULL) {
rc = proxy_needsdomain(r, uri, conf->domain);
if (ap_is_HTTP_REDIRECT(rc))
return HTTP_MOVED_PERMANENTLY;
}
/* Check URI's destination host against NoProxy hosts */
/* Bypass ProxyRemote server lookup if configured as NoProxy */
for (direct_connect = i = 0; i < conf->dirconn->nelts &&
!direct_connect; i++) {
direct_connect = list[i].matcher(&list[i], r);
}
#if DEBUGGING
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
(direct_connect) ? APLOGNO(03231) "NoProxy for %s" : APLOGNO(03232) "UseProxy for %s",
r->uri);
#endif
do {
char *url = uri;
/* Try to obtain the most suitable worker */
access_status = ap_proxy_pre_request(&worker, &balancer, r, conf, &url);
if (access_status != OK) {
/*
* Only return if access_status is not HTTP_SERVICE_UNAVAILABLE
* This gives other modules the chance to hook into the
* request_status hook and decide what to do in this situation.
*/
if (access_status != HTTP_SERVICE_UNAVAILABLE)
return access_status;
/*
* Ensure that balancer is NULL if worker is NULL to prevent
* potential problems in the post_request hook.
*/
if (!worker)
balancer = NULL;
goto cleanup;
}
/* Initialise worker if needed, note the shared area must be initialized by the balancer logic */
if (balancer) {
ap_proxy_initialize_worker(worker, r->server, conf->pool);
}
if (balancer && balancer->s->max_attempts_set && !max_attempts)
max_attempts = balancer->s->max_attempts;
/* firstly, try a proxy, unless a NoProxy directive is active */
if (!direct_connect) {
for (i = 0; i < proxies->nelts; i++) {
p2 = ap_strchr_c(ents[i].scheme, ':'); /* is it a partial URL? */
if (strcmp(ents[i].scheme, "*") == 0 ||
(ents[i].use_regex &&
ap_regexec(ents[i].regexp, url, 0, NULL, 0) == 0) ||
(p2 == NULL && ap_cstr_casecmp(scheme, ents[i].scheme) == 0) ||
(p2 != NULL &&
ap_cstr_casecmpn(url, ents[i].scheme,
strlen(ents[i].scheme)) == 0)) {
/* handle the scheme */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01142)
"Trying to run scheme_handler against proxy");
if (ents[i].creds) {
apr_table_set(r->notes, "proxy-basic-creds", ents[i].creds);
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
"Using proxy auth creds %s", ents[i].creds);
}
access_status = proxy_run_scheme_handler(r, worker,
conf, url,
ents[i].hostname,
ents[i].port);
if (ents[i].creds) apr_table_unset(r->notes, "proxy-basic-creds");
/* Did the scheme handler process the request? */
if (access_status != DECLINED) {
const char *cl_a;
apr_off_t cl;
/*
* An fatal error or success, so no point in
* retrying with a direct connection.
*/
if (access_status != HTTP_BAD_GATEWAY) {
goto cleanup;
}
cl_a = apr_table_get(r->headers_in, "Content-Length");
if (cl_a && (!ap_parse_strict_length(&cl, cl_a)
|| cl > 0)) {
/*
* The request body is of length > 0. We cannot
* retry with a direct connection since we already
* sent (parts of) the request body to the proxy
* and do not have any longer.
*/
goto cleanup;
}
/*
* Transfer-Encoding was set as input header, so we had
* a request body. We cannot retry with a direct
* connection for the same reason as above.
*/
if (apr_table_get(r->headers_in, "Transfer-Encoding")) {
goto cleanup;
}
}
}
}
}
/* otherwise, try it direct */
/* N.B. what if we're behind a firewall, where we must use a proxy or
* give up??
*/
/* handle the scheme */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01143)
"Running scheme %s handler (attempt %d)",
scheme, attempts);
AP_PROXY_RUN(r, worker, conf, url, attempts);
access_status = proxy_run_scheme_handler(r, worker, conf,
url, NULL, 0);
if (access_status == OK
|| apr_table_get(r->notes, "proxy-error-override"))
break;
else if (access_status == HTTP_INTERNAL_SERVER_ERROR) {
/* Unrecoverable server error.
* We can not failover to another worker.
* Mark the worker as unusable if member of load balancer
*/
if (balancer
&& !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
worker->s->status |= PROXY_WORKER_IN_ERROR;
worker->s->error_time = apr_time_now();
}
break;
}
else if (access_status == HTTP_SERVICE_UNAVAILABLE) {
/* Recoverable server error.
* We can failover to another worker
* Mark the worker as unusable if member of load balancer
*/
if (balancer
&& !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
worker->s->status |= PROXY_WORKER_IN_ERROR;
worker->s->error_time = apr_time_now();
}
}
else {
/* Unrecoverable error.
* Return the origin status code to the client.
*/
break;
}
/* Try again if the worker is unusable and the service is
* unavailable.
*/
} while (!PROXY_WORKER_IS_USABLE(worker) &&
max_attempts > attempts++);
if (DECLINED == access_status) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01144)
"No protocol handler was valid for the URL %s "
"(scheme '%s'). "
"If you are using a DSO version of mod_proxy, make sure "
"the proxy submodules are included in the configuration "
"using LoadModule.", r->uri, scheme);
access_status = HTTP_INTERNAL_SERVER_ERROR;
goto cleanup;
}
cleanup:
/*
* Save current r->status and set it to the value of access_status which
* might be different (e.g. r->status could be HTTP_OK if e.g. we override
* the error page on the proxy or if the error was not generated by the
* backend itself but by the proxy e.g. a bad gateway) in order to give
* ap_proxy_post_request a chance to act correctly on the status code.
* But only do the above if access_status is not OK and not DONE, because
* in this case r->status might contain the true status and overwriting
* it with OK or DONE would be wrong.
*/
if ((access_status != OK) && (access_status != DONE)) {
saved_status = r->status;
r->status = access_status;
ap_proxy_post_request(worker, balancer, r, conf);
/*
* Only restore r->status if it has not been changed by
* ap_proxy_post_request as we assume that this change was intentional.
*/
if (r->status == access_status) {
r->status = saved_status;
}
}
else {
ap_proxy_post_request(worker, balancer, r, conf);
}
proxy_run_request_status(&access_status, r);
AP_PROXY_RUN_FINISHED(r, attempts, access_status);
return access_status;
}