
Introduction to Web Application Development on PSGI / Plack
- Tutorial
Author: Dmitry Shamatrin.
With the permission of the author of the original articles of the cycle, I publish the cycle on Habré.
The original article on pragmaticperl.com magazine's website
PSGI / Plack is a modern way to write Perl web applications. Almost every framework supports or uses this technology in one way or another. The article provides 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 are changing very quickly. First there was CGI, then, when it was not enough, FastCGI appeared. FastCGI solved the main problem of CGI. In CGI, with each call, it was necessary to restart the server program; data was exchanged using STDIN and STDOUT. In FastCGI, communication with the server occurs via TCP / IP or Unix Domain Socket. Now we have a PSGI.
PSGI, as its developer Tatsuhiko Miyagawa says, is "Pearl barley superglue for web frameworks and web servers." The closest relatives are WSGI (Python) and Rack (Ruby). Here's the idea. The developer very often spends quite a lot of time to adapt his application to as many engines as possible, and PSGI provides a single interface for working with various servers, which greatly simplifies life.
Of course, the format of the article does not allow us to fully describe all the nuances, so hereinafter there will be only key points.
At this stage, this is all that is needed in order to begin to deal with the code directly.
Below is the code for the simplest PSGI application.
We save this application in the app.psgi file, or any other with the psgi extension. We look at the features. Then to the code. Then again on the features. It all fits together. We start.
When launching perl app.psgi, it silently executes, but the application is not running.
In order to run PSGI applications, we need a PSGI server. At the moment there are several servers.
All 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.
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. The launch is performed by the following command:
By default, PSGI servers use the 5000 port. We can change it by running the application with the --port 8080 switch, 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 many auxiliary functions, from receiving GET parameters to receiving cookie data. This all would not have been without the necessary functionality.
Plack is a PSGI implementation (Perl has a standard Pack module, so the implementation is 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 :: Request and Plack :: Response return different values of type Hash :: MultiValue, which are worth paying attention to.
The module, also authored by Tatsuhiko Miyagawa, is a hash, but with one nuance. It can store several values on one key. For example: $ hash-> get ('key') will return value, if there are several values for the key, then it will return the last, and if all values are needed, you can use the function $ hash-> get_all ('key'), then the result will be ('value1', 'value2'). Hash :: MultiValue also takes into account the context of the call, so be careful.
A module that contains functions for working with client requests. It contains a lot of methods; you can always find it on CPAN. In the framework of this article, further, we will use the following methods:
We will not consider methods, we only note that this is a very flexible router. For example, it allows you to install a handler (PSGI application) on a local address:
Result - calls to / will be redirected to the corresponding PSGI application. In this case, it is $ my_cool_app.
Routes can be nested, for example:
And these routes can be nested. In this example, everything that does not fall into / another is sent to /.
The base class for creating middleware applications. Middleware is "middleware." It is used when it is necessary to modify a PSGI request or a ready PSGI response, as well as provide specific conditions for launching a certain part of the application.
This is the simplest application using Plack. It perfectly demonstrates the principle of its work.
What you need to pay attention to. $ app is a link to a function. Very often, when there is a quick spelling of something similar, a symbol is forgotten; after ending the link to the function or creating Plack :: Request without passing $ env. Worth being careful.
You can use perl -c app.psgi to check the syntax.
Here is another important point regarding the writing of PSGI applications: when building the response body, make sure that there are bytes and not characters (for example, UTF-8). This error is found to be very difficult. Its presence leads to an empty server response with an error in psgi.error:
"Wide character at syswrite"
Our application starts similarly to the previous one.
Yes, Hello world is certainly not bad, but not very functional. Now, using all the tools, we will try to write the simplest application (but it will be much more useful, though).
We will write an API that implements three functions:
As a result of writing the code, we should get something that can do the following things:
To reverse the line, we will use the following construction:
To determine if a string is a palindrome, we will use the following function:
Plack :: Request allows you to receive parameters using the parameters method.
We finalize the application and bring it to the form:
We start. The first part is ready.
Going to localhost : 8080 /? String = 1 we will see an answer that will tell us that there is a line. 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, path_info parsing can be implemented as follows:
And now in $ path [0] is the path we need.
Important: after making changes to the code, the server must be restarted!
Now it’s worth taking a closer look at the router.
It makes it possible to use other PSGI applications as components. Still very useful will be the ability to connect middleware.
Let's remake the first application so that it uses a router.
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 worth making an important note: in this application, for simplicity, all functions are placed in one file. For more complex applications, this, of course, is not recommended.
Now connect the component to turn the line in the form of an application, which will be located at localhost : 8080 / reverse.
The verification address is localhost : 8080 / reverse? String = test% 20string.
2/3 tasks completed. However, in this case, $ app and $ reverse_app turned out to be very similar. Let's do a little refactoring. We make a function that will return another function (otherwise, a higher-order function).
Now the application looks like this:
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, an application of the form was obtained:
Verification link:
localhost : 8080 / palindrome? String = argentina% 20Manit% 20negra
Further articles will cover more advanced topics: middleware, sessions, cookies, server overview, with examples for each specific + small benchmarks, PSGI / Plack features and subtleties , PSGI under load, an overview of how to deploy PSGI applications, PSGI frameworks, profiling, Starman + Nginx, running CGI scripts in PSGI mode or “I have a CGI application, but I want a PSGI” and so on.
With the permission of the author of the original articles of the cycle, I publish the cycle on Habré.
The original article on pragmaticperl.com magazine's website
PSGI / Plack is a modern way to write Perl web applications. Almost every framework supports or uses this technology in one way or another. The article provides 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 are changing very quickly. First there was CGI, then, when it was not enough, FastCGI appeared. FastCGI solved the main problem of CGI. In CGI, with each call, it was necessary to restart the server program; data was exchanged using STDIN and STDOUT. In FastCGI, communication with the server occurs via TCP / IP or Unix Domain Socket. Now we have a PSGI.
What it is?
PSGI, as its developer Tatsuhiko Miyagawa says, is "Pearl barley superglue for web frameworks and web servers." The closest relatives are WSGI (Python) and Rack (Ruby). Here's the idea. The developer very often spends quite a lot of time to adapt his application to as many engines as possible, and PSGI provides a single interface for working with various servers, which greatly simplifies life.
Features
Of course, the format of the article does not allow us to fully describe all the nuances, so hereinafter there will be only key points.
- $ env is used to exchange information between the client and the server (it is a link to the hash);
- PSGI application - a link to a Perl function that takes $ env as a parameter;
- the function returns a link to an array that consists of 3 elements: HTTP status, [HTTP headers], [Response body];
- a function may return a link to another function, but this will be discussed in other more in-depth articles;
- the extension of the file containing the application startup 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;
# Производим необходимые манипуляции с $env
return [200, ['Content-Type' => 'text/plain'], ["hello, world\n"]];
};
We save this application in the app.psgi file, or any other with the psgi extension. We look at the features. Then to the code. Then again on the features. It all fits together. We start.
When launching perl app.psgi, it silently executes, but the application is not running.
Primary PSGI Servers
In order to run PSGI applications, we need a PSGI server. At the moment there are several servers.
- Twiggy
- Starman
- Feersum
- Corona
Briefly about PSGI servers
- Starman - pre-forking server; works pretty fast, can do a lot of things 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 is an asynchronous server based on Coro.
All 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. The launch is performed by the following command:
/usr/local/bin/starman app.psgi
By default, PSGI servers use the 5000 port. We can change it by running the application with the --port 8080 switch, 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 many auxiliary functions, from receiving GET parameters to receiving cookie data. This all would not have been without the necessary functionality.
Plack
Plack is a PSGI implementation (Perl has a standard Pack module, so the implementation is 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 different values of type Hash :: MultiValue, which are worth paying attention to.
Hash :: MultiValue
The module, also authored by Tatsuhiko Miyagawa, is a hash, but with one nuance. It can store several values on one key. For example: $ hash-> get ('key') will return value, if there are several values for the key, then it will return the last, and if all values are needed, you can use the function $ hash-> get_all ('key'), 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. It contains a lot of methods; you can always find it on CPAN. In the framework of 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 bar parameters) in the form Hash :: MultiValue;
- uploads - returns parameters (transferred 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 response headers;
- finalize - exit point, the last function of the application; returns a PSGI response according to the specification.
Plack :: Builder
We will not consider methods, we only note that this is a very flexible router. For example, it allows you to install a handler (PSGI application) on a local address:
my $app = builder {
mount "/" => builder { $my_cool_app; };
};
Result - calls to / will be redirected to the corresponding 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 fall into / another is sent to /.
Plack :: Middleware
The base class for creating middleware applications. Middleware is "middleware." It is used when it is necessary to modify a PSGI request or a ready PSGI response, as well as provide specific conditions for launching 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 perfectly demonstrates the principle of its work.
What you need to pay attention to. $ app is a link to a function. Very often, when there is a quick spelling of something similar, a symbol is forgotten; after ending the link to the function or creating Plack :: Request without passing $ env. Worth being careful.
You can use perl -c app.psgi to check the syntax.
Here is another important point regarding the writing of PSGI applications: when building the response body, make sure that there are bytes and not characters (for example, UTF-8). This error is found to be very difficult. Its presence leads to an empty server response with an error in psgi.error:
"Wide character at syswrite"
Our application starts similarly to the previous one.
- $ req is an object of type Plack :: Request; $ req contains client request data; it gets them from the $ env hash, which is passed to the function;
- $ res - Plack :: Response, this is the response to the client; built on request using the new_response method, takes a response code (200 in our case) as a parameter;
- body - sets the response body;
- finalize - conversion of the response object into a reference to the array of the PSGI response (which, as described above, consists of the status, headers and body of the response).
Yes, Hello world is certainly not bad, but not very functional. Now, using all the tools, we will try to write the simplest application (but it will be much more useful, though).
We will write an API that implements three functions:
- the first one will take the line as an input parameter and say that the line has been successfully accepted; address for contact - localhost: 8080 /;
- the second function will take a string as a parameter and return, for example, whether this string is a palindrome (a word or phrase that looks the same on both sides, for example, “Argentina beckons a black man”); 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 get something that can do the following things:
- when accessing / responding that everything is ok if the string parameter is passed;
- when contacting / palindrome, check for the presence of the string parameter, answer whether it is a palindrome or not;
- when accessing / reverse, give the inverted string.
To reverse the line, we will use the following construction:
$string = scalar reverse $string;
To determine if 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 receive parameters using the parameters method.
my $params = $req->parameters();
We 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 will tell us that there is a line. 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, path_info parsing 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
Now it’s worth taking a closer look at the router.
It makes it possible to use other PSGI applications as components. Still very useful will be the ability to connect middleware.
Let's remake the first application so that it uses a 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 worth making an important note: in this application, for simplicity, all functions are placed in one file. For more complex applications, this, of course, is not recommended.
Now connect the component to turn the line in the form of an application, which 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, $ app and $ reverse_app turned out to be very similar. Let's do a little refactoring. We 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, an application of the form was obtained:
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() };
};
Verification link:
localhost : 8080 / palindrome? String = argentina% 20Manit% 20negra
Further articles will cover more advanced topics: middleware, sessions, cookies, server overview, with examples for each specific + small benchmarks, PSGI / Plack features and subtleties , PSGI under load, an overview of how to deploy PSGI applications, PSGI frameworks, profiling, Starman + Nginx, running CGI scripts in PSGI mode or “I have a CGI application, but I want a PSGI” and so on.