add files
|
1 |
package Mojo::Message; |
2 |
use Mojo::Base 'Mojo::EventEmitter'; |
|
3 | ||
4 |
use Carp 'croak'; |
|
5 |
use Mojo::Asset::Memory; |
|
6 |
use Mojo::Content::Single; |
|
7 |
use Mojo::DOM; |
|
8 |
use Mojo::JSON; |
|
9 |
use Mojo::JSON::Pointer; |
|
10 |
use Mojo::Parameters; |
|
11 |
use Mojo::Upload; |
|
12 |
use Mojo::Util 'decode'; |
|
13 | ||
14 |
has content => sub { Mojo::Content::Single->new }; |
|
15 |
has default_charset => 'UTF-8'; |
|
16 |
has max_line_size => sub { $ENV{MOJO_MAX_LINE_SIZE} || 10240 }; |
|
17 |
has max_message_size => sub { $ENV{MOJO_MAX_MESSAGE_SIZE} // 10485760 }; |
|
18 |
has version => '1.1'; |
|
19 | ||
20 |
sub body { |
|
21 |
my $self = shift; |
|
22 | ||
23 |
# Downgrade multipart content |
|
24 |
my $content = $self->content; |
|
25 |
$content = $self->content(Mojo::Content::Single->new)->content |
|
26 |
if $content->is_multipart; |
|
27 | ||
28 |
# Get |
|
29 |
return $content->asset->slurp unless @_; |
|
30 | ||
31 |
# Set |
|
32 |
$content->asset(Mojo::Asset::Memory->new->add_chunk(@_)); |
|
33 | ||
34 |
return $self; |
|
35 |
} |
|
36 | ||
37 |
sub body_params { |
|
38 |
my $self = shift; |
|
39 | ||
40 |
return $self->{body_params} if $self->{body_params}; |
|
41 |
my $params = $self->{body_params} = Mojo::Parameters->new; |
|
42 |
$params->charset($self->content->charset || $self->default_charset); |
|
43 | ||
44 |
# "application/x-www-form-urlencoded" |
|
45 |
my $type = $self->headers->content_type // ''; |
|
46 |
if ($type =~ m!application/x-www-form-urlencoded!i) { |
|
47 |
$params->parse($self->content->asset->slurp); |
|
48 |
} |
|
49 | ||
50 |
# "multipart/form-data" |
|
51 |
elsif ($type =~ m!multipart/form-data!i) { |
|
52 |
$params->append(@$_[0, 1]) for @{$self->_parse_formdata}; |
|
53 |
} |
|
54 | ||
55 |
return $params; |
|
56 |
} |
|
57 | ||
58 |
sub body_size { shift->content->body_size } |
|
59 | ||
60 |
sub build_body { shift->_build('get_body_chunk') } |
|
61 |
sub build_headers { shift->_build('get_header_chunk') } |
|
62 |
sub build_start_line { shift->_build('get_start_line_chunk') } |
|
63 | ||
64 |
sub cookie { shift->_cache(cookies => @_) } |
|
65 | ||
66 |
sub cookies { croak 'Method "cookies" not implemented by subclass' } |
|
67 | ||
68 |
sub dom { |
|
69 |
my $self = shift; |
|
70 |
return undef if $self->content->is_multipart; |
|
71 |
my $dom = $self->{dom} ||= Mojo::DOM->new($self->text); |
|
72 |
return @_ ? $dom->find(@_) : $dom; |
|
73 |
} |
|
74 | ||
75 |
sub error { |
|
76 |
my $self = shift; |
|
77 | ||
78 |
# Set |
|
79 |
if (@_) { |
|
80 |
$self->{error} = [@_]; |
|
81 |
return $self->finish; |
|
82 |
} |
|
83 | ||
84 |
# Get |
|
85 |
return unless my $err = $self->{error}; |
|
86 |
return wantarray ? @$err : $err->[0]; |
|
87 |
} |
|
88 | ||
89 |
sub extract_start_line { |
|
90 |
croak 'Method "extract_start_line" not implemented by subclass'; |
|
91 |
} |
|
92 | ||
93 |
sub finish { |
|
94 |
my $self = shift; |
|
95 |
$self->{state} = 'finished'; |
|
96 |
return $self->{finished}++ ? $self : $self->emit('finish'); |
|
97 |
} |
|
98 | ||
99 |
sub fix_headers { |
|
100 |
my $self = shift; |
|
101 | ||
102 |
# Content-Length or Connection (unless chunked transfer encoding is used) |
|
103 |
my $content = $self->content; |
|
104 |
return $self if $self->{fix}++ || $content->is_chunked; |
|
105 |
my $headers = $self->headers; |
|
106 |
$content->is_dynamic |
|
107 |
? $headers->connection('close') |
|
108 |
: $headers->content_length($self->body_size) |
|
109 |
unless $headers->content_length; |
|
110 | ||
111 |
return $self; |
|
112 |
} |
|
113 | ||
114 |
sub get_body_chunk { |
|
115 |
my ($self, $offset) = @_; |
|
116 | ||
117 |
$self->emit('progress', 'body', $offset); |
|
118 |
my $chunk = $self->content->get_body_chunk($offset); |
|
119 |
return $chunk if !defined $chunk || length $chunk; |
|
120 |
$self->finish; |
|
121 | ||
122 |
return $chunk; |
|
123 |
} |
|
124 | ||
125 |
sub get_header_chunk { |
|
126 |
my ($self, $offset) = @_; |
|
127 |
$self->emit('progress', 'headers', $offset); |
|
128 |
return $self->fix_headers->content->get_header_chunk($offset); |
|
129 |
} |
|
130 | ||
131 |
sub get_start_line_chunk { |
|
132 |
croak 'Method "get_start_line_chunk" not implemented by subclass'; |
|
133 |
} |
|
134 | ||
135 |
sub header_size { shift->fix_headers->content->header_size } |
|
136 | ||
137 |
sub headers { shift->content->headers } |
|
138 | ||
139 |
sub is_finished { (shift->{state} // '') eq 'finished' } |
|
140 | ||
141 |
sub is_limit_exceeded { !!shift->{limit} } |
|
142 | ||
143 |
sub json { |
|
144 |
my ($self, $pointer) = @_; |
|
145 |
return undef if $self->content->is_multipart; |
|
146 |
my $data = $self->{json} ||= Mojo::JSON->new->decode($self->body); |
|
147 |
return $pointer ? Mojo::JSON::Pointer->new->get($data, $pointer) : $data; |
|
148 |
} |
|
149 | ||
150 |
sub param { shift->body_params->param(@_) } |
|
151 | ||
152 |
sub parse { |
|
153 |
my ($self, $chunk) = @_; |
|
154 | ||
155 |
# Check message size |
|
156 |
my $max = $self->max_message_size; |
|
157 |
return $self->_limit('Maximum message size exceeded', 413) |
|
158 |
if $max && ($self->{raw_size} += length($chunk //= '')) > $max; |
|
159 | ||
160 |
$self->{buffer} .= $chunk; |
|
161 | ||
162 |
# Start line |
|
163 |
unless ($self->{state}) { |
|
164 | ||
165 |
# Check line size |
|
166 |
my $len = index $self->{buffer}, "\x0a"; |
|
167 |
$len = length $self->{buffer} if $len < 0; |
|
168 |
return $self->_limit('Maximum line size exceeded', 431) |
|
169 |
if $len > $self->max_line_size; |
|
170 | ||
171 |
$self->{state} = 'content' if $self->extract_start_line(\$self->{buffer}); |
|
172 |
} |
|
173 | ||
174 |
# Content |
|
175 |
my $state = $self->{state} // ''; |
|
176 |
$self->content($self->content->parse(delete $self->{buffer})) |
|
177 |
if $state eq 'content' || $state eq 'finished'; |
|
178 | ||
179 |
# Check line size |
|
180 |
return $self->_limit('Maximum line size exceeded', 431) |
|
181 |
if $self->headers->is_limit_exceeded; |
|
182 | ||
183 |
# Check buffer size |
|
184 |
return $self->error('Maximum buffer size exceeded', 400) |
|
185 |
if $self->content->is_limit_exceeded; |
|
186 | ||
187 |
return $self->emit('progress')->content->is_finished ? $self->finish : $self; |
|
188 |
} |
|
189 | ||
190 |
sub start_line_size { length shift->build_start_line } |
|
191 | ||
192 |
sub text { |
|
193 |
my $self = shift; |
|
194 |
my $body = $self->body; |
|
195 |
my $charset = $self->content->charset; |
|
196 |
return $charset ? decode($charset, $body) // $body : $body; |
|
197 |
} |
|
198 | ||
199 |
sub to_string { |
|
200 |
my $self = shift; |
|
201 |
return $self->build_start_line . $self->build_headers . $self->build_body; |
|
202 |
} |
|
203 | ||
204 |
sub upload { shift->_cache(uploads => @_) } |
|
205 | ||
206 |
sub uploads { |
|
207 |
my $self = shift; |
|
208 | ||
209 |
my @uploads; |
|
210 |
for my $data (@{$self->_parse_formdata(1)}) { |
|
211 |
my $upload = Mojo::Upload->new( |
|
212 |
name => $data->[0], |
|
213 |
filename => $data->[2], |
|
214 |
asset => $data->[1]->asset, |
|
215 |
headers => $data->[1]->headers |
|
216 |
); |
|
217 |
push @uploads, $upload; |
|
218 |
} |
|
219 | ||
220 |
return \@uploads; |
|
221 |
} |
|
222 | ||
223 |
sub _build { |
|
224 |
my ($self, $method) = @_; |
|
225 | ||
226 |
my $buffer = ''; |
|
227 |
my $offset = 0; |
|
228 |
while (1) { |
|
229 | ||
230 |
# No chunk yet, try again |
|
231 |
next unless defined(my $chunk = $self->$method($offset)); |
|
232 | ||
233 |
# End of part |
|
234 |
last unless my $len = length $chunk; |
|
235 | ||
236 |
$offset += $len; |
|
237 |
$buffer .= $chunk; |
|
238 |
} |
|
239 | ||
240 |
return $buffer; |
|
241 |
} |
|
242 | ||
243 |
sub _cache { |
|
244 |
my ($self, $method, $name) = @_; |
|
245 | ||
246 |
# Cache objects by name |
|
247 |
unless ($self->{$method}) { |
|
248 |
$self->{$method} = {}; |
|
249 |
push @{$self->{$method}{$_->name}}, $_ for @{$self->$method}; |
|
250 |
} |
|
251 | ||
252 |
return unless my $objects = $self->{$method}{$name}; |
|
253 |
return wantarray ? @$objects : $objects->[0]; |
|
254 |
} |
|
255 | ||
256 |
sub _limit { |
|
257 |
my $self = shift; |
|
258 |
$self->{limit} = 1; |
|
259 |
$self->error(@_); |
|
260 |
} |
|
261 | ||
262 |
sub _parse_formdata { |
|
263 |
my ($self, $upload) = @_; |
|
264 | ||
265 |
my @formdata; |
|
266 |
my $content = $self->content; |
|
267 |
return \@formdata unless $content->is_multipart; |
|
268 |
my $charset = $content->charset || $self->default_charset; |
|
269 | ||
270 |
# Check all parts recursively |
|
271 |
my @parts = ($content); |
|
272 |
while (my $part = shift @parts) { |
|
273 | ||
274 |
if ($part->is_multipart) { |
|
275 |
unshift @parts, @{$part->parts}; |
|
276 |
next; |
|
277 |
} |
|
278 | ||
279 |
next unless my $disposition = $part->headers->content_disposition; |
|
280 |
my ($filename) = $disposition =~ /[; ]filename\s*=\s*"?((?:\\"|[^"])*)"?/i; |
|
281 |
next if ($upload && !defined $filename) || (!$upload && defined $filename); |
|
282 |
my ($name) = $disposition =~ /[; ]name\s*=\s*"?((?:\\"|[^";])+)"?/i; |
|
283 |
if ($charset) { |
|
284 |
$name = decode($charset, $name) // $name if $name; |
|
285 |
$filename = decode($charset, $filename) // $filename if $filename; |
|
286 |
} |
|
287 | ||
288 |
unless ($upload) { |
|
289 |
$part = $part->asset->slurp; |
|
290 |
$part = decode($charset, $part) // $part if $charset; |
|
291 |
} |
|
292 | ||
293 |
push @formdata, [$name, $part, $filename]; |
|
294 |
} |
|
295 | ||
296 |
return \@formdata; |
|
297 |
} |
|
298 | ||
299 |
1; |
|
300 | ||
301 |
=encoding utf8 |
|
302 | ||
303 |
=head1 NAME |
|
304 | ||
305 |
Mojo::Message - HTTP message base class |
|
306 | ||
307 |
=head1 SYNOPSIS |
|
308 | ||
309 |
package Mojo::Message::MyMessage; |
|
310 |
use Mojo::Base 'Mojo::Message'; |
|
311 | ||
312 |
sub cookies {...} |
|
313 |
sub extract_start_line {...} |
|
314 |
sub get_start_line_chunk {...} |
|
315 | ||
316 |
=head1 DESCRIPTION |
|
317 | ||
318 |
L<Mojo::Message> is an abstract base class for HTTP messages as described in |
|
319 |
RFC 2616 and RFC 2388. |
|
320 | ||
321 |
=head1 EVENTS |
|
322 | ||
323 |
L<Mojo::Message> inherits all events from L<Mojo::EventEmitter> and can emit |
|
324 |
the following new ones. |
|
325 | ||
326 |
=head2 finish |
|
327 | ||
328 |
$msg->on(finish => sub { |
|
329 |
my $msg = shift; |
|
330 |
... |
|
331 |
}); |
|
332 | ||
333 |
Emitted after message building or parsing is finished. |
|
334 | ||
335 |
my $before = time; |
|
336 |
$msg->on(finish => sub { |
|
337 |
my $msg = shift; |
|
338 |
$msg->headers->header('X-Parser-Time' => time - $before); |
|
339 |
}); |
|
340 | ||
341 |
=head2 progress |
|
342 | ||
343 |
$msg->on(progress => sub { |
|
344 |
my $msg = shift; |
|
345 |
... |
|
346 |
}); |
|
347 | ||
348 |
Emitted when message building or parsing makes progress. |
|
349 | ||
350 |
# Building |
|
351 |
$msg->on(progress => sub { |
|
352 |
my ($msg, $state, $offset) = @_; |
|
353 |
say qq{Building "$state" at offset $offset}; |
|
354 |
}); |
|
355 | ||
356 |
# Parsing |
|
357 |
$msg->on(progress => sub { |
|
358 |
my $msg = shift; |
|
359 |
return unless my $len = $msg->headers->content_length; |
|
360 |
my $size = $msg->content->progress; |
|
361 |
say 'Progress: ', $size == $len ? 100 : int($size / ($len / 100)), '%'; |
|
362 |
}); |
|
363 | ||
364 |
=head1 ATTRIBUTES |
|
365 | ||
366 |
L<Mojo::Message> implements the following attributes. |
|
367 | ||
368 |
=head2 content |
|
369 | ||
370 |
my $msg = $msg->content; |
|
371 |
$msg = $msg->content(Mojo::Content::Single->new); |
|
372 | ||
373 |
Message content, defaults to a L<Mojo::Content::Single> object. |
|
374 | ||
375 |
=head2 default_charset |
|
376 | ||
377 |
my $charset = $msg->default_charset; |
|
378 |
$msg = $msg->default_charset('UTF-8'); |
|
379 | ||
380 |
Default charset used for form-data parsing, defaults to C<UTF-8>. |
|
381 | ||
382 |
=head2 max_line_size |
|
383 | ||
384 |
my $size = $msg->max_line_size; |
|
385 |
$msg = $msg->max_line_size(1024); |
|
386 | ||
387 |
Maximum start line size in bytes, defaults to the value of the |
|
388 |
MOJO_MAX_LINE_SIZE environment variable or C<10240>. |
|
389 | ||
390 |
=head2 max_message_size |
|
391 | ||
392 |
my $size = $msg->max_message_size; |
|
393 |
$msg = $msg->max_message_size(1024); |
|
394 | ||
395 |
Maximum message size in bytes, defaults to the value of the |
|
396 |
MOJO_MAX_MESSAGE_SIZE environment variable or C<10485760>. Setting the value |
|
397 |
to C<0> will allow messages of indefinite size. Note that increasing this |
|
398 |
value can also drastically increase memory usage, should you for example |
|
399 |
attempt to parse an excessively large message body with the L</"body_params">, |
|
400 |
L</"dom"> or L</"json"> methods. |
|
401 | ||
402 |
=head2 version |
|
403 | ||
404 |
my $version = $msg->version; |
|
405 |
$msg = $msg->version('1.1'); |
|
406 | ||
407 |
HTTP version of message, defaults to C<1.1>. |
|
408 | ||
409 |
=head1 METHODS |
|
410 | ||
411 |
L<Mojo::Message> inherits all methods from L<Mojo::EventEmitter> and |
|
412 |
implements the following new ones. |
|
413 | ||
414 |
=head2 body |
|
415 | ||
416 |
my $bytes = $msg->body; |
|
417 |
$msg = $msg->body('Hello!'); |
|
418 | ||
419 |
Slurp or replace L</"content">, L<Mojo::Content::MultiPart> will be |
|
420 |
automatically downgraded to L<Mojo::Content::Single>. |
|
421 | ||
422 |
=head2 body_params |
|
423 | ||
424 |
my $params = $msg->body_params; |
|
425 | ||
426 |
POST parameters extracted from C<application/x-www-form-urlencoded> or |
|
427 |
C<multipart/form-data> message body, usually a L<Mojo::Parameters> object. |
|
428 |
Note that this method caches all data, so it should not be called before the |
|
429 |
entire message body has been received. Parts of the message body need to be |
|
430 |
loaded into memory to parse POST parameters, so you have to make sure it is |
|
431 |
not excessively large. |
|
432 | ||
433 |
# Get POST parameter value |
|
434 |
say $msg->body_params->param('foo'); |
|
435 | ||
436 |
=head2 body_size |
|
437 | ||
438 |
my $size = $msg->body_size; |
|
439 | ||
440 |
Content size in bytes. |
|
441 | ||
442 |
=head2 build_body |
|
443 | ||
444 |
my $bytes = $msg->build_body; |
|
445 | ||
446 |
Render whole body. |
|
447 | ||
448 |
=head2 build_headers |
|
449 | ||
450 |
my $bytes = $msg->build_headers; |
|
451 | ||
452 |
Render all headers. |
|
453 | ||
454 |
=head2 build_start_line |
|
455 | ||
456 |
my $bytes = $msg->build_start_line; |
|
457 | ||
458 |
Render start line. |
|
459 | ||
460 |
=head2 cookie |
|
461 | ||
462 |
my $cookie = $msg->cookie('foo'); |
|
463 |
my @cookies = $msg->cookie('foo'); |
|
464 | ||
465 |
Access message cookies, usually L<Mojo::Cookie::Request> or |
|
466 |
L<Mojo::Cookie::Response> objects. Note that this method caches all data, so |
|
467 |
it should not be called before all headers have been received. |
|
468 | ||
469 |
# Get cookie value |
|
470 |
say $msg->cookie('foo')->value; |
|
471 | ||
472 |
=head2 cookies |
|
473 | ||
474 |
my $cookies = $msg->cookies; |
|
475 | ||
476 |
Access message cookies. Meant to be overloaded in a subclass. |
|
477 | ||
478 |
=head2 dom |
|
479 | ||
480 |
my $dom = $msg->dom; |
|
481 |
my $collection = $msg->dom('a[href]'); |
|
482 | ||
483 |
Turns message body into a L<Mojo::DOM> object and takes an optional selector |
|
484 |
to perform a C<find> on it right away, which returns a L<Mojo::Collection> |
|
485 |
object. Note that this method caches all data, so it should not be called |
|
486 |
before the entire message body has been received. The whole message body needs |
|
487 |
to be loaded into memory to parse it, so you have to make sure it is not |
|
488 |
excessively large. |
|
489 | ||
490 |
# Perform "find" right away |
|
491 |
say $msg->dom('h1, h2, h3')->text; |
|
492 | ||
493 |
# Use everything else Mojo::DOM has to offer |
|
494 |
say $msg->dom->at('title')->text; |
|
495 |
say $msg->dom->html->body->children->type->uniq; |
|
496 | ||
497 |
=head2 error |
|
498 | ||
499 |
my $err = $msg->error; |
|
500 |
my ($err, $code) = $msg->error; |
|
501 |
$msg = $msg->error('Parser error'); |
|
502 |
$msg = $msg->error('Parser error', 500); |
|
503 | ||
504 |
Error and code. |
|
505 | ||
506 |
=head2 extract_start_line |
|
507 | ||
508 |
my $bool = $msg->extract_start_line(\$str); |
|
509 | ||
510 |
Extract start line from string. Meant to be overloaded in a subclass. |
|
511 | ||
512 |
=head2 finish |
|
513 | ||
514 |
$msg = $msg->finish; |
|
515 | ||
516 |
Finish message parser/generator. |
|
517 | ||
518 |
=head2 fix_headers |
|
519 | ||
520 |
$msg = $msg->fix_headers; |
|
521 | ||
522 |
Make sure message has all required headers. |
|
523 | ||
524 |
=head2 get_body_chunk |
|
525 | ||
526 |
my $bytes = $msg->get_body_chunk($offset); |
|
527 | ||
528 |
Get a chunk of body data starting from a specific position. |
|
529 | ||
530 |
=head2 get_header_chunk |
|
531 | ||
532 |
my $bytes = $msg->get_header_chunk($offset); |
|
533 | ||
534 |
Get a chunk of header data, starting from a specific position. |
|
535 | ||
536 |
=head2 get_start_line_chunk |
|
537 | ||
538 |
my $bytes = $msg->get_start_line_chunk($offset); |
|
539 | ||
540 |
Get a chunk of start line data starting from a specific position. Meant to be |
|
541 |
overloaded in a subclass. |
|
542 | ||
543 |
=head2 header_size |
|
544 | ||
545 |
my $size = $msg->header_size; |
|
546 | ||
547 |
Size of headers in bytes. |
|
548 | ||
549 |
=head2 headers |
|
550 | ||
551 |
my $headers = $msg->headers; |
|
552 | ||
553 |
Message headers, usually a L<Mojo::Headers> object. |
|
554 | ||
555 |
=head2 is_finished |
|
556 | ||
557 |
my $bool = $msg->is_finished; |
|
558 | ||
559 |
Check if message parser/generator is finished. |
|
560 | ||
561 |
=head2 is_limit_exceeded |
|
562 | ||
563 |
my $bool = $msg->is_limit_exceeded; |
|
564 | ||
565 |
Check if message has exceeded L</"max_line_size"> or L</"max_message_size">. |
|
566 | ||
567 |
=head2 json |
|
568 | ||
569 |
my $hash = $msg->json; |
|
570 |
my $array = $msg->json; |
|
571 |
my $value = $msg->json('/foo/bar'); |
|
572 | ||
573 |
Decode JSON message body directly using L<Mojo::JSON> if possible, returns |
|
574 |
C<undef> otherwise. An optional JSON Pointer can be used to extract a specific |
|
575 |
value with L<Mojo::JSON::Pointer>. Note that this method caches all data, so |
|
576 |
it should not be called before the entire message body has been received. |
|
577 |
The whole message body needs to be loaded into memory to parse it, so you have |
|
578 |
to make sure it is not excessively large. |
|
579 | ||
580 |
# Extract JSON values |
|
581 |
say $msg->json->{foo}{bar}[23]; |
|
582 |
say $msg->json('/foo/bar/23'); |
|
583 | ||
584 |
=head2 param |
|
585 | ||
586 |
my @names = $msg->param; |
|
587 |
my $foo = $msg->param('foo'); |
|
588 |
my @foo = $msg->param('foo'); |
|
589 | ||
590 |
Access POST parameters. Note that this method caches all data, so it should |
|
591 |
not be called before the entire message body has been received. Parts of the |
|
592 |
message body need to be loaded into memory to parse POST parameters, so you |
|
593 |
have to make sure it is not excessively large. |
|
594 | ||
595 |
=head2 parse |
|
596 | ||
597 |
$msg = $msg->parse('HTTP/1.1 200 OK...'); |
|
598 | ||
599 |
Parse message chunk. |
|
600 | ||
601 |
=head2 start_line_size |
|
602 | ||
603 |
my $size = $msg->start_line_size; |
|
604 | ||
605 |
Size of the start line in bytes. |
|
606 | ||
607 |
=head2 text |
|
608 | ||
609 |
my $str = $msg->text; |
|
610 | ||
611 |
Retrieve L</"body"> and try to decode it if a charset could be extracted with |
|
612 |
L<Mojo::Content/"charset">. |
|
613 | ||
614 |
=head2 to_string |
|
615 | ||
616 |
my $str = $msg->to_string; |
|
617 | ||
618 |
Render whole message. |
|
619 | ||
620 |
=head2 upload |
|
621 | ||
622 |
my $upload = $msg->upload('foo'); |
|
623 |
my @uploads = $msg->upload('foo'); |
|
624 | ||
625 |
Access C<multipart/form-data> file uploads, usually L<Mojo::Upload> objects. |
|
626 |
Note that this method caches all data, so it should not be called before the |
|
627 |
entire message body has been received. |
|
628 | ||
629 |
# Get content of uploaded file |
|
630 |
say $msg->upload('foo')->asset->slurp; |
|
631 | ||
632 |
=head2 uploads |
|
633 | ||
634 |
my $uploads = $msg->uploads; |
|
635 | ||
636 |
All C<multipart/form-data> file uploads, usually L<Mojo::Upload> objects. |
|
637 | ||
638 |
=head1 SEE ALSO |
|
639 | ||
640 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
641 | ||
642 |
=cut |