File Coverage

lib/PAGI/Endpoint/HTTP.pm
Criterion Covered Total %
statement 55 56 98.2
branch 11 16 68.7
condition 7 13 53.8
subroutine 12 12 100.0
pod 5 6 83.3
total 90 103 87.3


line stmt bran cond sub pod time code
1             package PAGI::Endpoint::HTTP;
2              
3 5     5   852200 use strict;
  5         9  
  5         155  
4 5     5   48 use warnings;
  5         31  
  5         299  
5              
6 5     5   27 use Future::AsyncAwait;
  5         7  
  5         25  
7 5     5   232 use Carp qw(croak);
  5         10  
  5         216  
8 5     5   1973 use Module::Load qw(load);
  5         7238  
  5         30  
9              
10              
11             # Factory class methods - override in subclass for customization
12 4     4 1 2349 sub request_class { 'PAGI::Request' }
13 4     4 1 13 sub response_class { 'PAGI::Response' }
14              
15             sub new {
16 9     9 0 746507 my ($class, %args) = @_;
17 9         26 return bless \%args, $class;
18             }
19              
20             # HTTP methods we support
21             our @HTTP_METHODS = qw(get post put patch delete head options);
22              
23             sub allowed_methods {
24 4     4 1 7 my ($self) = @_;
25 4         5 my @allowed;
26 4         10 for my $method (@HTTP_METHODS) {
27 28 100       93 push @allowed, uc($method) if $self->can($method);
28             }
29             # HEAD is allowed if GET is defined
30 4 50 33     35 push @allowed, 'HEAD' if $self->can('get') && !$self->can('head');
31             # OPTIONS is always allowed
32 4 50       7 push @allowed, 'OPTIONS' unless grep { $_ eq 'OPTIONS' } @allowed;
  15         30  
33 4         30 return sort @allowed;
34             }
35              
36 7     7 1 130 async sub dispatch {
37 7         15 my ($self, $req, $res) = @_;
38 7   50     20 my $http_method = lc($req->method // 'GET');
39              
40             # OPTIONS - return allowed methods
41 7 100       61 if ($http_method eq 'options') {
42 1 50       10 if ($self->can('options')) {
43 0         0 return await $self->options($req, $res);
44             }
45 1         4 my $allow = join(', ', $self->allowed_methods);
46 1         5 await $res->header('Allow', $allow)->empty;
47 1         85 return;
48             }
49              
50             # HEAD falls back to GET if not explicitly defined
51 6 50 66     32 if ($http_method eq 'head' && !$self->can('head') && $self->can('get')) {
      66        
52 1         3 $http_method = 'get';
53             }
54              
55             # Check if we have a handler for this method
56 6 100       35 if ($self->can($http_method)) {
57 4         14 return await $self->$http_method($req, $res);
58             }
59              
60             # 405 Method Not Allowed
61 2         9 my $allow = join(', ', $self->allowed_methods);
62 2         10 await $res->header('Allow', $allow)
63             ->status(405)
64             ->text("405 Method Not Allowed");
65             }
66              
67             sub to_app {
68 3     3 1 177779 my ($class) = @_;
69             # Load the request/response classes
70 3         14 my $req_class = $class->request_class;
71 3         14 my $res_class = $class->response_class;
72 3         14 load($req_class);
73 3         109 load($res_class);
74              
75 1     1   52 return async sub {
76 1         2 my ($scope, $receive, $send) = @_;
77              
78 1   50     6 my $type = $scope->{type} // 'http';
79 1 50       5 croak "Expected http scope, got '$type'" unless $type eq 'http';
80              
81 1         14 my $endpoint = $class->new;
82 1         7 my $req = $req_class->new($scope, $receive);
83 1         6 my $res = $res_class->new($scope, $send);
84              
85 1         7 await $endpoint->dispatch($req, $res);
86 3         97 };
87             }
88              
89             1;
90              
91             __END__