mirror of
https://github.com/clearlinux/uwsgi.git
synced 2026-08-26 16:28:00 +00:00
0389748d4a
Use PL_sv_yes & PL_sv_no instead of newSViv(1) & newSViv(0) for bools
as they're interpreter globals that don't need to be created everytime.
Added a test and refactored the tests that were added with a1e30157,
all .t files in t/perl are now ran under all major perl versions with
and without threading when t/perl/run is invoked.
On my box, psgi_plugin.o went from 62,296 bytes to 62,208 bytes. Not a
huge saving I'll grant you, but every little helps :-)
1035 lines
28 KiB
C
1035 lines
28 KiB
C
#include "psgi.h"
|
|
|
|
extern char **environ;
|
|
extern struct uwsgi_server uwsgi;
|
|
|
|
#ifdef __APPLE__
|
|
extern struct uwsgi_perl uperl;
|
|
#else
|
|
struct uwsgi_perl uperl;
|
|
#endif
|
|
|
|
struct uwsgi_plugin psgi_plugin;
|
|
|
|
static void uwsgi_opt_plshell(char *opt, char *value, void *foobar) {
|
|
|
|
uwsgi.honour_stdin = 1;
|
|
if (value) {
|
|
uperl.shell = value;
|
|
}
|
|
else {
|
|
uperl.shell = "";
|
|
}
|
|
|
|
if (!strcmp("plshell-oneshot", opt)) {
|
|
uperl.shell_oneshot = 1;
|
|
}
|
|
}
|
|
|
|
EXTERN_C void xs_init (pTHX);
|
|
int uwsgi_perl_init(void);
|
|
|
|
static void uwsgi_opt_early_perl(char *opt, char *value, void *foobar) {
|
|
// avoid duplicates
|
|
if (uperl.early_interpreter) return;
|
|
uwsgi_perl_init();
|
|
uperl.early_interpreter = uperl.main[0];
|
|
|
|
// HACK the following allocations ensure correct xs initialization
|
|
uperl.tmp_streaming_stash = uwsgi_calloc(sizeof(HV *) * uwsgi.threads);
|
|
uperl.tmp_input_stash = uwsgi_calloc(sizeof(HV *) * uwsgi.threads);
|
|
uperl.tmp_error_stash = uwsgi_calloc(sizeof(HV *) * uwsgi.threads);
|
|
uperl.tmp_stream_responder = uwsgi_calloc(sizeof(CV *) * uwsgi.threads);
|
|
uperl.tmp_psgix_logger = uwsgi_calloc(sizeof(CV *) * uwsgi.threads);
|
|
|
|
char *perl_e_arg = uwsgi_concat2("#line 0 ", value);
|
|
char *perl_init_arg[] = { "", "-e", perl_e_arg };
|
|
perl_parse(uperl.early_interpreter, xs_init, 3, perl_init_arg, NULL);
|
|
free(perl_e_arg);
|
|
}
|
|
|
|
static void uwsgi_opt_early_psgi(char *opt, char *value, void *foobar) {
|
|
uwsgi_perl_init();
|
|
init_psgi_app(NULL, value, strlen(value), uperl.main);
|
|
if (!uperl.early_psgi_callable) exit(1);
|
|
}
|
|
|
|
static void uwsgi_opt_early_exec(char *opt, char *value, void *foobar) {
|
|
uwsgi_perl_init();
|
|
if (!uperl.early_interpreter) {
|
|
perl_parse(uperl.main[0], xs_init, 3, uperl.embedding, NULL);
|
|
}
|
|
SV *dollar_zero = get_sv("0", GV_ADD);
|
|
sv_setsv(dollar_zero, newSVpv(value, strlen(value)));
|
|
uwsgi_perl_exec(value);
|
|
}
|
|
|
|
|
|
struct uwsgi_option uwsgi_perl_options[] = {
|
|
|
|
{"psgi", required_argument, 0, "load a psgi app", uwsgi_opt_set_str, &uperl.psgi, 0},
|
|
{"psgi-enable-psgix-io", no_argument, 0, "enable psgix.io support", uwsgi_opt_true, &uperl.enable_psgix_io, 0},
|
|
{"perl-no-die-catch", no_argument, 0, "do not catch $SIG{__DIE__}", uwsgi_opt_true, &uperl.no_die_catch, 0},
|
|
{"perl-local-lib", required_argument, 0, "set perl locallib path", uwsgi_opt_set_str, &uperl.locallib, 0},
|
|
#ifdef PERL_VERSION_STRING
|
|
{"perl-version", no_argument, 0, "print perl version", uwsgi_opt_print, PERL_VERSION_STRING, UWSGI_OPT_IMMEDIATE},
|
|
#endif
|
|
{"perl-args", required_argument, 0, "add items (space separated) to @ARGV", uwsgi_opt_set_str, &uperl.argv_items, 0},
|
|
{"perl-arg", required_argument, 0, "add an item to @ARGV", uwsgi_opt_add_string_list, &uperl.argv_item, 0},
|
|
{"perl-exec", required_argument, 0, "exec the specified perl file before fork()", uwsgi_opt_add_string_list, &uperl.exec, 0},
|
|
{"perl-exec-post-fork", required_argument, 0, "exec the specified perl file after fork()", uwsgi_opt_add_string_list, &uperl.exec_post_fork, 0},
|
|
{"perl-auto-reload", required_argument, 0, "enable perl auto-reloader with the specified frequency", uwsgi_opt_set_int, &uperl.auto_reload, UWSGI_OPT_MASTER},
|
|
{"perl-auto-reload-ignore", required_argument, 0, "ignore the specified files when auto-reload is enabled", uwsgi_opt_add_string_list, &uperl.auto_reload_ignore, UWSGI_OPT_MASTER},
|
|
|
|
{"plshell", optional_argument, 0, "run a perl interactive shell", uwsgi_opt_plshell, NULL, 0},
|
|
{"plshell-oneshot", no_argument, 0, "run a perl interactive shell (one shot)", uwsgi_opt_plshell, NULL, 0},
|
|
|
|
{"perl-no-plack", no_argument, 0, "force the use of do instead of Plack::Util::load_psgi", uwsgi_opt_true, &uperl.no_plack, 0},
|
|
{"early-perl", required_argument, 0, "initialize an early perl interpreter shared by all loaders", uwsgi_opt_early_perl, NULL, UWSGI_OPT_IMMEDIATE},
|
|
{"early-psgi", required_argument, 0, "load a psgi app soon after uWSGI initialization", uwsgi_opt_early_psgi, NULL, UWSGI_OPT_IMMEDIATE},
|
|
{"early-perl-exec", required_argument, 0, "load a perl script soon after uWSGI initialization", uwsgi_opt_early_exec, NULL, UWSGI_OPT_IMMEDIATE},
|
|
{0, 0, 0, 0, 0, 0, 0},
|
|
|
|
};
|
|
|
|
int uwsgi_perl_check_mtime(time_t now, HV *list, SV *key) {
|
|
// insert item with the current time
|
|
if (!hv_exists_ent(list, key, 0)) {
|
|
// useless if...
|
|
if (hv_store_ent(list, key, newSViv(now), 0)) return 0;
|
|
}
|
|
else {
|
|
// compare mtime
|
|
struct stat st;
|
|
if (stat(SvPV_nolen(key), &st)) return 0;
|
|
HE *mtime = hv_fetch_ent(list, key, 0, 0);
|
|
if (!mtime) return 0;
|
|
if (st.st_mtime > SvIV(HeVAL(mtime))) {
|
|
uwsgi_log_verbose("[perl-auto-reloader] %s has been modified !!!\n", SvPV_nolen(key));
|
|
kill(uwsgi.workers[0].pid, SIGHUP);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void uwsgi_perl_check_auto_reload() {
|
|
time_t now = uwsgi_now();
|
|
HE *he;
|
|
if (!uperl.auto_reload_hash) {
|
|
uperl.auto_reload_hash = newHV();
|
|
// useless return value
|
|
if (!SvREFCNT_inc(uperl.auto_reload_hash)) return;
|
|
}
|
|
GV *gv_inc = gv_fetchpv("INC", TRUE, SVt_PV);
|
|
if (!gv_inc) return;
|
|
HV *inc = GvHV(gv_inc);
|
|
hv_iterinit(inc);
|
|
while((he = hv_iternext(inc))) {
|
|
SV *filename = hv_iterval(inc, he);
|
|
struct uwsgi_string_list *usl;
|
|
int found = 0;
|
|
uwsgi_foreach(usl, uperl.auto_reload_ignore) {
|
|
if (!strcmp(usl->value, SvPV_nolen(filename))) {
|
|
found = 1; break;
|
|
}
|
|
}
|
|
if (found) continue;
|
|
if (uwsgi_perl_check_mtime(now, uperl.auto_reload_hash, filename)) return;
|
|
}
|
|
}
|
|
|
|
SV *uwsgi_perl_obj_new(char *class, size_t class_len) {
|
|
|
|
SV *newobj;
|
|
|
|
dSP;
|
|
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
XPUSHs(sv_2mortal(newSVpv( class, class_len)));
|
|
PUTBACK;
|
|
|
|
call_method( "new", G_SCALAR);
|
|
|
|
SPAGAIN;
|
|
|
|
newobj = SvREFCNT_inc(POPs);
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return newobj;
|
|
|
|
}
|
|
|
|
SV *uwsgi_perl_obj_new_from_fd(char *class, size_t class_len, int fd) {
|
|
SV *newobj;
|
|
|
|
dSP;
|
|
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
XPUSHs(sv_2mortal(newSVpv( class, class_len)));
|
|
XPUSHs(sv_2mortal(newSViv( fd )));
|
|
XPUSHs(sv_2mortal(newSVpv( "w", 1 )));
|
|
PUTBACK;
|
|
|
|
call_method( "new_from_fd", G_SCALAR);
|
|
|
|
SPAGAIN;
|
|
|
|
newobj = SvREFCNT_inc(POPs);
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return newobj;
|
|
}
|
|
|
|
SV *uwsgi_perl_call_stream(SV *func) {
|
|
|
|
SV *ret = NULL;
|
|
struct wsgi_request *wsgi_req = current_wsgi_req();
|
|
struct uwsgi_app *wi = &uwsgi_apps[wsgi_req->app_id];
|
|
|
|
dSP;
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
if (uwsgi.threads > 1) {
|
|
XPUSHs( sv_2mortal(newRV((SV*) ((SV **)wi->responder0)[wsgi_req->async_id])));
|
|
}
|
|
else {
|
|
XPUSHs( sv_2mortal(newRV((SV*) ((SV **)wi->responder0)[0])));
|
|
}
|
|
PUTBACK;
|
|
|
|
call_sv( func, G_SCALAR | G_EVAL);
|
|
|
|
SPAGAIN;
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("[uwsgi-perl error] %s", SvPV_nolen(ERRSV));
|
|
}
|
|
else {
|
|
ret = SvREFCNT_inc(POPs);
|
|
}
|
|
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int uwsgi_perl_obj_can(SV *obj, char *method, size_t len) {
|
|
|
|
int ret;
|
|
|
|
dSP;
|
|
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
XPUSHs(obj);
|
|
XPUSHs(sv_2mortal(newSVpv(method, len)));
|
|
PUTBACK;
|
|
|
|
call_method( "can", G_SCALAR|G_EVAL);
|
|
|
|
SPAGAIN;
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("%s", SvPV_nolen(ERRSV));
|
|
}
|
|
|
|
ret = SvROK(POPs);
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
SV *uwsgi_perl_obj_call(SV *obj, char *method) {
|
|
|
|
SV *ret = NULL;
|
|
|
|
dSP;
|
|
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
|
|
XPUSHs(obj);
|
|
|
|
PUTBACK;
|
|
|
|
call_method( method, G_SCALAR | G_EVAL);
|
|
|
|
SPAGAIN;
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("%s", SvPV_nolen(ERRSV));
|
|
}
|
|
else {
|
|
ret = SvREFCNT_inc(POPs);
|
|
}
|
|
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
AV *psgi_call(struct wsgi_request *wsgi_req, SV *psgi_func, SV *env) {
|
|
|
|
AV *ret = NULL;
|
|
|
|
dSP;
|
|
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
XPUSHs(env);
|
|
PUTBACK;
|
|
|
|
call_sv(psgi_func, G_SCALAR | G_EVAL);
|
|
|
|
SPAGAIN;
|
|
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_500(wsgi_req);
|
|
uwsgi_log("[uwsgi-perl error] %s", SvPV_nolen(ERRSV));
|
|
}
|
|
else {
|
|
SV *r = POPs;
|
|
if (SvROK(r)) {
|
|
ret = (AV *) SvREFCNT_inc(SvRV(r));
|
|
}
|
|
}
|
|
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return (AV *)ret;
|
|
|
|
}
|
|
|
|
SV *build_psgi_env(struct wsgi_request *wsgi_req) {
|
|
int i;
|
|
struct uwsgi_app *wi = &uwsgi_apps[wsgi_req->app_id];
|
|
HV *env = newHV();
|
|
|
|
// fill perl hash
|
|
for(i=0;i<wsgi_req->var_cnt;i++) {
|
|
if (wsgi_req->hvec[i+1].iov_len > 0) {
|
|
|
|
// check for multiline header
|
|
if (hv_exists(env, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len)) {
|
|
SV **already_avalable_header = hv_fetch(env, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len, 0);
|
|
STRLEN hlen;
|
|
char *old_value = SvPV(*already_avalable_header, hlen );
|
|
char *multiline_header = uwsgi_concat3n(old_value, hlen, ", ", 2, wsgi_req->hvec[i+1].iov_base, wsgi_req->hvec[i+1].iov_len);
|
|
if (!hv_store(env, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len,
|
|
newSVpv(multiline_header, hlen+wsgi_req->hvec[i+1].iov_len+2), 0)) { free(multiline_header); goto clear;}
|
|
free(multiline_header);
|
|
|
|
|
|
}
|
|
else {
|
|
if (!hv_store(env, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len,
|
|
newSVpv(wsgi_req->hvec[i+1].iov_base, wsgi_req->hvec[i+1].iov_len), 0)) goto clear;
|
|
}
|
|
}
|
|
else {
|
|
if (!hv_store(env, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len, newSVpv("", 0), 0)) goto clear;
|
|
}
|
|
//uwsgi_log("%.*s = %.*s\n", wsgi_req->hvec[i].iov_len, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i+1].iov_len, wsgi_req->hvec[i+1].iov_base);
|
|
i++;
|
|
}
|
|
|
|
// psgi.version
|
|
AV *av = newAV();
|
|
av_store( av, 0, newSViv(1));
|
|
av_store( av, 1, newSViv(1));
|
|
if (!hv_store(env, "psgi.version", 12, newRV_noinc((SV *)av ), 0)) goto clear;
|
|
|
|
// All the simple bools.
|
|
if (!hv_store(env, "psgi.multiprocess", 17, uwsgi.numproc > 1 ? &PL_sv_yes : &PL_sv_no, 0)) goto clear;
|
|
if (!hv_store(env, "psgi.multithread", 16, uwsgi.threads > 1 ? &PL_sv_yes : &PL_sv_no, 0)) goto clear;
|
|
if (!hv_store(env, "psgi.nonblocking", 16, uwsgi.async > 0 ? &PL_sv_yes : &PL_sv_no, 0)) goto clear;
|
|
if (!hv_store(env, "psgix.harakiri", 14, uwsgi.master_process ? &PL_sv_yes : &PL_sv_no, 0)) goto clear;
|
|
|
|
if (!hv_store(env, "psgi.run_once", 13, &PL_sv_no, 0)) goto clear;
|
|
if (!hv_store(env, "psgi.streaming", 14, &PL_sv_yes, 0)) goto clear;
|
|
if (!hv_store(env, "psgix.cleanup", 13, &PL_sv_yes, 0)) goto clear;
|
|
|
|
SV *us;
|
|
// psgi.url_scheme, honour HTTPS var or UWSGI_SCHEME
|
|
if (wsgi_req->scheme_len > 0) {
|
|
us = newSVpv(wsgi_req->scheme, wsgi_req->scheme_len);
|
|
}
|
|
else if (wsgi_req->https_len > 0) {
|
|
if (!strncasecmp(wsgi_req->https, "on", 2) || wsgi_req->https[0] == '1') {
|
|
us = newSVpv("https", 5);
|
|
}
|
|
else {
|
|
us = newSVpv("http", 4);
|
|
}
|
|
}
|
|
else {
|
|
us = newSVpv("http", 4);
|
|
}
|
|
|
|
if (!hv_store(env, "psgi.url_scheme", 15, us, 0)) goto clear;
|
|
|
|
|
|
SV *pi = uwsgi_perl_obj_new("uwsgi::input", 12);
|
|
if (!hv_store(env, "psgi.input", 10, pi, 0)) goto clear;
|
|
|
|
if (!hv_store(env, "psgix.input.buffered", 20, newSViv(uwsgi.post_buffering), 0)) goto clear;
|
|
|
|
if (uwsgi.threads > 1) {
|
|
if (!hv_store(env, "psgix.logger", 12,newRV((SV*) ((SV **)wi->responder1)[wsgi_req->async_id]) ,0)) goto clear;
|
|
}
|
|
else {
|
|
if (!hv_store(env, "psgix.logger", 12,newRV((SV*) ((SV **)wi->responder1)[0]) ,0)) goto clear;
|
|
}
|
|
|
|
// cleanup handlers array
|
|
av = newAV();
|
|
if (!hv_store(env, "psgix.cleanup.handlers", 22, newRV_noinc((SV *)av ), 0)) goto clear;
|
|
|
|
// this call requires a bunch of syscalls, so it hurts performance
|
|
if (uperl.enable_psgix_io) {
|
|
SV *io = uwsgi_perl_obj_new_from_fd("IO::Socket", 10, wsgi_req->fd);
|
|
if (!hv_store(env, "psgix.io", 8, io, 0)) goto clear;
|
|
}
|
|
|
|
SV *pe = uwsgi_perl_obj_new("uwsgi::error", 12);
|
|
if (!hv_store(env, "psgi.errors", 11, pe, 0)) goto clear;
|
|
|
|
(void) hv_delete(env, "HTTP_CONTENT_LENGTH", 19, G_DISCARD);
|
|
(void) hv_delete(env, "HTTP_CONTENT_TYPE", 17, G_DISCARD);
|
|
|
|
return newRV_noinc((SV *)env);
|
|
|
|
clear:
|
|
SvREFCNT_dec((SV *)env);
|
|
return NULL;
|
|
}
|
|
|
|
int uwsgi_perl_init(){
|
|
|
|
int argc;
|
|
int i;
|
|
|
|
if (uperl.main) {
|
|
goto already_initialized;
|
|
}
|
|
|
|
uperl.embedding[0] = "";
|
|
uperl.embedding[1] = "-e";
|
|
uperl.embedding[2] = "0";
|
|
|
|
#ifndef USE_ITHREADS
|
|
if (uwsgi.threads > 1) {
|
|
uwsgi_log("your Perl environment does not support threads\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
|
|
if (setenv("PLACK_ENV", "uwsgi", 0)) {
|
|
uwsgi_error("setenv()");
|
|
}
|
|
|
|
if (setenv("PLACK_SERVER", "uwsgi", 0)) {
|
|
uwsgi_error("setenv()");
|
|
}
|
|
|
|
argc = 3;
|
|
|
|
PERL_SYS_INIT3(&argc, (char ***) &uperl.embedding, &environ);
|
|
|
|
uperl.main = uwsgi_calloc(sizeof(PerlInterpreter *) * uwsgi.threads);
|
|
|
|
uperl.main[0] = uwsgi_perl_new_interpreter();
|
|
if (!uperl.main[0]) {
|
|
return -1;
|
|
}
|
|
|
|
for(i=1;i<uwsgi.threads;i++) {
|
|
uperl.main[i] = uwsgi_perl_new_interpreter();
|
|
if (!uperl.main[i]) {
|
|
uwsgi_log("unable to create new perl interpreter for thread %d\n", i+1);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
PERL_SET_CONTEXT(uperl.main[0]);
|
|
|
|
already_initialized:
|
|
#ifdef PERL_VERSION_STRING
|
|
uwsgi_log_initial("initialized Perl %s main interpreter at %p\n", PERL_VERSION_STRING, uperl.main[0]);
|
|
#else
|
|
uwsgi_log_initial("initialized Perl main interpreter at %p\n", uperl.main[0]);
|
|
#endif
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
int uwsgi_perl_request(struct wsgi_request *wsgi_req) {
|
|
|
|
if (wsgi_req->async_status == UWSGI_AGAIN) {
|
|
return psgi_response(wsgi_req, wsgi_req->async_placeholder);
|
|
}
|
|
|
|
/* Standard PSGI request */
|
|
if (!wsgi_req->len) {
|
|
uwsgi_log("Empty PSGI request. skip.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (uwsgi_parse_vars(wsgi_req)) {
|
|
return -1;
|
|
}
|
|
|
|
if (wsgi_req->dynamic) {
|
|
if (uwsgi.threads > 1) {
|
|
pthread_mutex_lock(&uperl.lock_loader);
|
|
}
|
|
}
|
|
|
|
wsgi_req->app_id = uwsgi_get_app_id(wsgi_req, wsgi_req->appid, wsgi_req->appid_len, psgi_plugin.modifier1);
|
|
// if it is -1, try to load a dynamic app
|
|
if (wsgi_req->app_id == -1) {
|
|
if (wsgi_req->dynamic) {
|
|
if (wsgi_req->script_len > 0) {
|
|
wsgi_req->app_id = init_psgi_app(wsgi_req, wsgi_req->script, wsgi_req->script_len, NULL);
|
|
}
|
|
else if (wsgi_req->file_len > 0) {
|
|
wsgi_req->app_id = init_psgi_app(wsgi_req, wsgi_req->file, wsgi_req->file_len, NULL);
|
|
}
|
|
}
|
|
|
|
if (wsgi_req->app_id == -1 && !uwsgi.no_default_app && uwsgi.default_app > -1) {
|
|
if (uwsgi_apps[uwsgi.default_app].modifier1 == psgi_plugin.modifier1) {
|
|
wsgi_req->app_id = uwsgi.default_app;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (wsgi_req->dynamic) {
|
|
if (uwsgi.threads > 1) {
|
|
pthread_mutex_unlock(&uperl.lock_loader);
|
|
}
|
|
}
|
|
|
|
if (wsgi_req->app_id == -1) {
|
|
uwsgi_500(wsgi_req);
|
|
uwsgi_log("--- unable to find perl application ---\n");
|
|
// nothing to clear/free
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
struct uwsgi_app *wi = &uwsgi_apps[wsgi_req->app_id];
|
|
wi->requests++;
|
|
|
|
|
|
if (uwsgi.threads < 2) {
|
|
if (((PerlInterpreter **)wi->interpreter)[0] != uperl.main[0]) {
|
|
PERL_SET_CONTEXT(((PerlInterpreter **)wi->interpreter)[0]);
|
|
}
|
|
}
|
|
else {
|
|
if (((PerlInterpreter **)wi->interpreter)[wsgi_req->async_id] != uperl.main[wsgi_req->async_id]) {
|
|
PERL_SET_CONTEXT(((PerlInterpreter **)wi->interpreter)[wsgi_req->async_id]);
|
|
}
|
|
}
|
|
|
|
ENTER;
|
|
SAVETMPS;
|
|
|
|
wsgi_req->async_environ = build_psgi_env(wsgi_req);
|
|
if (!wsgi_req->async_environ) goto clear;
|
|
|
|
|
|
if (uwsgi.threads > 1) {
|
|
wsgi_req->async_result = psgi_call(wsgi_req, ((SV **)wi->callable)[wsgi_req->async_id], wsgi_req->async_environ);
|
|
}
|
|
else {
|
|
wsgi_req->async_result = psgi_call(wsgi_req, ((SV **)wi->callable)[0], wsgi_req->async_environ);
|
|
}
|
|
if (!wsgi_req->async_result) goto clear;
|
|
|
|
if (SvTYPE((AV *)wsgi_req->async_result) == SVt_PVCV) {
|
|
SV *stream_result = uwsgi_perl_call_stream((SV*)wsgi_req->async_result);
|
|
if (!stream_result) {
|
|
uwsgi_500(wsgi_req);
|
|
}
|
|
else {
|
|
SvREFCNT_dec(stream_result);
|
|
}
|
|
goto clear2;
|
|
}
|
|
|
|
while (psgi_response(wsgi_req, wsgi_req->async_result) != UWSGI_OK) {
|
|
if (uwsgi.async > 0) {
|
|
FREETMPS;
|
|
LEAVE;
|
|
return UWSGI_AGAIN;
|
|
}
|
|
}
|
|
|
|
clear2:
|
|
// clear response
|
|
SvREFCNT_dec(wsgi_req->async_result);
|
|
clear:
|
|
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
// restore main interpreter if needed
|
|
if (uwsgi.threads > 1) {
|
|
if (((PerlInterpreter **)wi->interpreter)[wsgi_req->async_id] != uperl.main[wsgi_req->async_id]) {
|
|
PERL_SET_CONTEXT(uperl.main[wsgi_req->async_id]);
|
|
}
|
|
}
|
|
else {
|
|
if (((PerlInterpreter **)wi->interpreter)[0] != uperl.main[0]) {
|
|
PERL_SET_CONTEXT(uperl.main[0]);
|
|
}
|
|
}
|
|
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static void psgi_call_cleanup_hook(SV *hook, SV *env) {
|
|
dSP;
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
XPUSHs(env);
|
|
PUTBACK;
|
|
call_sv(hook, G_DISCARD);
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("[uwsgi-perl error] %s", SvPV_nolen(ERRSV));
|
|
}
|
|
FREETMPS;
|
|
LEAVE;
|
|
}
|
|
|
|
void uwsgi_perl_after_request(struct wsgi_request *wsgi_req) {
|
|
|
|
log_request(wsgi_req);
|
|
|
|
// We may be called after an early exit in XS_coroae_accept_request,
|
|
// before the environ is set up.
|
|
if (!wsgi_req->async_environ) return;
|
|
|
|
// we need to restore the context in case of multiple interpreters
|
|
struct uwsgi_app *wi = &uwsgi_apps[wsgi_req->app_id];
|
|
if (uwsgi.threads < 2) {
|
|
if (((PerlInterpreter **)wi->interpreter)[0] != uperl.main[0]) {
|
|
PERL_SET_CONTEXT(((PerlInterpreter **)wi->interpreter)[0]);
|
|
}
|
|
}
|
|
else {
|
|
if (((PerlInterpreter **)wi->interpreter)[wsgi_req->async_id] != uperl.main[wsgi_req->async_id]) {
|
|
PERL_SET_CONTEXT(((PerlInterpreter **)wi->interpreter)[wsgi_req->async_id]);
|
|
}
|
|
}
|
|
|
|
// dereference %env
|
|
SV *env = SvRV((SV *) wsgi_req->async_environ);
|
|
|
|
// check for cleanup handlers
|
|
if (hv_exists((HV *)env, "psgix.cleanup.handlers", 22)) {
|
|
SV **cleanup_handlers = hv_fetch((HV *)env, "psgix.cleanup.handlers", 22, 0);
|
|
if (SvROK(*cleanup_handlers)) {
|
|
if (SvTYPE(SvRV(*cleanup_handlers)) == SVt_PVAV) {
|
|
I32 n = av_len((AV *)SvRV(*cleanup_handlers));
|
|
I32 i;
|
|
for(i=0;i<=n;i++) {
|
|
SV **hook = av_fetch((AV *)SvRV(*cleanup_handlers), i, 0);
|
|
psgi_call_cleanup_hook(*hook, (SV *) wsgi_req->async_environ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for psgix.harakiri
|
|
if (hv_exists((HV *)env, "psgix.harakiri.commit", 21)) {
|
|
SV **harakiri = hv_fetch((HV *)env, "psgix.harakiri.commit", 21, 0);
|
|
if (SvTRUE(*harakiri)) wsgi_req->async_plagued = 1;
|
|
}
|
|
|
|
// Free the $env hash
|
|
SvREFCNT_dec(wsgi_req->async_environ);
|
|
|
|
// async plagued could be defined in other areas...
|
|
if (wsgi_req->async_plagued) {
|
|
uwsgi_log("*** psgix.harakiri.commit requested ***\n");
|
|
// Before we call exit(0) we'll run the
|
|
// uwsgi_perl_atexit() hook which'll properly tear
|
|
// down the interpreter.
|
|
|
|
// mark the request as ended (otherwise the atexit hook will be skipped)
|
|
uwsgi.workers[uwsgi.mywid].cores[wsgi_req->async_id].in_request = 0;
|
|
goodbye_cruel_world("async plagued");
|
|
}
|
|
|
|
// now we can check for changed files
|
|
if (uperl.auto_reload) {
|
|
time_t now = uwsgi_now();
|
|
if (now - uperl.last_auto_reload > uperl.auto_reload) {
|
|
uwsgi_perl_check_auto_reload();
|
|
}
|
|
}
|
|
|
|
// restore main interpreter if needed
|
|
if (uwsgi.threads > 1) {
|
|
if (((PerlInterpreter **)wi->interpreter)[wsgi_req->async_id] != uperl.main[wsgi_req->async_id]) {
|
|
PERL_SET_CONTEXT(uperl.main[wsgi_req->async_id]);
|
|
}
|
|
}
|
|
else {
|
|
if (((PerlInterpreter **)wi->interpreter)[0] != uperl.main[0]) {
|
|
PERL_SET_CONTEXT(uperl.main[0]);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
int uwsgi_perl_magic(char *mountpoint, char *lazy) {
|
|
|
|
if (!strcmp(lazy+strlen(lazy)-5, ".psgi")) {
|
|
uperl.psgi = lazy;
|
|
return 1;
|
|
}
|
|
else if (!strcmp(lazy+strlen(lazy)-3, ".pl")) {
|
|
uperl.psgi = lazy;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// taken from Torsten Foertsch AfterFork.xs
|
|
void uwsgi_perl_post_fork() {
|
|
|
|
GV *tmpgv = gv_fetchpv("$", TRUE, SVt_PV);
|
|
if (tmpgv) {
|
|
SvREADONLY_off(GvSV(tmpgv));
|
|
sv_setiv(GvSV(tmpgv), (IV)getpid());
|
|
SvREADONLY_on(GvSV(tmpgv));
|
|
}
|
|
|
|
struct uwsgi_string_list *usl;
|
|
uwsgi_foreach(usl, uperl.exec_post_fork) {
|
|
SV *dollar_zero = get_sv("0", GV_ADD);
|
|
sv_setsv(dollar_zero, newSVpv(usl->value, usl->len));
|
|
uwsgi_perl_exec(usl->value);
|
|
}
|
|
|
|
if (uperl.postfork) {
|
|
uwsgi_perl_run_hook(uperl.postfork);
|
|
}
|
|
}
|
|
|
|
int uwsgi_perl_mount_app(char *mountpoint, char *app) {
|
|
|
|
if (uwsgi_endswith(app, ".pl") || uwsgi_endswith(app, ".psgi")) {
|
|
uwsgi.wsgi_req->appid = mountpoint;
|
|
uwsgi.wsgi_req->appid_len = strlen(mountpoint);
|
|
|
|
return init_psgi_app(uwsgi.wsgi_req, app, strlen(app), NULL);
|
|
}
|
|
return -1;
|
|
|
|
}
|
|
|
|
void uwsgi_perl_init_thread(int core_id) {
|
|
|
|
#ifdef USE_ITHREADS
|
|
PERL_SET_CONTEXT(uperl.main[core_id]);
|
|
#endif
|
|
}
|
|
|
|
void uwsgi_perl_pthread_prepare(void) {
|
|
pthread_mutex_lock(&uperl.lock_loader);
|
|
}
|
|
|
|
void uwsgi_perl_pthread_parent(void) {
|
|
pthread_mutex_unlock(&uperl.lock_loader);
|
|
}
|
|
|
|
void uwsgi_perl_pthread_child(void) {
|
|
pthread_mutex_init(&uperl.lock_loader, NULL);
|
|
}
|
|
|
|
|
|
void uwsgi_perl_enable_threads(void) {
|
|
#ifdef USE_ITHREADS
|
|
pthread_mutex_init(&uperl.lock_loader, NULL);
|
|
pthread_atfork(uwsgi_perl_pthread_prepare, uwsgi_perl_pthread_parent, uwsgi_perl_pthread_child);
|
|
#endif
|
|
}
|
|
|
|
static int uwsgi_perl_signal_handler(uint8_t sig, void *handler) {
|
|
|
|
int ret = 0;
|
|
|
|
dSP;
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
XPUSHs( sv_2mortal(newSViv(sig)));
|
|
PUTBACK;
|
|
|
|
call_sv( SvRV((SV*)handler), G_DISCARD);
|
|
|
|
SPAGAIN;
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("[uwsgi-perl error] %s", SvPV_nolen(ERRSV));
|
|
ret = -1;
|
|
}
|
|
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void uwsgi_perl_run_hook(SV *hook) {
|
|
dSP;
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
PUTBACK;
|
|
|
|
call_sv( SvRV(hook), G_DISCARD);
|
|
|
|
SPAGAIN;
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("[uwsgi-perl error] %s", SvPV_nolen(ERRSV));
|
|
return;
|
|
}
|
|
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
}
|
|
|
|
static void uwsgi_perl_atexit() {
|
|
int i;
|
|
|
|
if (uwsgi.mywid == 0) goto realstuff;
|
|
|
|
// if hijacked do not run atexit hooks -- TODO: explain why
|
|
// not.
|
|
if (uwsgi.workers[uwsgi.mywid].hijacked)
|
|
goto destroyperl;
|
|
|
|
// if busy do not run atexit hooks (as this part could be called in a signal handler
|
|
// while a subroutine is running)
|
|
if (uwsgi_worker_is_busy(uwsgi.mywid))
|
|
return;
|
|
|
|
realstuff:
|
|
|
|
if (uperl.atexit) {
|
|
uwsgi_perl_run_hook(uperl.atexit);
|
|
}
|
|
|
|
destroyperl:
|
|
|
|
// We must free our perl context(s) so any DESTROY hooks
|
|
// etc. will run.
|
|
for(i=0;i<uwsgi.threads;i++) {
|
|
PERL_SET_CONTEXT(uperl.main[i]);
|
|
|
|
// Destroy the PerlInterpreter, see "perldoc perlembed"
|
|
perl_destruct(uperl.main[i]);
|
|
perl_free(uperl.main[i]);
|
|
}
|
|
PERL_SYS_TERM();
|
|
free(uperl.main);
|
|
}
|
|
|
|
static uint64_t uwsgi_perl_rpc(void *func, uint8_t argc, char **argv, uint16_t argvs[], char **buffer) {
|
|
|
|
int i;
|
|
uint64_t ret = 0;
|
|
|
|
dSP;
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
for(i=0;i<argc;i++) {
|
|
XPUSHs( sv_2mortal(newSVpv(argv[i], argvs[i])));
|
|
}
|
|
PUTBACK;
|
|
|
|
call_sv( SvRV((SV*)func), G_SCALAR | G_EVAL);
|
|
|
|
SPAGAIN;
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("[uwsgi-perl error] %s", SvPV_nolen(ERRSV));
|
|
}
|
|
else {
|
|
STRLEN rlen;
|
|
SV *response = POPs;
|
|
char *value = SvPV(response, rlen );
|
|
if (rlen > 0) {
|
|
*buffer = uwsgi_malloc(rlen);
|
|
memcpy(*buffer, value, rlen);
|
|
ret = rlen;
|
|
}
|
|
}
|
|
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void uwsgi_perl_hijack(void) {
|
|
if (uperl.shell_oneshot && uwsgi.workers[uwsgi.mywid].hijacked_count > 0) {
|
|
uwsgi.workers[uwsgi.mywid].hijacked = 0;
|
|
return;
|
|
}
|
|
if (uperl.shell && uwsgi.mywid == 1) {
|
|
uwsgi.workers[uwsgi.mywid].hijacked = 1;
|
|
uwsgi.workers[uwsgi.mywid].hijacked_count++;
|
|
// re-map stdin to stdout and stderr if we are logging to a file
|
|
if (uwsgi.logfile) {
|
|
if (dup2(0, 1) < 0) {
|
|
uwsgi_error("dup2()");
|
|
}
|
|
if (dup2(0, 2) < 0) {
|
|
uwsgi_error("dup2()");
|
|
}
|
|
}
|
|
|
|
if (uperl.shell[0] != 0) {
|
|
perl_eval_pv(uperl.shell, 0);
|
|
}
|
|
else {
|
|
perl_eval_pv("use Devel::REPL;my $repl = Devel::REPL->new;$repl->run;", 0);
|
|
}
|
|
if (uperl.shell_oneshot) {
|
|
exit(UWSGI_DE_HIJACKED_CODE);
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
}
|
|
|
|
static void uwsgi_perl_add_item(char *key, uint16_t keylen, char *val, uint16_t vallen, void *data) {
|
|
|
|
HV *spool_dict = (HV*) data;
|
|
|
|
(void)hv_store(spool_dict, key, keylen, newSVpv(val, vallen), 0);
|
|
}
|
|
|
|
|
|
static int uwsgi_perl_spooler(char *filename, char *buf, uint16_t len, char *body, size_t body_len) {
|
|
|
|
int ret = -1;
|
|
|
|
if (!uperl.spooler) return 0;
|
|
|
|
dSP;
|
|
ENTER;
|
|
SAVETMPS;
|
|
PUSHMARK(SP);
|
|
|
|
HV *spool_dict = newHV();
|
|
|
|
if (uwsgi_hooked_parse(buf, len, uwsgi_perl_add_item, (void *) spool_dict)) {
|
|
return 0;
|
|
}
|
|
|
|
(void) hv_store(spool_dict, "spooler_task_name", 18, newSVpv(filename, 0), 0);
|
|
|
|
if (body && body_len > 0) {
|
|
(void) hv_store(spool_dict, "body", 4, newSVpv(body, body_len), 0);
|
|
}
|
|
|
|
XPUSHs( sv_2mortal((SV*)newRV_noinc((SV*)spool_dict)) );
|
|
PUTBACK;
|
|
|
|
call_sv( SvRV((SV*)uperl.spooler), G_SCALAR|G_EVAL);
|
|
|
|
SPAGAIN;
|
|
if(SvTRUE(ERRSV)) {
|
|
uwsgi_log("[uwsgi-spooler-perl error] %s", SvPV_nolen(ERRSV));
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = POPi;
|
|
}
|
|
|
|
PUTBACK;
|
|
FREETMPS;
|
|
LEAVE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int uwsgi_perl_hook_perl(char *arg) {
|
|
SV *ret = perl_eval_pv(arg, 0);
|
|
if (!ret) return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void uwsgi_perl_register_features() {
|
|
uwsgi_register_hook("perl", uwsgi_perl_hook_perl);
|
|
}
|
|
|
|
struct uwsgi_plugin psgi_plugin = {
|
|
|
|
.name = "psgi",
|
|
.modifier1 = 5,
|
|
.init = uwsgi_perl_init,
|
|
.options = uwsgi_perl_options,
|
|
|
|
.preinit_apps = uwsgi_psgi_preinit_apps,
|
|
.init_apps = uwsgi_psgi_app,
|
|
.mount_app = uwsgi_perl_mount_app,
|
|
|
|
.init_thread = uwsgi_perl_init_thread,
|
|
.signal_handler = uwsgi_perl_signal_handler,
|
|
.rpc = uwsgi_perl_rpc,
|
|
|
|
.mule = uwsgi_perl_mule,
|
|
|
|
.hijack_worker = uwsgi_perl_hijack,
|
|
|
|
.post_fork = uwsgi_perl_post_fork,
|
|
.request = uwsgi_perl_request,
|
|
.after_request = uwsgi_perl_after_request,
|
|
.enable_threads = uwsgi_perl_enable_threads,
|
|
|
|
.atexit = uwsgi_perl_atexit,
|
|
|
|
.magic = uwsgi_perl_magic,
|
|
|
|
.spooler = uwsgi_perl_spooler,
|
|
.on_load = uwsgi_perl_register_features,
|
|
};
|