Lately I've been toying a lot with [[http://plackperl.org/][Plack]] and two Perl web framework: [[http://search.cpan.org/perldoc?Tatsumaki][Tatsumaki]] and [[http://search.cpan.org/perldoc?Dancer][Dancer]]. I use both of them for different purposes, as their features complete each other. ** Plack If you don't already know what Plack is, you would want to take a look at the following Plack resources: - [[http://plackperl.org][Plack (redesigned) website]] - [[http://search.cpan.org/perldoc?Plack][Plack documentation]] - [[http://bulknews.typepad.com/blog/2009/11/plack-and-psgi-screencast-and-feedbacks.html][Miyagawa's screencast]] - [[http://advent.plackperl.org/][Plack advent calendar]] #+BEGIN_QUOTE As [[http://www.sukria.net/][sukria]] is planning to talk about [[http://perldancer.org][Dancer]] during the [[http://journeesperl.fr/fpw2010/index.html][FPW 2010]], I will probably do a talk about Plack. #+END_QUOTE After reading some code, I've started to write two middleware: the first one add ETag header to the HTTP response, and the second one provides a way to limit access to your application. *** Plack::Middleware::ETag This middleware is really simple: for each request, an [[http://en.wikipedia.org/wiki/HTTP_ETag][ETag]] header is added to the response. The ETag value is a sha1 of the response's content. In case the content is a file, it works like apache, using various information from the file: inode, modified time and size. This middleware can be used with [[http://search.cpan.org/perldoc?Plack::Middleware::ConditionalGET][Plack::Middleware::ConditionalGET]], so the client will have the ETag information for the page, and when he will do a request next time, it will send an "if-modified" header. If the ETag is the same, a 304 response will be send, meaning the content have not been modified. This module is [[http://search.cpan.org/perldoc?Plack::Middleware::ETag][available on CPAN]]. Let's see how it works. First, we create a really simple application (we call it app.psgi): #+BEGIN_SRC perl #!/usr/bin/env perl use strict; use warnings; use Plack::Builder; builder { enable "Plack::Middleware::ConditionalGET"; enable "Plack::Middleware::ETag"; sub { ['200', ['Content-Type' => 'text/html'], ['Hello world']]; }; }; #+END_SRC Now we can test it: #+BEGIN_EXAMPLE % plackup app.psgi& % curl -D - http://localhost:5000 HTTP/1.0 200 OK Date: Sat, 03 Apr 2010 09:31:43 GMT Server: HTTP::Server::PSGI Content-Type: text/html ETag: 7b502c3a1f48c8609ae212cdfb639dee39673f5e Content-Length: 11 % curl -H "If-None-Match: 7b502c3a1f48c8609ae212cdfb639dee39673f5e" -D - http://localhost:5000 HTTP/1.0 304 Not Modified Date: Sat, 03 Apr 2010 09:31:45 GMT Server: HTTP::Server::PSGI ETag: 7b502c3a1f48c8609ae212cdfb639dee39673f5e #+END_EXAMPLE *** Plack::Middleware::Throttle [[http://git.lumberjaph.net/p5-plack-middleware-throttle.git/][With this middleware]], you can control how many times you want to provide an access to your application. This module is not yet on CPAN, has I want to add some features, but you can get the code from git. There is four methods to control access: - Plack::Middleware::Throttle::Hourly: how many times in one hour someone can access the application - P::M::T::Daily: the same, but for a day - P::M::T::Interval: which interval the client must wait between two query - by combining the three previous methods To store sessions informations, you can use any cache backend that provides =get=, =set= and =incr= methods. By default, if no backend is provided, it will store informations in a hash. You can easily modify the defaults throttling strategies by subclassing all the classes. Let's write another application to test it: #+BEGIN_SRC perl #!/usr/bin/env perl use strict; use warnings; use Plack::Builder; builder { enable "Plack::Middleware::Throttle::Hourly", max => 2; sub { ['200', ['Content-Type' => 'text/html'], ['Hello world']]; }; }; #+END_SRC then test #+BEGIN_EXAMPLE % curl -D - http://localhost:5000/ HTTP/1.0 200 OK Date: Sat, 03 Apr 2010 09:57:40 GMT Server: HTTP::Server::PSGI Content-Type: text/html X-RateLimit-Limit: 2 X-RateLimit-Remaining: 1 X-RateLimit-Reset: 140 Content-Length: 11 Hello world % curl -D - http://localhost:5000/ HTTP/1.0 200 OK Date: Sat, 03 Apr 2010 09:57:40 GMT Server: HTTP::Server::PSGI Content-Type: text/html X-RateLimit-Limit: 2 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 140 Content-Length: 11 Hello world % curl -D - http://localhost:5000/ HTTP/1.0 503 Service Unavailable Date: Sat, 03 Apr 2010 09:57:41 GMT Server: HTTP::Server::PSGI Content-Type: text/plain X-RateLimit-Reset: 139 Content-Length: 15 Over rate limit #+END_EXAMPLE Some HTTP headers are added to the response : - *X-RateLimit-Limit*: how many request can be done - *X-RateLimit-Remaining*: how many requests are available - *X-RateLimit-Reset*: when will the counter be reseted (in seconds) This middleware could be a very good companion to the [[http://www.sukria.net/fr/archives/2010/03/19/let-the-dancer-rest/][Dancer REST stuff]] [[/easily-create-rest-interface-with-the-dancer-1.170/][added recently]]. ** another Tatsumaki application with Plack middlewares To demonstrate the use of this two middleware, [[http://git.lumberjaph.net/p5-feeddiscovery.git/][I wrote a small application]] with Tatsumaki. This application fetch a page, parse it to find all the feeds declared, and return a JSON with the result. #+BEGIN_EXAMPLE % GET http://feeddiscover.tirnan0g.org/?url=http://lumberjaph.net/blog/ #+END_EXAMPLE will return #+BEGIN_EXAMPLE % [{"href":"http://lumberjaph.net/blog/index.php/feed/","type":"application/rss+xml","title":"i'm a lumberjaph RSS Feed"}] #+END_EXAMPLE This application is composed of one handler, that handle only *GET* request. The request will fetch the url given in the *url* parameter, scrap the content to find the links to feeds, and cache the result with Redis. The response is a JSON string with the informations. The interesting part is the app.psgi file: #+BEGIN_SRC perl my $app = Tatsumaki::Application->new(['/' => 'FeedDiscovery::Handler'],); builder { enable "Plack::Middleware::ConditionalGET"; enable "Plack::Middleware::ETag"; enable "Plack::Middleware::Throttle::Hourly", backend => Redis->new(server => '127.0.0.1:6379',), max => 100; $app; }; #+END_SRC The application itself is really simple: for a given url, the Tatsumaki::HTTPClient fetch an url, I use [[http://search.cpan.org/perldoc?Web::Scraper][Web::Scraper]] to find the *link rel="alternate"* from the page, if something is found, it's stored in Redis, then a JSON string is returned to the client.