Background
Web applications run within the context of a web server. Web client (browser, web crawler, a script making API calls etc) sends a request to a web server (Apache, Nginx, Starman etc) that the server either maps to a file system resource (for static pages) or, in case of dynamic pages, dispatches, according to the server configuration, to the relevant web application.The purpose of the PSGI specification is to help create portable web applications, applications that are independent of the web server environment they are run in, and can be, therefore run with ease under different web servers.
A PSGI compatible web application is a code reference, that accepts input and provides output in a prescribed format.
PSGI Middleware is a PSGI application, that can run another PSGI (web) application and can do preprocessing of an HTTP request and/or postprocessing after HTTP response is received. It is a wrapper around the web application/framework and it sits between the web server and the application/framework. From the server perspective, the middleware component is a web application and from the web application perspective, it is a web server.
PSCI Specification
PSGI application accepts input in the form of a hash reference with CGI like header information and psgi.xxx/psgix.yyy keys. The prescribed format of its output is:- array reference containing three elements:
- HTTP status
- arrayref with HTTP headers
- response body:
- arrayref with the response content
- a handle (Perl built-in filehandle or an IO::Handle-like object) containing the response body as byte strings
- code reference for a delayed/streaming response
Example of a hashref input providing information about the environment:
$env = {
'SERVER_PROTOCOL' => 'HTTP/1.1',
'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.5',
'psgi.input' => \*{'HTTP::Server::PSGI::$input'},
'psgi.errors' => *::STDERR,
'psgix.io' => bless( \*Symbol::GEN1, 'IO::Socket::INET' ),
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'psgi.version' => [
1,
1
],
'HTTP_USER_AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0',
'REMOTE_PORT' => 45541,
'SCRIPT_NAME' => '',
'REQUEST_URI' => '/',
'psgix.input.buffered' => 1,
'PATH_INFO' => '/',
'psgi.run_once' => '',
'REQUEST_METHOD' => 'GET',
'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
'psgi.url_scheme' => 'http',
'psgi.multithread' => '',
'psgi.streaming' => 1,
'REMOTE_ADDR' => '127.0.0.1',
'psgi.multiprocess' => '',
'QUERY_STRING' => '',
'SERVER_PORT' => 5000,
'SERVER_NAME' => 'localhost',
'HTTP_HOST' => '127.0.0.1:5000',
'psgi.nonblocking' => '',
'HTTP_CONNECTION' => 'keep-alive',
'psgix.harakiri' => 1
};
Example of output with an arrayref body:
$response = [
200,
['Content-Type' => 'text/html'],
['<h2>Hello World</h2>', 'How are you today?' ]
]
Example of output with a handle body:
$body = new IO::File $file, "r"
$body = MyClass->new # must implement read(), possibly seek()
$response = [
200,
['Content-Type' => 'text/plain'],
$body,
]
Plack
Plack is a Perl module, a toolkit for using the PSGI stack: middleware, helpers and adapters to web servers. Standard adapters included in the module are for CGI, FCGI, Apache 1 and 2 and HTTP::Server::Simple. Others can be found on CPAN. Plack comes with its own standalone server HTTP::Server::PSGI.Utilities provided by Plack are aimed at web server, middleware and web framework authors. While it is possible to write PSGI web applications, the recommended way is to build web applications on top of web frameworks. Catalyst, Dancer, Mojolicious are some of the web frameworks supporting PSGI. PSGI enabled web frameworks have an adaptor/engine (middleware component) conforming to the PSGI specification.
Examples
An example of running a PSGI application in three different ways:
1. running the application using the built-in Plack server
2. running the application using the Apache server
3. running the application using the built-in Plack server with Apache as a proxy server
Simple PSGI application
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #!/usr/bin/perl use strict; use warnings; use JSON qw(to_json); my $data = { data => { message1 => 'Hello world', message2 => 'Hello family', message3 => 'Hello son', }, }; my $serialized = to_json($data); my $app = sub { my ($env) = @_; # PSGI input, not used in this simple app return [ # PSGI output '200', [ 'Content-Type' => 'application/json' ], [ $serialized ], ]; }; |
More complex example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | #!/usr/bin/perl =head2 plack_app1.pl run: plackup -o localhost -p 8000 plack_app1.pl =cut use strict; use warnings; use v5.018; use utf8; #use Data::Dumper qw(Dumper); #$Data::Dumper::Sortkeys = 1; use Plack::Request; use CHI; =head3 Cache messages for later display together with the latest message messages are cached on the file system, so will survive the server restart the storage is limited to 4kb =cut my $cache = CHI->new( driver => 'FastMmap', root_dir => '/tmp/plack_app1_cache', cache_size => '4k'); my $cache_key = "${0}_message"; $cache->remove($cache_key); =head3 Plackup subroutine reference input: hashref containing request information output: arrayref of the prescribed format =cut my $app = sub { my $env = shift; # Create the HTML form where a message can be input my $html = _create_form(); # Wraps the $env in a Plack request object my $request = Plack::Request->new($env); # Recover old messages for display, store them all my $old_messages = $cache->get($cache_key) || ''; # The request is a sent form with filled in message field if ($request->param('message')) { say "[$request->param('message')]"; my $current_message = $request->param('message'); my $message = $old_messages . "<br />$current_message"; $message = (length $message > 2000) ? $current_message : $message; $cache->set($cache_key, $message); # Append the messages to the form html $message = $old_messages . "<br /><b>$current_message</b>"; $html .= "You told me:<br />$message"; } elsif ($old_messages) { $html .= "Old messages: <br />$old_messages"; } return [ '200', [ 'Content-Type' => 'text/html' ], [ $html ], ]; }; =head2 SUBROUTINES =head3 _create_form =cut sub _create_form { return q{ <hr> <form> <input name="message"> <input type="submit" value="Tell me"> </form> <hr> } } |
Inspired by Dave Cross's Plack application example
A) Built-in web server
plackup /path/to/my/my_app.psgi
B) Apache 2 - using directly as a web server
In /etc/apache2/sites-enabled/psgi_apps.conf:
<VirtualHost *:4000> LogLevel debug ErrorLog ${APACHE_LOG_DIR}/error_psgi.log CustomLog ${APACHE_LOG_DIR}/access_psgi.log combined PerlOptions +Parent <Location /test_my_app> SetHandler perl-script PerlResponseHandler Plack::Handler::Apache2 # PSGI adaptor for apache PerlSetVar psgi_app /var/www/apps/my_app.psgi </Location> </VirtualHost>
C) Apache 2 - using as a proxy for the built-in Plack web serverThe application runs locally on port 5000, on which the built-in Plack web server is listening (http://localhost:5000/). Locally or externally (if configured) the application can be accessed through:
a) cd /var/www; plackup --host localhost /my_app.psgi HTTP::Server::PSGI: Accepting connections at http://localhost:5000/ b) Apache configuration - In /etc/apache2/sites-enabled/psgi_apps.conf: <VirtualHost *:4000> LogLevel debug ErrorLog ${APACHE_LOG_DIR}/error_psgi.log CustomLog ${APACHE_LOG_DIR}/access_psgi.log combined PerlOptions +Parent ## Proxying an application ##------------------------ ProxyPass /test_my_app http://localhost:5000/ ProxyPassReverse /test_my_app http://localhost:5000/ <Location /test_my_app> Require all granted # Apache 2.4 or
Order allow,deny # Apache 2.2
Allow from all
</Location> </VirtualHost> |
Note
For the proxying to work, several prerequisites need to be satisfied:
- Apache proxy and proxy_http modules must be loaded
- on Ubuntu: a2enmod proxy proxy_http
- Uncomment the <proxy *> block in mods-enabled/proxy.conf
- Create a new site configuration sites-available/psgi_apps.conf and enable the configuration (on Ubuntu: a2ensite psgi_apps)
- In sites-available/psgi_apps.conf: allow access to the proxied location if the proxy configuration by default disallows access
No comments:
Post a Comment
Note: only a member of this blog may post a comment.