Geest - Perl Port of Kage
# app.psgi
use strict;
use Geest;
my $server = Geest->new;
$server->add_master(production => (
host => "myapp.production.example.com",
port => 80
));
$server->add_backend(staging => (
host => "myapp.staging.example.com",
port => 8000
));
$server->on(select_backend => sub {
return [ "staging", "production" ]
});
$server->on(backend_finished => sub {
my $responses = shift;
my $data_production = $responses->{production}->{response}->decoded_content;
my $data_staging = $responses->{staging}->{response}->decoded_content;
...
});
$server->psgi_app;
# run it
twiggy -a app.psgi
This is a port of kage (https://github.com/cookpad/kage) to perl. Why does this exist? Because I felt like writing it, duh.
In its essence, this module is just an HTTP proxy server. It receives requests from the client, and broadcasts the requests to multiple backends.
Why would you want this? For example, you can put this in front of your staging app server while you're refactoring your code. Geest can be configured to access your production server AND your refactored server, and to show any differences in the content received. This way you can avoid breaking your existing code.
Backends consist of one "master", and one or more others. The response received from the master is preferred over other backends, but in cases where you specified to retrieve from backends other than the master, the fastest response is used.
Backends can be registered through the add_backend()
method (or
add_master()
, if you are adding a master backend)
$server->add_backend(name_of_backend => (
host => ...,
port => ...
));
By default all backends are used, but you may change this by setting the
select_backend
hook:
$server->on(select_backend => sub {
my ($request) = @_; # HTTP::Request object
...
});
The callback receives the HTTP::Request object representing the original client request. You can, for example, choose to send all GET requests to servers A and B, and everything else to only B
$server->on(select_backend => sub {
my ($request) = @_;
if ($request->method eq 'GET') {
return [ 'A', 'B' ];
} else {
return [ 'A' ];
}
});
The proxy asynchronously connects to the hosts specified by the method
above, and then send them the same request. If you want to change the
requests being sent to each backend, you can do so in the munge_request
hook:
$server->on(munge_request => sub {
my ($backend, $request) = @_;
# Do what you will to $request
});
When the backend responds, the response is accumulated in memory. If the list of backends contains the "master" backend, then the client will receive the response when the proxy receives a response from the master. Otherwise, the first response is used to reply to the client.
When all the backends are finished, the backend_finished
hook is called.
You can do any verification you need to do in this hook. For example,
the most simple check would be to check the difference between the
responses:
use Text::Diff;
$server->on(backend_finished => sub {
my ($responses) = @_;
# $responses = {
# name_of_backend => {
# backend => ..., # Geest::Backend object
# response => ..., # HTTP::Response object
# request => ..., # HTTP::Request object
# },
# ...
# };
if (! $responses->{prod} || ! $responses->{dev}) {
return;
}
my $data_prod = $responses->{prod}->{response}->decoded_content;
my $data_dev = $responses->{dev}->{response}->decoded_content;
if ($data_prod ne $data_dev) {
# You probably want to check that both responses are
# content_type -> text/* before running diff()
print STDERR diff(\$data_prod, $data_dev);
}
});
And voila, you get to check if you have any differences between your current development version and the production server.
When you set GEEST_DEBUG
to a non-zero value, debug output will be available.
Daisuke Maki <daisuke@endeworks.jp>