/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
/* * Canonicalise http-like URLs. * scheme is the scheme for the URL * url is the URL starting with the first '/' * def_port is the default port for this scheme.
*/ staticint proxy_fcgi_canon(request_rec *r, char *url)
{ char *host, sport[7]; constchar *err; char *path;
apr_port_t port, def_port;
fcgi_req_config_t *rconf = NULL; constchar *pathinfo_type = NULL;
fcgi_dirconf_t *dconf = ap_get_module_config(r->per_dir_config,
&proxy_fcgi_module);
/* We do not call ap_proxy_canonenc_ex() on the path here, don't * let control characters pass still, and for php-fpm no '?' either.
*/ if (FCGI_MAY_BE_FPM(dconf)) { while (!apr_iscntrl(*c) && *c != '?')
c++;
} else { while (!apr_iscntrl(*c))
c++;
} if (*c) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10414) "To be forwarded path contains control characters%s (%s)",
FCGI_MAY_BE_FPM(dconf) ? " or '?'" : "", url); return HTTP_FORBIDDEN;
}
path = url; /* this is the raw path */
} else {
core_dir_config *d = ap_get_core_module_config(r->per_dir_config); int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
/* Resolve SCRIPT_NAME/FILENAME from the filesystem? */ if (rconf && rconf->dirwalk_uri_path) { char *saved_uri = r->uri; char *saved_path_info = r->path_info; char *saved_canonical_filename = r->canonical_filename; int saved_filetype = r->finfo.filetype; int i = 0;
r->proxyreq = PROXYREQ_NONE; do {
r->path_info = NULL;
r->finfo.filetype = APR_NOFILE;
r->uri = r->filename = r->canonical_filename = rconf->dirwalk_uri_path; /* Try without than with DocumentRoot prefix */ if (i && ap_core_translate(r) != OK) { continue;
}
ap_directory_walk(r);
} while (r->finfo.filetype != APR_REG && ++i < 2);
r->proxyreq = PROXYREQ_REVERSE;
/* If no actual script was found, fall back to the "proxy:" * SCRIPT_FILENAME dealt with below or by FPM directly.
*/ if (r->finfo.filetype != APR_REG) {
r->filename = proxy_filename;
r->canonical_filename = saved_canonical_filename;
r->finfo.filetype = saved_filetype;
r->path_info = saved_path_info;
}
/* Restore REQUEST_URI in any case */
r->uri = saved_uri;
}
if (!strncmp(r->filename, "proxy:balancer://", 17)) {
newfname = apr_pstrdup(r->pool, r->filename+17);
}
if (!FCGI_MAY_BE_FPM(dconf)) { if (!strncmp(r->filename, "proxy:fcgi://", 13)) { /* If we strip this under FPM, and any internal redirect occurs * on PATH_INFO, FPM may use PATH_TRANSLATED instead of * SCRIPT_FILENAME (a la mod_fastcgi + Action).
*/
newfname = apr_pstrdup(r->pool, r->filename+13);
}
/* Strip potential query string (nocanon) from SCRIPT_FILENAME * if it's the same as QUERY_STRING.
*/ if (newfname && r->args && *r->args) { char *qs = strchr(newfname, '?'); if (qs && !strcmp(qs+1, r->args)) {
*qs = '\0';
}
}
}
if (newfname) {
r->filename = ap_strchr(newfname, '/');
}
}
ap_add_common_vars(r);
ap_add_cgi_vars(r);
/* SCRIPT_NAME/FILENAME set, restore original */
r->filename = proxy_filename;
/* XXX are there any FastCGI specific env vars we need to send? */
/* Give admins final option to fine-tune env vars */ if (APR_SUCCESS != (rv = fix_cgivars(r, dconf))) { return rv;
}
/* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in * the TZ value specially. We could use that, but it would mean * parsing the key/value pairs back OUT of the allocated env array, * not to mention allocating a totally useless array in the first
* place, which would suck. */
for (i = 0; i < envarr->nelts; ++i) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(01062) "sending env var '%s' value '%s'",
elts[i].key, elts[i].val);
}
}
/* Send envvars over in as many FastCGI records as it takes, */
next_elem = 0; /* starting with the first one */
avail_len = 16 * 1024; /* our limit per record, which could have been up * to AP_FCGI_MAX_CONTENT_LEN
*/
if (!required_len) { if (next_elem < envarr->nelts) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
APLOGNO(02536) "couldn't encode envvar '%s' in %"
APR_SIZE_T_FMT " bytes",
elts[next_elem].key, avail_len); /* skip this envvar and continue */
++next_elem; continue;
} /* only an unused element at the end of the array */ break;
}
body = apr_palloc(temp_pool, required_len);
rv = ap_fcgi_encode_env(r, r->subprocess_env, body, required_len,
&starting_elem); /* we pre-compute, so we can't run out of space */
ap_assert(rv == APR_SUCCESS); /* compute and encode must be in sync */
ap_assert(starting_elem == next_elem);
/* Try to find the end of the script headers in the response from the back * end fastcgi server. STATE holds the current header parsing state for this * request. * * Returns 0 if it can't find the end of the headers, and 1 if it found the
* end of the headers. */ staticint handle_headers(request_rec *r, int *state, constchar *readbuf, apr_size_t readlen)
{ constchar *itr = readbuf;
while (readlen--) { if (*itr == '\r') { switch (*state) { case HDR_STATE_GOT_CRLF:
*state = HDR_STATE_GOT_CRLFCR; break;
/* Now get the actual data. Yes it sucks to do this in a second * recv call, this will eventually change when we move to real
* nonblocking recv calls. */ if (readbuflen != 0) {
rv = get_data(conn, iobuf, &readbuflen); if (rv != APR_SUCCESS) {
*err = "reading response body"; break;
}
}
switch (type) { case AP_FCGI_STDOUT: if (clen != 0) {
b = apr_bucket_transient_create(iobuf,
readbuflen,
c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(ob, b);
if (! seen_end_of_headers) { int st = handle_headers(r, &header_state,
iobuf, readbuflen);
if (st == 1) { int status;
seen_end_of_headers = 1;
status = ap_scan_script_header_err_brigade_ex(r, ob,
NULL, APLOG_MODULE_INDEX);
/* FCGI has its own body framing mechanism which we don't * match against any provided Content-Length, so let the * core determine C-L vs T-E based on what's actually sent.
*/ if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR))
apr_table_unset(r->headers_out, "Content-Length");
apr_table_unset(r->headers_out, "Transfer-Encoding");
/* suck in all the rest */ if (status != OK) {
apr_bucket *tmp_b;
apr_brigade_cleanup(ob);
tmp_b = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(ob, tmp_b);
*has_responded = 1;
r->status = status;
rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) {
*err = "passing headers brigade to output filters"; break;
} elseif (status == HTTP_NOT_MODIFIED
|| status == HTTP_PRECONDITION_FAILED) { /* Special 'status' cases handled: * 1) HTTP 304 response MUST NOT contain * a message-body, ignore it. * 2) HTTP 412 response. * The break is not added since there might * be more bytes to read from the FCGI * connection. Even if the message-body is * ignored (and the EOS bucket has already * been sent) we want to avoid subsequent
* bogus reads. */
ignore_body = 1;
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01070) "Error parsing script headers");
rv = APR_EINVAL; break;
}
}
if (ap_proxy_should_override(conf, r->status) && ap_is_initial_req(r)) { /* * set script_error_status to discard * everything after the headers
*/
script_error_status = r->status; /* * prevent ap_die() from treating this as a * recursive error, initially:
*/
r->status = HTTP_OK;
}
if (script_error_status == HTTP_OK
&& !APR_BRIGADE_EMPTY(ob) && !ignore_body) { /* Send the part of the body that we read while * reading the headers.
*/
*has_responded = 1;
rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) {
*err = "passing brigade to output filters"; break;
}
mayflush = 1;
}
apr_brigade_cleanup(ob);
apr_pool_clear(setaside_pool);
} else { /* We're still looking for the end of the * headers, so this part of the data will need
* to persist. */
apr_bucket_setaside(b, setaside_pool);
}
} else { /* we've already passed along the headers, so now pass * through the content. we could simply continue to * setaside the content and not pass until we see the * 0 content-length (below, where we append the EOS), * but that could be a huge amount of data; so we pass * along smaller chunks
*/ if (script_error_status == HTTP_OK && !ignore_body) {
*has_responded = 1;
rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) {
*err = "passing brigade to output filters"; break;
}
mayflush = 1;
}
apr_brigade_cleanup(ob);
}
/* If we didn't read all the data, go back and get the
* rest of it. */ if (clen > readbuflen) {
clen -= readbuflen; goto recv_again;
}
} else { /* XXX what if we haven't seen end of the headers yet? */
if (script_error_status == HTTP_OK) {
b = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(ob, b);
/* XXX Why don't we cleanup here? (logic from AJP) */
} break;
case AP_FCGI_STDERR: /* TODO: Should probably clean up this logging a bit... */ if (clen) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01071) "Got error '%.*s'", (int)readbuflen, iobuf);
}
/* * process the request and write the response.
*/ staticint fcgi_do_request(apr_pool_t *p, request_rec *r,
proxy_conn_rec *conn,
conn_rec *origin,
proxy_dir_conf *conf,
apr_uri_t *uri, char *url, char *server_portstr,
apr_bucket_brigade *input_brigade)
{ /* Request IDs are arbitrary numbers that we assign to a * single request. This would allow multiplex/pipelining of * multiple requests to the same FastCGI connection, but * we don't support that, and always use a value of '1' to
* keep things simple. */
apr_uint16_t request_id = 1;
apr_status_t rv;
apr_pool_t *temp_pool; constchar *err; int bad_request = 0,
has_responded = 0;
/* Step 3: Read records from the back end server and handle them. */
rv = dispatch(conn, conf, r, temp_pool, request_id,
&err, &bad_request, &has_responded,
input_brigade); if (rv != APR_SUCCESS) { /* If the client aborted the connection during retrieval or (partially) * sending the response, don't return a HTTP_SERVICE_UNAVAILABLE, since
* this is not a backend problem. */ if (r->connection->aborted) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "The client aborted the connection.");
conn->close = 1; return OK;
}
/* Create space for state information */
status = ap_proxy_acquire_connection(FCGI_SCHEME, &backend, worker,
r->server); if (status != OK) { if (backend) {
backend->close = 1;
ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
} return status;
}
backend->is_ssl = 0;
/* Step One: Determine Who To Connect To */
uri = apr_palloc(p, sizeof(*uri));
status = ap_proxy_determine_connection(p, r, conf, worker, backend,
uri, &url, proxyname, proxyport,
server_portstr, sizeof(server_portstr)); if (status != OK) { goto cleanup;
}
/* We possibly reuse input data prefetched in previous call(s), e.g. for a * balancer fallback scenario.
*/
apr_pool_userdata_get((void **)&input_brigade, "proxy-fcgi-input", p); if (input_brigade == NULL) { constchar *old_te = apr_table_get(r->headers_in, "Transfer-Encoding"); constchar *old_cl = NULL; if (old_te) {
apr_table_unset(r->headers_in, "Content-Length");
} else {
old_cl = apr_table_get(r->headers_in, "Content-Length");
}
/* Prefetch (nonlocking) the request body so to increase the chance * to get the whole (or enough) body and determine Content-Length vs * chunked or spooled. By doing this before connecting or reusing the * backend, we want to minimize the delay between this connection is * considered alive and the first bytes sent (should the client's link * be slow or some input filter retain the data). This is a best effort * to prevent the backend from closing (from under us) what it thinks is * an idle connection, hence to reduce to the minimum the unavoidable * local is_socket_connected() vs remote keepalive race condition.
*/
status = ap_proxy_prefetch_input(r, backend, input_brigade,
APR_NONBLOCK_READ, &input_bytes,
MAX_MEM_SPOOL); if (status != OK) { goto cleanup;
}
/* * The request body is streamed by default, using either C-L or * chunked T-E, like this: * * The whole body (including no body) was received on prefetch, i.e. * the input brigade ends with EOS => C-L = input_bytes. * * C-L is known and reliable, i.e. only protocol filters in the input * chain thus none should change the body => use C-L from client. * * The administrator has not "proxy-sendcl" which prevents T-E => use * T-E and chunks. * * Otherwise we need to determine and set a content-length, so spool * the entire request body to memory/temporary file (MAX_MEM_SPOOL), * such that we finally know its length => C-L = input_bytes.
*/ if (!APR_BRIGADE_EMPTY(input_brigade)
&& APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) { /* The whole thing fit, so our decision is trivial, use the input * bytes for the Content-Length. If we expected no body, and read * no body, do not set the Content-Length.
*/ if (old_cl || old_te || input_bytes) {
apr_table_setn(r->headers_in, "Content-Length",
apr_off_t_toa(p, input_bytes)); if (old_te) {
apr_table_unset(r->headers_in, "Transfer-Encoding");
}
}
} elseif (old_cl && r->input_filters == r->proto_input_filters) { /* Streaming is possible by preserving the existing C-L */
} elseif (!apr_table_get(r->subprocess_env, "proxy-sendcl")) { /* Streaming is possible using T-E: chunked */
} else { /* No streaming, C-L is the only option so spool to memory/file */
apr_bucket_brigade *tmp_bb;
apr_off_t remaining_bytes = 0;
/* This scheme handler does not reuse connections by default, to * avoid tying up a fastcgi that isn't expecting to work on * parallel requests. But if the user went out of their way to * type the default value of disablereuse=off, we'll allow it.
*/
backend->close = 1; if (worker->s->disablereuse_set && !worker->s->disablereuse) {
backend->close = 0;
}
/* Step Two: Make the Connection */ if (ap_proxy_check_connection(FCGI_SCHEME, backend, r->server, 0,
PROXY_CHECK_CONN_EMPTY)
&& ap_proxy_connect_backend(FCGI_SCHEME, backend, worker,
r->server)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01079) "failed to make connection to backend: %s",
backend->hostname);
status = HTTP_SERVICE_UNAVAILABLE; goto cleanup;
}
/* Step Three: Process the Request */
status = fcgi_do_request(p, r, backend, origin, dconf, uri, url,
server_portstr, input_brigade);
new = apr_array_push(dconf->env_fixups);
new->cond = ap_expr_parse_cmd(cmd, arg1, 0, &err, NULL); if (err) { return apr_psprintf(cmd->pool, "Could not parse expression \"%s\": %s",
arg1, err);
}
if (envvar[0] == '!') { /* Unset mode. */ if (arg3) { return apr_psprintf(cmd->pool, "Third argument (\"%s\") is not " "allowed when using ProxyFCGISetEnvIf's unset " "mode (%s)", arg3, envvar);
} elseif (!envvar[1]) { /* i.e. someone tried to give us a name of just "!" */ return"ProxyFCGISetEnvIf: \"!\" is not a valid variable name";
}
new->subst = NULL;
} else { /* Set mode. */ if (!arg3) { /* A missing expr-value should be treated as empty. */
arg3 = "";
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.