File Coverage

File:lib/Railsish/Dispatcher.pm
Coverage:88.5%

linestmtbrancondsubpodtimecode
1package Railsish::Dispatcher;
2# ABSTRACT: The first handler for requests.
3
4
7
7
7
143
23
592
use Railsish::Router;
5
7
7
7
704
30
56
use YAML::Any;
6
7
7
7
574
32
104
use Hash::Merge qw(merge);
7
7
7
7
152
24
100
use Encode;
8
7
7
7
141
22
98
use Railsish::CoreHelpers;
9
7
7
7
557
35
122
use MIME::Base64;
10
7
7
7
569
37
142
use Crypt::CBC;
11
7
7
7
153
22
129
use JSON::XS;
12
13sub dispatch {
14
19
0
115
    my ($class, $request) = @_;
15
19
160
    my $path = $request->path;
16
17
19
86
    $path =~ s/\.([a-z]+)$//;
18
19
212
    my $format = $1 || "html";
19
20
19
151
    my $method = lc($request->method);
21
19
588
    if ($method eq 'post') {
22
2
15
        if (my $m = $request->param("_method")) {
23
0
0
            $method = lc($m);
24        }
25    }
26
19
518
    my $matched = Railsish::Router->match(
27        $path, conditions => { method => $method }
28    );
29
30
19
420
    my $response = HTTP::Engine::Response->new;
31
19
1502
    unless($matched) {
32
4
28
        $response->body("internal server error");
33
4
261
        $response->status(500);
34
4
210
        return $response;
35    }
36
37
15
149
    my $mapping = $matched->mapping;
38
39
15
292
    my $controller = $mapping->{controller};
40
15
104
    my $action = $mapping->{action} || "index";
41
42
15
90
    my $controller_class = ucfirst(lc($controller)) . "Controller";
43
15
256
    my $sub = $controller_class->can($action);
44
45
15
191
    die "action $action is not defined in $controller_class." unless $sub;
46
47
15
70
    my %params = _preprocessed_parameters($request);
48
49
15
114
    my $params = merge(\%params, $mapping);
50
51
15
5213
    $Railsish::Controller::params = $params;
52
15
65
    $Railsish::Controller::request = $request;
53
15
50
    $Railsish::Controller::response = $response;
54
15
45
    $Railsish::Controller::controller = $controller;
55
15
410
    $Railsish::Controller::action = $action;
56
15
42
    $Railsish::Controller::format = $format;
57
58
15
73
    my $session = $Railsish::Controller::session = _load_session($request);
59
60
15
157
    logger->debug(Dump({
61        request_path => $path,
62        method => $method,
63        controller => $controller,
64        action => $action,
65        params => $params,
66        session => $session
67    }));
68
69
15
2668
    $sub->();
70
71
15
1157
    _store_session($response, $session);
72
73
15
47
    return $response;
74}
75
76sub _preprocessed_parameters {
77
15
63
    my ($request) = @_;
78
15
15
46
128
    my %params = %{$request->parameters};
79
15
1300
    for (keys %params) {
80
0
0
        $params{$_} = Encode::decode_utf8( $params{$_} );
81
82
0
0
        if (/^(\w+)\[(\w+)\]$/) {
83
0
0
            $params{$1} ||= {};
84
0
0
            $params{$1}->{$2}= delete $params{$_};
85        }
86    }
87
88
15
91
    return %params;
89}
90
91sub _load_session {
92
15
57
    my ($request) = @_;
93
94    # XXX: the -key here should be given from app config.
95
15
368
    my $cipher = Crypt::CBC->new(-key => "railsish", -cipher => "Rijndael");
96
15
5154
    my $session = {};
97
15
139
    my $session_cookie = $request->cookies->{_railsish_session};
98
15
626
    if ($session_cookie) {
99
1
9
        my $ciphertext_base64 = $session_cookie->value;
100
1
9
        my $ciphertext_unbase64 = decode_base64($ciphertext_base64);
101
1
7
        my $json = $cipher->decrypt($ciphertext_unbase64);
102
1
793
        $session = decode_json($json);
103    }
104
15
184
    return $session;
105}
106
107sub _store_session {
108
15
73
    my ($response, $session) = @_;
109
15
159
    my $cipher = Crypt::CBC->new(-key => "railsish", -cipher => "Rijndael");
110
15
7151
    my $json = encode_json($session);
111
15
131
    my $ciphertext = $cipher->encrypt($json);
112
15
36581
    my $ciphertext_base64 = encode_base64($ciphertext, '');
113
15
186
    $response->cookies->{_railsish_session} = {
114        value => $ciphertext_base64
115    };
116}
117
1181;
119
120 - 124
=head1 DESCRIPTION

This class contains the first handler for requests.

=cut