File Coverage

blib/lib/Mojo/Message/Response.pm
Criterion Covered Total %
statement 54 54 100.0
branch 26 26 100.0
condition 13 16 81.2
subroutine 18 18 100.0
pod 13 13 100.0
total 124 127 97.6


line stmt bran cond sub pod time code
1             package Mojo::Message::Response;
2 61     61   91929 use Mojo::Base 'Mojo::Message';
  61         443  
  61         374  
3              
4 61     61   23705 use Mojo::Cookie::Response;
  61         169  
  61         395  
5 61     61   342 use Mojo::Date;
  61         94  
  61         231  
6              
7             has [qw(code message)];
8             has max_message_size => sub { $ENV{MOJO_MAX_MESSAGE_SIZE} // 2147483648 };
9              
10             # Unmarked codes are from RFC 7231
11             my %MESSAGES = (
12             100 => 'Continue',
13             101 => 'Switching Protocols',
14             102 => 'Processing', # RFC 2518 (WebDAV)
15             103 => 'Early Hints', # RFC 8297
16             200 => 'OK',
17             201 => 'Created',
18             202 => 'Accepted',
19             203 => 'Non-Authoritative Information',
20             204 => 'No Content',
21             205 => 'Reset Content',
22             206 => 'Partial Content',
23             207 => 'Multi-Status', # RFC 2518 (WebDAV)
24             208 => 'Already Reported', # RFC 5842
25             226 => 'IM Used', # RFC 3229
26             300 => 'Multiple Choices',
27             301 => 'Moved Permanently',
28             302 => 'Found',
29             303 => 'See Other',
30             304 => 'Not Modified',
31             305 => 'Use Proxy',
32             307 => 'Temporary Redirect',
33             308 => 'Permanent Redirect', # RFC 7538
34             400 => 'Bad Request',
35             401 => 'Unauthorized',
36             402 => 'Payment Required',
37             403 => 'Forbidden',
38             404 => 'Not Found',
39             405 => 'Method Not Allowed',
40             406 => 'Not Acceptable',
41             407 => 'Proxy Authentication Required',
42             408 => 'Request Timeout',
43             409 => 'Conflict',
44             410 => 'Gone',
45             411 => 'Length Required',
46             412 => 'Precondition Failed',
47             413 => 'Request Entity Too Large',
48             414 => 'Request-URI Too Long',
49             415 => 'Unsupported Media Type',
50             416 => 'Request Range Not Satisfiable',
51             417 => 'Expectation Failed',
52             418 => "I'm a teapot", # RFC 2324 :)
53             421 => 'Misdirected Request', # RFC 7540
54             422 => 'Unprocessable Entity', # RFC 2518 (WebDAV)
55             423 => 'Locked', # RFC 2518 (WebDAV)
56             424 => 'Failed Dependency', # RFC 2518 (WebDAV)
57             425 => 'Too Early', # RFC 8470
58             426 => 'Upgrade Required', # RFC 2817
59             428 => 'Precondition Required', # RFC 6585
60             429 => 'Too Many Requests', # RFC 6585
61             431 => 'Request Header Fields Too Large', # RFC 6585
62             451 => 'Unavailable For Legal Reasons', # RFC 7725
63             500 => 'Internal Server Error',
64             501 => 'Not Implemented',
65             502 => 'Bad Gateway',
66             503 => 'Service Unavailable',
67             504 => 'Gateway Timeout',
68             505 => 'HTTP Version Not Supported',
69             506 => 'Variant Also Negotiates', # RFC 2295
70             507 => 'Insufficient Storage', # RFC 2518 (WebDAV)
71             508 => 'Loop Detected', # RFC 5842
72             509 => 'Bandwidth Limit Exceeded', # Unofficial
73             510 => 'Not Extended', # RFC 2774
74             511 => 'Network Authentication Required' # RFC 6585
75             );
76              
77             sub cookies {
78 1179     1179 1 1615 my $self = shift;
79              
80             # Parse cookies
81 1179         2458 my $headers = $self->headers;
82 1179 100       2656 return [@{Mojo::Cookie::Response->parse($headers->set_cookie)}] unless @_;
  1045         2914  
83              
84             # Add cookies
85 134 100       249 $headers->add('Set-Cookie' => "$_") for map { ref $_ eq 'HASH' ? Mojo::Cookie::Response->new($_) : $_ } @_;
  149         678  
86              
87 134         402 return $self;
88             }
89              
90 2095 100 100 2095 1 9243 sub default_message { $MESSAGES{$_[1] || $_[0]->code // 404} || '' }
      100        
91              
92             sub extract_start_line {
93 1081     1081 1 1948 my ($self, $bufref) = @_;
94              
95             # We have a full response line
96 1081 100       11894 return undef unless $$bufref =~ s/^(.*?)\x0d?\x0a//;
97 1065 100       7929 return !$self->error({message => 'Bad response start-line'}) unless $1 =~ m!^\s*HTTP/(\d\.\d)\s+(\d\d\d)\s*(.+)?$!;
98              
99 1064         2429 my $content = $self->content;
100 1064 100       2970 $content->skip_body(1) if $self->code($2)->is_empty;
101 1064   66     4581 defined $content->$_ or $content->$_(1) for qw(auto_decompress auto_relax);
102 1064         2579 return !!$self->version($1)->message($3);
103             }
104              
105             sub fix_headers {
106 2086     2086 1 2507 my $self = shift;
107 2086 100       6292 $self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
108              
109             # Date
110 1042         2149 my $headers = $self->headers;
111 1042 100       2773 $headers->date(Mojo::Date->new->to_string) unless $headers->date;
112              
113             # RFC 7230 3.3.2
114 1042 100       3488 $headers->remove('Content-Length') if $self->is_empty;
115              
116 1042         2338 return $self;
117             }
118              
119             sub get_start_line_chunk {
120 1036     1036 1 1792 my ($self, $offset) = @_;
121 1036         1912 $self->_start_line->emit(progress => 'start_line', $offset);
122 1036         3171 return substr $self->{start_buffer}, $offset, 131072;
123             }
124              
125 3     3 1 12 sub is_client_error { shift->_status_class(400) }
126              
127             sub is_empty {
128 3122     3122 1 4032 my $self = shift;
129 3122 100       5263 return undef unless my $code = $self->code;
130 3119   100     5852 return $self->is_info || $code == 204 || $code == 304;
131             }
132              
133 953     953 1 2072 sub is_error { shift->_status_class(400, 500) }
134 4112     4112 1 7263 sub is_info { shift->_status_class(100) }
135 33     33 1 103 sub is_redirect { shift->_status_class(300) }
136 3     3 1 11 sub is_server_error { shift->_status_class(500) }
137              
138 7     7 1 27 sub is_success { shift->_status_class(200) }
139              
140 1014     1014 1 2697 sub start_line_size { length shift->_start_line->{start_buffer} }
141              
142             sub _start_line {
143 2050     2050   2370 my $self = shift;
144              
145 2050 100       5719 return $self if defined $self->{start_buffer};
146 1024   50     2370 my $code = $self->code || 404;
147 1024   66     2391 my $msg = $self->message || $self->default_message;
148 1024         1830 $self->{start_buffer} = "HTTP/@{[$self->version]} $code $msg\x0d\x0a";
  1024         2513  
149              
150 1024         4413 return $self;
151             }
152              
153             sub _status_class {
154 5111     5111   8450 my ($self, @classes) = @_;
155 5111 100       8245 return undef unless my $code = $self->code;
156 5087 100       8042 return !!grep { $code >= $_ && $code < ($_ + 100) } @classes;
  6030         37372  
157             }
158              
159             1;
160              
161             =encoding utf8
162              
163             =head1 NAME
164              
165             Mojo::Message::Response - HTTP response
166              
167             =head1 SYNOPSIS
168              
169             use Mojo::Message::Response;
170              
171             # Parse
172             my $res = Mojo::Message::Response->new;
173             $res->parse("HTTP/1.0 200 OK\x0d\x0a");
174             $res->parse("Content-Length: 12\x0d\x0a");
175             $res->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
176             $res->parse('Hello World!');
177             say $res->code;
178             say $res->headers->content_type;
179             say $res->body;
180              
181             # Build
182             my $res = Mojo::Message::Response->new;
183             $res->code(200);
184             $res->headers->content_type('text/plain');
185             $res->body('Hello World!');
186             say $res->to_string;
187              
188             =head1 DESCRIPTION
189              
190             L is a container for HTTP responses, based on L
191             and L.
192              
193             =head1 EVENTS
194              
195             L inherits all events from L.
196              
197             =head1 ATTRIBUTES
198              
199             L inherits all attributes from L and implements the following new ones.
200              
201             =head2 code
202              
203             my $code = $res->code;
204             $res = $res->code(200);
205              
206             HTTP response status code.
207              
208             =head2 max_message_size
209              
210             my $size = $res->max_message_size;
211             $res = $res->max_message_size(1024);
212              
213             Maximum message size in bytes, defaults to the value of the C environment variable or
214             C<2147483648> (2GiB). Setting the value to C<0> will allow messages of indefinite size.
215              
216             =head2 message
217              
218             my $msg = $res->message;
219             $res = $res->message('OK');
220              
221             HTTP response status message.
222              
223             =head1 METHODS
224              
225             L inherits all methods from L and implements the following new ones.
226              
227             =head2 cookies
228              
229             my $cookies = $res->cookies;
230             $res = $res->cookies(Mojo::Cookie::Response->new);
231             $res = $res->cookies({name => 'foo', value => 'bar'});
232              
233             Access response cookies, usually L objects.
234              
235             # Names of all cookies
236             say $_->name for @{$res->cookies};
237              
238             =head2 default_message
239              
240             my $msg = $res->default_message;
241             my $msg = $res->default_message(418);
242              
243             Generate default response message for status code, defaults to using L.
244              
245             =head2 extract_start_line
246              
247             my $bool = $res->extract_start_line(\$str);
248              
249             Extract status-line from string.
250              
251             =head2 fix_headers
252              
253             $res = $res->fix_headers;
254              
255             Make sure response has all required headers.
256              
257             =head2 get_start_line_chunk
258              
259             my $bytes = $res->get_start_line_chunk($offset);
260              
261             Get a chunk of status-line data starting from a specific position. Note that this method finalizes the response.
262              
263             =head2 is_client_error
264              
265             my $bool = $res->is_client_error;
266              
267             Check if this response has a C<4xx> status L.
268              
269             =head2 is_empty
270              
271             my $bool = $res->is_empty;
272              
273             Check if this response has a C<1xx>, C<204> or C<304> status L.
274              
275             =head2 is_error
276              
277             my $bool = $res->is_error;
278              
279             Check if this response has a C<4xx> or C<5xx> status L.
280              
281             =head2 is_info
282              
283             my $bool = $res->is_info;
284              
285             Check if this response has a C<1xx> status L.
286              
287             =head2 is_redirect
288              
289             my $bool = $res->is_redirect;
290              
291             Check if this response has a C<3xx> status L.
292              
293             =head2 is_server_error
294              
295             my $bool = $res->is_server_error;
296              
297             Check if this response has a C<5xx> status L.
298              
299             =head2 is_success
300              
301             my $bool = $res->is_success;
302              
303             Check if this response has a C<2xx> status L.
304              
305             =head2 start_line_size
306              
307             my $size = $req->start_line_size;
308              
309             Size of the status-line in bytes. Note that this method finalizes the response.
310              
311             =head1 SEE ALSO
312              
313             L, L, L.
314              
315             =cut