[core] shared code for socket backends

common codebase for socket backends, based off mod_fastcgi with
some features added for mod_proxy

(mostly intended to reduce code duplication and enhance code isolation)

mod_fastcgi and mod_scgi can now use fastcgi.balance and scgi.balance
  for similar behavior as proxy.balance, but the balancing is per-host
  and not per-proc.  proxy.balance is also per-host and not per-proc.

mod_proxy and mod_scgi can now use proxy.map-extensions and
scgi.map-extensions, similar to fastcgi.map-extensions.

mod_fastcgi behavior change (affects only mod_status):
- statistics tags have been renamed from "fastcgi.*" to "gw.*"
  "fastcgi.backend.*"       -> "gw.backend.*"
  "fastcgi.active-requests" -> "gw.active-requests"
  ("fastcgi.requests" remains "fastcgi.requests")
  ("proxy.requests" is new)
  ("scgi.requests" is new)

mod_scgi behavior change (likely minor):
- removed scgi_proclist_sort_down() and scgi_proclist_sort_up().
  procs now chosen based on load as measured by num socket connnections

Note:
modules using gw_backend.[ch] are currently still independent modules.
If it had been written as a single module with fastcgi, scgi, proxy
implementations, then there would have been a chance of breaking some
existing user configurations where module ordering made a difference
for which module handled a given request, though for most people, this
would have made no difference.

Details about mod_fastcgi code transformations:
unsigned int debug -> int debug
fastcgi_env member removed from plugin_config
renamed "fcgi" and "fastcgi" to "gw", and "FCGI" to "GW"
reorganize routines for high-level and lower-level interfaces
some lower-level internal interfaces changed to use host,proc,debug
  args rather than knowing about higher-level (app) hctx and plugin_data
tabs->spaces and reformatting
personal/stbuehler/mod-csrf
Glenn Strauss 2017-07-14 01:29:18 -04:00
parent 81b8fffd31
commit 45b970e69b
11 changed files with 3018 additions and 5723 deletions

View File

@ -542,7 +542,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
set(COMMON_SRC
base64.c buffer.c log.c
keyvalue.c chunk.c
http_chunk.c stream.c fdevent.c
http_chunk.c stream.c fdevent.c gw_backend.c
stat_cache.c plugin.c joblist.c etag.c array.c
data_string.c data_array.c
data_integer.c md5.c

View File

@ -57,7 +57,7 @@ CLEANFILES = versionstamp.h versionstamp.h.tmp lemon$(BUILD_EXEEXT)
common_src=base64.c buffer.c log.c \
keyvalue.c chunk.c \
http_chunk.c stream.c fdevent.c \
http_chunk.c stream.c fdevent.c gw_backend.c \
stat_cache.c plugin.c joblist.c etag.c array.c \
data_string.c data_array.c \
data_integer.c md5.c \
@ -375,7 +375,7 @@ hdr = server.h base64.h buffer.h network.h log.h keyvalue.h \
response.h request.h fastcgi.h chunk.h \
first.h settings.h http_chunk.h \
md5.h http_auth.h http_vhostdb.h stream.h \
fdevent.h connections.h base.h stat_cache.h \
fdevent.h gw_backend.h connections.h base.h stat_cache.h \
plugin.h \
etag.h joblist.h array.h vector.h crc32.h \
network_backends.h configfile.h \

View File

@ -51,7 +51,7 @@ def GatherLibs(env, *libs):
common_src = Split("base64.c buffer.c log.c \
keyvalue.c chunk.c \
http_chunk.c stream.c fdevent.c \
http_chunk.c stream.c fdevent.c gw_backend.c \
stat_cache.c plugin.c joblist.c etag.c array.c \
data_string.c data_array.c \
data_integer.c md5.c \

2447
src/gw_backend.c Normal file

File diff suppressed because it is too large Load Diff

343
src/gw_backend.h Normal file
View File

@ -0,0 +1,343 @@
#ifndef INCLUDED_GW_BACKEND_H
#define INCLUDED_GW_BACKEND_H
#include "first.h"
#include <sys/types.h>
#include "array.h"
#include "buffer.h"
typedef struct {
char **ptr;
size_t size;
size_t used;
} char_array;
typedef struct gw_proc {
size_t id; /* id will be between 1 and max_procs */
buffer *unixsocket; /* config.socket + "-" + id */
unsigned port; /* config.port + pno */
/* either tcp:<host>:<port> or unix:<socket> for debugging purposes */
buffer *connection_name;
pid_t pid; /* PID of the spawned process (0 if not spawned locally) */
size_t load; /* number of requests waiting on this process */
time_t last_used; /* see idle_timeout */
size_t requests; /* see max_requests */
struct gw_proc *prev, *next; /* see first */
time_t disabled_until; /* proc disabled until given time */
int is_local;
enum {
PROC_STATE_RUNNING, /* alive */
PROC_STATE_OVERLOADED, /* listen-queue is full */
PROC_STATE_DIED_WAIT_FOR_PID, /* */
PROC_STATE_DIED, /* marked as dead, should be restarted */
PROC_STATE_KILLED /* killed (signal sent to proc) */
} state;
} gw_proc;
typedef struct {
/* the key that is used to reference this value */
buffer *id;
/* list of processes handling this extension
* sorted by lowest load
*
* whenever a job is done move it up in the list
* until it is sorted, move it down as soon as the
* job is started
*/
gw_proc *first;
gw_proc *unused_procs;
/*
* spawn at least min_procs, at max_procs.
*
* as soon as the load of the first entry
* is max_load_per_proc we spawn a new one
* and add it to the first entry and give it
* the load
*
*/
unsigned short min_procs;
unsigned short max_procs;
size_t num_procs; /* how many procs are started */
size_t active_procs; /* how many procs in state PROC_STATE_RUNNING */
unsigned short max_load_per_proc;
/*
* kick the process from the list if it was not
* used for idle_timeout until min_procs is
* reached. this helps to get the processlist
* small again we had a small peak load.
*
*/
unsigned short idle_timeout;
/*
* time after a disabled remote connection is tried to be re-enabled
*
*
*/
unsigned short disable_time;
/*
* some gw processes get a little bit larger
* than wanted. max_requests_per_proc kills a
* process after a number of handled requests.
*
*/
size_t max_requests_per_proc;
/* config */
/*
* host:port
*
* if host is one of the local IP adresses the
* whole connection is local
*
* if port is not 0, and host is not specified,
* "localhost" (INADDR_LOOPBACK) is assumed.
*
*/
buffer *host;
unsigned short port;
unsigned short family; /* sa_family_t */
/*
* Unix Domain Socket
*
* instead of TCP/IP we can use Unix Domain Sockets
* - more secure (you have fileperms to play with)
* - more control (on locally)
* - more speed (no extra overhead)
*/
buffer *unixsocket;
/* if socket is local we can start the gw process ourself
*
* bin-path is the path to the binary
*
* check min_procs and max_procs for the number
* of process to start up
*/
buffer *bin_path;
/* bin-path is set bin-environment is taken to
* create the environement before starting the
* FastCGI process
*
*/
array *bin_env;
array *bin_env_copy;
/*
* docroot-translation between URL->phys and the
* remote host
*
* reasons:
* - different dir-layout if remote
* - chroot if local
*
*/
buffer *docroot;
/*
* check_local tells you if the phys file is stat()ed
* or not. FastCGI doesn't care if the service is
* remote. If the web-server side doesn't contain
* the FastCGI-files we should not stat() for them
* and say '404 not found'.
*/
unsigned short check_local;
/*
* append PATH_INFO to SCRIPT_FILENAME
*
* php needs this if cgi.fix_pathinfo is provided
*
*/
unsigned short break_scriptfilename_for_php;
/*
* workaround for program when prefix="/"
*
* rule to build PATH_INFO is hardcoded for when check_local is disabled
* enable this option to use the workaround
*
*/
unsigned short fix_root_path_name;
/*
* If the backend includes X-Sendfile in the response
* we use the value as filename and ignore the content.
*
*/
unsigned short xsendfile_allow;
array *xsendfile_docroot;
ssize_t load;
size_t max_id; /* corresponds most of the time to num_procs */
buffer *strip_request_uri;
unsigned short kill_signal; /* we need a setting for this as libfcgi
applications prefer SIGUSR1 while the
rest of the world would use SIGTERM
*sigh* */
int listen_backlog;
int refcount;
char_array args;
} gw_host;
/*
* one extension can have multiple hosts assigned
* one host can spawn additional processes on the same
* socket (if we control it)
*
* ext -> host -> procs
* 1:n 1:n
*
* if the gw process is remote that whole goes down
* to
*
* ext -> host -> procs
* 1:n 1:1
*
* in case of PHP and FCGI_CHILDREN we have again a procs
* but we don't control it directly.
*
*/
typedef struct {
buffer *key; /* like .php */
int note_is_sent;
int last_used_ndx;
gw_host **hosts;
size_t used;
size_t size;
} gw_extension;
typedef struct {
gw_extension **exts;
size_t used;
size_t size;
} gw_exts;
#include "base.h"
#include "plugin.h"
#include "response.h"
typedef struct gw_plugin_config {
gw_exts *exts;
gw_exts *exts_auth;
gw_exts *exts_resp;
array *ext_mapping;
int balance;
int proto;
int debug;
} gw_plugin_config;
/* generic plugin data, shared between all connections */
typedef struct gw_plugin_data {
PLUGIN_DATA;
gw_plugin_config **config_storage;
gw_plugin_config conf; /* used only as long as no gw_handler_ctx is setup */
} gw_plugin_data;
/* connection specific data */
typedef enum {
GW_STATE_INIT,
GW_STATE_CONNECT_DELAYED,
GW_STATE_PREPARE_WRITE,
GW_STATE_WRITE,
GW_STATE_READ
} gw_connection_state_t;
#define GW_RESPONDER 1
#define GW_AUTHORIZER 2
#define GW_FILTER 3 /*(not implemented)*/
typedef struct gw_handler_ctx {
gw_proc *proc;
gw_host *host;
gw_extension *ext;
gw_extension *ext_auth; /* (future: might allow multiple authorizers)*/
unsigned short gw_mode; /* mode: GW_AUTHORIZER or GW_RESPONDER */
gw_connection_state_t state;
time_t state_timestamp;
chunkqueue *rb; /* read queue */
chunkqueue *wb; /* write queue */
off_t wb_reqlen;
buffer *response;
int fd; /* fd to the gw process */
int fde_ndx; /* index into the fd-event buffer */
pid_t pid;
int got_proc;
int reconnects; /* number of reconnect attempts */
int request_id;
int send_content_body;
http_response_opts opts;
gw_plugin_config conf;
connection *remote_conn; /* dumb pointer */
gw_plugin_data *plugin_data; /* dumb pointer */
handler_t(*stdin_append)(server *srv, struct gw_handler_ctx *hctx);
handler_t(*create_env)(server *srv, struct gw_handler_ctx *hctx);
void(*backend_error)(struct gw_handler_ctx *hctx);
void(*handler_ctx_free)(void *hctx);
} gw_handler_ctx;
void * gw_init(void);
void gw_plugin_config_free(gw_plugin_config *s);
handler_t gw_free(server *srv, void *p_d);
int gw_set_defaults_backend(server *srv, gw_plugin_data *p, data_unset *du, size_t i, int sh_exec);
int gw_set_defaults_balance(server *srv, gw_plugin_config *s, data_unset *du);
handler_t gw_check_extension(server *srv, connection *con, gw_plugin_data *p, int uri_path_handler, size_t hctx_sz);
handler_t gw_connection_reset(server *srv, connection *con, void *p_d);
handler_t gw_handle_subrequest(server *srv, connection *con, void *p_d);
void gw_handle_trigger_exts(server *srv, gw_exts *exts, int debug);
handler_t gw_handle_trigger(server *srv, void *p_d);
void gw_set_transparent(server *srv, gw_handler_ctx *hctx);
#endif

View File

@ -1191,7 +1191,8 @@ handler_t http_response_parse_headers(server *srv, connection *con, http_respons
}
}
return HANDLER_GO_ON;
/* (callback for response headers complete) */
return (opts->headers) ? opts->headers(srv, con, opts) : HANDLER_GO_ON;
}

View File

@ -37,8 +37,6 @@ static int pipe_cloexec(int pipefd[2]) {
: -1;
}
enum {EOL_UNSET, EOL_N, EOL_RN};
typedef struct {
char **ptr;
@ -413,16 +411,9 @@ static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) {
}
static handler_t cgi_response_read(server *srv, handler_ctx *hctx) {
connection * const con = hctx->remote_conn;
const int file_started = con->file_started;
const handler_t rc =
http_response_read(srv, con, &hctx->opts,
hctx->response, hctx->fd, &hctx->fde_ndx);
if (file_started || !con->file_started || con->mode == DIRECT) return rc;
static handler_t cgi_response_headers(server *srv, connection *con, struct http_response_opts_t *opts) {
/* response headers just completed */
handler_ctx *hctx = (handler_ctx *)opts->pdata;
if (con->parsed_response & HTTP_UPGRADE) {
if (hctx->conf.upgrade && con->http_status == 101) {
@ -449,12 +440,13 @@ static handler_t cgi_response_read(server *srv, handler_ctx *hctx) {
}
}
return rc;
return HANDLER_GO_ON;
}
static int cgi_recv_response(server *srv, handler_ctx *hctx) {
switch (cgi_response_read(srv, hctx)) {
switch (http_response_read(srv, hctx->remote_conn, &hctx->opts,
hctx->response, hctx->fd, &hctx->fde_ndx)) {
default:
return HANDLER_GO_ON;
case HANDLER_ERROR:
@ -1008,6 +1000,8 @@ URIHANDLER_FUNC(cgi_is_handled) {
hctx->opts.local_redir = hctx->conf.local_redir;
hctx->opts.xsendfile_allow = hctx->conf.xsendfile_allow;
hctx->opts.xsendfile_docroot = hctx->conf.xsendfile_docroot;
hctx->opts.pdata = hctx;
hctx->opts.headers = cgi_response_headers;
con->plugin_ctx[p->id] = hctx;
con->mode = p->id;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -35,8 +35,9 @@ typedef struct http_response_opts_t {
unsigned short local_redir;
unsigned short xsendfile_allow;
array *xsendfile_docroot;
handler_t(*parse)(server *, connection *, struct http_response_opts_t *, buffer *, size_t);
void *pdata;
handler_t(*parse)(server *, connection *, struct http_response_opts_t *, buffer *, size_t);
handler_t(*headers)(server *, connection *, struct http_response_opts_t *);
} http_response_opts;
typedef int (*http_cgi_header_append_cb)(void *vdata, const char *k, size_t klen, const char *v, size_t vlen);