1 | package Mojo::Message::Response;
|
---|
2 | use Mojo::Base 'Mojo::Message';
|
---|
3 |
|
---|
4 | use Mojo::Cookie::Response;
|
---|
5 | use Mojo::Date;
|
---|
6 |
|
---|
7 | has [qw(code message)];
|
---|
8 | has max_message_size => sub { $ENV{MOJO_MAX_MESSAGE_SIZE} // 2147483648 };
|
---|
9 |
|
---|
10 | # Umarked 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 => 'Unordered Colection', # RFC 3648 (WebDAV)
|
---|
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 | my $self = shift;
|
---|
79 |
|
---|
80 | # Parse cookies
|
---|
81 | my $headers = $self->headers;
|
---|
82 | return [@{Mojo::Cookie::Response->parse($headers->set_cookie)}] unless @_;
|
---|
83 |
|
---|
84 | # Add cookies
|
---|
85 | $headers->add('Set-Cookie' => "$_")
|
---|
86 | for map { ref $_ eq 'HASH' ? Mojo::Cookie::Response->new($_) : $_ } @_;
|
---|
87 |
|
---|
88 | return $self;
|
---|
89 | }
|
---|
90 |
|
---|
91 | sub default_message { $MESSAGES{$_[1] || $_[0]->code // 404} || '' }
|
---|
92 |
|
---|
93 | sub extract_start_line {
|
---|
94 | my ($self, $bufref) = @_;
|
---|
95 |
|
---|
96 | # We have a full response line
|
---|
97 | return undef unless $$bufref =~ s/^(.*?)\x0d?\x0a//;
|
---|
98 | return !$self->error({message => 'Bad response start-line'})
|
---|
99 | unless $1 =~ m!^\s*HTTP/(\d\.\d)\s+(\d\d\d)\s*(.+)?$!;
|
---|
100 |
|
---|
101 | my $content = $self->content;
|
---|
102 | $content->skip_body(1) if $self->code($2)->is_empty;
|
---|
103 | defined $content->$_ or $content->$_(1) for qw(auto_decompress auto_relax);
|
---|
104 | $content->expect_close(1) if $1 eq '1.0';
|
---|
105 | return !!$self->version($1)->message($3);
|
---|
106 | }
|
---|
107 |
|
---|
108 | sub fix_headers {
|
---|
109 | my $self = shift;
|
---|
110 | $self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
|
---|
111 |
|
---|
112 | # Date
|
---|
113 | my $headers = $self->headers;
|
---|
114 | $headers->date(Mojo::Date->new->to_string) unless $headers->date;
|
---|
115 |
|
---|
116 | # RFC 7230 3.3.2
|
---|
117 | $headers->remove('Content-Length') if $self->is_empty;
|
---|
118 |
|
---|
119 | return $self;
|
---|
120 | }
|
---|
121 |
|
---|
122 | sub get_start_line_chunk {
|
---|
123 | my ($self, $offset) = @_;
|
---|
124 | $self->_start_line->emit(progress => 'start_line', $offset);
|
---|
125 | return substr $self->{start_buffer}, $offset, 131072;
|
---|
126 | }
|
---|
127 |
|
---|
128 | sub is_client_error { shift->_status_class(400) }
|
---|
129 |
|
---|
130 | sub is_empty {
|
---|
131 | my $self = shift;
|
---|
132 | return undef unless my $code = $self->code;
|
---|
133 | return $self->is_info || $code == 204 || $code == 304;
|
---|
134 | }
|
---|
135 |
|
---|
136 | sub is_error { shift->_status_class(400, 500) }
|
---|
137 | sub is_info { shift->_status_class(100) }
|
---|
138 | sub is_redirect { shift->_status_class(300) }
|
---|
139 | sub is_server_error { shift->_status_class(500) }
|
---|
140 |
|
---|
141 | sub is_success { shift->_status_class(200) }
|
---|
142 |
|
---|
143 | sub start_line_size { length shift->_start_line->{start_buffer} }
|
---|
144 |
|
---|
145 | sub _start_line {
|
---|
146 | my $self = shift;
|
---|
147 |
|
---|
148 | return $self if defined $self->{start_buffer};
|
---|
149 | my $code = $self->code || 404;
|
---|
150 | my $msg = $self->message || $self->default_message;
|
---|
151 | $self->{start_buffer} = "HTTP/@{[$self->version]} $code $msg\x0d\x0a";
|
---|
152 |
|
---|
153 | return $self;
|
---|
154 | }
|
---|
155 |
|
---|
156 | sub _status_class {
|
---|
157 | my ($self, @classes) = @_;
|
---|
158 | return undef unless my $code = $self->code;
|
---|
159 | return !!grep { $code >= $_ && $code < ($_ + 100) } @classes;
|
---|
160 | }
|
---|
161 |
|
---|
162 | 1;
|
---|
163 |
|
---|
164 | =encoding utf8
|
---|
165 |
|
---|
166 | =head1 NAME
|
---|
167 |
|
---|
168 | Mojo::Message::Response - HTTP response
|
---|
169 |
|
---|
170 | =head1 SYNOPSIS
|
---|
171 |
|
---|
172 | use Mojo::Message::Response;
|
---|
173 |
|
---|
174 | # Parse
|
---|
175 | my $res = Mojo::Message::Response->new;
|
---|
176 | $res->parse("HTTP/1.0 200 OK\x0d\x0a");
|
---|
177 | $res->parse("Content-Length: 12\x0d\x0a");
|
---|
178 | $res->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
|
---|
179 | $res->parse('Hello World!');
|
---|
180 | say $res->code;
|
---|
181 | say $res->headers->content_type;
|
---|
182 | say $res->body;
|
---|
183 |
|
---|
184 | # Build
|
---|
185 | my $res = Mojo::Message::Response->new;
|
---|
186 | $res->code(200);
|
---|
187 | $res->headers->content_type('text/plain');
|
---|
188 | $res->body('Hello World!');
|
---|
189 | say $res->to_string;
|
---|
190 |
|
---|
191 | =head1 DESCRIPTION
|
---|
192 |
|
---|
193 | L<Mojo::Message::Response> is a container for HTTP responses, based on
|
---|
194 | L<RFC 7230|http://tools.ietf.org/html/rfc7230> and
|
---|
195 | L<RFC 7231|http://tools.ietf.org/html/rfc7231>.
|
---|
196 |
|
---|
197 | =head1 EVENTS
|
---|
198 |
|
---|
199 | L<Mojo::Message::Response> inherits all events from L<Mojo::Message>.
|
---|
200 |
|
---|
201 | =head1 ATTRIBUTES
|
---|
202 |
|
---|
203 | L<Mojo::Message::Response> inherits all attributes from L<Mojo::Message> and
|
---|
204 | implements the following new ones.
|
---|
205 |
|
---|
206 | =head2 code
|
---|
207 |
|
---|
208 | my $code = $res->code;
|
---|
209 | $res = $res->code(200);
|
---|
210 |
|
---|
211 | HTTP response status code.
|
---|
212 |
|
---|
213 | =head2 max_message_size
|
---|
214 |
|
---|
215 | my $size = $res->max_message_size;
|
---|
216 | $res = $res->max_message_size(1024);
|
---|
217 |
|
---|
218 | Maximum message size in bytes, defaults to the value of the
|
---|
219 | C<MOJO_MAX_MESSAGE_SIZE> environment variable or C<2147483648> (2GiB). Setting
|
---|
220 | the value to C<0> will allow messages of indefinite size.
|
---|
221 |
|
---|
222 | =head2 message
|
---|
223 |
|
---|
224 | my $msg = $res->message;
|
---|
225 | $res = $res->message('OK');
|
---|
226 |
|
---|
227 | HTTP response status message.
|
---|
228 |
|
---|
229 | =head1 METHODS
|
---|
230 |
|
---|
231 | L<Mojo::Message::Response> inherits all methods from L<Mojo::Message> and
|
---|
232 | implements the following new ones.
|
---|
233 |
|
---|
234 | =head2 cookies
|
---|
235 |
|
---|
236 | my $cookies = $res->cookies;
|
---|
237 | $res = $res->cookies(Mojo::Cookie::Response->new);
|
---|
238 | $res = $res->cookies({name => 'foo', value => 'bar'});
|
---|
239 |
|
---|
240 | Access response cookies, usually L<Mojo::Cookie::Response> objects.
|
---|
241 |
|
---|
242 | # Names of all cookies
|
---|
243 | say $_->name for @{$res->cookies};
|
---|
244 |
|
---|
245 | =head2 default_message
|
---|
246 |
|
---|
247 | my $msg = $res->default_message;
|
---|
248 | my $msg = $res->default_message(418);
|
---|
249 |
|
---|
250 | Generate default response message for status code, defaults to using
|
---|
251 | L</"code">.
|
---|
252 |
|
---|
253 | =head2 extract_start_line
|
---|
254 |
|
---|
255 | my $bool = $res->extract_start_line(\$str);
|
---|
256 |
|
---|
257 | Extract status-line from string.
|
---|
258 |
|
---|
259 | =head2 fix_headers
|
---|
260 |
|
---|
261 | $res = $res->fix_headers;
|
---|
262 |
|
---|
263 | Make sure response has all required headers.
|
---|
264 |
|
---|
265 | =head2 get_start_line_chunk
|
---|
266 |
|
---|
267 | my $bytes = $res->get_start_line_chunk($offset);
|
---|
268 |
|
---|
269 | Get a chunk of status-line data starting from a specific position. Note that
|
---|
270 | this method finalizes the response.
|
---|
271 |
|
---|
272 | =head2 is_client_error
|
---|
273 |
|
---|
274 | my $bool = $res->is_client_error;
|
---|
275 |
|
---|
276 | Check if this response has a C<4xx> status L</"code">.
|
---|
277 |
|
---|
278 | =head2 is_empty
|
---|
279 |
|
---|
280 | my $bool = $res->is_empty;
|
---|
281 |
|
---|
282 | Check if this response has a C<1xx>, C<204> or C<304> status L</"code">.
|
---|
283 |
|
---|
284 | =head2 is_error
|
---|
285 |
|
---|
286 | my $bool = $res->is_error;
|
---|
287 |
|
---|
288 | Check if this response has a C<4xx> or C<5xx> status L</"code">.
|
---|
289 |
|
---|
290 | =head2 is_info
|
---|
291 |
|
---|
292 | my $bool = $res->is_info;
|
---|
293 |
|
---|
294 | Check if this response has a C<1xx> status L</"code">.
|
---|
295 |
|
---|
296 | =head2 is_redirect
|
---|
297 |
|
---|
298 | my $bool = $res->is_redirect;
|
---|
299 |
|
---|
300 | Check if this response has a C<3xx> status L</"code">.
|
---|
301 |
|
---|
302 | =head2 is_server_error
|
---|
303 |
|
---|
304 | my $bool = $res->is_server_error;
|
---|
305 |
|
---|
306 | Check if this response has a C<5xx> status L</"code">.
|
---|
307 |
|
---|
308 | =head2 is_success
|
---|
309 |
|
---|
310 | my $bool = $res->is_success;
|
---|
311 |
|
---|
312 | Check if this response has a C<2xx> status L</"code">.
|
---|
313 |
|
---|
314 | =head2 start_line_size
|
---|
315 |
|
---|
316 | my $size = $req->start_line_size;
|
---|
317 |
|
---|
318 | Size of the status-line in bytes. Note that this method finalizes the response.
|
---|
319 |
|
---|
320 | =head1 SEE ALSO
|
---|
321 |
|
---|
322 | L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
|
---|
323 |
|
---|
324 | =cut
|
---|