/* 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 .
*/
/*
* mod_proxy_scgi . c
* Proxy backend module for the SCGI protocol
* ( http : //python.ca/scgi/protocol.txt)
*
* Andr � Malo ( nd / perlig . de ) , August 2007
*/
#define APR_WANT_MEMFUNC
#define APR_WANT_STRFUNC
#include "apr_strings.h"
#include "ap_hooks.h"
#include "apr_optional_hooks.h"
#include "apr_buckets.h"
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "util_script.h"
#include "mod_proxy.h"
#include "scgi.h"
#define SCHEME "scgi"
#define PROXY_FUNCTION "SCGI"
#define SCGI_MAGIC "SCGI"
#define SCGI_PROTOCOL_VERSION "1"
/* just protect from typos */
#define CONTENT_LENGTH "CONTENT_LENGTH"
#define GATEWAY_INTERFACE "GATEWAY_INTERFACE"
module AP_MODULE_DECLARE_DATA proxy_scgi_module;
typedef enum {
scgi_internal_redirect,
scgi_sendfile
} scgi_request_type;
typedef struct {
const char *location; /* target URL */
scgi_request_type type; /* type of request */
} scgi_request_config;
const char *scgi_sendfile_off = "off" ;
const char *scgi_sendfile_on = "X-Sendfile" ;
const char *scgi_internal_redirect_off = "off" ;
const char *scgi_internal_redirect_on = "Location" ;
typedef struct {
const char *sendfile;
const char *internal_redirect;
} scgi_config;
/*
* We create our own bucket type , which is actually derived ( c & p ) from the
* socket bucket .
* Maybe some time this should be made more abstract ( like passing an
* interception function to read or something ) and go into the ap_ or
* even apr_ namespace .
*/
typedef struct {
apr_socket_t *sock;
apr_off_t *counter;
} socket_ex_data;
static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
apr_bucket_alloc_t *list);
static apr_status_t bucket_socket_ex_read(apr_bucket *a, const char **str,
apr_size_t *len,
apr_read_type_e block)
{
socket_ex_data *data = a->data;
apr_socket_t *p = data->sock;
char *buf;
apr_status_t rv;
apr_interval_time_t timeout;
if (block == APR_NONBLOCK_READ) {
apr_socket_timeout_get(p, &timeout);
apr_socket_timeout_set(p, 0 );
}
*str = NULL;
*len = APR_BUCKET_BUFF_SIZE;
buf = apr_bucket_alloc(*len, a->list);
rv = apr_socket_recv(p, buf, len);
if (block == APR_NONBLOCK_READ) {
apr_socket_timeout_set(p, timeout);
}
if (rv != APR_SUCCESS && rv != APR_EOF) {
apr_bucket_free(buf);
return rv;
}
if (*len > 0 ) {
apr_bucket_heap *h;
/* count for stats */
*data->counter += *len;
/* Change the current bucket to refer to what we read */
a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
h = a->data;
h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
*str = buf;
APR_BUCKET_INSERT_AFTER(a, bucket_socket_ex_create(data, a->list));
}
else {
apr_bucket_free(buf);
a = apr_bucket_immortal_make(a, "" , 0 );
*str = a->data;
}
return APR_SUCCESS;
}
static const apr_bucket_type_t bucket_type_socket_ex = {
"SOCKET_EX" , 5 , APR_BUCKET_DATA,
apr_bucket_destroy_noop,
bucket_socket_ex_read,
apr_bucket_setaside_notimpl,
apr_bucket_split_notimpl,
apr_bucket_copy_notimpl
};
static apr_bucket *bucket_socket_ex_make(apr_bucket *b, socket_ex_data *data)
{
b->type = &bucket_type_socket_ex;
b->length = (apr_size_t)(-1 );
b->start = -1 ;
b->data = data;
return b;
}
static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
apr_bucket_alloc_t *list)
{
apr_bucket *b = apr_bucket_alloc(sizeof (*b), list);
APR_BUCKET_INIT(b);
b->free = apr_bucket_free;
b->list = list;
return bucket_socket_ex_make(b, data);
}
/*
* Canonicalize scgi - like URLs .
*/
static int scgi_canon(request_rec *r, char *url)
{
char *host, sport[sizeof (":65535" )];
const char *err, *path;
apr_port_t port, def_port;
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 ;
if (ap_cstr_casecmpn(url, SCHEME "://", sizeof(SCHEME) + 2)) {
return DECLINED;
}
url += sizeof (SCHEME); /* Keep slashes */
port = def_port = SCGI_DEF_PORT;
err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0 , r, APLOGNO(00857 )
"error parsing URL %s: %s" , url, err);
return HTTP_BAD_REQUEST;
}
if (port != def_port) {
apr_snprintf(sport, sizeof (sport), ":%u" , port);
}
else {
sport[0 ] = '\0' ;
}
if (ap_strchr(host, ':' )) { /* if literal IPv6 address */
host = apr_pstrcat(r->pool, "[" , host, "]" , NULL);
}
path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags,
r->proxyreq);
if (!path) {
return HTTP_BAD_REQUEST;
}
r->filename = apr_pstrcat(r->pool, "proxy:" SCHEME "://", host, sport, "/",
path, NULL);
if (apr_table_get(r->subprocess_env, "proxy-scgi-pathinfo" )) {
r->path_info = apr_pstrcat(r->pool, "/" , path, NULL);
}
return OK;
}
/*
* Send a block of data , ensure , everything is sent
*/
static int sendall(proxy_conn_rec *conn, const char *buf, apr_size_t length,
request_rec *r)
{
apr_status_t rv;
apr_size_t written;
while (length > 0 ) {
written = length;
if ((rv = apr_socket_send(conn->sock, buf, &written)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00858 )
"sending data to %s:%u failed" ,
conn->hostname, conn->port);
return HTTP_SERVICE_UNAVAILABLE;
}
/* count for stats */
conn->worker->s->transferred += written;
buf += written;
length -= written;
}
return OK;
}
/*
* Send SCGI header block
*/
static int send_headers(request_rec *r, proxy_conn_rec *conn)
{
char *buf, *cp, *bodylen;
const char *ns_len;
const apr_array_header_t *env_table;
const apr_table_entry_t *env;
int j;
apr_size_t len, bodylen_size;
apr_size_t headerlen = sizeof (CONTENT_LENGTH)
+ sizeof (SCGI_MAGIC)
+ sizeof (SCGI_PROTOCOL_VERSION);
ap_add_common_vars(r);
ap_add_cgi_vars(r);
/*
* The header blob basically takes the environment and concatenates
* keys and values using 0 bytes . There are special treatments here :
* - GATEWAY_INTERFACE and SCGI_MAGIC are dropped
* - CONTENT_LENGTH is always set and must be sent as the very first
* variable
*
* Additionally it ' s wrapped into a so - called netstring ( see SCGI spec )
*/
env_table = apr_table_elts(r->subprocess_env);
env = (apr_table_entry_t *)env_table->elts;
for (j = 0 ; j < env_table->nelts; ++j) {
if ( (!strcmp(env[j].key, GATEWAY_INTERFACE))
|| (!strcmp(env[j].key, CONTENT_LENGTH))
|| (!strcmp(env[j].key, SCGI_MAGIC))) {
continue ;
}
headerlen += strlen(env[j].key) + strlen(env[j].val) + 2 ;
}
bodylen = apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->remaining);
bodylen_size = strlen(bodylen) + 1 ;
headerlen += bodylen_size;
ns_len = apr_psprintf(r->pool, "%" APR_SIZE_T_FMT ":" , headerlen);
len = strlen(ns_len);
headerlen += len + 1 ; /* 1 == , */
cp = buf = apr_palloc(r->pool, headerlen);
memcpy(cp, ns_len, len);
cp += len;
memcpy(cp, CONTENT_LENGTH, sizeof (CONTENT_LENGTH));
cp += sizeof (CONTENT_LENGTH);
memcpy(cp, bodylen, bodylen_size);
cp += bodylen_size;
memcpy(cp, SCGI_MAGIC, sizeof (SCGI_MAGIC));
cp += sizeof (SCGI_MAGIC);
memcpy(cp, SCGI_PROTOCOL_VERSION, sizeof (SCGI_PROTOCOL_VERSION));
cp += sizeof (SCGI_PROTOCOL_VERSION);
for (j = 0 ; j < env_table->nelts; ++j) {
if ( (!strcmp(env[j].key, GATEWAY_INTERFACE))
|| (!strcmp(env[j].key, CONTENT_LENGTH))
|| (!strcmp(env[j].key, SCGI_MAGIC))) {
continue ;
}
len = strlen(env[j].key) + 1 ;
memcpy(cp, env[j].key, len);
cp += len;
len = strlen(env[j].val) + 1 ;
memcpy(cp, env[j].val, len);
cp += len;
}
*cp++ = ',' ;
return sendall(conn, buf, headerlen, r);
}
/*
* Send request body ( if any )
*/
static int send_request_body(request_rec *r, proxy_conn_rec *conn)
{
if (ap_should_client_block(r)) {
char *buf = apr_palloc(r->pool, AP_IOBUFSIZE);
int status;
long readlen;
readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
while (readlen > 0 ) {
status = sendall(conn, buf, (apr_size_t)readlen, r);
if (status != OK) {
return HTTP_SERVICE_UNAVAILABLE;
}
readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
}
if (readlen == -1 ) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0 , r, APLOGNO(00859 )
"receiving request body failed" );
return HTTP_INTERNAL_SERVER_ERROR;
}
}
return OK;
}
/*
* Fetch response from backend and pass back to the front
*/
static int pass_response(request_rec *r, proxy_conn_rec *conn)
{
apr_bucket_brigade *bb;
apr_bucket *b;
const char *location;
scgi_config *conf;
socket_ex_data *sock_data;
int status;
sock_data = apr_palloc(r->pool, sizeof (*sock_data));
sock_data->sock = conn->sock;
sock_data->counter = &conn->worker->s->read;
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
b = bucket_socket_ex_create(sock_data, r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
b = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
status = ap_scan_script_header_err_brigade_ex(r, bb, NULL,
APLOG_MODULE_INDEX);
if (status != OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0 , r, APLOGNO(00860 )
"error reading response headers from %s:%u" ,
conn->hostname, conn->port);
r->status_line = NULL;
apr_brigade_destroy(bb);
return status;
}
/* SCGI 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" );
conf = ap_get_module_config(r->per_dir_config, &proxy_scgi_module);
if (conf->sendfile && conf->sendfile != scgi_sendfile_off) {
short err = 1 ;
location = apr_table_get(r->err_headers_out, conf->sendfile);
if (!location) {
err = 0 ;
location = apr_table_get(r->headers_out, conf->sendfile);
}
if (location) {
scgi_request_config *req_conf = apr_palloc(r->pool,
sizeof (*req_conf));
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0 , r, APLOGNO(00861 )
"Found %s: %s - preparing subrequest." ,
conf->sendfile, location);
if (err) {
apr_table_unset(r->err_headers_out, conf->sendfile);
}
else {
apr_table_unset(r->headers_out, conf->sendfile);
}
req_conf->location = location;
req_conf->type = scgi_sendfile;
ap_set_module_config(r->request_config, &proxy_scgi_module,
req_conf);
apr_brigade_destroy(bb);
return OK;
}
}
if (r->status == HTTP_OK
&& (!conf->internal_redirect /* default === On */
|| conf->internal_redirect != scgi_internal_redirect_off)) {
short err = 1 ;
const char *location_header = conf->internal_redirect ?
conf->internal_redirect : scgi_internal_redirect_on;
location = apr_table_get(r->err_headers_out, location_header);
if (!location) {
err = 0 ;
location = apr_table_get(r->headers_out, location_header);
}
if (location && *location == '/' ) {
scgi_request_config *req_conf = apr_palloc(r->pool,
sizeof (*req_conf));
if (ap_cstr_casecmp(location_header, "Location" )) {
if (err) {
apr_table_unset(r->err_headers_out, location_header);
}
else {
apr_table_unset(r->headers_out, location_header);
}
}
req_conf->location = location;
req_conf->type = scgi_internal_redirect;
ap_set_module_config(r->request_config, &proxy_scgi_module,
req_conf);
apr_brigade_destroy(bb);
return OK;
}
}
if (ap_pass_brigade(r->output_filters, bb)) {
return AP_FILTER_ERROR;
}
return OK;
}
/*
* Internal redirect / subrequest handler , working on request_status hook
*/
static int scgi_request_status(int *status, request_rec *r)
{
scgi_request_config *req_conf;
if ( (*status == OK)
&& (req_conf = ap_get_module_config(r->request_config,
&proxy_scgi_module))) {
switch (req_conf->type) {
case scgi_internal_redirect:
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0 , r, APLOGNO(00862 )
"Internal redirect to %s" , req_conf->location);
r->status_line = NULL;
if (r->method_number != M_GET) {
/* keep HEAD, which is passed around as M_GET, too */
r->method = "GET" ;
r->method_number = M_GET;
}
apr_table_unset(r->headers_in, "Content-Length" );
ap_internal_redirect_handler(req_conf->location, r);
return OK;
/* break; */
case scgi_sendfile:
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0 , r, APLOGNO(00863 )
"File subrequest to %s" , req_conf->location);
do {
request_rec *rr;
rr = ap_sub_req_lookup_file(req_conf->location, r,
r->output_filters);
if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
/*
* We don ' t touch Content - Length here . It might be
* borked ( there ' s plenty of room for a race condition ) .
* Either the backend sets it or it ' s gonna be chunked .
*/
ap_run_sub_req(rr);
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0 , r, APLOGNO(00864 )
"Subrequest to file '%s' not possible. "
"(rr->status=%d, rr->finfo.filetype=%d)" ,
req_conf->location, rr->status,
rr->finfo.filetype);
*status = HTTP_INTERNAL_SERVER_ERROR;
return *status;
}
} while (0 );
return OK;
/* break; */
}
}
return DECLINED;
}
/*
* This handles scgi : ( dest ) URLs
*/
static int scgi_handler(request_rec *r, proxy_worker *worker,
proxy_server_conf *conf, char *url,
const char *proxyname, apr_port_t proxyport)
{
int status;
proxy_conn_rec *backend = NULL;
apr_pool_t *p = r->pool;
apr_uri_t *uri;
char dummy;
if (ap_cstr_casecmpn(url, SCHEME "://", sizeof(SCHEME) + 2)) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0 , r, APLOGNO(00865 )
"declining URL %s" , url);
return DECLINED;
}
/* Create space for state information */
status = ap_proxy_acquire_connection(PROXY_FUNCTION, &backend, worker,
r->server);
if (status != OK) {
goto cleanup;
}
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,
&dummy, 1 );
if (status != OK) {
goto cleanup;
}
/* Step Two: Make the Connection */
if (ap_proxy_connect_backend(PROXY_FUNCTION, backend, worker, r->server)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0 , r, APLOGNO(00866 )
"failed to make connection to backend: %s:%u" ,
backend->hostname, backend->port);
status = HTTP_SERVICE_UNAVAILABLE;
goto cleanup;
}
/* Step Three: Process the Request */
if ( ((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
|| ((status = send_headers(r, backend)) != OK)
|| ((status = send_request_body(r, backend)) != OK)
|| ((status = pass_response(r, backend)) != OK)) {
goto cleanup;
}
cleanup:
if (backend) {
backend->close = 1 ; /* always close the socket */
ap_proxy_release_connection(PROXY_FUNCTION, backend, r->server);
}
return status;
}
static void *create_scgi_config(apr_pool_t *p, char *dummy)
{
scgi_config *conf=apr_palloc(p, sizeof (*conf));
conf->sendfile = NULL; /* === default (off) */
conf->internal_redirect = NULL; /* === default (on) */
return conf;
}
static void *merge_scgi_config(apr_pool_t *p, void *base_, void *add_)
{
scgi_config *base=base_, *add=add_, *conf=apr_palloc(p, sizeof (*conf));
conf->sendfile = add->sendfile ? add->sendfile: base->sendfile;
conf->internal_redirect = add->internal_redirect
? add->internal_redirect
: base->internal_redirect;
return conf;
}
static const char *scgi_set_send_file(cmd_parms *cmd, void *mconfig,
const char *arg)
{
scgi_config *conf=mconfig;
if (!strcasecmp(arg, "Off" )) {
conf->sendfile = scgi_sendfile_off;
}
else if (!strcasecmp(arg, "On" )) {
conf->sendfile = scgi_sendfile_on;
}
else {
conf->sendfile = arg;
}
return NULL;
}
static const char *scgi_set_internal_redirect(cmd_parms *cmd, void *mconfig,
const char *arg)
{
scgi_config *conf = mconfig;
if (!strcasecmp(arg, "Off" )) {
conf->internal_redirect = scgi_internal_redirect_off;
}
else if (!strcasecmp(arg, "On" )) {
conf->internal_redirect = scgi_internal_redirect_on;
}
else {
conf->internal_redirect = arg;
}
return NULL;
}
static const command_rec scgi_cmds[] =
{
AP_INIT_TAKE1("ProxySCGISendfile" , scgi_set_send_file, NULL,
RSRC_CONF|ACCESS_CONF,
"The name of the X-Sendfile pseudo response header or "
"On or Off" ),
AP_INIT_TAKE1("ProxySCGIInternalRedirect" , scgi_set_internal_redirect, NULL,
RSRC_CONF|ACCESS_CONF,
"The name of the pseudo response header or On or Off" ),
{NULL}
};
static void register_hooks(apr_pool_t *p)
{
proxy_hook_scheme_handler(scgi_handler, NULL, NULL, APR_HOOK_FIRST);
proxy_hook_canon_handler(scgi_canon, NULL, NULL, APR_HOOK_FIRST);
APR_OPTIONAL_HOOK(proxy, request_status, scgi_request_status, NULL, NULL,
APR_HOOK_MIDDLE);
}
AP_DECLARE_MODULE(proxy_scgi) = {
STANDARD20_MODULE_STUFF,
create_scgi_config, /* create per-directory config structure */
merge_scgi_config, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
scgi_cmds, /* command table */
register_hooks /* register hooks */
};
Messung V0.5 in Prozent C=94 H=91 G=92
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland