├── .gitignore ├── t ├── apps │ ├── 00.plack_test_suite.psgi │ ├── 01.basic.psgi │ ├── frameworks │ │ ├── dancer.psgi │ │ └── mojolicious-lite.psgi │ └── 02.io_handle.psgi ├── 01.basic.t ├── frameworks │ ├── dancer.t │ └── mojolicious-lite.t ├── 00.plack_test_suite.t └── 02.io_handle.t ├── src ├── perlxsi.c ├── ngx_http_psgi_perl.h ├── ngx_http_psgi_error_stream.h ├── ngx_http_psgi_input_stream.h ├── ngx_http_psgi_response.h ├── ngx_http_psgi_module.h ├── ngx_http_psgi_error_stream.c ├── ngx_http_psgi_input_stream.c ├── ngx_http_psgi_module.c ├── ngx_http_psgi_response.c └── ngx_http_psgi_perl.c ├── eg ├── nginx.conf └── helloworld.psgi ├── config ├── util ├── make_coverage.sh └── ngx_coverage.pl ├── README └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | *~ 4 | log/* 5 | tmp/* 6 | nginx-* 7 | -------------------------------------------------------------------------------- /t/apps/00.plack_test_suite.psgi: -------------------------------------------------------------------------------- 1 | use Plack::Test::Suite; 2 | 3 | Plack::Test::Suite->test_app_handler; 4 | 5 | -------------------------------------------------------------------------------- /t/apps/01.basic.psgi: -------------------------------------------------------------------------------- 1 | #/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | my $app = sub { 6 | return [201, [foo => "bar"], ["Hello ", "World", "!"]]; 7 | }; 8 | 9 | $app; 10 | -------------------------------------------------------------------------------- /t/apps/frameworks/dancer.psgi: -------------------------------------------------------------------------------- 1 | use Dancer; 2 | 3 | set apphandler => 'PSGI'; 4 | 5 | get '/hello/:name' => sub { 6 | header('Foo' => 'Bar'); 7 | header('Hello' => 'World!'); 8 | return "Why, hello there " . params->{name}; 9 | }; 10 | 11 | dance; 12 | -------------------------------------------------------------------------------- /src/perlxsi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | EXTERN_C void xs_init (pTHX); 5 | 6 | EXTERN_C void boot_DynaLoader (pTHX_ CV* cv); 7 | 8 | EXTERN_C void 9 | xs_init(pTHX) 10 | { 11 | char *file = __FILE__; 12 | dXSUB_SYS; 13 | 14 | newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); 15 | } 16 | -------------------------------------------------------------------------------- /t/apps/frameworks/mojolicious-lite.psgi: -------------------------------------------------------------------------------- 1 | use Mojolicious::Lite; 2 | 3 | # Blocking 4 | get '/hello/:name' => sub { 5 | my $self = shift; 6 | 7 | $self->res->headers->add(Foo => 'Bar'); 8 | $self->res->headers->add(Hello => 'World!'); 9 | 10 | $self->render(data => "Why, hello there " . $self->stash('name')); 11 | }; 12 | 13 | app->start('psgi'); 14 | -------------------------------------------------------------------------------- /t/01.basic.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | 3 | no_root_location(); 4 | 5 | plan tests => 3; 6 | 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === hello world app 12 | --- config 13 | location / { 14 | psgi t/apps/01.basic.psgi; 15 | } 16 | --- request: GET / 17 | --- error_code: 201 18 | --- response_headers 19 | foo: bar 20 | --- response_body: Hello World! 21 | -------------------------------------------------------------------------------- /eg/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 4; 2 | 3 | events { 4 | worker_connections 768; 5 | } 6 | 7 | http { 8 | server { 9 | 10 | listen 127.0.0.1:3000; 11 | server_name localhost; 12 | 13 | location / { 14 | # Install psgi app 15 | # to server requests at certain address 16 | psgi /your/path/test.psgi; 17 | 18 | # Write some debugging logs 19 | error_log "/your/path/log/error.log" debug; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /t/frameworks/dancer.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | 3 | unless ($ENV{WITH_DANCER}) { 4 | eval { require Dancer } 5 | or plan skip_all => 6 | "You need Dancer framework installed to run this test"; 7 | 8 | diag "using Dancer v$Dancer::VERSION"; 9 | } 10 | 11 | no_root_location(); 12 | 13 | plan tests => 4 * repeat_each() * blocks(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 2: Mojolicious::Lite: get title 20 | --- config 21 | location / { 22 | psgi t/apps/frameworks/dancer.psgi; 23 | } 24 | --- request: GET /hello/nginx 25 | --- error_code: 200 26 | --- response_body: Why, hello there nginx 27 | --- response_headers 28 | Foo: Bar 29 | Hello: World! 30 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_psgi_module 2 | 3 | USE_PERL=YES 4 | 5 | HTTP_MODULES="$HTTP_MODULES ngx_http_psgi_module" 6 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_psgi_module.c $ngx_addon_dir/src/ngx_http_psgi_perl.c $ngx_addon_dir/src/ngx_http_psgi_response.c $ngx_addon_dir/src/ngx_http_psgi_error_stream.c $ngx_addon_dir/src/ngx_http_psgi_input_stream.c $ngx_addon_dir/src/perlxsi.c" 7 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ngx_http_psgi_perl.h $ngx_addon_dir/src/ngx_http_psgi_input_stream.h $ngx_addon_dir/src/ngx_http_psgi_error_stream.h $ngx_addon_dir/src/ngx_http_psgi_response.h $ngx_addon_dir/src/ngx_http_psgi_module.h" 8 | 9 | . auto/lib/perl/conf 10 | CFLAGS="$CFLAGS $NGX_PERL_CFLAGS" 11 | -------------------------------------------------------------------------------- /t/frameworks/mojolicious-lite.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | 3 | unless ($ENV{WITH_MOJOLICIOUS}) { 4 | eval { require Mojolicious } 5 | or plan skip_all => 6 | "You need Mojolicious framework installed to run this test"; 7 | 8 | diag "using Mojolicious v$Mojolicious::VERSION"; 9 | } 10 | 11 | no_root_location(); 12 | 13 | plan tests => 4 * repeat_each() * blocks(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === Mojolicious::Lite: sync request title 20 | --- config 21 | location / { 22 | psgi t/apps/frameworks/mojolicious-lite.psgi; 23 | } 24 | --- request: GET /hello/nginx 25 | --- error_code: 200 26 | --- response_headers 27 | Foo: Bar 28 | Hello: World! 29 | --- response_body: Why, hello there nginx 30 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_perl.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_PSGI_PERL_H_INCLUDED_ 2 | #define _NGX_HTTP_PSGI_PERL_H_INCLUDED_ 3 | #include 4 | #include 5 | #include "ngx_http_psgi_module.h" 6 | 7 | SV *ngx_http_psgi_create_env(pTHX_ ngx_http_request_t *r, char *app); 8 | 9 | ngx_int_t ngx_http_psgi_perl_init_worker(ngx_cycle_t *cycle); 10 | ngx_int_t ngx_http_psgi_init_app(pTHX_ ngx_http_psgi_loc_conf_t *psgilcf, ngx_log_t *log); 11 | 12 | PerlInterpreter *ngx_http_psgi_create_interpreter(ngx_conf_t *cf); 13 | 14 | ngx_int_t ngx_http_psgi_perl_handler(ngx_http_request_t *r, ngx_http_psgi_loc_conf_t *psgilcf, void *interpreter); 15 | 16 | void ngx_http_psgi_perl_exit(ngx_cycle_t *cycle); 17 | 18 | ngx_int_t ngx_http_psgi_perl_call_psgi_callback(ngx_http_request_t *r); 19 | #endif 20 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_error_stream.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_PERLIO_ERROR 2 | #define NGX_PERLIO_ERROR 3 | #include /* from the Perl distribution */ 4 | #include /* from the Perl distribution */ 5 | #include "perliol.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | struct _PerlIO base; 13 | ngx_log_t *log; 14 | } PerlIONginxError; 15 | 16 | IV 17 | PerlIONginxError_pushed(pTHX_ PerlIO * f, const char *mode, SV * arg, 18 | PerlIO_funcs * tab); 19 | 20 | PerlIO * 21 | PerlIONginxError_open(pTHX_ PerlIO_funcs * self, PerlIO_list_t * layers, IV n, 22 | const char *mode, int fd, int imode, int perm, 23 | PerlIO * f, int narg, SV ** args); 24 | 25 | 26 | PerlIO_funcs PerlIO_nginx_error; 27 | 28 | SV *PerlIONginxError_newhandle(pTHX_ ngx_http_request_t *r); 29 | 30 | #endif 31 | 32 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_input_stream.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_PERLIO_INPUT 2 | #define NGX_PERLIO_INPUT 3 | #include /* from the Perl distribution */ 4 | #include /* from the Perl distribution */ 5 | #include "perliol.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | struct _PerlIO base; 13 | ngx_http_request_t *r; 14 | off_t pos; 15 | off_t left; 16 | bool body_ready; 17 | } PerlIONginxInput; 18 | 19 | IV 20 | PerlIONginxInput_pushed(pTHX_ PerlIO * f, const char *mode, SV * arg, 21 | PerlIO_funcs * tab); 22 | 23 | PerlIO * 24 | PerlIONginxInput_open(pTHX_ PerlIO_funcs * self, PerlIO_list_t * layers, IV n, 25 | const char *mode, int fd, int imode, int perm, 26 | PerlIO * f, int narg, SV ** args); 27 | 28 | PerlIO_funcs PerlIO_nginx_input; 29 | 30 | SV *PerlIONginxInput_newhandle(pTHX_ ngx_http_request_t *r); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_response.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_PSGI_RESPONSE_H_INCLUDED_ 2 | #define _NGX_HTTP_PSGI_RESPONSE_H_INCLUDED_ 3 | #include 4 | #include 5 | #include "ngx_http_psgi_module.h" 6 | 7 | ngx_int_t ngx_http_psgi_process_response(pTHX_ ngx_http_request_t *r, SV *response, PerlInterpreter *perl); 8 | ngx_int_t ngx_http_psgi_process_array_response(pTHX_ ngx_http_request_t *r, SV *response); 9 | ngx_int_t ngx_http_psgi_process_headers(pTHX_ ngx_http_request_t *r, SV *headers, SV *status); 10 | ngx_int_t ngx_http_psgi_process_body(pTHX_ ngx_http_request_t *r, SV *body); 11 | ngx_int_t ngx_http_psgi_process_body_array(pTHX_ ngx_http_request_t *r, AV *body); 12 | ngx_int_t ngx_http_psgi_process_body_glob(pTHX_ ngx_http_request_t *r, SV *body); 13 | 14 | ngx_int_t chain_buffer(ngx_http_request_t *r, u_char *p, STRLEN len, ngx_chain_t **first, ngx_chain_t **last); 15 | ngx_int_t ngx_sv2str(ngx_http_request_t *r, ngx_str_t *dst, u_char* src, int len); 16 | 17 | #endif 18 | 19 | -------------------------------------------------------------------------------- /t/apps/02.io_handle.psgi: -------------------------------------------------------------------------------- 1 | #/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | { 5 | package IO::Handle::OK; 6 | 7 | sub new { 8 | my $class = shift; 9 | return bless {lines => 10, @_}, $class; 10 | } 11 | sub close { return 1 } 12 | 13 | sub getline { 14 | my $self = shift; 15 | return $self->{lines}-- ? "Line by " . ref($self) . "\n" : undef; 16 | } 17 | }; 18 | { 19 | package IO::Handle::ErrorClose; 20 | use base 'IO::Handle::OK'; 21 | sub close { die ref(shift) . " died in close()" } 22 | }; 23 | { 24 | package IO::Handle::ErrorGetline; 25 | use base 'IO::Handle::OK'; 26 | 27 | sub getline { 28 | if ($_[0]->{lines} < 5) { 29 | die ref(shift) . " died in getline()"; 30 | } 31 | shift->SUPER::getline(@_); 32 | } 33 | }; 34 | 35 | my $app = sub { 36 | my $env = shift; 37 | 38 | my $headers = ['test-header' => "test_val"]; 39 | 40 | my $body; 41 | if ($env->{REQUEST_URI} eq '/getline_exception') { 42 | $body = 'IO::Handle::ErrorGetline'; 43 | } 44 | elsif ($env->{REQUEST_URI} eq '/close_exception') { 45 | $body = 'IO::Handle::ErrorClose'; 46 | } 47 | else { 48 | $body = 'IO::Handle::OK'; 49 | } 50 | 51 | return [200, $headers, $body->new]; 52 | }; 53 | 54 | $app; 55 | -------------------------------------------------------------------------------- /t/00.plack_test_suite.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::Base -Base; 5 | use Test::More; 6 | 7 | use Test::Nginx::Util qw( 8 | run_tests 9 | $ServerPortForClient 10 | $RunTestHelper 11 | no_shuffle 12 | master_on 13 | master_off 14 | no_root_location 15 | get_nginx_version 16 | trim 17 | ); 18 | 19 | eval { require Plack::Test::Suite } 20 | or plan skip_all => "Plack::Test::Suite required for this test"; 21 | 22 | $RunTestHelper = sub ($$) { 23 | my ($block, $dry_run) = @_; 24 | 25 | diag $block->name; 26 | 27 | my $mp = lc(trim($block->master_process || 'off')); 28 | 29 | if ($mp eq 'off') { master_off() } 30 | elsif ($mp eq 'on') { master_on() } 31 | else { BAIL_OUT("Unsupported value for master_process: '$mp'") } 32 | 33 | if (my $pid = fork) { 34 | waitpid $pid, 0; 35 | if ($?) { 36 | BAIL_OUT("Plack::Test::Suite exited with " . ($? >> 8)); 37 | } 38 | } 39 | else { 40 | $Test::Nginx::Util::InSubprocess = 1; 41 | Plack::Test::Suite->run_server_tests(sub { }, $ServerPortForClient); 42 | exit; 43 | } 44 | }; 45 | 46 | get_nginx_version(); 47 | 48 | diag "Testing nginx v$Test::Nginx::Util::NginxRawVersion"; 49 | 50 | no_root_location(); 51 | 52 | # Order matters here: once you set master_off, you can not switch back 53 | no_shuffle(); 54 | 55 | run_tests(); 56 | done_testing(); 57 | 58 | __DATA__ 59 | 60 | === Plack::Test::Suite with master process 61 | --- config 62 | location / { 63 | psgi t/apps/00.plack_test_suite.psgi; 64 | } 65 | --- master_process: on 66 | 67 | === Plack::Test::Suite without master process 68 | --- config 69 | location / { 70 | psgi t/apps/00.plack_test_suite.psgi; 71 | } 72 | --- master_process: off 73 | -------------------------------------------------------------------------------- /t/02.io_handle.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | 3 | no_root_location(); 4 | 5 | plan tests => 3 * repeat_each() * blocks(); 6 | 7 | run_tests(); 8 | 9 | __DATA__ 10 | === IO::Handle-like object represents body 11 | --- config 12 | location / { 13 | psgi t/apps/02.io_handle.psgi; 14 | } 15 | --- request: GET / 16 | --- error_code: 200 17 | --- response_headers 18 | test_header: test_val 19 | --- response_body 20 | Line by IO::Handle::OK 21 | Line by IO::Handle::OK 22 | Line by IO::Handle::OK 23 | Line by IO::Handle::OK 24 | Line by IO::Handle::OK 25 | Line by IO::Handle::OK 26 | Line by IO::Handle::OK 27 | Line by IO::Handle::OK 28 | Line by IO::Handle::OK 29 | Line by IO::Handle::OK 30 | 31 | === IO::Handle-like object throws and error in getline() 32 | --- config 33 | location / { 34 | psgi t/apps/02.io_handle.psgi; 35 | } 36 | --- request: GET /getline_exception 37 | --- error_code: 200 38 | --- response_headers 39 | test-header: test_val 40 | --- response_body 41 | Line by IO::Handle::ErrorGetline 42 | Line by IO::Handle::ErrorGetline 43 | Line by IO::Handle::ErrorGetline 44 | Line by IO::Handle::ErrorGetline 45 | Line by IO::Handle::ErrorGetline 46 | Line by IO::Handle::ErrorGetline 47 | 48 | === IO::Handle-like object throws and error in close() 49 | --- config 50 | location / { 51 | psgi t/apps/02.io_handle.psgi; 52 | } 53 | --- request: GET /close_exception 54 | --- error_code: 200 55 | --- response_headers 56 | test-header: test_val 57 | --- response_body 58 | Line by IO::Handle::ErrorClose 59 | Line by IO::Handle::ErrorClose 60 | Line by IO::Handle::ErrorClose 61 | Line by IO::Handle::ErrorClose 62 | Line by IO::Handle::ErrorClose 63 | Line by IO::Handle::ErrorClose 64 | Line by IO::Handle::ErrorClose 65 | Line by IO::Handle::ErrorClose 66 | Line by IO::Handle::ErrorClose 67 | Line by IO::Handle::ErrorClose 68 | -------------------------------------------------------------------------------- /util/make_coverage.sh: -------------------------------------------------------------------------------- 1 | NGX_DIR=$1; 2 | NGX_OBJDIR=${NGX_DIR}/objs; 3 | NGX_BIN=${NGX_OBJDIR}/nginx; 4 | NGX_MAKEFILE=${NGX_OBJDIR}/Makefile 5 | NGX_COVER_MAKEFILE=${NGX_MAKEFILE}.cover 6 | 7 | if [ ! -d "$NGX_DIR" ]; then 8 | echo "No such dir: ${NGX_DIR}"; 9 | exit 255; 10 | fi 11 | 12 | if [ ! -f $NGX_MAKEFILE ]; then 13 | echo "No makefile '${NGX_MAKEFILE}'. Forgot to ./configure ?"; 14 | exit 255; 15 | fi 16 | 17 | if ! grep -- '-lgcov' ${NGX_MAKEFILE}; then 18 | echo "Makefile '${NGX_MAKEFILE}' is not configured with '--with-ld-opt=-lgcov'" ; 19 | echo "Please reconfigure."; 20 | exit 255; 21 | fi 22 | 23 | if ! which cover; then 24 | echo "Please install Devel::Cover:"; 25 | echo " curl -L http://cpanmin.us | perl - Devel::Cover\n"; 26 | exit 255; 27 | fi 28 | 29 | echo "Setting up makefile ${NGX_COVER_MAKEFILE}" 30 | cp ${NGX_MAKEFILE} ${NGX_COVER_MAKEFILE}; 31 | echo >> ${NGX_COVER_MAKEFILE}; 32 | echo "cover:" >> ${NGX_COVER_MAKEFILE}; 33 | find src -name \*.c | while read file; do \ 34 | oname=$( basename $file | sed -e 's/.c$/.o/' ); 35 | absname=$( readlink -f $file ); 36 | echo " \$(CC) -c \$(CFLAGS) -fprofile-arcs -ftest-coverage \$(ALL_INCS) -o objs/addon/src/$oname $absname" >> ${NGX_MAKEFILE}.cover; 37 | done 38 | 39 | find . -name \*.gcov -delete; 40 | find ${NGX_DIR} -name \*.gcda -name \*.gcno -delete; 41 | rm ${NGX_BIN} 42 | rm -r cover_db; 43 | 44 | make -C ${NGX_DIR} -f ${NGX_COVER_MAKEFILE} cover || exit $? 45 | make -C ${NGX_DIR} -f ${NGX_COVER_MAKEFILE} || exit $? 46 | 47 | PATH=$NGX_OBJDIR:$PATH prove -mr 48 | 49 | find src -name \*.c | while read cfile; 50 | do 51 | gcno=${NGX_OBJDIR}/addon/$( echo $cfile | sed -e 's/\.c$/.gcno/' ); 52 | echo "file ${gcno}" 53 | if [ -f $gcno ]; then 54 | gcov -o $(dirname $gcno) $gcno; 55 | fi 56 | done 57 | find . -name \*.gcov -print0 | xargs -0 gcov2perl 58 | cover 59 | rm *.gcov 60 | rm ${NGX_COVER_MAKEFILE} 61 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NAME 2 | ngx_http_psgi_module - proof of concept PSGI implementation for nginx server 3 | 4 | WARNING 5 | This software marked as 'proof of concept' and is under the heavy development. 6 | Anything you can or can not imagine may happen if you use it, 7 | even T. Rex may appear from your screen and eat you. 8 | 9 | YOU HAVE BEEN WARNED. 10 | 11 | DESCRIPTION 12 | ngx_http_psgi provides PSGI support for nginx HTTP server 13 | 14 | DEMO 15 | git clone git://github.com/yko/ngx_mod_psgi.git 16 | cd ngx_mod_psgi 17 | make 18 | make demo 19 | 20 | This builds nginx with psgi module and run demo PSGI application 21 | at address http://127.0.0.1:3000/ 22 | After nginx started, demo sends POST request to server 23 | and displays output. 24 | 25 | Feel free to replace demo application and/or nginx.conf by your one, 26 | re-run `make demo` and track down access and error logs 27 | to see what's happening on server side. 28 | 29 | You also may want to run tests from Plack::Test::Suite: 30 | 31 | make test 32 | 33 | PRODUCTION 34 | This module is not production-ready yet. 35 | However, if you want to try it in production, feel free to use 36 | 'eg/nginx.conf' as an example. Also make sure you configure your nginx 37 | with 'add-module' option: 38 | 39 | ./configure [YOUR OPTIONS] --add-module=/path/to/ngx_http_psgi 40 | make 41 | 42 | DEPENDENCIES 43 | You may need to install some dev libraries, like: 44 | 45 | libperl-dev 46 | 47 | AUTHOR 48 | Yaroslav Korshak 49 | 50 | COPYRIGHT 51 | Yaroslav Korshak, 2011-2013 52 | 53 | LICENSE 54 | This software is licensed under the same terms as Perl itself. 55 | 56 | SEE ALSO 57 | nginx 58 | 59 | PSGI spec 60 | 61 | yappo's psgi patch 62 | 63 | ngx_http_perl_module 64 | 65 | perlembed 66 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_module.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_PSGI_MODULE_H_INCLUDED_ 2 | #define _NGX_HTTP_PSGI_MODULE_H_INCLUDED_ 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | typedef ngx_http_request_t *nginx; 14 | 15 | extern ngx_module_t ngx_http_psgi_module; 16 | 17 | typedef struct { 18 | PerlInterpreter *perl; 19 | } ngx_http_psgi_main_conf_t; 20 | 21 | typedef struct { 22 | SV *sub; 23 | char *app; 24 | PerlInterpreter *perl; 25 | } ngx_http_psgi_loc_conf_t; 26 | 27 | typedef struct { 28 | SV *app; 29 | SV *env; 30 | SV *input; 31 | SV *errors; 32 | SV *callback; 33 | SV *responder; 34 | SV *writer; 35 | PerlInterpreter *perl; 36 | } ngx_http_psgi_ctx_t; 37 | 38 | void *ngx_http_psgi_create_main_conf(ngx_conf_t *cf); 39 | char *ngx_http_psgi_init_main_conf(ngx_conf_t *cf, void *conf); 40 | void *ngx_http_psgi_create_loc_conf(ngx_conf_t *cf); 41 | char *ngx_http_psgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 42 | char *ngx_http_psgi(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 43 | char *ngx_http_psgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 44 | ngx_int_t ngx_http_psgi_handler(ngx_http_request_t *r); 45 | ngx_int_t ngx_http_psgi_init_worker(ngx_cycle_t *cycle); 46 | char *ngx_http_psgi_init_interpreter(ngx_conf_t *cf, ngx_http_psgi_main_conf_t *psgimcf); 47 | char *ngx_http_psgi(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 48 | 49 | void ngx_http_psgi_handler_with_body(ngx_http_request_t *r); 50 | void ngx_http_psgi_exit(ngx_cycle_t *cycle); 51 | 52 | 53 | /* 54 | * workaround for "unused variable `Perl___notused'" warning 55 | * when building with perl 5.6.1 56 | */ 57 | #ifndef PERL_IMPLICIT_CONTEXT 58 | #undef dTHXa 59 | #define dTHXa(a) 60 | #endif 61 | 62 | #endif /* _NGX_HTTP_PERL_MODULE_H_INCLUDED_ */ 63 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_error_stream.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_psgi_error_stream.h" 2 | 3 | PerlIO * 4 | PerlIONginxError_open(pTHX_ PerlIO_funcs * self, PerlIO_list_t * layers, IV n, 5 | const char *mode, int fd, int imode, int perm, 6 | PerlIO * f, int narg, SV ** args) 7 | { 8 | die("NginxError layer can not be assigned from Perl land"); 9 | 10 | return NULL; 11 | } 12 | 13 | IV 14 | PerlIONginxError_fileno(pTHX_ PerlIO * f) 15 | { 16 | PERL_UNUSED_ARG(f); 17 | return -1; // I'm kinda socket. 18 | } 19 | 20 | SSize_t 21 | PerlIONginxError_write(pTHX_ PerlIO * f, const void *vbuf, Size_t count) 22 | { 23 | PerlIONginxError *st = PerlIOSelf(f, PerlIONginxError); 24 | 25 | // I need to quote '%' characters or disable printf in logging somehow 26 | ngx_log_error(NGX_LOG_ERR, st->log, 0, 27 | "%s", (const char *)vbuf); 28 | 29 | return count; 30 | } 31 | 32 | PERLIO_FUNCS_DECL(PerlIO_nginx_error) = { 33 | sizeof(PerlIO_funcs), 34 | "ngx_error", 35 | sizeof(PerlIONginxError), 36 | PERLIO_K_RAW, 37 | NULL, // PerlIONginxError_pushed, 38 | NULL, // PerlIONginxError_popped, 39 | PerlIONginxError_open, 40 | NULL, //PerlIOBase_binmode, 41 | NULL, //PerlIONginxError_arg, 42 | PerlIONginxError_fileno, 43 | NULL, //PerlIONginxError_dup, 44 | NULL, //PerlIONginxError_read, 45 | NULL, /* unread */ 46 | PerlIONginxError_write, 47 | NULL, //PerlIONginxError_seek, 48 | NULL, //PerlIONginxError_tell, 49 | NULL, //PerlIONginxError_close, 50 | NULL, //PerlIONginxError_flush, 51 | NULL, //PerlIONginxError_fill, 52 | NULL, //PerlIOBase_eof, 53 | NULL, //PerlIOBase_error, 54 | NULL, //PerlIOBase_clearerr, 55 | NULL, //PerlIOBase_setlinebuf, 56 | NULL, //PerlIONginxError_get_base, 57 | NULL, //PerlIONginxError_bufsiz, 58 | NULL, //PerlIONginxError_get_ptr, 59 | NULL, //PerlIONginxError_get_cnt, 60 | NULL, //PerlIONginxError_set_ptrcnt, 61 | }; 62 | 63 | SV *PerlIONginxError_newhandle(pTHX_ ngx_http_request_t *r) 64 | { 65 | GV *gv = (GV*)SvREFCNT_inc(newGVgen("Nginx::PSGI::Error")); 66 | if (!gv) 67 | return &PL_sv_undef; 68 | 69 | (void) hv_delete(GvSTASH(gv), GvNAME(gv), GvNAMELEN(gv), G_DISCARD); 70 | PerlIO *f = PerlIO_allocate(aTHX); 71 | 72 | if (!(f = PerlIO_push(aTHX_ f, PERLIO_FUNCS_CAST(&PerlIO_nginx_error), ">", NULL)) ) { 73 | return &PL_sv_undef; 74 | } 75 | 76 | if (!do_open(gv, "+>&", 3, FALSE, O_WRONLY, 0, f)) { 77 | return &PL_sv_undef; 78 | } 79 | 80 | PerlIONginxError *st = PerlIOSelf(f, PerlIONginxError); 81 | st->log = r->connection->log; 82 | 83 | return newRV_noinc((SV*)gv); 84 | } 85 | -------------------------------------------------------------------------------- /eg/helloworld.psgi: -------------------------------------------------------------------------------- 1 | #/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | use Data::Dumper; 6 | 7 | my $app = sub { 8 | my $env = shift; 9 | 10 | my $errors = $env->{'psgi.errors'}; 11 | my $input = $env->{'psgi.input'}; 12 | 13 | # Write some warnings to nginx log 14 | $errors->print("PSGI Hello World loaded"); 15 | 16 | # Read whole body if any 17 | local $/ = undef; 18 | my $request_body = <$input>; 19 | 20 | return psgi_simple_response($env, $request_body); 21 | # return psgi_callback_using_responder($env, $request_body); 22 | # return psgi_callback_using_writer($env, $request_body); 23 | }; 24 | 25 | # Applications MUST return a response as either a three element array reference, 26 | # or a code reference for a delayed/streaming response. 27 | # PSGI 1.09_3 28 | 29 | sub psgi_simple_response { 30 | my ($env, $request_body) = @_; 31 | return [ 32 | 200, 33 | ['Content-Type' => 'text/plain', 'X-Header' => 'X-Header content'], 34 | [ "Hello World from Perl $]\n", 35 | "\n-- request body start\n", 36 | $request_body, 37 | "\n-- request body end\n", 38 | "\nPSGI ENV: ", 39 | Dumper($env) 40 | ] 41 | ]; 42 | } 43 | 44 | # To enable a delayed response, the application SHOULD return a callback as its response. 45 | # This callback will be called with another subroutine reference [responder] as its only argument. 46 | # The responder should in turn be called with the standard three element array reference response. 47 | # PSGI 1.09_3 48 | 49 | sub psgi_callback_using_responder { 50 | my ($env, $request_body) = @_; 51 | return sub { 52 | my $responder = shift; 53 | $responder->( 54 | [ 200, 55 | [ 'Content-Type' => 'text/plain', 56 | 'X-Responder-Obeject' => $responder 57 | ], 58 | ["Sample response line\n" x 100] 59 | ] 60 | ); 61 | }; 62 | } 63 | 64 | # An application MAY omit the third element (the body) when calling the responder. 65 | # If the body is omitted, 66 | # the responder MUST return yet another object 67 | # which implements write and close methods. 68 | # PSGI 1.09_3 69 | 70 | sub psgi_callback_using_writer { 71 | my ($env, $request_body) = @_; 72 | return sub { 73 | my $responder = shift; 74 | my $writer = $responder->( 75 | [ 200, 76 | [ 'Content-Type' => 'text/plain', 77 | 'X-Responder-Obeject' => $responder 78 | ] 79 | ] 80 | ); 81 | 82 | for (1..1000) { 83 | $writer->write("I Want Cookies\n"); 84 | } 85 | 86 | $writer->close; 87 | }; 88 | } 89 | 90 | $app; 91 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NGX_VERSION = 1.0.4 2 | HOME = ${CURDIR} 3 | NGX_DIST = nginx-${NGX_VERSION}.tar.gz 4 | NGX_DIR = ${CURDIR}/nginx-${NGX_VERSION} 5 | NGX_MAKE = ${NGX_DIR}/Makefile 6 | TMP_CONF = ${HOME}/tmp/nginx.conf 7 | TMP_CONF_TEMPLATE = ${HOME}/eg/nginx.conf 8 | PIDFILE = ${HOME}/tmp/nginx.pid 9 | NGX_OBJDIR = ${NGX_DIR}/objs 10 | NGX_TMPROOT = ${HOME}/tmpserverroot 11 | NGX_BIN = ${NGX_OBJDIR}/nginx 12 | 13 | default: build 14 | 15 | build: ${NGX_MAKE} configs 16 | @make -C ${NGX_DIR} 17 | 18 | test: kill ${NGX_BIN} clean_logs configs 19 | @PATH=${NGX_DIR}/objs:$$PATH \ 20 | TEST_NGINX_PORT=3000 \ 21 | TEST_NGINX_SERVROOT="${NGX_TMPROOT}" \ 22 | prove -lr ${FLAGS} ${TESTS} 23 | 24 | demo: kill clean_logs build configs 25 | clear 26 | @${NGX_BIN} 27 | @echo 28 | @echo Sending simple request with body: \"Client request body\": 29 | 30 | curl http://127.0.0.1:3000/ -d "Client request body" -D - 31 | @echo 32 | @echo 33 | @echo What to do now? 34 | @echo 35 | @echo 36 | @echo You may want to see logs: 37 | @echo 38 | @echo ${HOME}/log/access.log 39 | @echo ${HOME}/log/error.log 40 | @echo 41 | @echo You also may want to edit psgi app or nginx.conf and run \`make demo\` again: 42 | @echo 43 | @echo ${HOME}/eg/helloworld.psgi 44 | @echo ${HOME}/tmp/nginx.conf 45 | @echo 46 | 47 | 48 | realclean: clean 49 | @if [ -f "${NGX_DIR}/Makefile" ]; then \ 50 | make -C "${NGX_DIR}" clean > /dev/null; \ 51 | fi 52 | @rm -rf "${NGX_DIR}" "${NGX_DIST}" 2>&1 || echo -n '' # Looks like I really need -f here 53 | @rm -r ${HOME}/tmp 2>/dev/null || echo -n '' 54 | @if [ -d "${NGX_TMPROOT}" ]; then rm -r "${NGX_TMPROOT}"; fi 55 | @rm -r ${HOME}/log 2>/dev/null || echo -n '' 56 | 57 | clean: kill clean_logs 58 | @rm -r ${HOME}/tmp/* 2>/dev/null || echo -n '' 59 | @if [ -f ${NGX_MAKE} ]; then make -C ${NGX_DIR} clean; fi 60 | @rm -f ${HOME}/*.gcov 61 | @if [ -d ${HOME}/cover_db ]; then rm -r ${HOME}/cover_db; fi 62 | 63 | clean_logs: 64 | @rm ${HOME}/log/* 2>/dev/null || echo -n '' 65 | 66 | kill: 67 | @if [ -f ${PIDFILE} ]; then \ 68 | kill -2 `cat ${PIDFILE}`; \ 69 | fi; 70 | 71 | dirs: 72 | @mkdir -p ${HOME}/tmp 73 | @mkdir -p ${HOME}/tmp/body 74 | @mkdir -p ${HOME}/log 75 | 76 | ${NGX_BIN}: 77 | @make build 78 | 79 | configs: dirs ${TMP_CONF} 80 | 81 | ${TMP_CONF}: 82 | cp "${TMP_CONF_TEMPLATE}" "${TMP_CONF}" 83 | @perl -pi -e 's#^(\s*error_log\s+).*#\1"${HOME}/log/error.log" debug;#;' "${TMP_CONF}" 84 | @perl -pi -e 's#^(\s*psgi)\s+.*#\1 "${HOME}/eg/helloworld.psgi";#;' "${TMP_CONF}" 85 | 86 | ${NGX_MAKE}: ${NGX_DIR} 87 | @cd ${NGX_DIR}; ./configure \ 88 | ${NGX_CONF_OPTS} \ 89 | --without-http_charset_module \ 90 | --without-http_gzip_module \ 91 | --without-http_ssi_module \ 92 | --without-http_userid_module \ 93 | --without-http_access_module \ 94 | --without-http_auth_basic_module \ 95 | --without-http_autoindex_module \ 96 | --without-http_geo_module \ 97 | --without-http_map_module \ 98 | --without-http_split_clients_module \ 99 | --without-http_referer_module \ 100 | --without-http_rewrite_module \ 101 | --without-http_proxy_module \ 102 | --without-http_fastcgi_module \ 103 | --without-http_uwsgi_module \ 104 | --without-http_scgi_module \ 105 | --without-http_memcached_module \ 106 | --without-http_limit_zone_module \ 107 | --without-http_limit_req_module \ 108 | --without-http_empty_gif_module \ 109 | --without-http_browser_module \ 110 | --without-http_upstream_ip_hash_module \ 111 | --conf-path="${TMP_CONF}" \ 112 | --error-log-path="${HOME}/log/error.log" \ 113 | --http-client-body-temp-path="${HOME}/tmp/body" \ 114 | --http-log-path="${HOME}/log/access.log" \ 115 | --lock-path="${HOME}/tmp/nginx.lock" \ 116 | --pid-path="${PIDFILE}" \ 117 | --with-debug \ 118 | --add-module="${HOME}" 119 | 120 | ${NGX_DIR}: ${NGX_DIST} 121 | tar xzf ${NGX_DIST} 122 | 123 | ${NGX_DIST}: 124 | @echo Downloading nginx dist: ${NGX_DIST} 125 | @curl -O http://nginx.org/download/${NGX_DIST} 126 | 127 | cover: dirs 128 | @if ! grep -s -- '-lgcov' ${NGX_OBJDIR}/Makefile; then \ 129 | rm -f ${NGX_MAKE}; \ 130 | NGX_CONF_OPTS="--with-ld-opt=-lgcov" make ${NGX_MAKE}; \ 131 | fi 132 | util/ngx_coverage.pl "${NGX_DIR}" 133 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_input_stream.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_psgi_input_stream.h" 2 | #include "ngx_http_psgi_module.h" 3 | 4 | PerlIO * 5 | PerlIONginxInput_open(pTHX_ PerlIO_funcs * self, PerlIO_list_t * layers, IV n, 6 | const char *mode, int fd, int imode, int perm, 7 | PerlIO * f, int narg, SV ** args) 8 | { 9 | die("NginxInput layer can not be assigned from Perl land"); 10 | 11 | return NULL; 12 | } 13 | 14 | IV 15 | PerlIONginxInput_fileno(pTHX_ PerlIO * f) 16 | { 17 | PERL_UNUSED_ARG(f); 18 | return -1; // I'm kinda socket. 19 | } 20 | 21 | 22 | IV 23 | PerlIONginxInput_eof(pTHX_ PerlIO *f) 24 | { 25 | 26 | PerlIONginxInput *st = PerlIOSelf(f, PerlIONginxInput); 27 | 28 | if (st->r->headers_in.content_length_n <= st->pos) { 29 | return 1; 30 | } 31 | return 0; 32 | } 33 | 34 | SSize_t 35 | PerlIONginxInput_read(pTHX_ PerlIO *f, void *vbuf, Size_t count) 36 | { 37 | PerlIONginxInput *st = PerlIOSelf(f, PerlIONginxInput); 38 | ngx_http_request_t *r = st->r; 39 | 40 | if (r->request_body == NULL 41 | || r->request_body->temp_file 42 | || r->request_body->bufs == NULL) 43 | { 44 | return 0; 45 | } 46 | 47 | off_t len = r->request_body->bufs->buf->last - r->request_body->bufs->buf->pos - st->pos; 48 | 49 | if (len == 0) { 50 | return 0; 51 | } 52 | 53 | len = len > (off_t)count ? (off_t)count : len; 54 | 55 | Copy(r->request_body->bufs->buf->pos + st->pos, vbuf, len, STDCHAR); 56 | 57 | st->pos+= len; 58 | return len; 59 | } 60 | 61 | PERLIO_FUNCS_DECL(PerlIO_nginx_input) = { 62 | sizeof(PerlIO_funcs), 63 | "ngx_input", 64 | sizeof(PerlIONginxInput), 65 | PERLIO_K_RAW, 66 | NULL, //PerlIOBase_pushed, 67 | NULL, //PerlIONginxInput_popped, 68 | PerlIONginxInput_open, 69 | NULL, //PerlIOBase_binmode, 70 | NULL, //PerlIONginxInput_arg, 71 | PerlIONginxInput_fileno, 72 | NULL, //PerlIONginxInput_dup, 73 | PerlIONginxInput_read, 74 | NULL, /* unread */ 75 | NULL, // PerlIONginxInput_write, 76 | NULL, //PerlIONginxInput_seek, 77 | NULL, //PerlIONginxInput_tell, 78 | NULL, //PerlIONginxInput_close, 79 | NULL, //PerlIONginxInput_flush, 80 | NULL, //PerlIONginxInput_fill, 81 | NULL, //PerlIONginxInput_eof, //PerlIOBase_eof, 82 | NULL, //PerlIOBase_error, 83 | NULL, //PerlIOBase_clearerr, 84 | NULL, //PerlIOBase_setlinebuf, 85 | NULL, //PerlIONginxInput_get_base, 86 | NULL, //PerlIONginxInput_bufsiz, 87 | NULL, //PerlIONginxInput_get_ptr, 88 | NULL, //PerlIONginxInput_get_cnt, 89 | NULL, //PerlIONginxInput_set_ptrcnt, 90 | }; 91 | 92 | SV *PerlIONginxInput_newhandle(pTHX_ ngx_http_request_t *r) 93 | { 94 | ngx_log_t *log = r->connection->log; 95 | 96 | GV *gv = (GV*)SvREFCNT_inc(newGVgen("Nginx::PSGI::Input")); 97 | if (!gv) 98 | return &PL_sv_undef; 99 | 100 | (void) hv_delete(GvSTASH(gv), GvNAME(gv), GvNAMELEN(gv), G_DISCARD); 101 | 102 | /* Body in memory */ 103 | if (r->request_body == NULL || r->request_body->temp_file == NULL) { 104 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 105 | "Open filehandle with 'ngx_input' layer to read from buffers"); 106 | 107 | PerlIO *f = PerlIO_allocate(aTHX); 108 | 109 | if (!(f = PerlIO_push(aTHX_ f, PERLIO_FUNCS_CAST(&PerlIO_nginx_input), "<", NULL)) ) { 110 | ngx_log_error(NGX_LOG_ERR, log, 0, 111 | "Error pushing layer to FH" 112 | ); 113 | return &PL_sv_undef; 114 | } 115 | 116 | if (!do_open(gv, "+<&", 3, FALSE, O_RDONLY, 0, f)) { 117 | ngx_log_error(NGX_LOG_ERR, log, 0, 118 | "Error opening GV" 119 | ); 120 | // FIXME PerlIO_close 121 | return &PL_sv_undef; 122 | } 123 | 124 | PerlIONginxInput *st = PerlIOSelf(f, PerlIONginxInput); 125 | st->r = r; 126 | 127 | } else { 128 | /* Body in temp file */ 129 | 130 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 131 | "Open PSGI request body temp file '%s'", 132 | r->request_body->temp_file->file.name.data 133 | ); 134 | bool result = do_open(gv,(char*)r->request_body->temp_file->file.name.data, r->request_body->temp_file->file.name.len,FALSE,O_RDONLY,0,NULL); 135 | 136 | if (!result) { 137 | ngx_log_error(NGX_LOG_ERR, log, 0, 138 | "Error opening file" 139 | ); 140 | // FIXME PerlIO_close 141 | return NULL; 142 | 143 | } 144 | } 145 | 146 | return (SV*)newRV_noinc((SV *)gv); 147 | } 148 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_module.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_psgi_module.h" 2 | #include "ngx_http_psgi_perl.h" 3 | 4 | ngx_command_t ngx_http_psgi_commands[] = { 5 | /* psgi should be set to absolute (?) path to .psgi application launcher */ 6 | { ngx_string("psgi"), 7 | NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, 8 | ngx_http_psgi, 9 | NGX_HTTP_LOC_CONF_OFFSET, 10 | offsetof(ngx_http_psgi_loc_conf_t, perl), 11 | NULL }, 12 | ngx_null_command 13 | }; 14 | 15 | ngx_http_module_t ngx_http_psgi_module_ctx = { 16 | NULL, /* preconfiguration */ 17 | NULL, /* postconfiguration */ 18 | 19 | ngx_http_psgi_create_main_conf, /* create main configuration */ 20 | ngx_http_psgi_init_main_conf, /* init main configuration */ 21 | 22 | NULL, /* create server configuration */ 23 | NULL, /* merge server configuration */ 24 | 25 | ngx_http_psgi_create_loc_conf, /* create location configuration */ 26 | ngx_http_psgi_merge_loc_conf /* merge location configuration */ 27 | }; 28 | 29 | ngx_module_t ngx_http_psgi_module = { 30 | NGX_MODULE_V1, 31 | &ngx_http_psgi_module_ctx, // module context 32 | ngx_http_psgi_commands, // module directives 33 | NGX_HTTP_MODULE, // module type 34 | NULL, // init master 35 | NULL, // init module 36 | ngx_http_psgi_init_worker, // init process 37 | NULL, // init thread 38 | NULL, // exit thread 39 | ngx_http_psgi_exit, // exit process 40 | NULL, // exit master 41 | NGX_MODULE_V1_PADDING 42 | }; 43 | 44 | char * 45 | ngx_http_psgi(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 46 | { 47 | ngx_http_psgi_loc_conf_t *psgilcf = conf; 48 | 49 | ngx_str_t *value; 50 | ngx_http_core_loc_conf_t *clcf; 51 | ngx_http_psgi_main_conf_t *psgimcf; 52 | 53 | value = cf->args->elts; 54 | 55 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cf->log, 0, 56 | "Installing psgi handler \"%V\"", &value[1]); 57 | 58 | if (psgilcf->app != NULL) { 59 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 60 | "duplicate psgi app \"%V\"", &value[1]); 61 | return NGX_CONF_ERROR; 62 | } 63 | 64 | psgimcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_psgi_module); 65 | 66 | if (psgimcf->perl == NULL) { 67 | if (ngx_http_psgi_init_interpreter(cf, psgimcf) != NGX_CONF_OK) { 68 | return NGX_CONF_ERROR; 69 | } 70 | } 71 | 72 | psgilcf->app = ngx_palloc(cf->pool, value[1].len + 1); 73 | ngx_cpymem(psgilcf->app, value[1].data, value[1].len + 1); 74 | 75 | clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); 76 | clcf->handler = ngx_http_psgi_handler; 77 | 78 | return NGX_CONF_OK; 79 | } 80 | 81 | 82 | ngx_int_t 83 | ngx_http_psgi_init_worker(ngx_cycle_t *cycle) 84 | { 85 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "perl term"); 86 | 87 | /* TODO: Should I do here something nginx-related? 88 | * Or just bind init_worker right to Perl land? 89 | */ 90 | 91 | return ngx_http_psgi_perl_init_worker(cycle); 92 | } 93 | 94 | ngx_int_t 95 | ngx_http_psgi_handler(ngx_http_request_t *r) 96 | { 97 | r->request_body_in_single_buf = 1; 98 | r->request_body_in_persistent_file = 1; 99 | r->request_body_in_clean_file = 1; 100 | r->request_body_file_log_level = 0; 101 | 102 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 103 | "Loading body for PSGI request"); 104 | 105 | ngx_http_read_client_request_body(r, ngx_http_psgi_handler_with_body); 106 | /* TODO: Handle errors */ 107 | 108 | return NGX_OK; 109 | } 110 | 111 | void ngx_http_psgi_handler_with_body(ngx_http_request_t *r) 112 | { 113 | ngx_http_psgi_main_conf_t *psgimcf; 114 | ngx_http_psgi_loc_conf_t *psgilcf; 115 | ngx_log_t *log = r->connection->log; 116 | 117 | psgilcf = ngx_http_get_module_loc_conf(r, ngx_http_psgi_module); 118 | psgimcf = ngx_http_get_module_main_conf(r, ngx_http_psgi_module); 119 | 120 | 121 | if (psgilcf->app == NULL) { 122 | ngx_log_error(NGX_LOG_EMERG, log, 0, 123 | "PSGI panic: NULL application"); 124 | return; 125 | } 126 | 127 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 128 | "Serving request with PSGI app \"%s\"", 129 | psgilcf->app); 130 | 131 | /* No local interpreter. Reuse main */ 132 | if (psgilcf->perl == NULL) { 133 | if (psgilcf->perl == NULL) { 134 | return; 135 | } 136 | } 137 | 138 | ngx_http_psgi_perl_handler(r, psgilcf, psgimcf->perl); 139 | 140 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, 141 | "Finished serving request"); 142 | } 143 | 144 | void * 145 | ngx_http_psgi_create_main_conf(ngx_conf_t *cf) 146 | { 147 | ngx_http_psgi_main_conf_t *pmcf; 148 | 149 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, 150 | "Create PSGI main conf"); 151 | 152 | pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_psgi_main_conf_t)); 153 | 154 | if (pmcf == NULL) { 155 | return NULL; 156 | } 157 | 158 | pmcf->perl = NULL; 159 | 160 | return pmcf; 161 | } 162 | 163 | char * 164 | ngx_http_psgi_init_main_conf(ngx_conf_t *cf, void *conf) 165 | { 166 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, 167 | "Init psgi main conf"); 168 | 169 | return NGX_CONF_OK; 170 | } 171 | 172 | void * 173 | ngx_http_psgi_create_loc_conf(ngx_conf_t *cf) 174 | { 175 | ngx_http_psgi_loc_conf_t *psgilcf; 176 | 177 | psgilcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_psgi_loc_conf_t)); 178 | 179 | if (psgilcf == NULL) { 180 | return NULL; 181 | } 182 | 183 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, 184 | "create PSGI local conf"); 185 | 186 | psgilcf->sub = NULL; 187 | 188 | return psgilcf; 189 | } 190 | 191 | char * 192 | ngx_http_psgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 193 | { 194 | ngx_http_psgi_loc_conf_t *conf = child; 195 | ngx_http_psgi_loc_conf_t *prev = parent; 196 | 197 | /* FIXME: Do I need this at all? */ 198 | if (conf->app == NULL) { 199 | conf->sub = prev->sub; 200 | conf->app = prev->app; 201 | } 202 | 203 | /* TODO: optional localization of Perl interpreter per app? */ 204 | if (conf->app != NULL && conf->perl == NULL) { 205 | conf->perl = prev->perl; 206 | if (conf->perl == NULL) { 207 | conf->perl = ngx_http_psgi_create_interpreter(cf); 208 | } 209 | } 210 | 211 | return NGX_CONF_OK; 212 | } 213 | 214 | char * 215 | ngx_http_psgi_init_interpreter(ngx_conf_t *cf, ngx_http_psgi_main_conf_t *psgimcf) 216 | { 217 | /* Already have Perl interpreter */ 218 | if (psgimcf->perl != NULL) { 219 | return NGX_CONF_OK; 220 | } 221 | 222 | psgimcf->perl = ngx_http_psgi_create_interpreter(cf); 223 | 224 | if (psgimcf->perl == NULL) { 225 | return NGX_CONF_ERROR; 226 | } 227 | 228 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, 229 | "Perl interpreter created"); 230 | 231 | return NGX_CONF_OK; 232 | } 233 | 234 | void 235 | ngx_http_psgi_exit(ngx_cycle_t *cycle) 236 | { 237 | ngx_http_psgi_perl_exit(cycle); 238 | } 239 | -------------------------------------------------------------------------------- /util/ngx_coverage.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use Carp (); 5 | use Cwd 'realpath', 'getcwd'; 6 | use File::Basename; 7 | use File::Find; 8 | use File::Path 'remove_tree'; 9 | use File::Spec::Functions ':ALL'; 10 | use File::Temp; 11 | use Getopt::Long; 12 | use Pod::Usage; 13 | 14 | my %opts = ( 15 | nginx_dir => undef, 16 | verbose => 0, 17 | module_dir => '.', 18 | module_sources => 'src', 19 | prove_command => undef, 20 | test_sources => [] 21 | ); 22 | 23 | GetOptions( 24 | 'm|module-dir=s' => \$opts{module_dir}, 25 | 'module-sources=s' => \$opts{module_sources}, 26 | 'v|verbose' => \$opts{verbose}, 27 | 'test-source=s' => $opts{test_sources}, 28 | 'prove-command=s' => \$opts{prove_command}, 29 | 'h|help' => sub { pod2usage(1) }, 30 | ); 31 | 32 | pod2usage(-message => basename($0) . ": Nginx dir required.\n") 33 | unless @ARGV; 34 | 35 | pod2usage(-message => basename($0) . ": Too many argumets given.\n") 36 | if @ARGV > 1; 37 | 38 | $opts{nginx_dir} = $ARGV[0]; 39 | 40 | $opts{nginx_objdir} = catdir($opts{nginx_dir}, 'objs'); 41 | $opts{nginx_makefile} = catdir($opts{nginx_objdir}, 'Makefile'); 42 | $opts{nginx_bin} = catdir($opts{nginx_objdir}, 'nginx'); 43 | 44 | $opts{module_dir} = realpath($opts{module_dir}); 45 | 46 | if (!@{$opts{test_sources}}) { 47 | $opts{test_sources} = ['t']; 48 | verbose("No test-sources defined. Using default @{$opts{test_sources}}"); 49 | } 50 | 51 | check_opts(\%opts); 52 | my @sources = find_sources(catdir($opts{module_dir}, $opts{module_sources})); 53 | make_nginx(\%opts, \@sources); 54 | make_coverage(\%opts, \@sources); 55 | 56 | sub make_coverage { 57 | my ($opts, $sources) = @_; 58 | 59 | local $ENV{PATH} = join ':', realpath(rel2abs($opts{nginx_objdir})), 60 | $ENV{PATH}; 61 | 62 | if ($opts->{prove_command}) { 63 | verbose("Using custom prove command: `$opts->{prove_command}`"); 64 | verbose("Ignoring test-sources: @{$opts->{test_sources}}"); 65 | 66 | system $opts->{prove_command} 67 | and die "$opts->{prove_command} failed\n"; 68 | 69 | return; 70 | } 71 | 72 | eval { require App::Prove } 73 | or die "Please install App::Prove:\n" 74 | . " curl -L http://cpanmin.us | perl - App::Prove\n"; 75 | 76 | verbose("Using App::Prove v${App::Prove::VERSION}"); 77 | verbose("Using test-sources: @{$opts->{test_sources}}"); 78 | 79 | # It's usual for tests to expect working dir is package dir 80 | my $working = getcwd; 81 | { 82 | chdir $opts->{module_dir}; 83 | 84 | # I don't care here if any test fails 85 | system 'prove', '-lmr', @{$opts->{test_sources}}; 86 | }; 87 | chdir $working; 88 | 89 | foreach my $src (@$sources) { 90 | $src = abs2rel($src, $opts->{module_dir}); 91 | my $gcno = catfile($opts->{nginx_objdir}, 'addon', $src); 92 | $gcno =~ s/\.c$/.gcno/; 93 | system 'gcov', '-p', $opts->{module_dir}, '-o', 94 | dirname($gcno), $gcno; 95 | } 96 | 97 | my @gcov = grep { -f $_ } <*.gcov>; 98 | system 'gcov2perl', @gcov and die "gcov2perl failed\n"; 99 | system 'cover'; 100 | } 101 | 102 | sub find_sources { 103 | my ($dir) = @_; 104 | my @sources; 105 | find( 106 | { no_chdir => 1, 107 | wanted => sub { 108 | return unless /\.c$/; 109 | my $srcname = rel2abs($_, $dir); 110 | push @sources, $srcname; 111 | } 112 | }, 113 | $dir 114 | ); 115 | @sources = map { realpath($_) } @sources; 116 | return @sources; 117 | } 118 | 119 | sub make_nginx { 120 | my ($opts, $sources) = @_; 121 | verbose("Setting up makefile $opts->{nginx_makefile}"); 122 | 123 | my $make = slurp($opts->{nginx_makefile}); 124 | if ($make !~ /-lgcov/) { 125 | die 126 | "Makefile '$opts->{nginx_makefile}' is not configured with '--with-ld-opt=-lgcov'.\n" 127 | . "Please reconfigure.\n"; 128 | } 129 | else { 130 | verbose("Nginx configured --with-ld-opt=-lgcov"); 131 | 132 | # TODO: reconfigure with opts from ./objs/ngx_auto_config.h 133 | } 134 | $make .= "\n\ncover:\n"; 135 | foreach my $src (@$sources) { 136 | my $objname = abs2rel($src, $opts->{module_dir}); 137 | ($objname) = no_upwards($objname); 138 | $objname =~ s/\.c$/.o/; 139 | $make 140 | .= ' $(CC) -c $(CFLAGS) -fprofile-arcs -ftest-coverage $(ALL_INCS)'; 141 | $make .= " -o objs/addon/$objname $src\n"; 142 | } 143 | 144 | my $MAKE_COVER = File::Temp->new(TEMPLATE => 'Makefile-XXXXXX'); 145 | print $MAKE_COVER $make; 146 | close $MAKE_COVER; 147 | 148 | verbose("Remove old coverage files"); 149 | find( 150 | { no_chdir => 1, 151 | wanted => sub { 152 | return unless /\.(?:gcno|gcda)$/; 153 | unlink($_) or die "Failed to remove file '$_': $!"; 154 | } 155 | }, 156 | $opts->{nginx_dir} 157 | ); 158 | my @gcov = grep { -f $_ } <*.gcov>; 159 | unlink(@gcov) if @gcov; 160 | remove_tree('cover_db') if -d 'cover_db'; 161 | if (-f $opts{nginx_bin}) { 162 | verbose("Remove old nginx"); 163 | unlink($opts{nginx_bin}) 164 | or die "Failed to remove $opts{nginx_bin}: $!"; 165 | } 166 | 167 | my $makefile = rel2abs($MAKE_COVER->filename); 168 | system('make', '-C', $opts->{nginx_dir}, '-f', $makefile, 'cover') 169 | and die "Failed to make cover\n"; 170 | 171 | system('make', '-C', $opts->{nginx_dir}, '-f', $makefile) 172 | and die "Failed to make nginx\n"; 173 | } 174 | 175 | sub check_opts { 176 | my ($opts) = @_; 177 | 178 | eval { require Devel::Cover } 179 | or die "Please install Devel::Cover:\n" 180 | . " curl -L http://cpanmin.us | perl - Devel::Cover\n"; 181 | 182 | verbose("Using Devel::Cover v${Devel::Cover::VERSION}"); 183 | 184 | if (!-d $opts->{nginx_dir}) { 185 | die "Nginx dir '$opts->{nginx_dir}' does not exist.\n"; 186 | } 187 | else { 188 | verbose("Nginx dir '$opts->{nginx_dir}' exists"); 189 | } 190 | 191 | if (!-f $opts->{nginx_makefile}) { 192 | die 193 | "No makefile found: '$opts->{nginx_makefile}'. Forgot to ./configure ?\n"; 194 | } 195 | else { 196 | verbose("Nginx Makefile '$opts->{nginx_makefile}' exists"); 197 | } 198 | 199 | if (!-d $opts->{module_dir}) { 200 | die "Nginx module dir '$opts->{module_dir}' does not exist.\n"; 201 | } 202 | else { 203 | verbose("Module dir '$opts->{module_dir}' exists"); 204 | } 205 | } 206 | 207 | sub verbose { 208 | return unless $opts{verbose}; 209 | print basename($0), ": ", @_, "\n"; 210 | } 211 | 212 | sub slurp { 213 | local $/; 214 | open my $M, $_[0] or Carp::croak("Failed to read file '$_[0]': $!"); 215 | <$M>; 216 | } 217 | 218 | 219 | __END__ 220 | 221 | =head1 NAME 222 | 223 | ngx_coverage.pl - produces test coverage for nginx third-party modules 224 | 225 | =head1 SYNOPSIS 226 | 227 | ngx_coverage.pl [options] 228 | 229 | Options: 230 | --help brief help message 231 | --module-dir module directory 232 | --test-source 't' by default 233 | --prove-command custom prove command 234 | --verbose detailed output 235 | 236 | =head1 OPTIONS 237 | 238 | =over 8 239 | 240 | =item B<--help> 241 | 242 | Print a brief help message and exits. 243 | 244 | =item B<--module-dir> 245 | 246 | A dir with module to cover. Defaults to current working dir. 247 | 248 | =item B<--module-sources> 249 | 250 | A dir that contains module sources. Relative to a module dir. Defaults to 'src'. 251 | 252 | =item B<--test-source> 253 | 254 | Test source for prove. Defaults to 't' dir in a module dir. 255 | Multiple test sources are supported. 256 | 257 | =item B<--prove-command> 258 | 259 | A command to use instead of standard prove. Ignores L<--test-source>. 260 | 261 | =back 262 | 263 | =head1 DESCRIPTION 264 | 265 | B collects test coverage of your module 266 | and provides detailed information in HTML format. 267 | 268 | =cut 269 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_response.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_psgi_response.h" 2 | #include "ngx_http_psgi_perl.h" 3 | 4 | ngx_int_t 5 | ngx_http_psgi_process_response(pTHX_ ngx_http_request_t *r, SV *response, PerlInterpreter *perl) 6 | { 7 | 8 | if (SvROK(response)) 9 | response = SvRV(response); 10 | 11 | if (SvTYPE(response) == SVt_PVCV || SvTYPE(response) == SVt_PVMG) { 12 | ngx_http_psgi_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_psgi_module); 13 | if (ctx == NULL) { 14 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_psgi_module)); 15 | if (ctx == NULL) { 16 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 17 | "PSGI panic: no psgi context found while processing response"); 18 | return NGX_ERROR; 19 | } 20 | 21 | ngx_http_set_ctx(r, ctx, ngx_http_psgi_module); 22 | } 23 | ctx->callback = response; 24 | SvREFCNT_inc(ctx->callback); 25 | return ngx_http_psgi_perl_call_psgi_callback(aTHX_ r); 26 | } 27 | 28 | return ngx_http_psgi_process_array_response(aTHX_ r, response); 29 | } 30 | 31 | ngx_int_t 32 | ngx_http_psgi_process_array_response(pTHX_ ngx_http_request_t *r, SV *response) 33 | { 34 | // Response should be reference to ARRAY 35 | if (SvTYPE(response) != SVt_PVAV) { 36 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 37 | "PSGI app returned wrong value: %s", SvPV_nolen(response)); 38 | 39 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 40 | } 41 | 42 | /* Create chained response from ARRAY: 43 | * convert each array element to buffer 44 | * and pass to filter 45 | */ 46 | 47 | AV *psgir = (AV*)response; 48 | 49 | // Array should contain at least 3 elements 50 | if (av_len(psgir) < 2) { 51 | ngx_http_psgi_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_psgi_module); 52 | if (!ctx->callback) { 53 | 54 | ngx_log_error( 55 | NGX_LOG_ERR, r->connection->log, 56 | 0, 57 | "PSGI app is expected to return array of 3 elements. Returned %d", 58 | av_len(psgir) 59 | ); 60 | 61 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 62 | 63 | } else if (av_len(psgir) < 1) { 64 | 65 | ngx_log_error( 66 | NGX_LOG_ERR, r->connection->log, 67 | 0, 68 | "PSGI app returned an array of %d elements. Expected 2 or 3", 69 | av_len(psgir) 70 | ); 71 | 72 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 73 | } 74 | } 75 | 76 | // Process HTTP status code 77 | SV **http_status = av_fetch(psgir, 0, 0); 78 | 79 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 80 | "PSGI app returned status code: %d", SvIV(http_status[0])); 81 | 82 | // Process headers 83 | SV **headers = av_fetch(psgir, 1, 0); 84 | 85 | if (ngx_http_psgi_process_headers(aTHX_ r, *headers, *http_status) != NGX_OK) { 86 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 87 | "Failed to process PSGI response headers"); 88 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 89 | } 90 | 91 | // Process body 92 | 93 | 94 | SV **body = av_fetch(psgir, 2, 0); 95 | return ngx_http_psgi_process_body(aTHX_ r, *body); 96 | 97 | } 98 | 99 | ngx_int_t 100 | ngx_http_psgi_process_headers(pTHX_ ngx_http_request_t *r, SV *headers, SV *status) 101 | { 102 | 103 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 104 | "Process PSGI headers"); 105 | 106 | if (r->headers_out.status == 0) { 107 | r->headers_out.status = SvIV(status); 108 | } 109 | 110 | if (!SvROK(headers) || SvTYPE(SvRV(headers)) != SVt_PVAV) { 111 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 112 | "PSGI app returned wrong headers: %s", SvPV_nolen(headers)); 113 | return NGX_ERROR; 114 | } 115 | 116 | AV *h = (AV *)SvRV(headers); 117 | 118 | int len = av_len(h); 119 | int i; 120 | 121 | if (!(len % 2)) { 122 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 123 | "Even number of header-value elements: %i. Possible error.", len); 124 | } 125 | 126 | for (i = 0; i <= len; i+=2) { 127 | if (i + 1 > len) 128 | break; 129 | 130 | SV **header = av_fetch(h, i, 0); 131 | u_char *key, *value; 132 | STRLEN klen, vlen; 133 | key = (u_char *) SvPV(header[0], klen); 134 | value = (u_char *) SvPV(header[1], vlen); 135 | 136 | if (ngx_strncasecmp(key, (u_char *)"CONTENT-TYPE", klen) == 0) { 137 | 138 | r->headers_out.content_type.data = ngx_pnalloc(r->pool, vlen); 139 | if (r->headers_out.content_type.data == NULL) { 140 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 141 | "In PSGI response: header 'Content-Type' not defined"); 142 | return NGX_ERROR; 143 | } 144 | r->headers_out.content_type.len = vlen; 145 | ngx_memcpy(r->headers_out.content_type.data, value, vlen); 146 | } else { 147 | ngx_table_elt_t *header_ent; 148 | 149 | header_ent = ngx_list_push(&r->headers_out.headers); 150 | 151 | header_ent->hash = 1; 152 | if (header_ent == NULL) { 153 | return NGX_ERROR; 154 | } 155 | 156 | if (ngx_sv2str(r, &header_ent->key, key, klen) != NGX_OK) { 157 | return NGX_ERROR; 158 | } 159 | 160 | if (ngx_sv2str(r, &header_ent->value, value, vlen) != NGX_OK) { 161 | return NGX_ERROR; 162 | } 163 | } 164 | 165 | } 166 | 167 | ngx_http_send_header(r); 168 | return NGX_OK; 169 | } 170 | 171 | ngx_int_t 172 | ngx_http_psgi_process_body(pTHX_ ngx_http_request_t *r, SV *body) 173 | { 174 | 175 | /* If response object is something blessed (even ARRAYref) 176 | * than we consider it as IO::Handle-like object according to PSGI spec. 177 | * Thanks to au on #plack 178 | */ 179 | 180 | if (!SvROK(body)) { 181 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 182 | "PSGI app should return body as reference to something, but returned: %s", SvPV_nolen(body)); 183 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 184 | } 185 | 186 | int body_type = SvTYPE(SvRV(body)); 187 | 188 | if (body_type == SVt_PVGV || sv_isobject(body)) { 189 | return ngx_http_psgi_process_body_glob(aTHX_ r, body); 190 | } 191 | 192 | if (body_type == SVt_PVAV) { 193 | return ngx_http_psgi_process_body_array(aTHX_ r, (AV*)SvRV(body)); 194 | } 195 | 196 | ngx_log_error( 197 | NGX_LOG_ERR, r->connection->log, 0, 198 | "PSGI app returned body of unsupported type [%i] : '%s'", 199 | body_type, SvPV_nolen(body)); 200 | 201 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 202 | } 203 | 204 | ngx_int_t 205 | ngx_http_psgi_process_body_glob(pTHX_ ngx_http_request_t *r, SV *body) 206 | { 207 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 208 | "PSGI app returned handle '%s'", SvPV_nolen((SV*)body)); 209 | 210 | ngx_chain_t *first_chain = NULL; 211 | ngx_chain_t *last_chain = NULL; 212 | int result = NGX_OK; 213 | bool data = 1; 214 | 215 | /* TODO: Call $body->close when done 216 | * TODO: Support sendfile option 217 | * FIXME: This sucks. Push handle to stack and loop readline, save time 218 | * FIXME: This sucks. Do async event-based writing 219 | * FIXME: This sucks. Readline can return lines 1-10 bytes long. Buffer data instead of chaining each line 220 | */ 221 | 222 | // TODO: bufsize should be defined in context and then reused 223 | SV * ngx_sv_bufsize = newSViv(8192); 224 | SV * ngx_PL_rs = sv_2mortal(newRV_noinc(ngx_sv_bufsize)); 225 | 226 | // TODO: find out what is the right way to do local $/ = \123 227 | SV *old_rs = PL_rs; 228 | sv_setsv(PL_rs, ngx_PL_rs); // $/ = \8192 229 | sv_setsv(get_sv("/", GV_ADD), PL_rs); 230 | 231 | while (data && result < NGX_HTTP_SPECIAL_RESPONSE) { 232 | dSP; 233 | ENTER; 234 | SAVETMPS; 235 | 236 | PUSHMARK(SP); 237 | XPUSHs(body); 238 | PUTBACK; 239 | 240 | call_method("getline", G_SCALAR|G_EVAL); 241 | 242 | SPAGAIN; 243 | 244 | SV *buffer = POPs; 245 | 246 | if (SvTRUE(ERRSV)) 247 | { 248 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 249 | "Error reading from a handle: '%s'", SvPV_nolen(ERRSV)); 250 | result = NGX_HTTP_INTERNAL_SERVER_ERROR; 251 | } else if (!SvOK(buffer)) { 252 | data = 0; 253 | } else { 254 | u_char *p; 255 | STRLEN len; 256 | p = (u_char*)SvPV(buffer, len); 257 | if (len) { // Skip zero-length but defined chunks 258 | if (chain_buffer(r, p, len, &first_chain, &last_chain) != NGX_OK) { 259 | ngx_log_error(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 260 | "Error chaining psgi response buffer"); 261 | result = NGX_HTTP_INTERNAL_SERVER_ERROR; 262 | } 263 | } else { 264 | ngx_http_output_filter(r, first_chain); 265 | first_chain = last_chain = NULL; 266 | } 267 | } 268 | 269 | PUTBACK; 270 | FREETMPS; 271 | LEAVE; 272 | } 273 | 274 | PL_rs = old_rs; 275 | sv_setsv(get_sv("/", GV_ADD), old_rs); 276 | 277 | if (first_chain != NULL) { 278 | ngx_http_output_filter(r, first_chain); 279 | return result; 280 | } 281 | 282 | return result < NGX_HTTP_SPECIAL_RESPONSE ? NGX_DONE : result; 283 | } 284 | 285 | ngx_int_t 286 | ngx_http_psgi_process_body_array(pTHX_ ngx_http_request_t *r, AV *body) 287 | { 288 | int len = av_len((AV*)body); 289 | int i; 290 | 291 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 292 | "PSGI app returned %d body chunks", len + 1); 293 | 294 | ngx_chain_t *first_chain = NULL, *last_chain = NULL; 295 | 296 | if (len < 0) { 297 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 298 | "PSGI app returned zerro-elements body"); 299 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 300 | } 301 | 302 | for (i = 0; i <= len; i++) { 303 | u_char *p; 304 | STRLEN plen; 305 | 306 | SV **body_chunk = av_fetch(body, i, 0); 307 | 308 | p = (u_char *) SvPV(*body_chunk, plen); 309 | 310 | if (chain_buffer(r, p, plen, &first_chain, &last_chain) != NGX_OK) { 311 | ngx_log_error(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 312 | "Error chaining psgi response buffer"); 313 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 314 | } 315 | } 316 | 317 | if (first_chain == NULL) { 318 | return NGX_DONE; 319 | } 320 | 321 | ngx_http_output_filter(r, first_chain); 322 | return NGX_OK; 323 | } 324 | 325 | ngx_int_t 326 | ngx_sv2str(ngx_http_request_t *r, ngx_str_t *dst, u_char* src, int len) 327 | { 328 | dst->data = ngx_pnalloc(r->pool, len); 329 | if (dst->data == NULL) { 330 | return NGX_ERROR; 331 | } 332 | dst->len = len; 333 | ngx_memcpy(dst->data, src, len); 334 | return NGX_OK; 335 | } 336 | 337 | ngx_int_t 338 | chain_buffer(ngx_http_request_t *r, u_char *p, STRLEN len, ngx_chain_t **first, ngx_chain_t **last) 339 | { 340 | ngx_chain_t *out = ngx_alloc_chain_link(r->pool); 341 | if (out == NULL) 342 | return NGX_ERROR; 343 | 344 | ngx_buf_t *b = ngx_create_temp_buf(r->pool, len); 345 | if (b == NULL) 346 | return NGX_ERROR; 347 | 348 | ngx_memcpy(b->start, p, len); 349 | b->end = b->last = b->start + len; 350 | b->last_buf = 1; 351 | 352 | out->buf = b; 353 | out->next = NULL; 354 | 355 | if (*first == NULL) { 356 | *first = out; 357 | } 358 | if (*last != NULL) { 359 | (*last)->buf->last_buf = 0; 360 | (*last)->next = out; 361 | } 362 | *last = out; 363 | 364 | return NGX_OK; 365 | } 366 | -------------------------------------------------------------------------------- /src/ngx_http_psgi_perl.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_psgi_perl.h" 2 | #include "ngx_http_psgi_response.h" 3 | #include "ngx_http_psgi_error_stream.h" 4 | #include "ngx_http_psgi_input_stream.h" 5 | EXTERN_C void xs_init (pTHX); 6 | 7 | SV *ngx_http_psgi_create_env(pTHX_ ngx_http_request_t *r, char *app) 8 | { 9 | ngx_list_part_t *part; 10 | ngx_table_elt_t *h; 11 | ngx_uint_t i, c, x; 12 | 13 | 14 | SV *_version[2]; 15 | AV *version; 16 | 17 | HV* env = newHV(); 18 | 19 | /* PSGI version 1.0, arrayref [1,0] */ 20 | _version[0] = newSViv(1); 21 | _version[1] = newSViv(0); 22 | 23 | version = av_make(2, _version); 24 | SvREFCNT_dec(_version[0]); 25 | SvREFCNT_dec(_version[1]); 26 | 27 | hv_store(env, "psgi.version", sizeof("psgi.version")-1, newRV_noinc((SV*)version), 0); 28 | 29 | /* FIXME: after any of this two operations $! is set to 'Inappropriate ioctl for device' */ 30 | SV *errors_h = PerlIONginxError_newhandle(aTHX_ r); 31 | if (errors_h == NULL) 32 | return NULL; 33 | hv_store(env, "psgi.errors", sizeof("psgi.errors")-1, errors_h, 0); 34 | 35 | SV *input_h = PerlIONginxInput_newhandle(aTHX_ r); 36 | if (input_h == NULL) 37 | return NULL; 38 | hv_store(env, "psgi.input", sizeof("psgi.input")-1, input_h, 0); 39 | 40 | /* Detect scheme. 41 | * TODO: Check if only http and https schemes allowed here. What about ws and others? 42 | * FIXME: mb nginx should parse scheme in safe way: [a-z][a-z0-9\=\0\.]* allowed to be valid scheme (rfc3986) 43 | * but nginx allows only [a-z]+ 44 | */ 45 | #if (NGX_HTTP_SSL) 46 | char *scheme; 47 | if (r->connection->ssl) { 48 | scheme = "https"; 49 | } else { 50 | scheme = "http"; 51 | } 52 | hv_store(env, "psgi.url_scheme", sizeof("psgi.url_scheme")-1, newSVpv(scheme, 0), 0); 53 | #else 54 | hv_store(env, "psgi.url_scheme", sizeof("psgi.url_scheme")-1, newSVpv("http", 0), 0); 55 | #endif 56 | 57 | // Buffered body in file 58 | if (r->request_body != NULL && r->request_body->temp_file != NULL) { 59 | hv_store(env, "psgix.input.buffered", sizeof("psgix.input.buffered")-1, newSViv(1), 0); 60 | } 61 | 62 | /* port defined in first line of HTTP request and parsed by nginx */ 63 | if (r->port_start) { 64 | STRLEN port_len = r->port_end - r->port_start; 65 | hv_store(env, "SERVER_PORT", sizeof("SERVER_PORT")-1, newSVpv((char *)r->port_start, port_len), 0); 66 | } else { 67 | /* copypasted from ngx_http_variables.c: get port from nginx conf */ 68 | /* TODO: Maybe reuse code from ngx_http_variables.c is a good idea? */ 69 | ngx_uint_t port; 70 | struct sockaddr_in *sin; 71 | #if (NGX_HAVE_INET6) 72 | struct sockaddr_in6 *sin6; 73 | #endif 74 | u_char *strport; 75 | 76 | if (ngx_connection_local_sockaddr(r->connection, NULL, 0) != NGX_OK) { 77 | // TODO: Throw error 78 | return NULL; 79 | } 80 | 81 | strport = ngx_pnalloc(r->pool, sizeof("65535") - 1); 82 | if (strport == NULL) { 83 | return NULL; 84 | } 85 | 86 | switch (r->connection->local_sockaddr->sa_family) { 87 | 88 | #if (NGX_HAVE_INET6) 89 | case AF_INET6: 90 | sin6 = (struct sockaddr_in6 *) r->connection->local_sockaddr; 91 | port = ntohs(sin6->sin6_port); 92 | break; 93 | #endif 94 | 95 | default: /* AF_INET */ 96 | sin = (struct sockaddr_in *) r->connection->local_sockaddr; 97 | port = ntohs(sin->sin_port); 98 | break; 99 | } 100 | 101 | if (port > 0 && port < 65536) { 102 | hv_store(env, "SERVER_PORT", sizeof("SERVER_PORT")-1, newSVuv(port), 0); 103 | } else { 104 | hv_store(env, "SERVER_PORT", sizeof("SERVER_PORT")-1, newSVpv("", 0), 0); 105 | } 106 | } 107 | hv_store(env, "SERVER_PROTOCOL", sizeof("SERVER_PROTOCOL")-1, newSVpv((char *)r->http_protocol.data, r->http_protocol.len), 0); 108 | 109 | if (r->headers_in.content_length_n != -1) { 110 | hv_store(env, "CONTENT_LENGTH", sizeof("CONTENT_LENGTH")-1, newSViv(r->headers_in.content_length_n), 0); 111 | } 112 | 113 | if (r->headers_in.content_type != NULL) { 114 | hv_store(env, "CONTENT_TYPE", sizeof("CONTENT_TYPE")-1, 115 | newSVpv((char*)r->headers_in.content_type->value.data, r->headers_in.content_type->value.len), 0); 116 | } 117 | 118 | hv_store(env, "REQUEST_URI", sizeof("REQUEST_URI")-1, newSVpv((char *)r->unparsed_uri.data, r->unparsed_uri.len), 0); 119 | 120 | /* TODO: SCRIPT_NAME should be string matched by 'location' value in nginx.conf */ 121 | hv_store(env, "SCRIPT_NAME", sizeof("SCRIPT_NAME")-1, newSVpv("", 0), 0); 122 | 123 | /* FIXME: 124 | * PATH_INFO should be relative to SCRIPT_NAME (current 'location') path in nginx.conf 125 | * How to achieve this? Should I allow psgi only in 'exact match' locations? 126 | * It would be hard to find PATH_INFO for locations like "location ~ /(foo|bar)/.* { }". Or it wouldn't? 127 | */ 128 | hv_store(env, "PATH_INFO", sizeof("PATH_INFO")-1, newSVpv((char *)r->uri.data, r->uri.len), 0); 129 | hv_store(env, "REQUEST_METHOD", sizeof("REQUEST_METHOD")-1, newSVpv((char *)r->method_name.data, r->method_name.len), 0); 130 | if (r->args.len > 0) { 131 | hv_store(env, "QUERY_STRING", sizeof("QUERY_STRING")-1, newSVpv((char *)r->args.data, r->args.len), 0); 132 | } else { 133 | hv_store(env, "QUERY_STRING", sizeof("QUERY_STRING")-1, newSVpv("", 0), 0); 134 | } 135 | 136 | if (r->host_start && r->host_end) { 137 | hv_store(env, "SERVER_NAME", sizeof("SERVER_NAME")-1, newSVpv((char *)r->host_start, r->host_end - r->host_start), 0); 138 | } else { 139 | ngx_http_core_srv_conf_t *cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); 140 | hv_store(env, "SERVER_NAME", sizeof("SERVER_NAME")-1, newSVpv((char *)cscf->server_name.data, cscf->server_name.len), 0); 141 | } 142 | 143 | hv_store(env, "REMOTE_ADDR", sizeof("REMOTE_ADDR")-1, newSVpv((char *)r->connection->addr_text.data, r->connection->addr_text.len), 0); 144 | /* TODO 145 | * 146 | * psgi.multithread 147 | * psgi.multiprocess 148 | */ 149 | 150 | part = &r->headers_in.headers.part; 151 | h = part->elts; 152 | 153 | c = 0; 154 | for (i = 0; /* void */ ; i++) { 155 | ngx_str_t name; 156 | u_char *p; 157 | if (i >= part->nelts) { 158 | if (part->next == NULL) { 159 | break; 160 | } 161 | 162 | part = part->next; 163 | h = part->elts; 164 | i = 0; 165 | } 166 | 167 | /* The environment MUST NOT contain keys named HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH. 168 | * PSGI 1.09_3 169 | */ 170 | if (ngx_strncasecmp(h[i].key.data, (u_char*)"CONTENT-LENGTH", h[i].key.len) == 0) { 171 | continue; 172 | } 173 | 174 | if (ngx_strncasecmp(h[i].key.data, (u_char*)"CONTENT-TYPE", h[i].key.len) == 0) { 175 | continue; 176 | } 177 | 178 | p = ngx_pnalloc(r->pool, sizeof("HTTP_") - 1 + h[i].key.len); 179 | 180 | if (p == NULL) { 181 | return NULL; 182 | } 183 | 184 | name.data = p; 185 | name.len = sizeof("HTTP_") + h[i].key.len -1 ; 186 | 187 | p = ngx_copy(p, (u_char*)"HTTP_", sizeof("HTTP_")-1); 188 | p = ngx_copy(p, h[i].key.data, h[i].key.len ); 189 | 190 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 191 | "Set env header: '%s' => '%s'", 192 | h[i].key.data, h[i].value.data); 193 | 194 | x = h[i].key.len + sizeof("HTTP_"); 195 | while (x > 0) { 196 | if (name.data[x] == '-') { 197 | name.data[x] = '_'; 198 | } else { 199 | name.data[x] = ngx_toupper(name.data[x]); 200 | } 201 | x--; 202 | } 203 | SV **exists = hv_fetch(env, (char*)name.data, name.len, 0); 204 | if (exists == NULL) { 205 | hv_store(env, (char *)name.data, name.len, newSVpv((char *)h[i].value.data, h[i].value.len), 0); 206 | } else { 207 | /* join ',', @values; 208 | * FIXME: Can I do this better 209 | */ 210 | SV *newval = newSVpvf("%s,%s", SvPV_nolen(*exists), h[i].value.data); 211 | hv_store(env, (char *)name.data, name.len, newval, 0); 212 | } 213 | c += 2; 214 | } 215 | 216 | return newRV_noinc((SV*)env); 217 | } 218 | 219 | ngx_int_t 220 | ngx_http_psgi_perl_handler(ngx_http_request_t *r, ngx_http_psgi_loc_conf_t *psgilcf, void *interpreter) 221 | { 222 | PerlInterpreter *perl = (PerlInterpreter *) interpreter; 223 | ngx_int_t retval = NGX_ERROR; 224 | ngx_log_t *log = r->connection->log; 225 | 226 | dTHXa(perl); 227 | PERL_SET_CONTEXT(perl); 228 | 229 | if (psgilcf->sub == NULL) { 230 | 231 | if(ngx_http_psgi_init_app(aTHX_ psgilcf, log) != NGX_OK) { 232 | return NGX_ERROR; 233 | } 234 | } 235 | 236 | { 237 | int count; 238 | 239 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, 240 | "Running PSGI app \"%s\"", 241 | psgilcf->app); 242 | 243 | 244 | dSP; 245 | 246 | ENTER; 247 | SAVETMPS; 248 | 249 | // ngx_http_psgi_create_env should be called between SAVETMPS and FREETMPS 250 | SV *env = ngx_http_psgi_create_env(aTHX_ r, psgilcf->app); 251 | 252 | PUSHMARK(SP); 253 | 254 | XPUSHs(sv_2mortal(env)); 255 | 256 | PUTBACK; 257 | 258 | count = call_sv(psgilcf->sub, G_EVAL|G_SCALAR); 259 | 260 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, 261 | "PSGI app response: %d elements", count); 262 | 263 | SPAGAIN; 264 | 265 | if (SvTRUE(ERRSV)) 266 | { 267 | ngx_log_error(NGX_LOG_ERR, log, 0, 268 | "PSGI handler execution failed: %s", SvPV_nolen(ERRSV)); 269 | 270 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); 271 | retval = NGX_ERROR; 272 | } 273 | else if (count < 1) { 274 | ngx_log_error(NGX_LOG_ERR, log, 0, 275 | "PSGI app \"%s\" did not returned value: %s", psgilcf->app, SvPV_nolen(ERRSV)); 276 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); 277 | retval = NGX_ERROR; 278 | } else { 279 | retval = ngx_http_psgi_process_response(aTHX_ r, POPs, perl); 280 | ngx_http_finalize_request(r, retval); 281 | } 282 | 283 | PUTBACK; 284 | FREETMPS; 285 | LEAVE; 286 | } 287 | 288 | return retval; 289 | } 290 | 291 | ngx_int_t 292 | ngx_http_psgi_init_app(pTHX_ ngx_http_psgi_loc_conf_t *psgilcf, ngx_log_t *log) 293 | { 294 | ngx_int_t retval = NGX_ERROR; 295 | 296 | /* Check if we have Perl interpreter */ 297 | if (psgilcf->perl == NULL) { 298 | ngx_log_error(NGX_LOG_ERR, log, 0, 299 | "Panic: NULL Perl interpreter"); 300 | return retval; 301 | } 302 | 303 | /* Already have PSGI app */ 304 | if (psgilcf->sub != NULL) { 305 | return NGX_OK; 306 | } 307 | 308 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, 309 | "Loading app \"%s\"", psgilcf->app); 310 | 311 | /* Init PSGI application */ 312 | { 313 | dSP; 314 | 315 | ENTER; 316 | SAVETMPS; 317 | 318 | PUSHMARK(SP); 319 | 320 | /* FIXME: This should be written way cleaner! */ 321 | SV *call = newSVpvf("sub { return do '%s' }", psgilcf->app); 322 | 323 | SV *cvrv = eval_pv(SvPV_nolen(call), FALSE); 324 | 325 | int count = call_sv(cvrv, G_EVAL|G_SCALAR); 326 | 327 | if (SvTRUE(ERRSV)) 328 | { 329 | ngx_log_error(NGX_LOG_ERR, log, 0, 330 | "Failed to initialize psgi app \"%s\": %s", psgilcf->app, SvPV_nolen(ERRSV)); 331 | } else if (count < 1) { 332 | ngx_log_error(NGX_LOG_ERR, log, 0, 333 | "Application '%s' returned empty list", psgilcf->app); 334 | } else { 335 | SPAGAIN; 336 | psgilcf->sub = (SV*)POPs; 337 | PUTBACK; 338 | 339 | /* Dereference */ 340 | if (SvROK(psgilcf->sub)) { 341 | psgilcf->sub = SvRV(psgilcf->sub); 342 | } 343 | 344 | if (SvTYPE(psgilcf->sub) == SVt_PVCV || SvTYPE(psgilcf->sub) == SVt_PVMG) { 345 | SvREFCNT_inc(psgilcf->sub); 346 | 347 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, 348 | "Application successfully initialized: %s", SvPV_nolen(psgilcf->sub)); 349 | retval = NGX_OK; 350 | } else { 351 | 352 | ngx_log_error(NGX_LOG_ERR, log, 0, 353 | "psgi app \"%s\" returned something that is not a code reference: '%s'", 354 | psgilcf->app, SvPV_nolen(psgilcf->sub)); 355 | } 356 | } 357 | 358 | FREETMPS; 359 | LEAVE; 360 | } 361 | 362 | return retval; 363 | } 364 | 365 | ngx_int_t 366 | ngx_http_psgi_perl_init_worker(ngx_cycle_t *cycle) 367 | { 368 | ngx_http_psgi_main_conf_t *psgimcf = 369 | ngx_http_cycle_get_module_main_conf(cycle, ngx_http_psgi_module); 370 | 371 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cycle->log, 0, 372 | "Init Perl interpreter in worker %d", ngx_pid); 373 | 374 | if (psgimcf) { 375 | 376 | dTHXa(psgimcf->perl); 377 | PERL_SET_CONTEXT(psgimcf->perl); 378 | 379 | /* FIXME: It looks very wrong. 380 | * Has new worker it's own Perl instance? 381 | * I think I should perl_clone() or something like that 382 | * Also $0 (script path) should be set somewhere. 383 | * I don't think it's right place for it. It should be done somewhere in local conf init stuff 384 | * Or, if many handlers share single Perl interpreter - before each handler call 385 | * 386 | * TODO 387 | * Test PID and related stuff 388 | * Test what happens if user try to change 389 | * Test what happens if user does 'fork' inside PSGI app 390 | */ 391 | 392 | sv_setiv(GvSV(gv_fetchpv("$$", TRUE, SVt_PV)), (I32) ngx_pid); 393 | } else { 394 | ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "PSGI panic: no main configuration supplied for init worker %d", ngx_pid); 395 | return NGX_ERROR; 396 | } 397 | 398 | return NGX_OK; 399 | } 400 | 401 | PerlInterpreter * 402 | ngx_http_psgi_create_interpreter(ngx_conf_t *cf) 403 | { 404 | int n; 405 | PerlInterpreter *perl; 406 | 407 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, 408 | "Create PSGI Perl interpreter"); 409 | 410 | /* FIXME: Some code from ngx_http_perl_module.c I don't understand */ 411 | if (ngx_set_environment(cf->cycle, NULL) == NULL) { 412 | return NULL; 413 | } 414 | 415 | perl = perl_alloc(); 416 | 417 | if (perl == NULL) { 418 | ngx_log_error(NGX_LOG_ALERT, cf->log, 0, "perl_alloc() failed"); 419 | return NULL; 420 | } 421 | 422 | { 423 | char *my_argv[] = { "", "-MIO::Handle", "-e", "0" }; 424 | 425 | dTHXa(perl); 426 | PERL_SET_CONTEXT(perl); 427 | 428 | perl_construct(perl); 429 | 430 | n = perl_parse(perl, xs_init, 3, my_argv, NULL); 431 | 432 | if (n != 0) { 433 | ngx_log_error(NGX_LOG_ALERT, cf->log, 3, "perl_parse() failed: %d", n); 434 | goto fail; 435 | } 436 | 437 | PerlIO_define_layer(aTHX_ PERLIO_FUNCS_CAST(&PerlIO_nginx_error)); 438 | 439 | } 440 | 441 | return perl; 442 | 443 | fail: 444 | 445 | (void) perl_destruct(perl); 446 | 447 | perl_free(perl); 448 | 449 | return NULL; 450 | } 451 | 452 | void 453 | ngx_http_psgi_perl_exit(ngx_cycle_t *cycle) 454 | { 455 | ngx_http_psgi_main_conf_t *psgimcf = 456 | ngx_http_cycle_get_module_main_conf(cycle, ngx_http_psgi_module); 457 | 458 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "psgi perl term"); 459 | 460 | (void) perl_destruct(psgimcf->perl); 461 | 462 | perl_free(psgimcf->perl); 463 | 464 | PERL_SYS_TERM(); 465 | } 466 | 467 | ngx_int_t 468 | ngx_http_psgi_perl_call_psgi_callback(pTHX_ ngx_http_request_t *r) 469 | { 470 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 471 | "Call PSGI callback"); 472 | 473 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 474 | "PSGI callback is not implemented yet"); 475 | 476 | return NGX_ERROR; 477 | 478 | } 479 | --------------------------------------------------------------------------------