Author:
Dmitriy Shamatrin.With the permission of the author of the original articles of the cycle, I publish a cycle on Habré.
The original article on the website of the magazine pragmaticperl.comPSGI / Plack is a modern way to write Perl web applications. Virtually every framework supports or uses this technology in one way or another. The article presents a brief introduction that will help you quickly navigate and move on.
We live in a time when technologies and approaches in the field of web development change very quickly. First there was CGI, then when it was not enough, FastCGI appeared. FastCGI solved the main CGI problem. In CGI, with each call, it was necessary to restart the server program, data exchange was performed using STDIN and STDOUT. In FastCGI, interaction with the server occurs via TCP / IP or Unix Domain Socket. Now we have PSGI.
')
What it is?
PSGI, as its developer Tatsuhiko Miyagawa says, is “Pearl barley for web frameworks and web servers”. The closest relatives are WSGI (Python) and Rack (Ruby). The idea here is this. A developer often spends quite a lot of time to adapt his application to as many engines as possible, while PSGI provides a single interface for working with various servers, which greatly simplifies life.
Special features
Of course, the article format does not allow to fully describe all the nuances, therefore, hereinafter there will be only key points.
- $ env is used to exchange information between the client and the server (this is a link to the hash);
- PSGI application - a link to the Perl-function, which takes as a parameter $ env;
- the function returns a link to an array, which consists of 3 elements: HTTP status, [HTTP headers], [Response body];
- the function may return a link to another function, but this will be discussed in other more in-depth articles;
- the file extension containing the application launch code must be .psgi.
At this stage, this is all that is needed in order to begin to deal with the code directly.
PSGI application
Below is the code for the simplest PSGI application.
my $app = sub { my $env = shift;
We save this application in the app.psgi file, or any other with the psgi extension. We look at the features. Then on the code. Then again on the features. It all fits. We start.
When you run perl app.psgi, it "silently" works, but the application is not running.
Basic PSGI Servers
In order to run PSGI applications we need a PSGI server. At the moment there are several servers.
- Twiggy
- Starman
- Feersum
- Corona
PSGI Servers in Brief
- Starman - pre-forking server; works pretty fast, can do a lot out of the box, support for unix domain sockets, for example;
- Twiggy - asynchronous server, based on AnyEvent;
- Feersum - subjectively, the fastest of this entire list; the main part is implemented as XS-modules. Based on EV;
- Corona - asynchronous server, based on Coro.
All of these servers are available on CPAN. In the future, we will use Starman, then change it to Twiggy, and then to Feersum. Each task has its own server.
Application launch
The application will run exactly the same on any of these servers, maybe under Corona it will have to be slightly modified. After installing the server, and in our case it is Starman, the starman executable should appear in / usr / bin or / usr / local / bin. Start is made by the following command:
/usr/local/bin/starman app.psgi
By default, PSGI servers use port 5000. We can change it by running the application with the key --port 8080, for example. Recall that PSGI is a specification. In this case, we used this specification to write a simple web application. Obviously, for normal development we need to implement a lot of auxiliary functions, from getting GET parameters to getting cookie data. This all would not be without the necessary functionality.
Plack
Plack is a PSGI implementation (Perl has a standard Pack module, because the implementation was named Plack). Plack makes life easier for us, as developers. It contains a huge number of functions for working with $ env.
In the basic configuration Plack consists of a fairly large number of modules. At this stage, we are only interested in these:
- Plack
- Plack :: Request
- Plack :: Response
- Plack :: Builder
- Plack :: Middleware
Plack :: Request and Plack :: Response return various values of type Hash :: MultiValue, which should be noted.
Hash :: MultiValue
The module, the author of which is also Tatsuhiko Miyagawa, is a hash, but with one nuance. It can store multiple values in a single key. For example: $ hash-> get ('key') returns value, if there are several values by key, then it will return the last one, and if all values are needed, then you can use the $ hash-> get_all ('key') function, then the result will be ('value1', 'value2'). Hash :: MultiValue also takes into account the context of the call, so be careful.
Plack :: Request
A module that contains functions for working with client requests. There are many methods, you can always find it on CPAN. Within this article, further, we will use the following methods:
- env - returns $ env;
- method - returns the request method: GET, POST, OPTIONS, HEAD, etc .;
- path_info is an important method; returns the local path to the current script;
- parameters - returns parameters (x-www-form-url-encoded, address line parameters) in the form of Hash :: MultiValue;
- uploads - returns parameters (transmitted using multipart-form-data) also in the form of Hash :: MultiValue.
Plack :: Response
- status - sets the status (HTTP response code), when called without parameters, returns the previously set status;
- headers - sets the response headers;
- finalize - exit point, the last function of the application; returns PSGI response according to specification.
Plack :: Builder
We will not consider the methods; we only note that this is a very flexible router. For example, it allows you to install a handler (PSGI application) to a local address:
my $app = builder { mount "/" => builder { $my_cool_app; }; };
Result - calls to / will be redirected to the appropriate PSGI application. In this case, it is $ my_cool_app.
Routes can be nested, for example:
my $app = builder { mount "/" => builder { mount "/another" => builder { $my_another_cool_app; }; mount "/" => builder { $my_cool_app; }; }; };
And these routes can be nested. In this example, everything that does not go into / another is sent to /.
Plack :: Middleware
Base class for creating middleware applications. Middleware is “middleware”. It is used when you need to modify a PSGI request or a ready PSGI response, as well as provide specific conditions for running a certain part of the application.
Rewrite the application on Plack
use strict; use Plack; use Plack::Request; my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->body('Hello World!'); return $res->finalize(); };
This is the simplest application using Plack. It clearly demonstrates the principle of its work.
What to look for. $ app - function reference. Very often, when there is a rapid spelling of something similar, a symbol is forgotten; after the end of a reference to a function or the creation of a Plack :: Request without passing $ env. It should be attentive.
You can use perl -c app.psgi to check the syntax.
Here is another important point regarding the writing of PSGI applications: when forming the response body, you should make sure that there are bytes, and not characters (for example, UTF-8). Such an error is very difficult. Its presence results in an empty server response with an error in psgi.error:
"Wide character at syswrite"
Our application starts like the previous one.
- $ req is an object of type Plack :: Request; $ req contains customer request data; it gets them from the $ env hash, which is passed to the function;
- $ res - Plack :: Response, this is the answer to the client; built on request using the new_response method, accepts a response code as a parameter (200 in our case);
- body - sets the response body;
- finalize - the transformation of the response object into a reference to an array of a PSGI response (which, as described above, consists of the status, headers, and body of the response).
Yes, Hello world is certainly not bad, but a little functional. Now, using all the tools, let's try to write the simplest application (but it will be much more useful, really).
We write an API that implements three functions:
- the first will take the string as an input parameter and say that the string was successfully accepted; address to address - localhost: 8080 /;
- second function will take a string as parameter and return, for example, whether it is a palindrome string (word or phrase that appears equally on both sides, for example - "Argentina mannitol Negro"); will be located at localhost: 8080 / palindrome;
- the third function will take the same string as a parameter and return it upside down; will be located at localhost: 8080 / reverse.
As a result of writing the code, we should have something that can do the following things:
- when accessing / responding that everything is ok, if the string parameter is passed;
- when accessing / palindrome, check the presence of the string parameter, respond whether it is a palindrome or not;
- when accessing / reverse, give an inverted string.
To turn the string, we will use the following construction:
$string = scalar reverse $string;
To determine whether a string is a palindrome, we will use the following function:
sub palindrome { my $string = shift; $string = lc $string; $string =~ s/\s//gs; if ($string eq scalar reverse $string) { return 1; } else { return 0; } }
application
Plack :: Request allows you to get parameters using the parameters method.
my $params = $req->parameters();
We will finalize the application and bring it to the form:
use strict; use Plack; use Plack::Request; my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); my $params = $req->parameters(); my $body; if ($params->{string}) { $body = 'string exists'; } else { $body = 'empty string'; } $res->body($body); return $res->finalize(); };
We start. The first part is ready.
Going to
localhost : 8080 /? String = 1 we will see an answer that tells us that there is a string. Going to
localhost : 8080 / will return an error.
The rest of the logic can be implemented directly in the same application, dividing the logic by path_info, which will contain the current path. For reference, parsing path_info can be implemented as follows:
my @path = split '\/', $req->path_info(); shift @path;
And now in $ path [0] is the path we need.
Important: after making changes to the code, the server must be restarted!Plack :: Builder
And now you should take a closer look at the router.
It allows you to use other PSGI applications as components. The ability to connect middleware will also be very useful.
We will remake the first application so that it uses the router.
use strict; use Plack; use Plack::Request; use Plack::Builder; my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->header('Content-Type' => 'text/html', charset => 'Utf-8'); my $params = $req->parameters(); my $body; if ($params->{string}) { $body = 'string exists'; } else { $body = 'empty string'; } $res->body($body); return $res->finalize(); }; my $main_app = builder { mount "/" => builder { $app; }; };
Now $ main_app is the main PSGI application. $ app joins it at /. In addition, a function was added to set the headers in the response (via the header method). It is important to make an important note: in this application, to simplify all the functions are placed in one file. For more complex applications, of course, this is not recommended.
Now we connect the component to turn the string in the form of an application that will be located at
localhost : 8080 / reverse.
use strict; use Plack; use Plack::Request; use Plack::Builder; my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->header('Content-Type' => 'text/html', charset => 'Utf-8'); my $params = $req->parameters(); my $body; if ($params->{string}) { $body = 'string exists'; } else { $body = 'empty string'; } $res->body($body); return $res->finalize(); }; my $reverse_app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); my $params = $req->parameters(); my $body; if ($params->{string}) { $body = scalar reverse $params->{string}; } else { $body = 'empty string'; } $res->body($body); return $res->finalize(); }; my $main_app = builder { mount "/reverse" => builder { $reverse_app }; mount "/" => builder { $app; }; };
The verification address is
localhost : 8080 / reverse? String = test% 20string.
2/3 tasks completed. However, in this case, the $ app and $ reverse_app are very similar. Let's do a little refactoring. Make a function that will return another function (otherwise, a higher order function).
Now the application looks like this:
use strict; use Plack; use Plack::Request; use Plack::Builder; sub build_app { my $param = shift; return sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->header('Content-Type' => 'text/html', charset => 'Utf-8'); my $params = $req->parameters(); my $body; if ($params->{string}) { if ($param eq 'reverse') { $body = scalar reverse $params->{string}; } else { $body = 'string exists'; } } else { $body = 'empty string'; } $res->body($body); return $res->finalize(); }; } my $main_app = builder { mount "/reverse" => builder { build_app('reverse') }; mount "/" => builder { build_app() }; };
So much better. Now add the third and last function to our API and finally finish the application. As a result of all the improvements, the following application appeared:
use strict; use Plack; use Plack::Request; use Plack::Builder; sub build_app { my $param = shift; return sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->header('Content-Type' => 'text/html', charset => 'Utf-8'); my $params = $req->parameters(); my $body; if ($params->{string}) { if ($param eq 'reverse') { $body = scalar reverse $params->{string}; } elsif ($param eq 'palindrome') { $body = palindrome($params->{string}) ? 'Palindrome' : 'Not a palindrome'; } else { $body = 'string exists'; } } else { $body = 'empty string'; } $res->body($body); return $res->finalize(); }; } sub palindrome { my $string = shift; $string = lc $string; $string =~ s/\s//gs; if ($string eq scalar reverse $string) { return 1; } else { return 0; } } my $main_app = builder { mount "/reverse" => builder { build_app('reverse') }; mount "/palindrome" => builder { build_app('palindrome') }; mount "/" => builder { build_app() }; };
Link to check:
localhost : 8080 / palindrome? string = argentina% 20Manit% 20negra
Further articles will cover more in-depth topics: middleware, sessions, cookies, server overview, with examples for each specific + small benchmarks, PSGI / Plack features and subtleties, PSGI under load, review of ways to deploy PSGI applications, PSGI framework, profiling , Starman + Nginx, running CGI scripts in PSGI mode or “I have a CGI application, but I want PSGI” and so on.