remove use of 'use base'
[catagits/Web-Simple.git] / lib / Web / Dispatch / HTTPMethods.pm
CommitLineData
456dc2bb 1package Web::Dispatch::HTTPMethods;
2
3use strictures 1;
4use Web::Dispatch::Predicates qw(match_method);
5use Scalar::Util qw(blessed);
659a3608 6use Exporter 'import';
456dc2bb 7
8our @EXPORT = qw(GET HEAD POST PUT DELETE OPTIONS);
9
10sub HEAD(&;@) { method_helper(HEAD => @_) }
11sub GET(&;@) { method_helper(GET => @_) }
12sub POST(&;@) { method_helper(POST => @_) }
13sub PUT(&;@) { method_helper(PUT => @_) }
14sub DELETE(&;@) { method_helper(DELETE => @_) }
15sub OPTIONS(&;@) { method_helper(OPTIONS => @_) }
16
17{
18 package Web::Dispatch::HTTPMethods::Endpoint;
19
20 sub new { bless { map { $_=>0 } @EXPORT }, shift }
21 sub hdrs { 'Content-Type' => 'text/plain' }
22
23 sub create_implicit_HEAD {
24 my $self = shift;
25 if($self->{GET} && not $self->{HEAD}) {
26 $self->{HEAD} = sub { [ @{$self->{GET}->(@_)}[0,1], []] };
27 }
28 }
29
30 sub create_implicit_OPTIONS {
31 my $self = shift;
32 $self->{OPTIONS} = sub {
33 [200, [$self->hdrs, Allow=>$self->allowed] , [] ];
34 };
35 }
36
37 sub allowed { join ',', grep { $_[0]->{$_} } @EXPORT }
38
39 sub to_app {
40 my $self = shift;
41 my $implicit_HEAD = $self->create_implicit_HEAD;
42 my $implicit_OPTIONS = $self->create_implicit_OPTIONS;
43
44 return sub {
45 my $env = shift;
46 if($env->{REQUEST_METHOD} eq 'HEAD') {
47 $implicit_HEAD->($env);
48 } elsif($env->{REQUEST_METHOD} eq 'OPTIONS') {
49 $implicit_OPTIONS->($env);
50 } else {
51 [405, [$self->hdrs, Allow=>$self->allowed] , ['Method Not Allowed'] ];
52 }
53 };
54 }
55}
56
57sub isa_endpoint {
58 blessed($_[0]) &&
59 $_[0]->isa('Web::Dispatch::HTTPMethods::Endpoint')
60}
61
62sub endpoint_from { return $_[-1] }
63sub new_endpoint { Web::Dispatch::HTTPMethods::Endpoint->new(@_) }
64
65sub method_helper {
66 my $predicate = match_method(my $method = shift);
67 my ($code, @following ) = @_;
68 endpoint_from( my @dispatchers =
69 scalar(@following) ? ($predicate, @_) : ($predicate, @_, new_endpoint)
70 )->{$method} = $code;
71
72 die "Non HTTP Method dispatcher detected in HTTP Method scope"
73 unless(isa_endpoint($dispatchers[-1]));
74
75 return @dispatchers;
76}
77
78
791;
80
81=head1 NAME
82
83Web::Dispatch::HTTPMethods - Helpers to make RESTFul Dispatchers Easier
84
85=head1 SYNOPSIS
86
87 package MyApp:WithHTTPMethods;
88
89 use Web::Simple;
90 use Web::Dispatch::HTTPMethods;
91
92 sub as_text {
93 [200, ['Content-Type' => 'text/plain'],
94 [$_[0]->{REQUEST_METHOD}, $_[0]->{REQUEST_URI}] ]
95 }
96
97 sub dispatch_request {
98 sub (/get) {
99 GET { as_text(pop) }
100 },
101 sub (/get-head) {
102 GET { as_text(pop) }
103 HEAD { [204,[],[]] },
104 },
105 sub (/get-post-put) {
106 GET { as_text(pop) } ## NOTE: no commas separating http methods
107 POST { as_text(pop) }
108 PUT { as_text(pop) }
109 },
110 }
111
112=head1 DESCRIPTION
113
29c7cff1 114Exports the most commonly used HTTP methods as subroutine helpers into your
b54172e8 115L<Web::Simple> based application.
116Use of these methods additionally adds an automatic HTTP code 405
117C<Method Not Allowed> response if none of the HTTP methods match for a given dispatch and
456dc2bb 118also adds a dispatch rule for C<HEAD> if no C<HEAD> exists but a C<GET> does
119(in which case the C<HEAD> returns the C<GET> dispatch with an empty body.)
120
b54172e8 121We also add support at the end of the chain for the OPTIONS method.
122This defaults to HTTP 200 OK + Allows http headers.
456dc2bb 123
b54172e8 124We also try to set correct HTTP headers such as C<Allows> as makes sense based
456dc2bb 125on your dispatch chain.
126
127The following dispatch chains are basically the same:
128
129 sub dispatch_request {
130 sub (/get-http-methods) {
131 GET { [200, ['Content-Type' => 'text/plain'], ['Hello World']] }
132 },
133 sub(/get-classic) {
134 sub (GET) { [200, ['Content-Type' => 'text/plain'], ['Hello World']] },
135 sub (HEAD) { [200, ['Content-Type' => 'text/plain'], []] },
136 sub (OPTIONS) {
137 [200, ['Content-Type' => 'text/plain', Allows=>'GET,HEAD,OPTIONS'], []];
138 },
139 sub () {
140 [405, ['Content-Type' => 'text/plain', Allows=>'GET,HEAD,OPTIONS'],
141 ['Method Not Allowed']]
142 },
143 }
144 }
145
146The idea here is less boilerplate to distract the reader from the main point of
147the code and also to encapsulate some best practices.
148
149B<NOTE> You currently cannot mix http method style and prototype sub style in
150the same scope, as in the following example:
151
152 sub dispatch_request {
153 sub (/get-head) {
154 GET { ... }
155 sub (HEAD) { ... }
156 },
157 }
158
159If you try this our code will notice and issue a C<die>. If you have a good use
160case please bring it to the authors.
161
162=head2 EXPORTS
163
164This automatically exports the following subroutines:
165
166 GET
167 PUT
168 POST
169 HEAD
170 DELETE
171 OPTIONS
172
173=head1 AUTHOR
174
175See L<Web::Simple> for AUTHOR
176
177=head1 CONTRIBUTORS
178
179See L<Web::Simple> for CONTRIBUTORS
180
181=head1 COPYRIGHT
182
183See L<Web::Simple> for COPYRIGHT
184
185=head1 LICENSE
186
187See L<Web::Simple> for LICENSE
188
189=cut
190