File Coverage

blib/lib/PAGI/Context/HTTP.pm
Criterion Covered Total %
statement 31 31 100.0
branch 5 6 83.3
condition 13 15 86.6
subroutine 13 13 100.0
pod 10 10 100.0
total 72 75 96.0


line stmt bran cond sub pod time code
1             package PAGI::Context::HTTP;
2             $PAGI::Context::HTTP::VERSION = '0.002000';
3 27     27   147 use strict;
  27         43  
  27         960  
4 27     27   89 use warnings;
  27         39  
  27         1207  
5 27     27   104 use Carp qw(croak);
  27         27  
  27         14209  
6              
7             our @ISA = ('PAGI::Context');
8              
9             =encoding UTF-8
10              
11             =head1 NAME
12              
13             PAGI::Context::HTTP - HTTP-specific context subclass
14              
15             =head1 DESCRIPTION
16              
17             Returned by C<< PAGI::Context->new(...) >> when C<< $scope->{type} >> is
18             C<'http'>. Adds lazy accessors for L and L,
19             plus an HTTP C accessor.
20              
21             Inherits all shared methods from L.
22              
23             =head1 METHODS
24              
25             =head2 request
26              
27             my $req = $ctx->request;
28              
29             Returns a L instance. Lazy-constructed and cached.
30              
31             =head2 response
32              
33             my $res = $ctx->response;
34              
35             Returns a detached L accumulator. Lazy-constructed and cached
36             for the lifetime of the context. The response holds no connection — it is a
37             pure value object you mutate via the chainer methods (C, C
,
38             C, etc.) and then pass to L when ready to send.
39              
40             =head2 respond
41              
42             $ctx->respond($res);
43              
44             Guarded send. Sends the L value C<$res> over this request's
45             connection, marks the request as done, and returns a L that resolves
46             when all protocol events have been emitted.
47              
48             The sent state is read from C<< $scope->{'pagi.connection'}->response_started >>,
49             the server-seeded per-request object — a shared reference, so the fact propagates
50             across the whole middleware stack even though middleware shallow-clone the scope.
51             A second C on the same context is rejected synchronously via a per-context
52             flag; the server enforces single-response at the protocol level as the backstop.
53              
54             Delegates to the unguarded primitive C<< $res->respond($send) >>.
55              
56             =head2 method
57              
58             my $method = $ctx->method; # 'GET', 'POST', etc.
59              
60             Returns the HTTP method from the scope.
61              
62             =head2 req
63              
64             my $req = $ctx->req;
65              
66             Alias for C.
67              
68             =head2 resp
69              
70             my $res = $ctx->resp;
71              
72             Alias for C.
73              
74             =head2 text, html, json, redirect
75              
76             return $ctx->text('Hello');
77             return $ctx->html('

Hi

');
78             return $ctx->json({ ok => 1 });
79             return $ctx->json($data, status => 201);
80             return $ctx->redirect('/login');
81              
82             Shorthands that set the body — and any trailing options such as C,
83             C, or C — on this context's L, then return the
84             L value, so a handler can build and return its response in a
85             single call. C<< $ctx->json($data) >> is exactly C<< $ctx->response->json($data) >>.
86             They operate on the one response accumulator, so you can still reach for
87             C<< $ctx->response >> to set cookies or extra headers alongside them.
88              
89             =cut
90              
91             sub request {
92 8     8 1 35 my ($self) = @_;
93 8   66     33 return $self->{_request} //= do {
94 6         2505 require PAGI::Request;
95 6         63 PAGI::Request->new($self->{scope}, $self->{receive});
96             };
97             }
98              
99             sub response {
100 51     51 1 858 my ($self) = @_;
101 51   66     191 return $self->{_response} //= do {
102 47         6133 require PAGI::Response;
103 47         372 PAGI::Response->new($self->{scope}); # detached accumulator; no $send
104             };
105             }
106              
107             sub respond {
108 47     47 1 980 my ($self, $res) = @_;
109 47 50       129 my $conn = $self->{scope} ? $self->{scope}{'pagi.connection'} : undef;
110 47 100 100     154 if ($conn && !$conn->can('response_started')) {
111 1         97 croak("pagi.connection lacks response_started (non-conforming server)");
112             }
113             croak("response already sent")
114 46 100 100     481 if $self->{_responded} || ($conn && $conn->response_started); # mutex + cross-context read
      100        
115 43         74 $self->{_responded} = 1; # synchronous, self-owned
116 43         117 return $res->respond($self->{send});
117             }
118              
119 14     14 1 93 sub method { shift->{scope}{method} }
120              
121 1     1 1 9 sub req { shift->request }
122 1     1 1 3 sub resp { shift->response }
123              
124             # Value-contract response shorthands: set the body (+ opts) on the cached
125             # response accumulator and return it, so handlers can `return $ctx->json(...)`.
126 2     2 1 17 sub text { shift->response->text(@_) }
127 2     2 1 10 sub html { shift->response->html(@_) }
128 3     3 1 14 sub json { shift->response->json(@_) }
129 1     1 1 8 sub redirect { shift->response->redirect(@_) }
130              
131             1;
132              
133             __END__