scgi-1.13/0000775000175000017500000000000011031275132010504 5ustar nasnasscgi-1.13/apache1/0000775000175000017500000000000011031275132012006 5ustar nasnasscgi-1.13/apache1/Makefile0000664000175000017500000000032511024061346013450 0ustar nasnasall: mod_scgi.so mod_scgi.so: mod_scgi.c apxs -c $< clean: rm -f mod_scgi.so mod_scgi.o install: install -m 0755 -d $(DESTDIR)/usr/lib/apache/1.3/ install -m 0755 mod_scgi.so $(DESTDIR)/usr/lib/apache/1.3/ scgi-1.13/apache1/README.txt0000664000175000017500000000164111024061346013510 0ustar nasnasBuilding -------- Using the 'apxs' tool: $ apxs -i -c mod_scgi.c Alternatively, you can use 'make' and 'make install'. Configuration ------------- To enable, add LoadModule scgi_module //mod_scgi.so in your httpd.conf file. Note that it is best to load mod_scgi after mod_rewrite (mod_rewrite needs a higher priority in order to rewrite URLs served by mod_scgi). To serve a set of URLs under one path with an SCGI server, use the SCGIMount directive: SCGIMount /dynamic 127.0.0.1:4000 Demo ----- Quixote >= 2.0a2 has a demo application you can try out by running: python $QUIXOTE/server/scgi_server.py --port 4000 with the appropriate path for $QUIXOTE. If you have Quixote 1.3 or 2.0a1 installed, use the driver bundled with SCGI instead: python scgi/quixote_handler.py -F If you don't have Quixote, the SCGI server doubles as a standalone demo: python scgi/scgi_server.py 4000 scgi-1.13/apache1/mod_scgi.c0000664000175000017500000003616611024061354013753 0ustar nasnas#define MOD_SCGI_VERSION "1.13" #define SCGI_PROTOCOL_VERSION "1" /* #define VERBOSE_DEBUG */ #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include /* for TCP_NODELAY */ #define UNSET 0 #define ENABLED 1 #define DISABLED 2 typedef struct { char *path; unsigned long addr; /* address in network byte order */ unsigned short port; /* port in network byte order */ } mount_entry; /* * Configuration record. Used per-directory configuration data. */ typedef struct { mount_entry mount; int enabled; /* mod_scgi enabled from this directory */ } scgi_cfg; /* Server level configuration */ typedef struct { array_header *mounts; } scgi_server_cfg; /* * Declare ourselves so the configuration routines can find and know us. * We'll fill it in at the end of the module. */ module MODULE_VAR_EXPORT scgi_module; /* * Locate our directory configuration record for the current request. */ static scgi_cfg *our_dconfig(request_rec *r) { return (scgi_cfg *) ap_get_module_config(r->per_dir_config, &scgi_module); } static scgi_server_cfg *our_sconfig(server_rec *s) { return (scgi_server_cfg *) ap_get_module_config(s->module_config, &scgi_module); } static int mount_entry_matches(char *url, char *prefix, char **path_info) { int i; for (i=0; prefix[i] != '\0'; i++) { if (url[i] == '\0' || url[i] != prefix[i]) return 0; } if (url[i] == '\0' || url[i] == '/') { *path_info = url + i; return 1; } return 0; } static int scgi_trans(request_rec *r) { scgi_cfg *cfg = our_dconfig(r); scgi_server_cfg *scfg = our_sconfig(r->server); if (cfg->enabled == DISABLED) { return DECLINED; } if (cfg->mount.addr != UNSET) { r->handler = "scgi-handler"; return OK; } else { int i; mount_entry *entries = (mount_entry *) scfg->mounts->elts; for (i = 0; i < scfg->mounts->nelts; ++i) { char *path_info; mount_entry *p = &entries[i]; if (mount_entry_matches(r->uri, p->path, &path_info)) { r->handler = "scgi-handler"; r->path_info = path_info; ap_set_module_config(r->request_config, &scgi_module, p); return OK; } } } return DECLINED; } int open_socket(request_rec *r) { int retries, sleeptime, rv; struct sockaddr_in addr; int sock; scgi_cfg *cfg = our_dconfig(r); mount_entry *p = (mount_entry *) ap_get_module_config(r->request_config, &scgi_module); if (!p) { p = &cfg->mount; } if (p->addr == UNSET) addr.sin_addr.s_addr = inet_addr("127.0.0.1"); else addr.sin_addr.s_addr = p->addr; if (p->port == UNSET) addr.sin_port = htons(4000); else addr.sin_port = p->port; addr.sin_family = AF_INET; /* try to connect */ retries = 4; sleeptime = 1; restart: /* create the socket */ sock = ap_psocket(r->pool, AF_INET, SOCK_STREAM, 0); if (sock == -1) return -1; rv = connect(sock, (struct sockaddr *)&addr, sizeof addr); if (rv != 0) { ap_pclosesocket(r->pool, sock); if (errno == EINTR) goto restart; /* signals suck */ if (errno == ECONNREFUSED && retries > 0) { /* server may be temporarily down, retry */ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "scgi: connection refused, retrying"); sleep(sleeptime); --retries; sleeptime *= 2; goto restart; } ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "scgi: connecting to server"); return -1; } #ifdef TCP_NODELAY if (addr.sin_family == AF_INET) { /* disable Nagle */ int set = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&set, sizeof(set)); } #endif return sock; } static char *http2env(pool *p, const char *name) { char *env_name = ap_pstrcat(p, "HTTP_", name, NULL); char *cp; for (cp = env_name + 5; *cp != 0; cp++) { if (*cp == '-') { *cp = '_'; } else { *cp = ap_toupper(*cp); } } return env_name; } static char *lookup_name(table *t, const char *name) { array_header *hdrs_arr; table_entry *hdrs; int i; hdrs_arr = ap_table_elts(t); hdrs = (table_entry *)hdrs_arr->elts; for (i = 0; i < hdrs_arr->nelts; ++i) { if (hdrs[i].key == NULL) { continue; } if (strcasecmp(hdrs[i].key, name) == 0) { return hdrs[i].val; } } return NULL; } static char *lookup_header(request_rec *r, const char *name) { return lookup_name(r->headers_in, name); } static char *original_uri(request_rec *r) { char *first, *last; if (r->the_request == NULL) { return (char *) ap_pcalloc(r->pool, 1); } first = r->the_request; /* use the request-line */ while (*first && !ap_isspace(*first)) { ++first; /* skip over the method */ } while (ap_isspace(*first)) { ++first; /* and the space(s) */ } last = first; while (*last && !ap_isspace(*last)) { ++last; /* end at next whitespace */ } return ap_pstrndup(r->pool, first, last - first); } static void log_err(request_rec *r, const char *msg) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "scgi: %s", msg); } static void log_debug(request_rec *r, const char *msg) { #ifdef VERBOSE_DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "scgi: %s", msg); #endif } static void add_header(table *t, const char *name, const char *value) { if (name != NULL && value != NULL) { ap_table_addn(t, name, value); } } static int send_headers(request_rec *r, BUFF *f) { table *t; array_header *hdrs_arr, *env_arr; table_entry *hdrs, *env; int i; unsigned long n; log_debug(r, "sending headers"); t = ap_make_table(r->pool, 40); /* headers to send */ if (!t) return 0; /* CONTENT_LENGTH must come first and always be present */ add_header(t, "CONTENT_LENGTH", ap_psprintf(r->pool, "%ld", r->remaining)); add_header(t, "SCGI", SCGI_PROTOCOL_VERSION); add_header(t, "SERVER_SOFTWARE", ap_get_server_version()); add_header(t, "SERVER_PROTOCOL", r->protocol); add_header(t, "SERVER_NAME", ap_get_server_name(r)); add_header(t, "SERVER_ADMIN", r->server->server_admin); add_header(t, "SERVER_ADDR", r->connection->local_ip); add_header(t, "SERVER_PORT", ap_psprintf(r->pool, "%u", ap_get_server_port(r))); add_header(t, "REMOTE_ADDR", r->connection->remote_ip); add_header(t, "REMOTE_PORT", ap_psprintf(r->pool, "%d", ntohs(r->connection->remote_addr.sin_port))); add_header(t, "REMOTE_USER", r->connection->user); add_header(t, "REQUEST_METHOD", r->method); add_header(t, "REQUEST_URI", original_uri(r)); add_header(t, "QUERY_STRING", r->args ? r->args : ""); if (r->path_info) { /* This request uri apparently matched one of the mount points. We want the matching mount point to be the SCRIPT_NAME, always. We want the rest of the uri to be the PATH_INFO, always. Under certain apache configurations, r->path_info is modified between the time the match is found and the scgi handler is called, so we go back to the matching mount entry to make sure that we get the right SCRIPT_NAME, and we use it to find the corresponding value for PATH_INFO. */ mount_entry *entry; entry = ap_get_module_config(r->request_config, &scgi_module); if (entry) { char *mount_point; char *path_info; mount_point = entry->path; mount_entry_matches(r->uri, mount_point, &path_info); add_header(t, "SCRIPT_NAME", mount_point); add_header(t, "PATH_INFO", path_info); } else { /* skip PATH_INFO, don't know it */ add_header(t, "SCRIPT_NAME", r->uri); } } else { /* skip PATH_INFO, don't know it */ add_header(t, "SCRIPT_NAME", r->uri); } add_header(t, "CONTENT_TYPE", lookup_header(r, "Content-type")); add_header(t, "DOCUMENT_ROOT", ap_document_root(r)); /* HTTP headers */ hdrs_arr = ap_table_elts(r->headers_in); hdrs = (table_entry *) hdrs_arr->elts; for (i = 0; i < hdrs_arr->nelts; ++i) { if (!hdrs[i].key) continue; add_header(t, http2env(r->pool, hdrs[i].key), hdrs[i].val); } /* environment variables */ env_arr = ap_table_elts(r->subprocess_env); env = (table_entry *)env_arr->elts; for (i = 0; i < env_arr->nelts; ++i) { add_header(t, env[i].key, env[i].val); } hdrs_arr = ap_table_elts(t); hdrs = (table_entry *)hdrs_arr->elts; /* calculate length of header data (including nulls) */ n = 0; for (i = 0; i < hdrs_arr->nelts; ++i) { n += strlen(hdrs[i].key) + 1; n += strlen(hdrs[i].val) + 1; } /* send header data as netstring */ if (ap_bprintf(f, "%lu:", n) < 0) { return 0; } for (i = 0; i < hdrs_arr->nelts; ++i) { if (ap_bputs(hdrs[i].key, f) < 0) return 0; if (ap_bputc('\0', f) < 0) return 0; if (ap_bputs(hdrs[i].val, f) < 0) return 0; if (ap_bputc('\0', f) < 0) return 0; } if (ap_bputc(',', f) < 0) return 0; return 1; } static int send_request_body (request_rec *r, BUFF *f) { if (ap_should_client_block(r)) { int n; char buffer[HUGE_STRING_LEN]; while ((n = ap_get_client_block(r, buffer, sizeof buffer)) > 0) { if (ap_bwrite(f, buffer, n) != n) return 0; ap_reset_timeout(r); } } if (ap_bflush(f) < 0) return 0; return 1; } static int scgi_handler(request_rec *r) { int ret, sock; BUFF *f; char *request_body = NULL; const char *location; if (strcmp(r->handler, "scgi-handler")) return DECLINED; if ((ret = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) { return ret; } /* connect to scgi server */ ap_hard_timeout("scgi connect", r); log_debug(r, "connecting to server"); sock = open_socket(r); if (sock == -1) { if (request_body) free(request_body); return SERVER_ERROR; } ap_kill_timeout(r); f = ap_bcreate(r->pool, B_RDWR | B_SOCKET); ap_bpushfd(f, sock, sock); ap_hard_timeout("scgi sending request", r); /* send headers */ if (!send_headers(r, f)) { log_err(r, "error sending response headers"); return SERVER_ERROR; } /* send request data */ if (!send_request_body(r, f)) { log_err(r, "error sending response body"); return SERVER_ERROR; } ap_kill_timeout(r); log_debug(r, "reading response headers"); ret = ap_scan_script_header_err_buff(r, f, NULL); if (ret) { if (ret == SERVER_ERROR) { log_err(r, "error reading response headers"); } else { /* Work around an Apache bug whereby the returned * status is ignored and status_line is used instead. * This bug is present at least in Apache 1.3.33. */ r->status_line = NULL; } ap_bclose(f); return ret; } location = ap_table_get(r->headers_out, "Location"); if (location && location[0] == '/' && ((r->status == HTTP_OK) || ap_is_HTTP_REDIRECT(r->status))) { ap_bclose(f); /* Internal redirect -- fake-up a pseudo-request */ r->status = HTTP_OK; /* This redirect needs to be a GET no matter what the original * method was. */ r->method = ap_pstrdup(r->pool, "GET"); r->method_number = M_GET; ap_internal_redirect_handler(location, r); return OK; } /* write headers to client */ ap_send_http_header(r); /* write body to client */ if (!r->header_only) { ap_send_fb(f, r); } ap_bclose(f); return OK; } static void scgi_init(server_rec *s, pool *p) { ap_add_version_component("mod_scgi/" MOD_SCGI_VERSION); } static void * create_dir_config(pool *p, char *dirspec) { scgi_cfg *cfg = (scgi_cfg *) ap_pcalloc(p, sizeof(scgi_cfg)); cfg->enabled = UNSET; cfg->mount.addr = UNSET; cfg->mount.port = UNSET; return (void *) cfg; } #define MERGE(b, n, a) (n->a == UNSET ? b->a : n->a) static void * merge_dir_config(pool *p, void *basev, void *newv) { scgi_cfg *base, *new; scgi_cfg *cfg = (scgi_cfg *) ap_pcalloc(p, sizeof(scgi_cfg)); base = (scgi_cfg *) basev; new = (scgi_cfg *) newv; cfg->enabled = MERGE(base, new, enabled); cfg->mount.addr = MERGE(base, new, mount.addr); cfg->mount.port = MERGE(base, new, mount.port); return (void *) cfg; } static void * create_server_config(pool *p, server_rec *s) { scgi_server_cfg *c = (scgi_server_cfg *) ap_pcalloc(p, sizeof(scgi_server_cfg)); c->mounts = ap_make_array(p, 20, sizeof(mount_entry)); return c; } static void * merge_server_config(pool *p, void *basev, void *overridesv) { scgi_server_cfg *c = (scgi_server_cfg *) ap_pcalloc(p, sizeof(scgi_server_cfg)); scgi_server_cfg *base = (scgi_server_cfg *) basev; scgi_server_cfg *overrides = (scgi_server_cfg *) overridesv; c->mounts = ap_append_arrays(p, overrides->mounts, base->mounts); return c; } static const char * cmd_server(cmd_parms *cmd, scgi_cfg *dcfg, char *addr, char *port) { int n; char *tmp; if (cmd->path == NULL) { /* server command */ return "not a server command"; } if ((dcfg->mount.addr = inet_addr(addr)) == INADDR_NONE) return "Invalid syntax for server address"; n = strtol(port, &tmp, 0); if (tmp[0] != 0 || n < 0 || n > 65535) return "Invalid server port"; dcfg->mount.port = htons((unsigned short) n); return NULL; } static const char * cmd_handler(cmd_parms *cmd, scgi_cfg *dcfg, int flag) { if (cmd->path == NULL) { /* server command */ return "not a server command"; } if (flag) { dcfg->enabled = ENABLED; } else { dcfg->enabled = DISABLED; } return NULL; } static const char * cmd_mount(cmd_parms *cmd, void *dummy, char *path, char *addr) { int n; char *colon; char *addr2; char *tmp; scgi_server_cfg *scfg = our_sconfig(cmd->server); mount_entry *new = ap_push_array(scfg->mounts); n = strlen(path); while (n > 0 && path[n-1] == '/') { n--; /* strip trailing slashes */ } new->path = ap_pstrndup(cmd->pool, path, n); if ((colon = strchr(addr, ':')) == NULL) return "Invalid syntax for server address"; addr2 = ap_pstrndup(cmd->pool, addr, colon - addr); if ((new->addr = inet_addr(addr2)) == INADDR_NONE) return "Invalid syntax for server address"; n = strtol(colon + 1, &tmp, 0); if (tmp[0] != 0 || n < 0 || n > 65535) return "Invalid server port"; new->port = htons((unsigned short) n); return NULL; } static const command_rec scgi_cmds[] = { { "SCGIServer", cmd_server, NULL, ACCESS_CONF, TAKE2, "address and port of an SCGI server (e.g. 127.0.0.1 4000)"}, { "SCGIHandler", cmd_handler, NULL, ACCESS_CONF, FLAG, "On or Off to enable or disable the SCGI handler"}, { "SCGIMount", cmd_mount, NULL, RSRC_CONF, TAKE2, "path prefix and address of SCGI server"}, {NULL} }; static const handler_rec scgi_handlers[] = { {"scgi-handler", scgi_handler}, {NULL} }; module scgi_module = { STANDARD_MODULE_STUFF, scgi_init, /* module initializer */ create_dir_config, /* per-directory config creator */ merge_dir_config, /* dir config merger */ create_server_config, /* server config creator */ merge_server_config, /* server config merger */ scgi_cmds, /* command table */ scgi_handlers, /* [7] list of handlers */ scgi_trans, /* [2] filename-to-URI translation */ NULL, /* [5] check/validate user_id */ NULL, /* [6] check user_id is valid *here* */ NULL, /* [4] check access by host address */ NULL, /* [7] MIME type checker/setter */ NULL, /* [8] fixups */ NULL, /* [10] logger */ #if MODULE_MAGIC_NUMBER >= 19970103 NULL, /* [3] header parser */ #endif #if MODULE_MAGIC_NUMBER >= 19970719 NULL, /* process initializer */ #endif #if MODULE_MAGIC_NUMBER >= 19970728 NULL, /* process exit/cleanup */ #endif #if MODULE_MAGIC_NUMBER >= 19970902 NULL /* [1] post read_request handling */ #endif }; scgi-1.13/apache2/0000775000175000017500000000000011031275132012007 5ustar nasnasscgi-1.13/apache2/Makefile0000664000175000017500000000050611024061346013452 0ustar nasnas APXS=apxs2 APACHECTL=apache2ctl all: $(APXS) -c mod_scgi.c # XXX should not need the -c option but for some reason it's required :-( install: $(APXS) -i -c mod_scgi.c clean: rm -rf mod_scgi.o mod_scgi.lo mod_scgi.slo mod_scgi.la .libs start: $(APACHECTL) start restart: $(APACHECTL) restart stop: $(APACHECTL) stop scgi-1.13/apache2/README.txt0000664000175000017500000000141211024061346013505 0ustar nasnasBuilding -------- Using the 'apxs' tool: $ apxs -i -c mod_scgi.c Alternatively, you can use 'make install'. Configuration ------------- To enable, add LoadModule scgi_module //mod_scgi.so in your httpd.conf file. To serve a set of URLs under one path with an SCGI server, use the SCGIMount directive: SCGIMount /dynamic 127.0.0.1:4000 Demo ----- Quixote >= 2.0a2 has a demo application you can try out by running: python $QUIXOTE/server/scgi_server.py --port 4000 with the appropriate path for $QUIXOTE. If you have Quixote 1.3 or 2.0a1 installed, use the driver bundled with SCGI instead: python scgi/quixote_handler.py -F If you don't have Quixote, the SCGI server doubles as a standalone demo: python scgi/scgi_server.py 4000 scgi-1.13/apache2/mod_scgi.c0000664000175000017500000005037211024061354013747 0ustar nasnas/* mod_scgi.c * * Apache 2 implementation of the SCGI protocol. * */ #define MOD_SCGI_VERSION "1.13" #define SCGI_PROTOCOL_VERSION "1" #include "ap_config.h" #include "apr_version.h" #include "apr_lib.h" #include "apr_strings.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_request.h" #include "http_log.h" #include "http_protocol.h" #include "util_script.h" #define DEFAULT_TIMEOUT 60 /* default socket timeout */ #define UNSET 0 #define ENABLED 1 #define DISABLED 2 #if APR_MAJOR_VERSION == 0 #define apr_socket_send apr_send #define GET_PORT(port, addr) apr_sockaddr_port_get(&(port), addr) #define CREATE_SOCKET(sock, family, pool) \ apr_socket_create(sock, family, SOCK_STREAM, pool) #else #define GET_PORT(port, addr) ((port) = (addr)->port) #define CREATE_SOCKET(sock, family, pool) \ apr_socket_create(sock, family, SOCK_STREAM, APR_PROTO_TCP, pool) #endif typedef struct { char *path; char *addr; apr_port_t port; } mount_entry; /* * Configuration record. Used per-directory configuration data. */ typedef struct { mount_entry mount; int enabled; /* mod_scgi is enabled from this directory */ int timeout; } scgi_cfg; /* Server level configuration */ typedef struct { apr_array_header_t *mounts; int timeout; } scgi_server_cfg; /* * Declare ourselves so the configuration routines can find and know us. * We'll fill it in at the end of the module. */ module AP_MODULE_DECLARE_DATA scgi_module; /* * Locate our directory configuration record for the current request. */ static scgi_cfg * our_dconfig(request_rec *r) { return (scgi_cfg *) ap_get_module_config(r->per_dir_config, &scgi_module); } static scgi_server_cfg *our_sconfig(server_rec *s) { return (scgi_server_cfg *) ap_get_module_config(s->module_config, &scgi_module); } static int mount_entry_matches(const char *url, const char *prefix, const char **path_info) { int i; for (i=0; prefix[i] != '\0'; i++) { if (url[i] == '\0' || url[i] != prefix[i]) return 0; } if (url[i] == '\0' || url[i] == '/') { *path_info = url + i; return 1; } return 0; } static int scgi_translate(request_rec *r) { scgi_cfg *cfg = our_dconfig(r); if (cfg->enabled == DISABLED) { return DECLINED; } if (cfg->mount.addr != UNSET) { ap_assert(cfg->mount.port != UNSET); r->handler = "scgi-handler"; r->filename = r->uri; return OK; } else { int i; scgi_server_cfg *scfg = our_sconfig(r->server); mount_entry *entries = (mount_entry *) scfg->mounts->elts; for (i = 0; i < scfg->mounts->nelts; ++i) { const char *path_info; mount_entry *mount = &entries[i]; if (mount_entry_matches(r->uri, mount->path, &path_info)) { r->handler = "scgi-handler"; r->path_info = apr_pstrdup(r->pool, path_info); r->filename = r->uri; ap_set_module_config(r->request_config, &scgi_module, mount); return OK; } } } return DECLINED; } static int scgi_map_location(request_rec *r) { if (r->handler && strcmp(r->handler, "scgi-handler") == 0) { return OK; /* We don't want directory walk. */ } return DECLINED; } static void log_err(const char *file, int line, request_rec *r, apr_status_t status, const char *msg) { ap_log_rerror(file, line, APLOG_ERR, status, r, "scgi: %s", msg); } static void log_debug(const char *file, int line, request_rec *r, const char *msg) { ap_log_rerror(file, line, APLOG_DEBUG, APR_SUCCESS, r, msg); } static char *http2env(apr_pool_t *p, const char *name) { char *env_name = apr_pstrcat(p, "HTTP_", name, NULL); char *cp; for (cp = env_name + 5; *cp != 0; cp++) { if (*cp == '-') { *cp = '_'; } else { *cp = apr_toupper(*cp); } } return env_name; } static char *lookup_name(apr_table_t *t, const char *name) { const apr_array_header_t *hdrs_arr = apr_table_elts(t); apr_table_entry_t *hdrs = (apr_table_entry_t *) hdrs_arr->elts; int i; for (i = 0; i < hdrs_arr->nelts; ++i) { if (hdrs[i].key == NULL) continue; if (strcasecmp(hdrs[i].key, name) == 0) return hdrs[i].val; } return NULL; } static char *lookup_header(request_rec *r, const char *name) { return lookup_name(r->headers_in, name); } static void add_header(apr_table_t *t, const char *name, const char *value) { if (name != NULL && value != NULL) apr_table_addn(t, name, value); } static int find_path_info(const char *uri, const char *path_info) { int n; n = strlen(uri) - strlen(path_info); ap_assert(n >= 0); return n; } /* This code is a duplicate of what's in util_script.c. We can't use * r->unparsed_uri because it gets changed if there was a redirect. */ static char *original_uri(request_rec *r) { char *first, *last; if (r->the_request == NULL) { return (char *) apr_pcalloc(r->pool, 1); } first = r->the_request; /* use the request-line */ while (*first && !apr_isspace(*first)) { ++first; /* skip over the method */ } while (apr_isspace(*first)) { ++first; /* and the space(s) */ } last = first; while (*last && !apr_isspace(*last)) { ++last; /* end at next whitespace */ } return apr_pstrmemdup(r->pool, first, last - first); } /* buffered socket implementation (buckets are overkill) */ #define BUFFER_SIZE 8000 struct sockbuff { apr_socket_t *sock; char buf[BUFFER_SIZE]; int used; }; static void binit(struct sockbuff *s, apr_socket_t *sock) { s->sock = sock; s->used = 0; } static apr_status_t sendall(apr_socket_t *sock, char *buf, apr_size_t len) { apr_status_t rv; apr_size_t n; while (len > 0) { n = len; if ((rv = apr_socket_send(sock, buf, &n))) return rv; buf += n; len -= n; } return APR_SUCCESS; } static apr_status_t bflush(struct sockbuff *s) { apr_status_t rv; ap_assert(s->used >= 0 && s->used <= BUFFER_SIZE); if (s->used) { if ((rv = sendall(s->sock, s->buf, s->used))) return rv; s->used = 0; } return APR_SUCCESS; } static apr_status_t bwrite(struct sockbuff *s, char *buf, apr_size_t len) { apr_status_t rv; if (len >= BUFFER_SIZE - s->used) { if ((rv = bflush(s))) return rv; while (len >= BUFFER_SIZE) { if ((rv = sendall(s->sock, buf, BUFFER_SIZE))) return rv; buf += BUFFER_SIZE; len -= BUFFER_SIZE; } } if (len > 0) { ap_assert(len < BUFFER_SIZE - s->used); memcpy(s->buf + s->used, buf, len); s->used += len; } return APR_SUCCESS; } static apr_status_t bputs(struct sockbuff *s, char *buf) { return bwrite(s, buf, strlen(buf)); } static apr_status_t bputc(struct sockbuff *s, char c) { char buf[1]; buf[0] = c; return bwrite(s, buf, 1); } static apr_status_t send_headers(request_rec *r, struct sockbuff *s) { /* headers to send */ apr_table_t *t; const apr_array_header_t *hdrs_arr, *env_arr; apr_table_entry_t *hdrs, *env; unsigned long int n = 0; char *buf; int i; apr_status_t rv = 0; apr_port_t port = 0; GET_PORT(port, r->connection->remote_addr); log_debug(APLOG_MARK,r, "sending headers"); t = apr_table_make(r->pool, 40); if (!t) return APR_ENOMEM; /* CONTENT_LENGTH must come first and always be present */ buf = lookup_header(r, "Content-Length"); if (buf == NULL) buf = "0"; add_header(t, "CONTENT_LENGTH", buf); add_header(t, "SCGI", SCGI_PROTOCOL_VERSION); add_header(t, "SERVER_SOFTWARE", ap_get_server_version()); add_header(t, "SERVER_PROTOCOL", r->protocol); add_header(t, "SERVER_NAME", ap_get_server_name(r)); add_header(t, "SERVER_ADMIN", r->server->server_admin); add_header(t, "SERVER_ADDR", r->connection->local_ip); add_header(t, "SERVER_PORT", apr_psprintf(r->pool, "%u", ap_get_server_port(r))); add_header(t, "REMOTE_ADDR", r->connection->remote_ip); add_header(t, "REMOTE_PORT", apr_psprintf(r->pool, "%d", port)); add_header(t, "REMOTE_USER", r->user); add_header(t, "REQUEST_METHOD", r->method); add_header(t, "REQUEST_URI", original_uri(r)); add_header(t, "QUERY_STRING", r->args ? r->args : ""); if (r->path_info) { int path_info_start = find_path_info(r->uri, r->path_info); add_header(t, "SCRIPT_NAME", apr_pstrndup(r->pool, r->uri, path_info_start)); add_header(t, "PATH_INFO", r->path_info); } else { /* skip PATH_INFO, don't know it */ add_header(t, "SCRIPT_NAME", r->uri); } add_header(t, "CONTENT_TYPE", lookup_header(r, "Content-type")); add_header(t, "DOCUMENT_ROOT", ap_document_root(r)); /* HTTP headers */ hdrs_arr = apr_table_elts(r->headers_in); hdrs = (apr_table_entry_t *) hdrs_arr->elts; for (i = 0; i < hdrs_arr->nelts; ++i) { if (hdrs[i].key) { add_header(t, http2env(r->pool, hdrs[i].key), hdrs[i].val); } } /* environment variables */ env_arr = apr_table_elts(r->subprocess_env); env = (apr_table_entry_t*) env_arr->elts; for (i = 0; i < env_arr->nelts; ++i) { add_header(t, env[i].key, env[i].val); } hdrs_arr = apr_table_elts(t); hdrs = (apr_table_entry_t*) hdrs_arr->elts; /* calculate length of header data (including nulls) */ for (i = 0; i < hdrs_arr->nelts; ++i) { n += strlen(hdrs[i].key) + 1; n += strlen(hdrs[i].val) + 1; } buf = apr_psprintf(r->pool, "%lu:", n); if (!buf) return APR_ENOMEM; rv = bputs(s, buf); if (rv) return rv; for (i = 0; i < hdrs_arr->nelts; ++i) { rv = bputs(s, hdrs[i].key); if (rv) return rv; rv = bputc(s, '\0'); if (rv) return rv; rv = bputs(s, hdrs[i].val); if (rv) return rv; rv = bputc(s, '\0'); if (rv) return rv; } rv = bputc(s, ','); if (rv) return rv; return APR_SUCCESS; } static apr_status_t send_request_body(request_rec *r, struct sockbuff *s) { if (ap_should_client_block(r)) { char buf[BUFFER_SIZE]; apr_status_t rv; apr_off_t len; while ((len = ap_get_client_block(r, buf, sizeof buf)) > 0) { if ((rv = bwrite(s, buf, len))) return rv; } if (len == -1) return HTTP_INTERNAL_SERVER_ERROR; /* what to return? */ } return APR_SUCCESS; } #define CONFIG_VALUE(value, fallback) ((value) != UNSET ? (value) : (fallback)) static apr_status_t open_socket(apr_socket_t **sock, request_rec *r) { int timeout; int retries = 4; int sleeptime = 1; apr_status_t rv; apr_sockaddr_t *sockaddr; scgi_server_cfg *scfg = our_sconfig(r->server); scgi_cfg *cfg = our_dconfig(r); mount_entry *m = (mount_entry *) ap_get_module_config(r->request_config, &scgi_module); if (!m) { m = &cfg->mount; } timeout = CONFIG_VALUE(cfg->timeout, CONFIG_VALUE(scfg->timeout, DEFAULT_TIMEOUT)); rv = apr_sockaddr_info_get(&sockaddr, CONFIG_VALUE(m->addr, "localhost"), APR_UNSPEC, CONFIG_VALUE(m->port, 4000), 0, r->pool); if (rv) { log_err(APLOG_MARK, r, rv, "apr_sockaddr_info_get() error"); return rv; } restart: *sock = NULL; rv = CREATE_SOCKET(sock, sockaddr->family, r->pool); if (rv) { log_err(APLOG_MARK, r, rv, "apr_socket_create() error"); return rv; } rv = apr_socket_timeout_set(*sock, apr_time_from_sec(timeout)); if (rv) { log_err(APLOG_MARK, r, rv, "apr_socket_timeout_set() error"); return rv; } rv = apr_socket_connect(*sock, sockaddr); if (rv) { apr_socket_close(*sock); if ((APR_STATUS_IS_ECONNREFUSED(rv) | APR_STATUS_IS_EINPROGRESS(rv)) && retries > 0) { /* server may be temporarily down, retry */ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, rv, r, "scgi: connection failed, retrying"); apr_sleep(apr_time_from_sec(sleeptime)); --retries; sleeptime *= 2; goto restart; } log_err(APLOG_MARK, r, rv, "scgi: can't connect to server"); return rv; } #ifdef APR_TCP_NODELAY /* disable Nagle, we don't send small packets */ apr_socket_opt_set(*sock, APR_TCP_NODELAY, 1); #endif return APR_SUCCESS; } static int scgi_handler(request_rec *r) { apr_status_t rv = 0; int http_status = 0; struct sockbuff s; apr_socket_t *sock; apr_bucket_brigade *bb = NULL; apr_bucket *b = NULL; const char *location; if (strcmp(r->handler, "scgi-handler")) return DECLINED; http_status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR); if (http_status != OK) return http_status; log_debug(APLOG_MARK, r, "connecting to server"); rv = open_socket(&sock, r); if (rv) { return HTTP_INTERNAL_SERVER_ERROR; } binit(&s, sock); rv = send_headers(r, &s); if (rv) { log_err(APLOG_MARK, r, rv, "error sending request headers"); return HTTP_INTERNAL_SERVER_ERROR; } rv = send_request_body(r, &s); if (rv) { log_err(APLOG_MARK, r, rv, "error sending request body"); return HTTP_INTERNAL_SERVER_ERROR; } rv = bflush(&s); if (rv) { log_err(APLOG_MARK, r, rv, "error sending request"); return HTTP_INTERNAL_SERVER_ERROR; } log_debug(APLOG_MARK, r, "reading response headers"); bb = apr_brigade_create(r->connection->pool, r->connection->bucket_alloc); b = apr_bucket_socket_create(sock, 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); rv = ap_scan_script_header_err_brigade(r, bb, NULL); if (rv) { if (rv == HTTP_INTERNAL_SERVER_ERROR) { log_err(APLOG_MARK, r, rv, "error reading response headers"); } else { /* Work around an Apache bug whereby the returned status is * ignored and status_line is used instead. This bug is * present at least in 2.0.54. */ r->status_line = NULL; } apr_brigade_destroy(bb); return rv; } location = apr_table_get(r->headers_out, "Location"); if (location && location[0] == '/' && ((r->status == HTTP_OK) || ap_is_HTTP_REDIRECT(r->status))) { apr_brigade_destroy(bb); /* Internal redirect -- fake-up a pseudo-request */ r->status = HTTP_OK; /* This redirect needs to be a GET no matter what the original * method was. */ r->method = apr_pstrdup(r->pool, "GET"); r->method_number = M_GET; ap_internal_redirect_handler(location, r); return OK; } rv = ap_pass_brigade(r->output_filters, bb); if (rv) { log_err(APLOG_MARK, r, rv, "ap_pass_brigade()"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } static int scgi_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *base_server) { ap_add_version_component(p, "mod_scgi/" MOD_SCGI_VERSION); return OK; } static void * create_dir_config(apr_pool_t *p, char *dirspec) { scgi_cfg *cfg = apr_pcalloc(p, sizeof(scgi_cfg)); cfg->enabled = UNSET; cfg->mount.addr = UNSET; cfg->mount.port = UNSET; cfg->timeout = UNSET; return cfg; } #define MERGE(b, n, a) (n->a == UNSET ? b->a : n->a) static void * merge_dir_config(apr_pool_t *p, void *basev, void *newv) { scgi_cfg* cfg = apr_pcalloc(p, sizeof(scgi_cfg)); scgi_cfg* base = basev; scgi_cfg* new = newv; cfg->enabled = MERGE(base, new, enabled); cfg->mount.addr = MERGE(base, new, mount.addr); cfg->mount.port = MERGE(base, new, mount.port); cfg->timeout = MERGE(base, new, timeout); return cfg; } static void * create_server_config(apr_pool_t *p, server_rec *s) { scgi_server_cfg *c = (scgi_server_cfg *) apr_pcalloc(p, sizeof(scgi_server_cfg)); c->mounts = apr_array_make(p, 20, sizeof(mount_entry)); c->timeout = UNSET; return c; } static void * merge_server_config(apr_pool_t *p, void *basev, void *overridesv) { scgi_server_cfg *c = (scgi_server_cfg *) apr_pcalloc(p, sizeof(scgi_server_cfg)); scgi_server_cfg *base = (scgi_server_cfg *) basev; scgi_server_cfg *overrides = (scgi_server_cfg *) overridesv; c->mounts = apr_array_append(p, overrides->mounts, base->mounts); c->timeout = MERGE(base, overrides, timeout); return c; } static const char * cmd_mount(cmd_parms *cmd, void *dummy, const char *path, const char *addr) { int n; apr_status_t rv; char *scope_id = NULL; /* A ip6 parameter - not used here. */ scgi_server_cfg *scfg = our_sconfig(cmd->server); mount_entry *new = apr_array_push(scfg->mounts); n = strlen(path); while (n > 0 && path[n-1] == '/') { n--; /* strip trailing slashes */ } new->path = apr_pstrndup(cmd->pool, path, n); rv = apr_parse_addr_port(&new->addr, &scope_id, &new->port, addr, cmd->pool); if (rv) return "error parsing address:port string"; return NULL; } static const char * cmd_server(cmd_parms *cmd, void *pcfg, const char *addr_and_port) { apr_status_t rv; scgi_cfg *cfg = pcfg; char *scope_id = NULL; /* A ip6 parameter - not used here. */ if (cmd->path == NULL) return "not a server command"; rv = apr_parse_addr_port(&cfg->mount.addr, &scope_id, &cfg->mount.port, addr_and_port, cmd->pool); if (rv) return "error parsing address:port string"; return NULL; } static const char * cmd_handler(cmd_parms* cmd, void* pcfg, int flag) { scgi_cfg *cfg = pcfg; if (cmd->path == NULL) /* server command */ return "not a server command"; if (flag) cfg->enabled = ENABLED; else cfg->enabled = DISABLED; return NULL; } static const char * cmd_timeout(cmd_parms *cmd, void* pcfg, const char *strtimeout) { scgi_cfg *dcfg = pcfg; int timeout = atoi(strtimeout); if (cmd->path == NULL) { scgi_server_cfg *scfg = our_sconfig(cmd->server); scfg->timeout = timeout; } else { dcfg->timeout = timeout; } return NULL; } static const command_rec scgi_cmds[] = { AP_INIT_TAKE2("SCGIMount", cmd_mount, NULL, RSRC_CONF, "path prefix and address of SCGI server"), AP_INIT_TAKE1("SCGIServer", cmd_server, NULL, ACCESS_CONF, "Address and port of an SCGI server (e.g. localhost:4000)"), AP_INIT_FLAG( "SCGIHandler", cmd_handler, NULL, ACCESS_CONF, "On or Off to enable or disable the SCGI handler"), AP_INIT_TAKE1("SCGIServerTimeout", cmd_timeout, NULL, ACCESS_CONF|RSRC_CONF, "Timeout (in seconds) for communication with the SCGI server."), {NULL} }; static void scgi_register_hooks(apr_pool_t *p) { ap_hook_post_config(scgi_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(scgi_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_translate_name(scgi_translate, NULL, NULL, APR_HOOK_LAST); ap_hook_map_to_storage(scgi_map_location, NULL, NULL, APR_HOOK_FIRST); } /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA scgi_module = { STANDARD20_MODULE_STUFF, create_dir_config, /* create per-dir config structs */ merge_dir_config, /* merge per-dir config structs */ create_server_config, /* create per-server config structs */ merge_server_config, /* merge per-server config structs */ scgi_cmds, /* table of config file commands */ scgi_register_hooks, /* register hooks */ }; scgi-1.13/doc/0000775000175000017500000000000011031275132011251 5ustar nasnasscgi-1.13/doc/CHANGES_110.txt0000664000175000017500000001271611024061346013454 0ustar nasnas1.10 (2006-02-01) r27892 * Compile fixes for Apache 2.2.0 (some APR functions have been renamed). 1.9 (2005-12-13) r27717 * Make passfd.c work on 64-bit machines. Thanks to Dryice Liu. * For Apache 2, set REQUEST_URI using the original request URI (r->unparsed_uri may be different if there was a redirect). 1.8 (2005-10-18) r27556 * Pass all HTTP headers to SCGI servers. * Relax address family specification (allows IPv6). * In some versions of Apache, ap_sock_disable_nagle() is not always available. Use lower-level API. 1.7 (2005-08-17) r27222 * Fix another bug in Apache 2 mod_scgi implementation of SCGIMount. * Make the SCGIServerTimeout directive work for SCGIMount and allow it at the server level. * Change SCGIHandler to default to "On" (instead of Off). Make the SCGIHandler option work with SCGIMount. 1.6 (2005-08-11) r27186 * Fix bugs in Apache 2 mod_scgi implementation of SCGIMount directive. 1.5 (2005-08-10) r27184 * Implement SCGIMount directive for mod_scgi. In addition to being simpler than SCGIServer and SCGIHandler, it allows the PATH_INFO variable to be properly set (thanks to Ian Bicking for suggesting the idea). 1.4 (2005-06-15) r26909 * Have cgi2scgi.c send all environment variables to the SCGI server, not just a fixed set of CGI related ones. * Fix connection retry logic. The code in cgi2scgi.c and apache1/mod_scgi.c was non-portable and the apache2/mod_scgi.c code was completely broken. 1.3 (2005-06-02) r26869 * Change mod_scgi to include environment variables in the SCGI request (thanks to Charles Hornberger). * Add read_env() function to scgi_server module. * Add note to README about using scgi_server.py script from Quixote 2. (thanks to Mike Orr). * Work around a conditional request Apache bug. One symptom of the bug was that Apache would send a 200 response when the status should have been some special status like 304. 1.2 (2004-07-20) r24736 * Fix "SCGIHandler On/Off" option for Apache 2 module. * Add CGI to SCGI adaptor. Tweak quixote_handler so that it doesn't throw away PATH_INFO information (mod_scgi doesn't use it but CGI does). 1.2a2 (2004-02-27) r23588 * Fix a major bug in ns_reads(). It used to read too much data if the entire contents of the netstring were not returned by the first read(). * In the Apache 2 version of mod_scgi, implement buffered write functions for sockets. Use them instead of the APR buckets implementation when sending data to the SCGI server. The code using buckets was almost certainly wrong. * Add a 'translate' hook for the Apache 2 module. That way the handler does not need to be set explicitly (matching the behavior of the 1.3 module). * Send the HTTP_HOST header to the SCGI server. * Include Python.h before any other headers. * Remove pid file if SCGI server receives SIGTERM. 1.2a1 (2003-07-28) r22096 * add mod_scgi implementation for Apache 2 1.1 (2003-07-02) r21963 mod_scgi.c * Pass HTTP/1.1 request headers to SCGI servers. passfd.c * Try to make passfd compile on OpenBSD and older versions of Solaris. scgi_server.py * Merge Jon Corbet's graceful restart patch. 1.0 (2003-05-29) r21747 quixote_handler.py * Add -m option to specify the maximum number of children (from Jonathan Corbet). passfd.c * Remove some dead code (spotted by Jonathan Corbet). 1.0b1 (2003-02-07) mod_scgi.c * Reset the timeout while reading the request body. This allows large file uploads without the connection being reset. * Slight simplification of cmd_server and cmd_handler. quixote_handler.py * By default, bind to local IP address 127.0.0.1 instead of 0. Allow the local address to be specified. scgi_server.py * After receiving a passed socket, explicitly set it to non-blocking. This works around what seems to be a bug in FreeBSD. Thanks to Mike Watkins for helping track it down. 0.5 (2002-09-05) scgi_server.py * Rewrite most of the scgi_server parent code. Drop the table of busy children and the shared pipe between the parent and children. Instead, the children write a byte on the Unix domain pipe when they are ready for a request. In the process, fix a bug that caused the parent to wait until all the children were ready before delegating a request (found by Changjune Kim). mod_scgi.c * Pass REMOTE_USER to SCGI server (patch from Hamish Lawson). 0.4 (2002-07-31) quixote_handler.py * Make debug() message a little more useful when we catch IOError on closing connection. scgi_server.py * Fix a bug that caused the manager process to hang. The hang was trigged when the maximum number of child processes were running and one died while the manager was trying to find an idle child to process a request. 0.3 (2002-06-04) mod_scgi.c: * disable verbose debugging output * if the connection to the SCGI server is refused, sleep a little and try again since it might have been restarted (as opposed giving up right away and returning a 500 error to the poor user) scgi_server.py: * restart interrupted select() calls * remove graceful restart code (on HUP signal) in scgi_server.py. It's complicated and now that mod_scgi retries it's not needed. quixote_handler.py: * close stdin /* vim: set ai tw=74 et sw=4 sts=4: */ scgi-1.13/doc/LICENSE_110.txt0000664000175000017500000000557711024061346013475 0ustar nasnasCNRI OPEN SOURCE LICENSE AGREEMENT IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY. BY COPYING, INSTALLING OR OTHERWISE USING SCGI-1.10 SOFTWARE, YOU ARE DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS LICENSE AGREEMENT. 1. This LICENSE AGREEMENT is between Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") copying, installing or otherwise using scgi-1.10 software in source or binary form and its associated documentation ("scgi-1.10"). 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a nonexclusive, royalty-free, world- wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use scgi-1.10 alone or in any derivative version, provided, however, that CNRI's License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 2004 Corporation for National Research Initiatives; All Rights Reserved" are retained in scgi-1.10 alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates scgi-1.10 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to scgi-1.10. 4. CNRI is making scgi-1.10 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF SCGI-1.10 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF SCGI- 1.10 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING SCGI- 1.10, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by and interpreted in all respects by the law of the State of Virginia, excluding Virginia's conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using scgi-1.10, Licensee agrees to be bound by the terms and conditions of this License Agreement. scgi-1.13/doc/guide.html0000664000175000017500000003402111024061346013236 0ustar nasnas SCGI Guide

Using SCGI with Apache 2.x and Python

This guide describes how to use SCGI with Apache 2.x, to serve an application written in Python. The SCGI package also comes with modules to use the protocol with Apache 1.x or lighttpd, and frameworks for applications in other languages are available (including LISP).

A complete setup for an SCGI application involves these four components:

  1. A web server to process incoming http requests--in our case, Apache 2.

  2. The Apache SCGI module to delegate requests over the SCGI protocol.

  3. An SCGI server process based on the Python scgi_server module.

  4. An application to be run in child processes forked off by the server process.

A single SCGI server process serves only one application, but multiple instances of it. Each instance lives in its own child process of the SCGI server. More child processes are created on demand.

Apache module

To install the module by hand, cd into the apache2 subdirectory of the SCGI source tree, and, as root, make install. On Debian-based systems and Gentoo, however, just install the libapache2-mod-scgi package. RPM users can install apache2-mod_scgi.

Next, set up the webserver to load the module and delegate requests (in this example, for the http path "/dynamic") to a separate server process accepting SCGI requests over a TCP socket. That server will typically run on the same machine as the http server, but it doesn't have to. Here's a snippet of Apache 2 config to delegate requests for the http path /dynamic to local TCP port 4000, which is the default:

# (This actually better set up permanently with the command line
# "a2enmod scgi" but shown here for completeness)
LoadModule scgi_module /usr/lib/apache2/modules/mod_scgi.so

# Set up a location to be served by an SCGI server process
SCGIMount /dynamic/ 127.0.0.1:4000

The deprecated way of delegating requests to an SCGI server is as follows:

<Location "/dynamic">
    # Enable SCGI delegation
    SCGIHandler On
    # Delegate requests in the "/dynamic" path to daemon on local
    # server, port 4000
    SCGIServer 127.0.0.1:4000
</Location>

Note that using SCGIMount instead of SCGIServer allows Apache to compute PATH_INFO as most software expects.

Here's a description of the configuration directives accepted by this Apache module. The information is largely derived from the source, so please excuse (or better yet: help improve) poor descriptions:

Directive

Arguments

Example

Description

SCGIMount

http path and IP/port pair

/dynamic 127.0.0.1:4000

Path prefix and address of SCGI server

SCGIServer

IP address and port

127.0.0.1:4000

Address and port of an SCGI server

SCGIHandler

On or Off

On

Enable delegation of requests through SCGI

SCGIServerTimeout

Number of seconds

10

Timeout for communication with SCGI server

Server process

To install the Python module for creating SCGI applications, go to the main SCGI source directory and run python setup.py build and then, as root, python setup.py install. Or, on most GNU/Linux systems, just install the python-scgi package.

The hard part is creating that application server process. The SCGI home page provides a server implementation in Python, called scgi_server.py, but this is really just a kind of support module. In order to port an application to use this server, write a main program in Python that imports this module and uses it. There is one, fairly complex, example that makes an application called Quixote run over SCGI.

The main program that you write for yourself creates a handler class describing how requests should be processed. You then tell the SCGI server module to loop forever, handling incoming requests and creating processes running your handler class as needed. It is your own responsibility to ensure that the server process is running.

Once the SCGI server process runs, it can accept requests forwarded by the http server. It will attempt to delegate every request that comes in to an existing child process, invoking its handler. If no child process is free to take the request, an additional child process is spawned (if the configured maximum number of child processes is not exceeded; if it is, the request blocks until a child process becomes free). If a handler "dies" (e.g. exits with an exception), a new child is spawned to replace it.

Writing a handler

The server process must import scgi_server, then derive handler classes from scgi_server.SCGIHandler. Just scgi_server.py can also be run standalone, in which case it return pages displaying the details of your browser's request.

Your handler class should be derived from the SCGIHandler class defined in scgi_server.py, and override its produce() function to process a request. This is new in SCGI 1.12. Before that, the function to override was handle_connection() which involved a lot more work.

Besides self, the produce() function takes several arguments:

  1. a dict mapping names of CGI parameters to request details

  2. size (in bytes) of the request body

  3. an input stream providing the request body

  4. an output stream to write a page to.

Alternatively, override SCGIHandler.produce_cgilike() which can read the request body from standard input, write its output to standard output, and receives its request details both as an argument dict and added to its environment variables.

Your output should start out with an http header, so normally you'd want to start out with something like "Content-Type: text/plain\r\n\r\n" followed by page content.

Besides a header containing CGI variables, the request may also contain a body. The length of this body is passed as the CGI parameter CONTENT_LENGTH, but for your convenience is also converted to an integer and passed separately to the produce() function. If your handler needs the request body, it can read this number of bytes from its input socket. Do not rely on EOF; explicitly read the correct number of bytes (or less, if that's what you want).

Writing a server

Your main SCGI server program should create an SCGIServer object (defined in scgi_server.py), passing in your handler class for the handler_class parameter, and then call its serve() method which will loop indefinitely to process requests:

def main():
    SCGIServer(handler_class=MyAppHandler).serve()

if __name__ == "__main__":
    main()

You may want to support command-line options to influence various options of the server's operation. The SCGIServer initialization function takes several arguments:

Name

Meaning

Default

handler_class

Class (not object!) of handler to invoke for requests

SCGIHandler

host

Local IP address to bind to

empty string

port

TCP port to listen on

4000

max_children

Maximum number of child processes to spawn

5

Links

scgi-1.13/scgi/0000775000175000017500000000000011031275132011431 5ustar nasnasscgi-1.13/scgi/__init__.py0000664000175000017500000000002511024061354013540 0ustar nasnas__version__ = "1.13" scgi-1.13/scgi/passfd.c0000664000175000017500000000665011024061347013067 0ustar nasnas/* * Passing file descriptions with Python. Tested with Linux and FreeBSD. * Should also work on Solaris. Portability fixes or success stories welcome. * * Neil Schemenauer */ #include "Python.h" #ifndef __OpenBSD__ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 500 #endif #ifndef _XOPEN_SOURCE_EXTENDED #define _XOPEN_SOURCE_EXTENDED 1 /* Solaris <= 2.7 needs this too */ #endif #endif /* __OpenBSD__ */ #include #include #include #include /* for platforms that don't provide CMSG_* macros */ #ifndef ALIGNBYTES #define ALIGNBYTES (sizeof(int) - 1) #endif #ifndef ALIGN #define ALIGN(p) (((unsigned int)(p) + ALIGNBYTES) & ~ ALIGNBYTES) #endif #ifndef CMSG_LEN #define CMSG_LEN(len) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(len)) #endif #ifndef CMSG_SPACE #define CMSG_SPACE(len) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(len)) #endif static int recv_fd(int sockfd) { char tmp[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cmsg; struct iovec iov; struct msghdr msg; char ch = '\0'; memset(&msg, 0, sizeof(msg)); iov.iov_base = &ch; iov.iov_len = 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = tmp; msg.msg_controllen = sizeof(tmp); if (recvmsg(sockfd, &msg, 0) <= 0) return -1; cmsg = CMSG_FIRSTHDR(&msg); return *(int *) CMSG_DATA(cmsg); } static int send_fd (int sockfd, int fd) { char tmp[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cmsg; struct iovec iov; struct msghdr msg; char ch = '\0'; memset(&msg, 0, sizeof(msg)); msg.msg_control = (caddr_t) tmp; msg.msg_controllen = CMSG_LEN(sizeof(int)); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; *(int *)CMSG_DATA(cmsg) = fd; iov.iov_base = &ch; iov.iov_len = 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; if (sendmsg(sockfd, &msg, 0) != 1) return -1; return 0; } static char sendfd_doc [] = "sendfd(sockfd, fd)"; static PyObject * passfd_sendfd(PyObject *self, PyObject *args) { int sockfd, fd; if (!PyArg_ParseTuple(args, "ii:sendfd", &sockfd, &fd)) return NULL; if (send_fd(sockfd, fd) < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } Py_INCREF(Py_None); return Py_None; } static char recvfd_doc [] = "recvfd(sockfd) -> fd"; static PyObject * passfd_recvfd(PyObject *self, PyObject *args) { int sockfd, fd; if (!PyArg_ParseTuple(args, "i:recvfd", &sockfd)) return NULL; if ((fd = recv_fd(sockfd)) < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } return PyInt_FromLong((long) fd); } static char socketpair_doc [] = "socketpair(family, type, proto=0) -> (fd, fd)"; static PyObject * passfd_socketpair(PyObject *self, PyObject *args) { int family, type, proto=0; int fd[2]; if (!PyArg_ParseTuple(args, "ii|i:socketpair", &family, &type, &proto)) return NULL; if (socketpair(family, type, proto, fd) < 0) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } return Py_BuildValue("(ii)", (long) fd[0], (long) fd[1]); } /* List of functions */ static PyMethodDef passfd_methods[] = { {"sendfd", passfd_sendfd, METH_VARARGS, sendfd_doc}, {"recvfd", passfd_recvfd, METH_VARARGS, recvfd_doc}, {"socketpair", passfd_socketpair, METH_VARARGS, socketpair_doc}, {NULL, NULL} /* sentinel */ }; DL_EXPORT(void) initpassfd(void) { PyObject *m; /* Create the module and add the functions and documentation */ m = Py_InitModule3("passfd", passfd_methods, NULL); } scgi-1.13/scgi/quixote_handler.py0000664000175000017500000001117511024061347015206 0ustar nasnas#!/usr/bin/env python """ A SCGI handler that uses Quixote to publish dynamic content. """ import sys import time import os import getopt import signal from quixote import enable_ptl, publish import scgi_server pidfilename = None # set by main() def debug(msg): timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) sys.stderr.write("[%s] %s\n" % (timestamp, msg)) class QuixoteHandler(scgi_server.SCGIHandler): # override in subclass publisher_class = publish.Publisher root_namespace = None prefix = "" def __init__(self, *args, **kwargs): debug("%s created" % self.__class__.__name__) scgi_server.SCGIHandler.__init__(self, *args, **kwargs) assert self.root_namespace, "You must provide a namespace to publish" self.publisher = self.publisher_class(self.root_namespace) def handle_connection (self, conn): input = conn.makefile("r") output = conn.makefile("w") env = self.read_env(input) # mod_scgi never passes PATH_INFO, fake it prefix = self.prefix path = env['SCRIPT_NAME'] assert path[:len(prefix)] == prefix, ( "path %r doesn't start with prefix %r" % (path, prefix)) env['SCRIPT_NAME'] = prefix env['PATH_INFO'] = path[len(prefix):] + env.get('PATH_INFO', '') self.publisher.publish(input, output, sys.stderr, env) try: input.close() output.close() conn.close() except IOError, err: debug("IOError while closing connection ignored: %s" % err) if self.publisher.config.run_once: sys.exit(0) class DemoHandler(QuixoteHandler): root_namespace = "quixote.demo" prefix = "/dynamic" # must match Location directive def __init__(self, *args, **kwargs): enable_ptl() QuixoteHandler.__init__(self, *args, **kwargs) def change_uid_gid(uid, gid=None): "Try to change UID and GID to the provided values" # This will only work if this script is run by root. # Try to convert uid and gid to integers, in case they're numeric import pwd, grp try: uid = int(uid) default_grp = pwd.getpwuid(uid)[3] except ValueError: uid, default_grp = pwd.getpwnam(uid)[2:4] if gid is None: gid = default_grp else: try: gid = int(gid) except ValueError: gid = grp.getgrnam(gid)[2] os.setgid(gid) os.setuid(uid) def term_signal(signum, frame): global pidfilename try: os.unlink(pidfilename) except OSError: pass sys.exit() def main(handler=DemoHandler): usage = """Usage: %s [options] -F -- stay in foreground (don't fork) -P -- PID filename -l -- log filename -m -- max children -p -- TCP port to listen on -u -- user id to run under """ % sys.argv[0] nofork = 0 global pidfilename pidfilename = "/var/tmp/quixote-scgi.pid" logfilename = "/var/tmp/quixote-scgi.log" max_children = 5 # scgi default uid = "nobody" port = 4000 host = "127.0.0.1" try: opts, args = getopt.getopt(sys.argv[1:], 'FP:l:m:p:u:') except getopt.GetoptError, exc: print >>sys.stderr, exc print >>sys.stderr, usage sys.exit(1) for o, v in opts: if o == "-F": nofork = 1 elif o == "-P": pidfilename = v elif o == "-l": logfilename = v elif o == "-m": max_children = int(v) elif o == "-p": port = int(v) elif o == "-u": uid = v log = open(logfilename, "a", 1) os.dup2(log.fileno(), 1) os.dup2(log.fileno(), 2) os.close(0) if os.getuid() == 0: change_uid_gid(uid) if nofork: scgi_server.SCGIServer(handler, host=host, port=port, max_children=max_children).serve() else: pid = os.fork() if pid == 0: pid = os.getpid() pidfile = open(pidfilename, 'w') pidfile.write(str(pid)) pidfile.close() signal.signal(signal.SIGTERM, term_signal) try: scgi_server.SCGIServer(handler, host=host, port=port, max_children=max_children).serve() finally: # grandchildren get here too, don't let them unlink the pid if pid == os.getpid(): try: os.unlink(pidfilename) except OSError: pass if __name__ == '__main__': main() scgi-1.13/scgi/scgi_server.py0000664000175000017500000002743511024061347014334 0ustar nasnas#!/usr/bin/env python """ A pre-forking SCGI server that uses file descriptor passing to off-load requests to child worker processes. """ import sys import socket import os import select import errno import fcntl import signal from scgi import passfd # netstring utility functions def ns_read_size(input): size = "" while 1: c = input.read(1) if c == ':': break elif not c: raise IOError, 'short netstring read' size = size + c return long(size) def ns_reads(input): size = ns_read_size(input) data = "" while size > 0: s = input.read(size) if not s: raise IOError, 'short netstring read' data = data + s size -= len(s) if input.read(1) != ',': raise IOError, 'missing netstring terminator' return data def read_env(input): headers = ns_reads(input) items = headers.split("\0") items = items[:-1] assert len(items) % 2 == 0, "malformed headers" env = {} for i in range(0, len(items), 2): env[items[i]] = items[i+1] return env class SCGIHandler: # Subclasses should override the handle_connection method. def __init__(self, parent_fd): self.parent_fd = parent_fd def serve(self): while 1: try: os.write(self.parent_fd, "1") # indicates that child is ready fd = passfd.recvfd(self.parent_fd) except (IOError, OSError): # parent probably exited (EPIPE comes thru as OSError) raise SystemExit conn = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) # Make sure the socket is blocking. Apparently, on FreeBSD the # socket is non-blocking. I think that's an OS bug but I don't # have the resources to track it down. conn.setblocking(1) os.close(fd) self.handle_connection(conn) def read_env(self, input): return read_env(input) def handle_connection(self, conn): """Handle an incoming request. This used to be the function to override in your own handler class, and doing so will still work. It will be easier (and therefore probably safer) to override produce() or produce_cgilike() instead. """ input = conn.makefile("r") output = conn.makefile("w") env = self.read_env(input) bodysize = int(env.get('CONTENT_LENGTH', 0)) try: self.produce(env, bodysize, input, output) finally: output.close() input.close() conn.close() def produce(self, env, bodysize, input, output): """This is the function you normally override to run your application. It is called once for every incoming request that this process is expected to handle. Parameters: env - a dict mapping CGI parameter names to their values. bodysize - an integer giving the length of the request body, in bytes (or zero if there is none). input - a file allowing you to read the request body, if any, over a socket. The body is exactly bodysize bytes long; don't try to read more than bodysize bytes. This parameter is taken from the CONTENT_LENGTH CGI parameter. output - a file allowing you to write your page over a socket back to the client. Before writing the page's contents, you must write an http header, e.g. "Content-Type: text/plain\\r\\n" The default implementation of this function sets up a CGI-like environment, calls produce_cgilike(), and then restores the original environment for the next request. It is probably faster and cleaner to override produce(), but produce_cgilike() may be more convenient. """ # Preserve current system environment stdin = sys.stdin stdout = sys.stdout environ = os.environ # Set up CGI-like environment for produce_cgilike() sys.stdin = input sys.stdout = output os.environ = env # Call CGI-like version of produce() function try: self.produce_cgilike(env, bodysize) finally: # Restore original environment no matter what happens sys.stdin = stdin sys.stdout = stdout os.environ = environ def produce_cgilike(self, env, bodysize): """A CGI-like version of produce. Override this function instead of produce() if you want a CGI-like environment: CGI parameters are added to your environment variables, the request body can be read on standard input, and the resulting page is written to standard output. The CGI parameters are also passed as env, and the size of the request body in bytes is passed as bodysize (or zero if there is no body). Default implementation is to produce a text page listing the request's CGI parameters, which can be useful for debugging. """ sys.stdout.write("Content-Type: text/plain\r\n\r\n") for k, v in env.items(): print "%s: %r" % (k, v) class SCGIServer: DEFAULT_PORT = 4000 def __init__(self, handler_class=SCGIHandler, host="", port=DEFAULT_PORT, max_children=5): self.handler_class = handler_class self.host = host self.port = port self.max_children = max_children self.children = {} # { pid : fd } self.spawn_child() self.restart = 0 # # Deal with a hangup signal. All we can really do here is # note that it happened. # def hup_signal(self, signum, frame): self.restart = 1 def spawn_child(self, conn=None): parent_fd, child_fd = passfd.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) # make child fd non-blocking flags = fcntl.fcntl(child_fd, fcntl.F_GETFL, 0) fcntl.fcntl(child_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) pid = os.fork() if pid == 0: if conn: conn.close() # in the midst of handling a request, close # the connection in the child os.close(child_fd) self.handler_class(parent_fd).serve() sys.exit(0) else: os.close(parent_fd) self.children[pid] = child_fd def reap_children(self): while self.children: (pid, status) = os.waitpid(-1, os.WNOHANG) if pid <= 0: break os.close(self.children[pid]) del self.children[pid] def do_stop(self): # # First close connections to the children, which will cause them # to exit after finishing what they are doing. # for fd in self.children.values(): os.close(fd) # # Then do a blocking wait on each until we have cleared the # slate. # for pid in self.children.keys(): (pid, status) = os.waitpid(pid, 0) self.children = {} def do_restart(self): # Stop self.do_stop() # # Fire off a new child, we'll be wanting it soon. # self.spawn_child() self.restart = 0 def delegate_request(self, conn): """Pass a request fd to a child process to handle. This method blocks if all the children are busy and we have reached the max_children limit.""" # There lots of subtleties here. First, we can't use the write # status of the pipes to the child since select will return true # if the buffer is not filled. Instead, each child writes one # byte of data when it is ready for a request. The normal case # is that a child is ready for a request. We want that case to # be fast. Also, we want to pass requests to the same child if # possible. Finally, we need to gracefully handle children # dying at any time. # If no children are ready and we haven't reached max_children # then we want another child to be started without delay. timeout = 0 while 1: try: r, w, e = select.select(self.children.values(), [], [], timeout) except select.error, e: if e[0] == errno.EINTR: # got a signal, try again continue raise if r: # One or more children look like they are ready. Sort # the file descriptions so that we keep preferring the # same child. r.sort() child_fd = r[0] # Try to read the single byte written by the child. # This can fail if the child died or the pipe really # wasn't ready (select returns a hint only). The fd has # been made non-blocking by spawn_child. If this fails # we fall through to the "reap_children" logic and will # retry the select call. try: ready_byte = os.read(child_fd, 1) if not ready_byte: raise IOError # child died? assert ready_byte == "1", repr(ready_byte) except socket.error, exc: if exc[0] == errno.EWOULDBLOCK: pass # select was wrong else: raise except (OSError, IOError): pass # child died? else: # The byte was read okay, now we need to pass the fd # of the request to the child. This can also fail # if the child died. Again, if this fails we fall # through to the "reap_children" logic and will # retry the select call. try: passfd.sendfd(child_fd, conn.fileno()) except IOError, exc: if exc.errno == errno.EPIPE: pass # broken pipe, child died? else: raise else: # fd was apparently passed okay to the child. # The child could die before completing the # request but that's not our problem anymore. return # didn't find any child, check if any died self.reap_children() # start more children if we haven't met max_children limit if len(self.children) < self.max_children: self.spawn_child(conn) # Start blocking inside select. We might have reached # max_children limit and they are all busy. timeout = 2 def get_listening_socket(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((self.host, self.port)) return s def serve_on_socket(self, s): self.socket = s self.socket.listen(40) signal.signal(signal.SIGHUP, self.hup_signal) while 1: try: conn, addr = self.socket.accept() self.delegate_request(conn) conn.close() except socket.error, e: if e[0] != errno.EINTR: raise # something weird if self.restart: self.do_restart() def serve(self): self.serve_on_socket(self.get_listening_socket()) def main(): if len(sys.argv) == 2: port = int(sys.argv[1]) else: port = SCGIServer.DEFAULT_PORT SCGIServer(port=port).serve() if __name__ == "__main__": main() scgi-1.13/scgi/test_passfd.py0000664000175000017500000000211211024061347014321 0ustar nasnas#!/usr/bin/env python # import os, sys, socket import passfd import tempfile # # Create a pipe for sending the fd. # rfd, wfd = passfd.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) print "rfd", rfd, "wfd", wfd # We will pass this to the child fileObj = tempfile.TemporaryFile() line = 'Hello world!\n' fileObj.write(line) fileObj.flush() fileObj.seek(0) # # fork() off! # pid = os.fork() if pid != 0: # We're in the parent. # ioctl() will only pass raw filedescriptors. Find fd of fileObj. fd = fileObj.fileno() # Send to the child os.write(wfd, "x") passfd.sendfd(wfd, fd) # Wait for child to terminate, then exit. os.waitpid(pid, 0) fileObj.close() sys.exit(0) else: # We're in the child. fileObj.close() print os.read(rfd, 1) fd = passfd.recvfd(rfd) # Reopen the filedescriptor as a Python File-object. fileObj = os.fdopen(fd, 'r') # Example usage: Read file, print the first line. data = fileObj.readline() print "Read line: %r, expected %r" % (data, line) assert line == data sys.exit(0) scgi-1.13/CHANGES.txt0000664000175000017500000001072711031275132012324 0ustar nasnasAuthor: Neil Schemenauer Date: Wed Jun 11 17:40:47 2008 -0600 Send Content-Length provided by client, rather than r->remaining. This new approach is simpler and appears to solve a bug that shows up on OS X Leopard. Author: Neil Schemenauer Date: Sun Apr 6 15:01:08 2008 -0600 Generate CHANGES.txt from git log. Generate CHANGES.txt using "git log" instead of manually maintaining it. Improve README text. Update version numbers to 1.13. Author: Daniel Rall Date: Wed Mar 26 15:20:39 2008 -0600 Fix error message typo in passfd.c. Author: Neil Schemenauer Date: Fri May 25 11:29:37 2007 -0600 Remove duplicated text from Apache error messages. Author: Neil Schemenauer Date: Wed Feb 7 11:29:04 2007 -0600 Ensure that PATH_INFO is correct even with mod_rewrite mod_rewrite can modify r->path_info. One way this could happen is if the path being served by SCGI exists on the filesystem. Ensure that PATH_INFO is correct. Thanks to David Binger for point out the fix. Author: Jeroen T. Vermeulen Date: Wed Feb 7 11:28:22 2007 -0600 Add guide.html. Author: Neil Schemenauer Date: Fri Aug 25 17:43:42 2006 -0600 Provide CMSG_* macros if they are missing. Some platforms don't provide them. Author: Neil Schemenauer Date: Fri Aug 25 17:25:48 2006 -0600 Preserve original os.environ dictionary. Also, make some formatting tweaks. Author: Jeroen T. Vermeulen Date: Fri Aug 25 15:50:39 2006 +0700 Document produce() and produce_cgilike() Author: Neil Schemenauer Date: Fri Aug 25 15:29:50 2006 +0700 Remove unnecessary os.putenv() call. Calling os.putenv() is not necessary since os.environ alone will do the job. Author: Neil Schemenauer Date: Fri Aug 25 13:41:38 2006 +0700 Add more friendly overridable methods in SCGIHandler. Optional, more user-friendly overridable methods in SCGIHandler: produce() and produce_cgilike(). Overriding handle_connection() will still work, but overriding produce() instead takes a lot of the household activities (reading CGI variables, opening socket for reading/writing, cleanup) out of the application's hands. Another alternative, produce_cgilike(), makes things even easier (at least for some uses): it can read its message body from stdin and write its output from stdout, and get its CGI parameters from its set of environment variables. Thanks to Jeroen Vermeulen for code and ideas. Author: Neil Schemenauer Date: Wed Aug 23 20:42:07 2006 -0600 Add some pypi information. Author: Neil Schemenauer Date: Mon Aug 14 15:49:04 2006 -0600 Update version numbers in mod_scgi. Alos, make version number check in setup.py smarter. Author: Neil Schemenauer Date: Mon Aug 14 12:11:33 2006 -0600 Update MANIFEST for 1.11 release. Author: Neil Schemenauer Date: Mon Aug 14 12:08:10 2006 -0600 Prepare for 1.11 release. Author: Neil Schemenauer Date: Mon Aug 14 12:07:59 2006 -0600 Improve passfd test a little. Author: Neil Schemenauer Date: Mon Aug 14 12:06:41 2006 -0600 Improve portability of passfd extension. Author: Joseph Tate Date: Thu May 25 12:55:49 2006 -0600 Allow SCGIServer to use an already open socket. Add serve_on_socket() method that allows an already open socket to be used. The existing serve() method remains the same. Author: Neil Schemenauer Date: Fri May 19 11:46:19 2006 -0600 In passfd.c, initialize tmpbuf contents. Jonathan Corbet suggested this as a fix for trouble on an AMD64 machine. Author: Neil Schemenauer Date: Fri May 19 11:41:59 2006 -0600 Fix a mod_scgi bug that caused a segfault Fix a bug that resulted in a NULL pointer dereference (under certain configurations). Thanks to Thomas Yandell for helping track down the bug. Author: Neil Schemenauer Date: Fri May 19 09:41:25 2006 -0600 Update README and LICENSE files. Author: Neil Schemenauer Date: Fri May 19 09:30:23 2006 -0600 Import scgi 1.10. scgi-1.13/LICENSE.txt0000664000175000017500000000260311024061354012331 0ustar nasnasThis version of the SCGI package is derived from scgi 1.10, released by CNRI. See doc/LICENSE_110.txt for the licensing terms of that release. Changes made since that release are summarized in the CHANGES.txt file along with a list of authors. Those changes are made available under the following terms (commonly known as the MIT/X license). Copyright (c) the SCGI package developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. scgi-1.13/MANIFEST.in0000664000175000017500000000042411024061346012244 0ustar nasnasinclude *.txt MANIFEST.in setup.py cgi2scgi.c include doc/*.txt include doc/*.html include apache1/mod_scgi.c include apache1/README.txt include apache1/Makefile include apache2/mod_scgi.c include apache2/README.txt include apache2/Makefile include scgi/*.py include scgi/*.c scgi-1.13/README.txt0000664000175000017500000000370711024061354012212 0ustar nasnasSCGI: A Simple Common Gateway Interface alternative =================================================== Protocol -------- SCGI is a protocol for connecting web application servers to HTTP servers (e.g. Apache). For typical applications, it provides much better performance verses using CGI. See http://python.ca/scgi/ for details on the SCGI protocol including a specification. Software -------- See doc/guide.html for an overview of how SCGI works. Below is a list of components included in this package. scgi ---- A Python package implementing the server side of the SCGI protocol. apache1 ------- An Apache 1.3 module that implements the client side of the protocol. See the README file in the apache1 directory for more details. apache2 ------- An Apache 2.0 module that implements the client side of the protocol. See the README file in the apache2 directory for more details. cgi2scgi -------- A CGI script that forwards requests to a SCGI server. This is useful in situations where you cannot or do not want to use the mod_scgi module. Because the CGI script is small performance is quite good. To use, edit the source and specify the correct address and port of the SCGI server. Next, compile using a C compiler, e.g.: $ cc -o myapp.cgi cgi2scgi.c Finally, put the script in the proper directory (depends on web server software used). Source ------ The source code is managed using git. You can checkout a copy using the command: git clone http://quixote.ca/src/scgi.git License ------- The SCGI package is copyrighted and made available under open source licensing terms. See the LICENSE.txt file for the details. The CHANGES.txt file summarizes recent changes made to the package. /* vim: set ai tw=74 et sw=4 sts=4: */ scgi-1.13/cgi2scgi.c0000664000175000017500000000702211024061346012345 0ustar nasnas/* * cgi2scgi: A CGI to SCGI translator * */ /* configuration settings */ #ifndef HOST #define HOST "127.0.0.1" #endif #ifndef PORT #define PORT 4000 #endif #include #include #include #include #include #include #include #include #include /* for TCP_NODELAY */ #define SCGI_PROTOCOL_VERSION "1" struct scgi_header { struct scgi_header *next; char *name; char *value; }; extern char **environ; static void die(void) { _exit(2); } static void die_perror(char *msg) { char buf[500]; snprintf(buf, sizeof buf, "error: %s", msg); perror(buf); die(); } static void die_msg(char *msg) { fprintf(stderr, "error: %s\n", msg); die(); } static int open_socket(void) { int sock, set; int tries = 4, retrytime = 1; struct in_addr host; struct sockaddr_in addr; /* create socket */ if (!inet_aton(HOST, &(host))) { die_perror("parsing host IP"); } addr.sin_addr = host; addr.sin_port = htons(PORT); addr.sin_family = AF_INET; retry: sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) { die_perror("creating socket"); } /* connect */ if (connect(sock, (struct sockaddr *)&addr, sizeof addr) == -1) { close(sock); if (errno == ECONNREFUSED && tries > 0) { sleep(retrytime); tries--; retrytime *= 2; goto retry; } die_perror("connecting to server"); } #ifdef TCP_NODELAY /* disable Nagle */ set = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&set, sizeof(set)); #endif return sock; } static int send_headers(FILE *fp) { int n; char **p; /* set the CONTENT_LENGTH header if it is not already set */ if (setenv("CONTENT_LENGTH", "0", 0) < 0) return 0; if (setenv("SCGI", SCGI_PROTOCOL_VERSION, 1) < 0) return 0; /* calculate the total length of the headers */ n = 0; for (p = environ; *p != NULL; *p++) { if (strchr(*p, '=') == NULL) continue; n += strlen(*p) + 1; } /* send header data as netstring */ if (fprintf(fp, "%u:", n) < 0) return 0; if (fputs("CONTENT_LENGTH", fp) < 0) return 0; if (fputc('\0', fp) == EOF) return 0; if (fputs(getenv("CONTENT_LENGTH"), fp) < 0) return 0; if (fputc('\0', fp) == EOF) return 0; for (p = environ; *p != NULL; *p++) { char *eq = strchr(*p, '='); if (eq == NULL) continue; if (!strncmp(*p, "CONTENT_LENGTH=", 15)) continue; n = eq - *p; if (fwrite(*p, 1, n, fp) < n) return 0; if (fputc('\0', fp) == EOF) return 0; if (fputs(eq + 1, fp) < 0) return 0; if (fputc('\0', fp) == EOF) return 0; } if (fputc(',', fp) == EOF) return 0; return 1; } static int copyfp(FILE *in, FILE *out) { size_t n, n2; char buf[8000]; for (;;) { n = fread(buf, 1, sizeof buf, in); if (n != sizeof buf && ferror(in)) return 0; if (n == 0) break; /* EOF */ n2 = fwrite(buf, 1, n, out); if (n2 != n) return 0; } return 1; } int main(int argc, char **argv) { int sock, fd; FILE *fp; sock = open_socket(); /* send request */ if ((fd = dup(sock)) < 0) die_perror("duplicating fd"); if ((fp = fdopen(fd, "w")) == NULL) die_perror("creating buffered file"); if (!send_headers(fp)) { die_msg("sending request headers"); } if (!copyfp(stdin, fp)) { die_msg("sending request body"); } if (fclose(fp) != 0) die_perror("sending request body"); /* send reponse */ if ((fd = dup(sock)) < 0 || (fp = fdopen(fd, "r")) == NULL) die_perror("creating buffered file from socket"); if (!copyfp(fp, stdout)) { die_msg("sending response"); } if (fclose(fp) != 0) die_perror("closing socket"); return 0; } scgi-1.13/setup.py0000664000175000017500000000333011024061354012216 0ustar nasnas#!/usr/bin/env python import sys from distutils import core from distutils.extension import Extension from scgi.__init__ import __version__ # Ensure that version number is correct. def _check_version_numbers(): import re PAT = re.compile(r'(^|VERSION ")%s\b' % re.escape(__version__), re.M) for fn in ["apache1/mod_scgi.c", "apache2/mod_scgi.c"]: if not PAT.search(open(fn).read(200)): raise AssertionError("version number mismatch in %r" % fn) if 'sdist' in sys.argv[1:]: _check_version_numbers() kw = dict( name = "scgi", version = __version__, description = "A Python package for implementing SCGI servers.", author = "Neil Schemenauer", author_email = "nas@arctrix.com", #url = "http://", license = "DFSG approved (see LICENSE.txt)", packages = ['scgi'], ext_modules = [Extension(name="scgi.passfd", sources=['scgi/passfd.c'])], ) # If we're running Python 2.3, add extra information if hasattr(core, 'setup_keywords'): if 'classifiers' in core.setup_keywords: kw['classifiers'] = ['Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'License :: DFSG approved', 'Intended Audience :: Developers', 'Operating System :: Unix', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ] if 'download_url' in core.setup_keywords: kw['download_url'] = ('http://python.ca/scgi/releases/' 'scgi-%s.tar.gz' % kw['version']) if 'url' in core.setup_keywords: kw['url'] = 'http://python.ca/scgi/' core.setup(**kw) scgi-1.13/PKG-INFO0000664000175000017500000000131311031275132011577 0ustar nasnasMetadata-Version: 1.0 Name: scgi Version: 1.13 Summary: A Python package for implementing SCGI servers. Home-page: http://python.ca/scgi/ Author: Neil Schemenauer Author-email: nas@arctrix.com License: DFSG approved (see LICENSE.txt) Download-URL: http://python.ca/scgi/releases/scgi-1.13.tar.gz Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: License :: DFSG approved Classifier: Intended Audience :: Developers Classifier: Operating System :: Unix Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content