├── dist.ini ├── t ├── 00_compile.t ├── hello.t └── hello_mount.t ├── xt ├── synopsis.t ├── pod.t └── perlcritic.t ├── .gitignore ├── Build.PL ├── cpanfile ├── lib ├── Catalyst │ ├── Helper │ │ └── PSGI.pm │ ├── Controller │ │ └── Metal.pm │ └── Engine │ │ └── PSGI.pm └── Plack │ └── Test │ └── Adopt │ └── Catalyst.pm ├── Changes ├── META.json ├── README.md └── LICENSE /dist.ini: -------------------------------------------------------------------------------- 1 | [@Milla] 2 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More tests => 1; 3 | 4 | BEGIN { use_ok 'Catalyst::Engine::PSGI' } 5 | -------------------------------------------------------------------------------- /xt/synopsis.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | eval "use Test::Synopsis"; 3 | plan skip_all => "Test::Synopsis required" if $@; 4 | all_synopsis_ok(); 5 | -------------------------------------------------------------------------------- /xt/pod.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | eval "use Test::Pod 1.00"; 3 | plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; 4 | all_pod_files_ok(); 5 | -------------------------------------------------------------------------------- /xt/perlcritic.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | eval q{ use Test::Perl::Critic }; 4 | plan skip_all => "Test::Perl::Critic is not installed." if $@; 5 | all_critic_ok("lib"); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | META.yml 2 | Makefile 3 | inc/ 4 | pm_to_blib 5 | *~ 6 | /Catalyst-Engine-PSGI-* 7 | /.build 8 | /_build_params 9 | /Build 10 | /Build.bat 11 | !Build/ 12 | !META.json 13 | !LICENSE 14 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # This Build.PL for Catalyst-Engine-PSGI was generated by Dist::Zilla::Plugin::ModuleBuildTiny 0.010. 2 | use strict; 3 | use warnings; 4 | 5 | use 5.008001; 6 | use Module::Build::Tiny 0.039; 7 | Build_PL(); 8 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Catalyst::Action::RenderView'; 2 | requires 'Catalyst::Runtime', '5.80007'; 3 | requires 'Filter::Util::Call'; 4 | requires 'perl', '5.008001'; 5 | 6 | on build => sub { 7 | requires 'ExtUtils::MakeMaker', '6.59'; 8 | requires 'Test::More'; 9 | requires 'Test::Requires'; 10 | requires 'Test::TCP'; 11 | }; 12 | -------------------------------------------------------------------------------- /lib/Catalyst/Helper/PSGI.pm: -------------------------------------------------------------------------------- 1 | package Catalyst::Helper::PSGI; 2 | use strict; 3 | use warnings; 4 | use File::Spec; 5 | 6 | sub mk_stuff { 7 | my($self, $helper, @args) = @_; 8 | 9 | my $base = $helper->{base}; 10 | my $app = lc $helper->{app}; 11 | 12 | $app =~ s/::/_/g; 13 | 14 | my $script = File::Spec->catfile($base, 'script', "$app.psgi"); 15 | 16 | $helper->render_file('psgi_app', $script); 17 | chmod 0755, $script; 18 | } 19 | 20 | =head1 NAME 21 | 22 | Catalyst::Helper::PSGI - PSGI helper to create a .psgi application script 23 | 24 | =head1 SYNOPSIS 25 | 26 | > script/myapp_create.pl PSGI 27 | 28 | =head1 DESCRIPTION 29 | 30 | This helper module creates a C application script so you 31 | can run your Catalyst with PSGI servers using L or L. 32 | 33 | =head1 AUTHOR 34 | 35 | Tatsuhiko Miyagawa 36 | 37 | =head1 SEE ALSO 38 | 39 | L 40 | 41 | =cut 42 | 43 | 1; 44 | 45 | __DATA__ 46 | 47 | __psgi_app__ 48 | #!/usr/bin/env perl 49 | use strict; 50 | use warnings; 51 | use [% app %]; 52 | 53 | [% app %]->setup_engine('PSGI'); 54 | my $app = sub { [% app %]->run(@_) }; 55 | 56 | -------------------------------------------------------------------------------- /t/hello.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More skip_all => 1; 3 | use Test::Requires qw( Plack::Loader LWP ); 4 | use lib "t/Hello/lib"; 5 | use Test::TCP; 6 | use LWP::UserAgent; 7 | 8 | use Hello; 9 | Hello->setup_engine('PSGI'); 10 | 11 | my $app = sub { Hello->run(@_) }; 12 | 13 | test_tcp( 14 | client => sub { 15 | my $port = shift; 16 | my $ua = LWP::UserAgent->new; 17 | my $res = $ua->get("http://127.0.0.1:$port/welcome"); 18 | like $res->content, qr/Welcome/; 19 | 20 | $res = $ua->get("http://127.0.0.1:$port/?name=foo"); 21 | is $res->content_type, 'text/plain'; 22 | like $res->content, qr/Hello foo/; 23 | 24 | $res = $ua->post("http://127.0.0.1:$port/", { name => "bar" }); 25 | like $res->content, qr/Hello bar/; 26 | 27 | $res = $ua->get("http://127.0.0.1:$port/metal"); 28 | like $res->content, qr/Hello Metal/; 29 | 30 | $res = $ua->get("http://127.0.0.1:$port/headers"); 31 | is $res->content, 'blah'; 32 | like $res->header('X-Foo'), qr/^bar\s+baz$/; 33 | }, 34 | server => sub { 35 | my $port = shift; 36 | Plack::Loader->auto(port => $port)->run($app); 37 | }, 38 | ); 39 | 40 | done_testing; 41 | 42 | -------------------------------------------------------------------------------- /lib/Catalyst/Controller/Metal.pm: -------------------------------------------------------------------------------- 1 | package Catalyst::Controller::Metal; 2 | use Moose; 3 | extends 'Catalyst::Component'; 4 | with 'Catalyst::Component::ApplicationAttribute'; 5 | 6 | my %metals; 7 | 8 | sub BUILD { 9 | my($self, $args) = @_; 10 | push @{$metals{$self->_application}}, $self; 11 | } 12 | 13 | sub metals_for { 14 | my($class, $application) = @_; 15 | @{$metals{$application} || []}; 16 | } 17 | 18 | __PACKAGE__->meta->make_immutable; 19 | 20 | 1; 21 | 22 | __END__ 23 | 24 | =head1 NAME 25 | 26 | Catalyst::Controller::Metal - Raw PSGI handling in Catalyst controllers 27 | 28 | =head1 SYNOPSIS 29 | 30 | package MyApp::Controller::Metalic; 31 | use parent 'Catalyst::Controller::Metal'; 32 | 33 | sub call { 34 | my($self, $env) = @_; 35 | if ($env->{PATH_INFO} =~ m!^/hello!) { 36 | return [ 200, [ "Content-Type" => 'text/plain' ], [ "Hello World" ] ]; 37 | } else { 38 | return [ 404, [], [] ]; 39 | } 40 | } 41 | 42 | Catalyst::Controller::Metal allows you to write a raw PSGI handler in 43 | your Catalyst application, inspired by Rails Metal that allows you to 44 | write raw Rack applications inside Ruby on Rails. 45 | 46 | =head1 SEE ALSO 47 | 48 | Rails Metal. 49 | 50 | =cut 51 | -------------------------------------------------------------------------------- /t/hello_mount.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More skip_all => 1; 3 | use Test::Requires qw( Plack::Loader LWP Plack::Builder); 4 | use lib "t/Hello/lib"; 5 | use Test::TCP; 6 | use LWP::UserAgent; 7 | 8 | use Hello; 9 | Hello->setup_engine('PSGI'); 10 | 11 | my $app = builder { 12 | mount '/test' => sub { Hello->run(@_) }; 13 | }; 14 | 15 | 16 | test_tcp( 17 | client => sub { 18 | my $port = shift; 19 | my $ua = LWP::UserAgent->new; 20 | my $res = $ua->get("http://127.0.0.1:$port/test/welcome"); 21 | like $res->content, qr/Welcome/; 22 | 23 | $res = $ua->get("http://127.0.0.1:$port/test/?name=foo"); 24 | is $res->content_type, 'text/plain'; 25 | like $res->content, qr/Hello foo/; 26 | 27 | $res = $ua->post("http://127.0.0.1:$port/test", { name => "bar" }); 28 | like $res->content, qr/Hello bar/; 29 | 30 | $res = $ua->post("http://127.0.0.1:$port/test/", { name => "bar2" }); 31 | like $res->content, qr/Hello bar2/; 32 | 33 | $res = $ua->get("http://127.0.0.1:$port/test/metal"); 34 | like $res->content, qr/Hello Metal/; 35 | }, 36 | server => sub { 37 | my $port = shift; 38 | Plack::Loader->auto(port => $port)->run($app); 39 | }, 40 | ); 41 | 42 | done_testing; 43 | 44 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl extension Catalyst::Engine::PSGI 2 | 3 | {{$NEXT}} 4 | 5 | 0.14 2015-04-19 11:08:06 CEST 6 | - Deprecate this module 7 | 8 | 0.13 Thu Jun 9 23:58:10 PDT 2011 9 | - Fixed a bug in PSGI header generation to prevent potential HTTP header injection 10 | - Misc doc fixes 11 | 12 | 0.12 Thu Jan 6 14:37:53 PST 2011 13 | - Fix for Catalyst::Runtime >= 5.80030 (pedromelo) 14 | 15 | 0.11 Fri Jul 30 12:49:46 PDT 2010 16 | - Allows setting a code reference to the $c->res->body as 17 | a raw PSGI streaming (t0m) 18 | 19 | 0.10 Thu May 20 14:38:03 PDT 2010 20 | - Added docs for frontend proxy and X-Forwarded-* headers 21 | 22 | 0.09 Wed Apr 7 20:17:57 PDT 2010 23 | - Escape :: in the helpers for .psgi files (lestrrat) 24 | 25 | 0.08 Fri Mar 19 00:58:35 PDT 2010 26 | - Fixed a problem with URLMap where root access gets an empty path (omega) 27 | 28 | 0.07 Thu Dec 10 22:14:04 PST 2009 29 | - Clear env reference to avoid leaks in Servers like AnyEvent (chiba) 30 | - Added Catalyst::Controller::Metal 31 | 32 | 0.06 Sat Nov 7 20:20:45 PST 2009 33 | - Added Action::RenderView dependency 34 | 35 | 0.05 Mon Oct 26 00:44:11 PDT 2009 36 | - Removed another Catalyst dev dependency 37 | 38 | 0.04 Mon Oct 19 19:41:51 PDT 2009 39 | - Bump up Catalyst dep to 5.8 (thanks to mst) 40 | 41 | 0.03 Fri Oct 16 00:21:37 PDT 2009 42 | - Added Test::Requires 43 | 44 | 0.02 Tue Oct 13 13:28:49 PDT 2009 45 | - Do not use Static::Simple in the test (Thanks to beppu) 46 | 47 | 0.01 Sat Sep 5 17:29:03 2009 48 | - original version 49 | -------------------------------------------------------------------------------- /lib/Plack/Test/Adopt/Catalyst.pm: -------------------------------------------------------------------------------- 1 | package Plack::Test::Adopt::Catalyst; 2 | use strict; 3 | use Catalyst::Engine::PSGI; 4 | BEGIN { $ENV{CATALYST_ENGINE} = 'PSGI' }; 5 | 6 | use Class::MOP; 7 | use Test::TCP; 8 | use App::Prove; 9 | use Plack::Loader; 10 | 11 | sub import { 12 | my($self, $class) = @_; 13 | 14 | my $caller = caller; 15 | no strict; ## no critic 16 | *{"$caller\::runtests"} = make_runtests($class); 17 | } 18 | 19 | sub make_runtests { 20 | my $class = shift; 21 | 22 | return sub { 23 | my @tests = @_; 24 | 25 | my %apps2tests = analyze_tests($class, @tests); 26 | while (my($app_class, $tests) = each %apps2tests) { 27 | warn "Testing $app_class\n"; 28 | Class::MOP::load_class($app_class); 29 | my $app = sub { $app_class->run(@_) }; 30 | test_tcp( 31 | client => sub { 32 | my $port = shift; 33 | $ENV{CATALYST_SERVER} = "http://127.0.0.1:$port/"; 34 | 35 | my $p = App::Prove->new; 36 | $p->process_args(@$tests); 37 | $p->run; 38 | }, 39 | server => sub { 40 | my $port = shift; 41 | Plack::Loader->auto(port => $port, host => "127.0.0.1")->run($app); 42 | }, 43 | ); 44 | } 45 | }; 46 | } 47 | 48 | sub analyze_tests { 49 | my($class, @tests) = @_; 50 | 51 | my %map; 52 | for my $test (@tests) { 53 | my $cat_app_class = test_app_for($test) || $class; 54 | push @{$map{$cat_app_class}}, $test; 55 | } 56 | 57 | return %map; 58 | } 59 | 60 | sub test_app_for { 61 | my $test = shift; 62 | 63 | open my $fh, "<", $test or return; 64 | while (<$fh>) { 65 | m@^\s*use Catalyst::Test (?:q[qw]?)?[/'"\(]?\s*([a-zA-Z0-9:]+)@ 66 | and return $1; 67 | } 68 | 69 | return; 70 | } 71 | 72 | 1; 73 | 74 | __END__ 75 | 76 | =head1 NAME 77 | 78 | Plack::Test::Adopt::Catalyst - Run Catalyst::Test based tests against Plack implementations 79 | 80 | =head1 SYNOPSIS 81 | 82 | env PLACK_SERVER=Standalone \ 83 | perl -MPlack::Test::Adopt::Catalyst=TestApp -e 'runtests @ARGV' *.t 84 | 85 | =head1 AUTHOR 86 | 87 | Tatsuhiko Miyagawa 88 | 89 | =head1 SEE ALSO 90 | 91 | L L 92 | 93 | =cut 94 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "PSGI engine for Catalyst", 3 | "author" : [ 4 | "Tatsuhiko Miyagawa " 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Dist::Zilla version 5.035, Dist::Milla version v1.0.15, CPAN::Meta::Converter version 2.150001", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : 2 14 | }, 15 | "name" : "Catalyst-Engine-PSGI", 16 | "no_index" : { 17 | "directory" : [ 18 | "t", 19 | "xt", 20 | "inc", 21 | "share", 22 | "eg", 23 | "examples" 24 | ] 25 | }, 26 | "prereqs" : { 27 | "build" : { 28 | "requires" : { 29 | "ExtUtils::MakeMaker" : "6.59", 30 | "Test::More" : "0", 31 | "Test::Requires" : "0", 32 | "Test::TCP" : "0" 33 | } 34 | }, 35 | "configure" : { 36 | "requires" : { 37 | "Module::Build::Tiny" : "0.039" 38 | } 39 | }, 40 | "develop" : { 41 | "requires" : { 42 | "Dist::Milla" : "v1.0.15", 43 | "Test::Pod" : "1.41" 44 | } 45 | }, 46 | "runtime" : { 47 | "requires" : { 48 | "Catalyst::Action::RenderView" : "0", 49 | "Catalyst::Runtime" : "5.80007", 50 | "Filter::Util::Call" : "0", 51 | "perl" : "5.008001" 52 | } 53 | } 54 | }, 55 | "release_status" : "stable", 56 | "resources" : { 57 | "bugtracker" : { 58 | "web" : "https://github.com/miyagawa/Catalyst-Engine-PSGI/issues" 59 | }, 60 | "homepage" : "https://github.com/miyagawa/Catalyst-Engine-PSGI", 61 | "repository" : { 62 | "type" : "git", 63 | "url" : "https://github.com/miyagawa/Catalyst-Engine-PSGI.git", 64 | "web" : "https://github.com/miyagawa/Catalyst-Engine-PSGI" 65 | } 66 | }, 67 | "version" : "0.14", 68 | "x_contributors" : [ 69 | "Andreas Marienborg ", 70 | "chromatic ", 71 | "Dagfinn Ilmari Mannsåker ", 72 | "Justin Hunter ", 73 | "lestrrat ", 74 | "Pedro Melo ", 75 | "Tomas Doran " 76 | ] 77 | } 78 | 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | Catalyst::Engine::PSGI - PSGI engine for Catalyst 4 | 5 | # WARNINGS 6 | 7 | **Catalyst 5.9000 or later has a built-in PSGI support and this module is deprecated.** 8 | 9 | # SYNOPSIS 10 | 11 | # app.psgi 12 | use strict; 13 | use MyApp; 14 | 15 | MyApp->setup_engine('PSGI'); 16 | my $app = sub { MyApp->run(@_) }; 17 | 18 | # DESCRIPTION 19 | 20 | Catalyst::Engine::PSGI is a Catalyst Engine that adapts Catalyst into the PSGI gateway protocol. 21 | 22 | # COMPATIBILITY 23 | 24 | - Currently this engine works with Catalyst 5.8 (Catamoose) or newer. 25 | - Your application is supposed to work with any PSGI servers without any 26 | code modifications, but if your application uses `$c->res->write` 27 | to do streaming write, this engine will buffer the ouput until your 28 | app finishes. 29 | 30 | To do real streaming with this engine, you should implement an 31 | IO::Handle-like object that responds to `getline` method that returns 32 | chunk or undef when done, and set that object to `$c->res->body`. 33 | 34 | Alternatively, it is possible to set the body to a code reference, 35 | which will be used to stream content as documented in the 36 | [PSGI spec](https://metacpan.org/pod/PSGI#Delayed_Reponse_and_Streaming_Body). 37 | 38 | - When your application runs behind the frontend proxy like nginx or 39 | lighttpd, this Catalyst engine doesn't automatically recognize the 40 | incoming headers like `X-Forwarded-For`, because respecting these 41 | headers by default causes a potential security issue. 42 | 43 | You have to enable [Plack::Middleware::ReverseProxy](https://metacpan.org/pod/Plack::Middleware::ReverseProxy) or 44 | [Plack::Middleware::ForwardedHeaders](https://metacpan.org/pod/Plack::Middleware::ForwardedHeaders) to automatically promote those 45 | forwarded headers into `REMOTE_ADDR` hence IP address of the request. 46 | 47 | ReverseProxy middleware is pretty simple and has no configuration 48 | while ForwardedHeaders allows you to configure which upstream host to 49 | trust, etc. 50 | 51 | # AUTHOR 52 | 53 | Tatsuhiko Miyagawa 54 | 55 | Most of the code is taken and modified from Catalyst::Engine::CGI. 56 | 57 | # LICENSE 58 | 59 | This library is free software; you can redistribute it and/or modify 60 | it under the same terms as Perl itself. 61 | 62 | # SEE ALSO 63 | 64 | _Catalyst::Engine_ [PSGI](https://metacpan.org/pod/PSGI) _Plack_ 65 | -------------------------------------------------------------------------------- /lib/Catalyst/Engine/PSGI.pm: -------------------------------------------------------------------------------- 1 | package Catalyst::Engine::PSGI; 2 | 3 | use strict; 4 | use 5.008_001; 5 | our $VERSION = '0.14'; 6 | 7 | use Moose; 8 | extends 'Catalyst::Engine'; 9 | 10 | { 11 | # Temporary hack to see if there are better ways like TraitFor, 12 | # but without requiring downstream changes. 13 | sub Catalyst::Request::env { 14 | my $req = shift; 15 | $req->{_psgi_env} = shift if @_; 16 | $req->{_psgi_env}; 17 | } 18 | } 19 | 20 | use Scalar::Util qw(blessed); 21 | use URI; 22 | use Catalyst::Controller::Metal; 23 | 24 | # This is what Catalyst does to decode path. Not compatible to CGI RFC 3875 25 | my %reserved = map { sprintf('%02x', ord($_)) => 1 } split //, $URI::reserved; 26 | sub _uri_safe_unescape { 27 | my ($s) = @_; 28 | $s =~ s/%([a-fA-F0-9]{2})/$reserved{lc($1)} ? "%$1" : pack('C', hex($1))/ge; 29 | $s 30 | } 31 | 32 | sub prepare_connection { 33 | my ( $self, $c ) = @_; 34 | 35 | my $request = $c->request; 36 | my $env = $self->env; 37 | 38 | $request->env($env); 39 | $request->address( $env->{REMOTE_ADDR} ); 40 | $request->hostname( $env->{REMOTE_HOST} ) if exists $env->{REMOTE_HOST}; 41 | $request->protocol( $env->{SERVER_PROTOCOL} ); 42 | $request->user( $env->{REMOTE_USER} ); # XXX: Deprecated. See Catalyst::Request for removal information 43 | $request->remote_user( $env->{REMOTE_USER} ); 44 | $request->method( $env->{REQUEST_METHOD} ); 45 | 46 | $request->secure( $env->{'psgi.url_scheme'} eq 'https' ); 47 | } 48 | 49 | sub prepare_headers { 50 | my ( $self, $c ) = @_; 51 | 52 | my $env = $c->request->env; 53 | my $headers = $c->request->headers; 54 | foreach my $header ( keys %$env ) { 55 | next unless $header =~ /^(HTTP|CONTENT|COOKIE)/i; 56 | ( my $field = $header ) =~ s/^HTTPS?_//; 57 | $field =~ tr/_/-/; 58 | $headers->header( $field => $env->{$header} ); 59 | } 60 | } 61 | 62 | sub prepare_path { 63 | my ( $self, $c ) = @_; 64 | 65 | my $env = $c->request->env; 66 | 67 | my $scheme = $c->request->secure ? 'https' : 'http'; 68 | my $host = $env->{HTTP_HOST} || $env->{SERVER_NAME}; 69 | my $port = $env->{SERVER_PORT} || 80; 70 | my $base_path = $env->{SCRIPT_NAME} || "/"; 71 | 72 | # set the request URI 73 | my $req_uri = $env->{REQUEST_URI}; 74 | $req_uri =~ s/\?.*$//; 75 | my $path = _uri_safe_unescape($req_uri); 76 | if ($path eq $base_path) { 77 | $path .= "/"; # To fool catalyst a bit 78 | } 79 | $path =~ s{^/+}{}; 80 | 81 | # Using URI directly is way too slow, so we construct the URLs manually 82 | my $uri_class = "URI::$scheme"; 83 | 84 | # HTTP_HOST will include the port even if it's 80/443 85 | $host =~ s/:(?:80|443)$//; 86 | 87 | if ( $port !~ /^(?:80|443)$/ && $host !~ /:/ ) { 88 | $host .= ":$port"; 89 | } 90 | 91 | # Escape the path 92 | $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; 93 | $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE 94 | 95 | my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : ''; 96 | my $uri = $scheme . '://' . $host . '/' . $path . $query; 97 | 98 | $c->request->uri( bless \$uri, $uri_class ); 99 | 100 | # set the base URI 101 | # base must end in a slash 102 | $base_path .= '/' unless $base_path =~ m{/$}; 103 | 104 | my $base_uri = $scheme . '://' . $host . $base_path; 105 | 106 | $c->request->base( bless \$base_uri, $uri_class ); 107 | } 108 | 109 | around prepare_query_parameters => sub { 110 | my $orig = shift; 111 | my ( $self, $c ) = @_; 112 | 113 | if ( my $qs = $c->request->env->{QUERY_STRING} ) { 114 | $self->$orig( $c, $qs ); 115 | } 116 | }; 117 | 118 | sub prepare_request { 119 | my ( $self, $c, %args ) = @_; 120 | 121 | if ( $args{env} ) { 122 | $self->env( $args{env} ); 123 | } 124 | 125 | $self->{buffer} = ''; 126 | } 127 | 128 | sub write { 129 | my($self, $c, $buffer) = @_; 130 | $self->{buffer} .= $buffer if defined $buffer; 131 | } 132 | 133 | sub finalize_body { 134 | # do nothing since we serve content 135 | } 136 | 137 | sub read_chunk { 138 | my($self, $c) = (shift, shift); 139 | $self->env->{'psgi.input'}->read(@_); 140 | } 141 | 142 | sub run { 143 | my($self, $class, $env) = @_; 144 | 145 | # short circuit with Metal 146 | for my $metal (Catalyst::Controller::Metal->metals_for($class)) { 147 | my $res = $metal->call($env); 148 | if (defined $res && !(ref $res eq 'ARRAY' && $res->[0] == 404)) { 149 | return $res; 150 | } 151 | } 152 | 153 | # what Catalyst->handle_request does 154 | my $status = -1; 155 | my $c; 156 | eval { 157 | $c = $class->prepare(env => $env); 158 | $c->dispatch; 159 | $status = $c->finalize; 160 | }; 161 | 162 | # clear the $env ref to avoid leaks 163 | $self->env(undef); 164 | 165 | if (my $error = $@) { 166 | chomp $error; 167 | $class->log->error(qq/Caught exception in engine "$error"/); 168 | } 169 | 170 | if (my $coderef = $class->log->can('_flush')){ 171 | $class->log->$coderef(); 172 | } 173 | 174 | return [ 500, [ 'Content-Type' => 'text/plain', 'Content-Length' => 11 ], [ 'Bad request' ] ] 175 | unless $c; 176 | 177 | my $body = $c->res->body; 178 | $body = '' unless defined $body; 179 | if (!ref $body && $body eq '' && $self->{buffer}) { 180 | $body = [ $self->{buffer} ]; 181 | } elsif (ref($body) eq 'GLOB' || (Scalar::Util::blessed($body) && $body->can('getline'))) { 182 | # $body is FH 183 | } elsif (ref($body) eq 'CODE') { 184 | return $body; 185 | } else { 186 | $body = [ $body ]; 187 | } 188 | 189 | my $headers = []; 190 | $c->res->headers->scan(sub { my($k, $v) = @_; push @$headers, $k, _escape($v) }); 191 | return [ $c->res->status, $headers, $body ]; 192 | } 193 | 194 | sub _escape { 195 | local $_ = shift; 196 | s/(\r?\n)+/ /g; 197 | return $_; 198 | } 199 | 200 | no Moose; 201 | 202 | 1; 203 | __END__ 204 | 205 | =encoding utf-8 206 | 207 | =for stopwords 208 | 209 | =head1 NAME 210 | 211 | Catalyst::Engine::PSGI - PSGI engine for Catalyst 212 | 213 | =head1 WARNINGS 214 | 215 | B 216 | See L for details. 217 | 218 | =head1 SYNOPSIS 219 | 220 | # app.psgi 221 | use strict; 222 | use MyApp; 223 | 224 | MyApp->setup_engine('PSGI'); 225 | my $app = sub { MyApp->run(@_) }; 226 | 227 | =head1 DESCRIPTION 228 | 229 | Catalyst::Engine::PSGI is a Catalyst Engine that adapts Catalyst into the PSGI gateway protocol. 230 | 231 | =head1 COMPATIBILITY 232 | 233 | =over 4 234 | 235 | =item * 236 | 237 | Currently this engine works with Catalyst 5.8 (Catamoose) or newer. 238 | 239 | =item * 240 | 241 | Your application is supposed to work with any PSGI servers without any 242 | code modifications, but if your application uses C<< $c->res->write >> 243 | to do streaming write, this engine will buffer the ouput until your 244 | app finishes. 245 | 246 | To do real streaming with this engine, you should implement an 247 | IO::Handle-like object that responds to C method that returns 248 | chunk or undef when done, and set that object to C<< $c->res->body >>. 249 | 250 | Alternatively, it is possible to set the body to a code reference, 251 | which will be used to stream content as documented in the 252 | L. 253 | 254 | =item * 255 | 256 | When your application runs behind the frontend proxy like nginx or 257 | lighttpd, this Catalyst engine doesn't automatically recognize the 258 | incoming headers like C, because respecting these 259 | headers by default causes a potential security issue. 260 | 261 | You have to enable L or 262 | L to automatically promote those 263 | forwarded headers into C hence IP address of the request. 264 | 265 | ReverseProxy middleware is pretty simple and has no configuration 266 | while ForwardedHeaders allows you to configure which upstream host to 267 | trust, etc. 268 | 269 | =back 270 | 271 | =head1 AUTHOR 272 | 273 | Tatsuhiko Miyagawa Emiyagawa@bulknews.netE 274 | 275 | Most of the code is taken and modified from Catalyst::Engine::CGI. 276 | 277 | =head1 LICENSE 278 | 279 | This library is free software; you can redistribute it and/or modify 280 | it under the same terms as Perl itself. 281 | 282 | =head1 SEE ALSO 283 | 284 | I L I 285 | 286 | =cut 287 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2015 by Tatsuhiko Miyagawa . 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2015 by Tatsuhiko Miyagawa . 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2015 by Tatsuhiko Miyagawa . 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | 380 | --------------------------------------------------------------------------------