add files
|
1 |
package Mojo::UserAgent; |
2 |
use Mojo::Base 'Mojo::EventEmitter'; |
|
3 | ||
4 |
# "Fry: Since when is the Internet about robbing people of their privacy? |
|
5 |
# Bender: August 6, 1991." |
|
6 |
use Carp 'croak'; |
|
7 |
use Mojo::IOLoop; |
|
8 |
use Mojo::URL; |
|
9 |
use Mojo::Util qw(deprecated monkey_patch); |
|
10 |
use Mojo::UserAgent::CookieJar; |
|
11 |
use Mojo::UserAgent::Proxy; |
|
12 |
use Mojo::UserAgent::Server; |
|
13 |
use Mojo::UserAgent::Transactor; |
|
14 |
use Scalar::Util 'weaken'; |
|
15 | ||
16 |
use constant DEBUG => $ENV{MOJO_USERAGENT_DEBUG} || 0; |
|
17 | ||
18 |
has ca => sub { $ENV{MOJO_CA_FILE} }; |
|
19 |
has cert => sub { $ENV{MOJO_CERT_FILE} }; |
|
20 |
has connect_timeout => sub { $ENV{MOJO_CONNECT_TIMEOUT} || 10 }; |
|
21 |
has cookie_jar => sub { Mojo::UserAgent::CookieJar->new }; |
|
22 |
has 'local_address'; |
|
23 |
has inactivity_timeout => sub { $ENV{MOJO_INACTIVITY_TIMEOUT} // 20 }; |
|
24 |
has ioloop => sub { Mojo::IOLoop->new }; |
|
25 |
has key => sub { $ENV{MOJO_KEY_FILE} }; |
|
26 |
has max_connections => 5; |
|
27 |
has max_redirects => sub { $ENV{MOJO_MAX_REDIRECTS} || 0 }; |
|
28 |
has proxy => sub { Mojo::UserAgent::Proxy->new }; |
|
29 |
has request_timeout => sub { $ENV{MOJO_REQUEST_TIMEOUT} // 0 }; |
|
30 |
has server => sub { Mojo::UserAgent::Server->new(ioloop => shift->ioloop) }; |
|
31 |
has transactor => sub { Mojo::UserAgent::Transactor->new }; |
|
32 | ||
33 |
# Common HTTP methods |
|
34 |
for my $name (qw(DELETE GET HEAD OPTIONS PATCH POST PUT)) { |
|
35 |
monkey_patch __PACKAGE__, lc($name), sub { |
|
36 |
my $self = shift; |
|
37 |
my $cb = ref $_[-1] eq 'CODE' ? pop : undef; |
|
38 |
return $self->start($self->build_tx($name, @_), $cb); |
|
39 |
}; |
|
40 |
} |
|
41 | ||
42 |
sub DESTROY { shift->_cleanup } |
|
43 | ||
44 |
# DEPRECATED in Top Hat! |
|
45 |
sub new { |
|
46 |
my $self = shift->SUPER::new; |
|
47 |
while (my $name = shift) { $self->$name(shift) } |
|
48 |
return $self; |
|
49 |
} |
|
50 | ||
51 |
# DEPRECATED in Top Hat! |
|
52 |
sub app { |
|
53 |
deprecated "Mojo::UserAgent::app is DEPRECATED in favor of" |
|
54 |
. " Mojo::UserAgent::Server::app"; |
|
55 |
shift->_delegate('server', 'app', @_); |
|
56 |
} |
|
57 | ||
58 |
# DEPRECATED in Top Hat! |
|
59 |
sub app_url { |
|
60 |
deprecated "Mojo::UserAgent::app_url is DEPRECATED in favor of" |
|
61 |
. " Mojo::UserAgent::Server::url"; |
|
62 |
shift->_delegate('server', 'url', @_); |
|
63 |
} |
|
64 | ||
65 |
sub build_tx { shift->transactor->tx(@_) } |
|
66 |
sub build_websocket_tx { shift->transactor->websocket(@_) } |
|
67 | ||
68 |
# DEPRECATED in Top Hat! |
|
69 |
sub detect_proxy { |
|
70 |
deprecated "Mojo::UserAgent::detect_proxy is DEPRECATED in favor of" |
|
71 |
. " Mojo::UserAgent::Proxy::detect"; |
|
72 |
shift->tap(sub { $_->proxy->detect }); |
|
73 |
} |
|
74 | ||
75 |
# DEPRECATED in Top Hat! |
|
76 |
sub http_proxy { |
|
77 |
deprecated "Mojo::UserAgent::http_proxy is DEPRECATED in favor of" |
|
78 |
. " Mojo::UserAgent::Proxy::http"; |
|
79 |
shift->_delegate('proxy', 'http', @_); |
|
80 |
} |
|
81 | ||
82 |
# DEPRECATED in Top Hat! |
|
83 |
sub https_proxy { |
|
84 |
deprecated "Mojo::UserAgent::https_proxy is DEPRECATED in favor of" |
|
85 |
. " Mojo::UserAgent::Proxy::https"; |
|
86 |
shift->_delegate('proxy', 'https', @_); |
|
87 |
} |
|
88 | ||
89 |
# DEPRECATED in Top Hat! |
|
90 |
sub name { |
|
91 |
deprecated "Mojo::UserAgent::name is DEPRECATED in favor of" |
|
92 |
. " Mojo::UserAgent::Transactor::name"; |
|
93 |
shift->_delegate('transactor', 'name', @_); |
|
94 |
} |
|
95 | ||
96 |
# DEPRECATED in Top Hat! |
|
97 |
sub no_proxy { |
|
98 |
deprecated "Mojo::UserAgent::no_proxy is DEPRECATED in favor of" |
|
99 |
. " Mojo::UserAgent::Proxy::not"; |
|
100 |
shift->_delegate('proxy', 'not', @_); |
|
101 |
} |
|
102 | ||
103 |
# DEPRECATED in Top Hat! |
|
104 |
sub need_proxy { |
|
105 |
deprecated "Mojo::UserAgent::need_proxy is DEPRECATED in favor of" |
|
106 |
. " Mojo::UserAgent::Proxy::is_needed"; |
|
107 |
shift->proxy->is_needed(@_); |
|
108 |
} |
|
109 | ||
110 |
sub start { |
|
111 |
my ($self, $tx, $cb) = @_; |
|
112 | ||
113 |
# Fork safety |
|
114 |
unless (($self->{pid} //= $$) eq $$) { |
|
115 |
delete $self->_cleanup->{pid}; |
|
116 |
$self->server->restart; |
|
117 |
} |
|
118 | ||
119 |
# Non-blocking |
|
120 |
if ($cb) { |
|
121 |
warn "-- Non-blocking request (@{[$tx->req->url->to_abs]})\n" if DEBUG; |
|
122 |
unless ($self->{nb}) { |
|
123 |
croak 'Blocking request in progress' if keys %{$self->{connections}}; |
|
124 |
warn "-- Switching to non-blocking mode\n" if DEBUG; |
|
125 |
$self->server->ioloop(Mojo::IOLoop->singleton); |
|
126 |
$self->_cleanup->{nb}++; |
|
127 |
} |
|
128 |
return $self->_start($tx, $cb); |
|
129 |
} |
|
130 | ||
131 |
# Blocking |
|
132 |
warn "-- Blocking request (@{[$tx->req->url->to_abs]})\n" if DEBUG; |
|
133 |
if ($self->{nb}) { |
|
134 |
croak 'Non-blocking requests in progress' if keys %{$self->{connections}}; |
|
135 |
warn "-- Switching to blocking mode\n" if DEBUG; |
|
136 |
$self->server->ioloop($self->ioloop); |
|
137 |
delete $self->_cleanup->{nb}; |
|
138 |
} |
|
139 |
$self->_start($tx => sub { shift->ioloop->stop; $tx = shift }); |
|
140 |
$self->ioloop->start; |
|
141 | ||
142 |
return $tx; |
|
143 |
} |
|
144 | ||
145 |
sub websocket { |
|
146 |
my $self = shift; |
|
147 |
my $cb = ref $_[-1] eq 'CODE' ? pop : undef; |
|
148 |
$self->start($self->build_websocket_tx(@_), $cb); |
|
149 |
} |
|
150 | ||
151 |
sub _cleanup { |
|
152 |
my $self = shift; |
|
153 |
return unless my $loop = $self->_loop; |
|
154 | ||
155 |
# Clean up active connections (by closing them) |
|
156 |
$self->_handle($_, 1) for keys %{$self->{connections} || {}}; |
|
157 | ||
158 |
# Clean up keep-alive connections |
|
159 |
$loop->remove($_->[1]) for @{delete $self->{queue} || []}; |
|
160 | ||
161 |
return $self; |
|
162 |
} |
|
163 | ||
164 |
sub _connect { |
|
165 |
my ($self, $proto, $host, $port, $handle, $cb) = @_; |
|
166 | ||
167 |
weaken $self; |
|
168 |
my $id; |
|
169 |
return $id = $self->_loop->client( |
|
170 |
address => $host, |
|
171 |
handle => $handle, |
|
172 |
local_address => $self->local_address, |
|
173 |
port => $port, |
|
174 |
timeout => $self->connect_timeout, |
|
175 |
tls => $proto eq 'https', |
|
176 |
tls_ca => $self->ca, |
|
177 |
tls_cert => $self->cert, |
|
178 |
tls_key => $self->key, |
|
179 |
sub { |
|
180 |
my ($loop, $err, $stream) = @_; |
|
181 | ||
182 |
# Connection error |
|
183 |
return unless $self; |
|
184 |
return $self->_error($id, $err) if $err; |
|
185 | ||
186 |
# Connection established |
|
187 |
$stream->on( |
|
188 |
timeout => sub { $self->_error($id, 'Inactivity timeout', 1) }); |
|
189 |
$stream->on(close => sub { $self->_handle($id, 1) }); |
|
190 |
$stream->on(error => sub { $self && $self->_error($id, pop) }); |
|
191 |
$stream->on(read => sub { $self->_read($id, pop) }); |
|
192 |
$cb->(); |
|
193 |
} |
|
194 |
); |
|
195 |
} |
|
196 | ||
197 |
sub _connect_proxy { |
|
198 |
my ($self, $old, $cb) = @_; |
|
199 | ||
200 |
# Start CONNECT request |
|
201 |
return undef unless my $new = $self->transactor->proxy_connect($old); |
|
202 |
return $self->_start( |
|
203 |
$new => sub { |
|
204 |
my ($self, $tx) = @_; |
|
205 | ||
206 |
# CONNECT failed (connection needs to be kept alive) |
|
207 |
unless ($tx->keep_alive && $tx->res->is_status_class(200)) { |
|
208 |
$old->req->error('Proxy connection failed'); |
|
209 |
return $self->$cb($old); |
|
210 |
} |
|
211 | ||
212 |
# Prevent proxy reassignment and start real transaction |
|
213 |
$old->req->proxy(0); |
|
214 |
my $id = $tx->connection; |
|
215 |
return $self->_start($old->connection($id), $cb) |
|
216 |
unless $tx->req->url->protocol eq 'https'; |
|
217 | ||
218 |
# TLS upgrade |
|
219 |
my $loop = $self->_loop; |
|
220 |
my $handle = $loop->stream($id)->steal_handle; |
|
221 |
my $c = delete $self->{connections}{$id}; |
|
222 |
$loop->remove($id); |
|
223 |
weaken $self; |
|
224 |
$id = $self->_connect($self->transactor->endpoint($old), |
|
225 |
$handle, sub { $self->_start($old->connection($id), $cb) }); |
|
226 |
$self->{connections}{$id} = $c; |
|
227 |
} |
|
228 |
); |
|
229 |
} |
|
230 | ||
231 |
sub _connected { |
|
232 |
my ($self, $id) = @_; |
|
233 | ||
234 |
# Inactivity timeout |
|
235 |
my $stream = $self->_loop->stream($id)->timeout($self->inactivity_timeout); |
|
236 | ||
237 |
# Store connection information in transaction |
|
238 |
my $tx = $self->{connections}{$id}{tx}->connection($id); |
|
239 |
my $handle = $stream->handle; |
|
240 |
$tx->local_address($handle->sockhost)->local_port($handle->sockport); |
|
241 |
$tx->remote_address($handle->peerhost)->remote_port($handle->peerport); |
|
242 | ||
243 |
# Start writing |
|
244 |
weaken $self; |
|
245 |
$tx->on(resume => sub { $self->_write($id) }); |
|
246 |
$self->_write($id); |
|
247 |
} |
|
248 | ||
249 |
sub _connection { |
|
250 |
my ($self, $tx, $cb) = @_; |
|
251 | ||
252 |
# Reuse connection |
|
253 |
my $id = $tx->connection; |
|
254 |
my ($proto, $host, $port) = $self->transactor->endpoint($tx); |
|
255 |
$id ||= $self->_dequeue("$proto:$host:$port", 1); |
|
256 |
if ($id && !ref $id) { |
|
257 |
warn "-- Reusing connection ($proto:$host:$port)\n" if DEBUG; |
|
258 |
$self->{connections}{$id} = {cb => $cb, tx => $tx}; |
|
259 |
$tx->kept_alive(1) unless $tx->connection; |
|
260 |
$self->_connected($id); |
|
261 |
return $id; |
|
262 |
} |
|
263 | ||
264 |
# CONNECT request to proxy required |
|
265 |
if (my $id = $self->_connect_proxy($tx, $cb)) { return $id } |
|
266 | ||
267 |
# Connect |
|
268 |
warn "-- Connect ($proto:$host:$port)\n" if DEBUG; |
|
269 |
($proto, $host, $port) = $self->transactor->peer($tx); |
|
270 |
weaken $self; |
|
271 |
$id = $self->_connect( |
|
272 |
($proto, $host, $port, $id) => sub { $self->_connected($id) }); |
|
273 |
$self->{connections}{$id} = {cb => $cb, tx => $tx}; |
|
274 | ||
275 |
return $id; |
|
276 |
} |
|
277 | ||
278 |
# DEPRECATED in Top Hat! |
|
279 |
sub _delegate { |
|
280 |
my ($self, $attr, $name) = (shift, shift, shift); |
|
281 |
return $self->$attr->$name unless @_; |
|
282 |
$self->$attr->$name(@_); |
|
283 |
return $self; |
|
284 |
} |
|
285 | ||
286 |
sub _dequeue { |
|
287 |
my ($self, $name, $test) = @_; |
|
288 | ||
289 |
my $found; |
|
290 |
my $loop = $self->_loop; |
|
291 |
my $old = $self->{queue} || []; |
|
292 |
my $new = $self->{queue} = []; |
|
293 |
for my $queued (@$old) { |
|
294 |
push @$new, $queued and next if $found || !grep { $_ eq $name } @$queued; |
|
295 | ||
296 |
# Search for id/name and sort out corrupted connections if necessary |
|
297 |
next unless my $stream = $loop->stream($queued->[1]); |
|
298 |
$test && $stream->is_readable ? $stream->close : ($found = $queued->[1]); |
|
299 |
} |
|
300 | ||
301 |
return $found; |
|
302 |
} |
|
303 | ||
304 |
sub _enqueue { |
|
305 |
my ($self, $name, $id) = @_; |
|
306 | ||
307 |
# Enforce connection limit |
|
308 |
my $queue = $self->{queue} ||= []; |
|
309 |
my $max = $self->max_connections; |
|
310 |
$self->_remove(shift(@$queue)->[1]) while @$queue > $max; |
|
311 |
push @$queue, [$name, $id] if $max; |
|
312 |
} |
|
313 | ||
314 |
sub _error { |
|
315 |
my ($self, $id, $err, $timeout) = @_; |
|
316 |
if (my $tx = $self->{connections}{$id}{tx}) { $tx->res->error($err) } |
|
317 |
elsif (!$timeout) { return $self->emit(error => $err) } |
|
318 |
$self->_handle($id, 1); |
|
319 |
} |
|
320 | ||
321 |
sub _handle { |
|
322 |
my ($self, $id, $close) = @_; |
|
323 | ||
324 |
# Remove request timeout |
|
325 |
return unless my $loop = $self->_loop; |
|
326 |
my $c = $self->{connections}{$id}; |
|
327 |
$loop->remove($c->{timeout}) if $c->{timeout}; |
|
328 | ||
329 |
# Finish WebSocket |
|
330 |
my $old = $c->{tx}; |
|
331 |
if ($old && $old->is_websocket) { |
|
332 |
delete $self->{connections}{$id}; |
|
333 |
$self->_remove($id, $close); |
|
334 |
$old->client_close; |
|
335 |
} |
|
336 | ||
337 |
# Upgrade connection to WebSocket |
|
338 |
elsif ($old && (my $new = $self->_upgrade($id))) { |
|
339 |
if (my $jar = $self->cookie_jar) { $jar->extract($old) } |
|
340 |
$old->client_close; |
|
341 |
$c->{cb}->($self, $new); |
|
342 |
$new->client_read($old->res->content->leftovers); |
|
343 |
} |
|
344 | ||
345 |
# Finish normal connection |
|
346 |
else { |
|
347 |
$self->_remove($id, $close); |
|
348 |
return unless $old; |
|
349 |
if (my $jar = $self->cookie_jar) { $jar->extract($old) } |
|
350 |
$old->client_close($close); |
|
351 | ||
352 |
# Handle redirects |
|
353 |
$c->{cb}->($self, $new || $old) unless $self->_redirect($c, $old); |
|
354 |
} |
|
355 |
} |
|
356 | ||
357 |
sub _loop { $_[0]{nb} ? Mojo::IOLoop->singleton : $_[0]->ioloop } |
|
358 | ||
359 |
sub _read { |
|
360 |
my ($self, $id, $chunk) = @_; |
|
361 | ||
362 |
# Corrupted connection |
|
363 |
return unless my $c = $self->{connections}{$id}; |
|
364 |
return $self->_remove($id) unless my $tx = $c->{tx}; |
|
365 | ||
366 |
# Process incoming data |
|
367 |
warn "-- Client <<< Server (@{[$tx->req->url->to_abs]})\n$chunk\n" if DEBUG; |
|
368 |
$tx->client_read($chunk); |
|
369 |
if ($tx->is_finished) { $self->_handle($id) } |
|
370 |
elsif ($c->{tx}->is_writing) { $self->_write($id) } |
|
371 |
} |
|
372 | ||
373 |
sub _remove { |
|
374 |
my ($self, $id, $close) = @_; |
|
375 | ||
376 |
# Close connection |
|
377 |
my $tx = (delete($self->{connections}{$id}) || {})->{tx}; |
|
378 |
if ($close || !$tx || !$tx->keep_alive || $tx->error) { |
|
379 |
$self->_dequeue($id); |
|
380 |
return $self->_loop->remove($id); |
|
381 |
} |
|
382 | ||
383 |
# Keep connection alive (CONNECT requests get upgraded) |
|
384 |
$self->_enqueue(join(':', $self->transactor->endpoint($tx)), $id) |
|
385 |
unless uc $tx->req->method eq 'CONNECT'; |
|
386 |
} |
|
387 | ||
388 |
sub _redirect { |
|
389 |
my ($self, $c, $old) = @_; |
|
390 |
return undef unless my $new = $self->transactor->redirect($old); |
|
391 |
return undef unless @{$old->redirects} < $self->max_redirects; |
|
392 |
return $self->_start($new, delete $c->{cb}); |
|
393 |
} |
|
394 | ||
395 |
sub _start { |
|
396 |
my ($self, $tx, $cb) = @_; |
|
397 | ||
398 |
# Application server |
|
399 |
my $url = $tx->req->url; |
|
400 |
unless ($url->is_abs) { |
|
401 |
my $base = $self->server->url; |
|
402 |
$url->scheme($base->scheme)->authority($base->authority); |
|
403 |
} |
|
404 | ||
405 |
$self->proxy->inject($tx); |
|
406 |
if (my $jar = $self->cookie_jar) { $jar->inject($tx) } |
|
407 | ||
408 |
# Connect and add request timeout if necessary |
|
409 |
my $id = $self->emit(start => $tx)->_connection($tx, $cb); |
|
410 |
if (my $timeout = $self->request_timeout) { |
|
411 |
weaken $self; |
|
412 |
$self->{connections}{$id}{timeout} = $self->_loop->timer( |
|
413 |
$timeout => sub { $self->_error($id, 'Request timeout') }); |
|
414 |
} |
|
415 | ||
416 |
return $id; |
|
417 |
} |
|
418 | ||
419 |
sub _upgrade { |
|
420 |
my ($self, $id) = @_; |
|
421 | ||
422 |
my $c = $self->{connections}{$id}; |
|
423 |
return undef unless my $new = $self->transactor->upgrade($c->{tx}); |
|
424 |
weaken $self; |
|
425 |
$new->on(resume => sub { $self->_write($id) }); |
|
426 | ||
427 |
return $c->{tx} = $new; |
|
428 |
} |
|
429 | ||
430 |
sub _write { |
|
431 |
my ($self, $id) = @_; |
|
432 | ||
433 |
# Get and write chunk |
|
434 |
return unless my $c = $self->{connections}{$id}; |
|
435 |
return unless my $tx = $c->{tx}; |
|
436 |
return unless $tx->is_writing; |
|
437 |
return if $c->{writing}++; |
|
438 |
my $chunk = $tx->client_write; |
|
439 |
delete $c->{writing}; |
|
440 |
warn "-- Client >>> Server (@{[$tx->req->url->to_abs]})\n$chunk\n" if DEBUG; |
|
441 |
my $stream = $self->_loop->stream($id)->write($chunk); |
|
442 |
$self->_handle($id) if $tx->is_finished; |
|
443 | ||
444 |
# Continue writing |
|
445 |
return unless $tx->is_writing; |
|
446 |
weaken $self; |
|
447 |
$stream->write('' => sub { $self->_write($id) }); |
|
448 |
} |
|
449 | ||
450 |
1; |
|
451 | ||
452 |
=encoding utf8 |
|
453 | ||
454 |
=head1 NAME |
|
455 | ||
456 |
Mojo::UserAgent - Non-blocking I/O HTTP and WebSocket user agent |
|
457 | ||
458 |
=head1 SYNOPSIS |
|
459 | ||
460 |
use Mojo::UserAgent; |
|
461 |
my $ua = Mojo::UserAgent->new; |
|
462 | ||
463 |
# Say hello to the Unicode snowman with "Do Not Track" header |
|
464 |
say $ua->get('www.☃.net?hello=there' => {DNT => 1})->res->body; |
|
465 | ||
466 |
# Form POST with exception handling |
|
467 |
my $tx = $ua->post('https://metacpan.org/search' => form => {q => 'mojo'}); |
|
468 |
if (my $res = $tx->success) { say $res->body } |
|
469 |
else { |
|
470 |
my ($err, $code) = $tx->error; |
|
471 |
say $code ? "$code response: $err" : "Connection error: $err"; |
|
472 |
} |
|
473 | ||
474 |
# Quick JSON API request with Basic authentication |
|
475 |
say $ua->get('https://sri:s3cret@example.com/search.json?q=perl') |
|
476 |
->res->json('/results/0/title'); |
|
477 | ||
478 |
# Extract data from HTML and XML resources |
|
479 |
say $ua->get('www.perl.org')->res->dom->html->head->title->text; |
|
480 | ||
481 |
# Scrape the latest headlines from a news site |
|
482 |
say $ua->get('perlnews.org')->res->dom('h2 > a')->text->shuffle; |
|
483 | ||
484 |
# IPv6 PUT request with content |
|
485 |
my $tx |
|
486 |
= $ua->put('[::1]:3000' => {'Content-Type' => 'text/plain'} => 'Hello!'); |
|
487 | ||
488 |
# Grab the latest Mojolicious release :) |
|
489 |
$ua->max_redirects(5)->get('latest.mojolicio.us') |
|
490 |
->res->content->asset->move_to('/Users/sri/mojo.tar.gz'); |
|
491 | ||
492 |
# TLS certificate authentication and JSON POST |
|
493 |
my $tx = $ua->cert('tls.crt')->key('tls.key') |
|
494 |
->post('https://example.com' => json => {top => 'secret'}); |
|
495 | ||
496 |
# Blocking parallel requests (does not work inside a running event loop) |
|
497 |
my $delay = Mojo::IOLoop->delay; |
|
498 |
for my $url ('mojolicio.us', 'cpan.org') { |
|
499 |
my $end = $delay->begin(0); |
|
500 |
$ua->get($url => sub { |
|
501 |
my ($ua, $tx) = @_; |
|
502 |
$end->($tx->res->dom->at('title')->text); |
|
503 |
}); |
|
504 |
} |
|
505 |
my @titles = $delay->wait; |
|
506 | ||
507 |
# Non-blocking parallel requests (does work inside a running event loop) |
|
508 |
my $delay = Mojo::IOLoop->delay(sub { |
|
509 |
my ($delay, @titles) = @_; |
|
510 |
... |
|
511 |
}); |
|
512 |
for my $url ('mojolicio.us', 'cpan.org') { |
|
513 |
my $end = $delay->begin(0); |
|
514 |
$ua->get($url => sub { |
|
515 |
my ($ua, $tx) = @_; |
|
516 |
$end->($tx->res->dom->at('title')->text); |
|
517 |
}); |
|
518 |
} |
|
519 |
$delay->wait unless Mojo::IOLoop->is_running; |
|
520 | ||
521 |
# Non-blocking WebSocket connection sending and receiving JSON messages |
|
522 |
$ua->websocket('ws://example.com/echo.json' => sub { |
|
523 |
my ($ua, $tx) = @_; |
|
524 |
say 'WebSocket handshake failed!' and return unless $tx->is_websocket; |
|
525 |
$tx->on(json => sub { |
|
526 |
my ($tx, $hash) = @_; |
|
527 |
say "WebSocket message via JSON: $hash->{msg}"; |
|
528 |
$tx->finish; |
|
529 |
}); |
|
530 |
$tx->send({json => {msg => 'Hello World!'}}); |
|
531 |
}); |
|
532 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
533 | ||
534 |
=head1 DESCRIPTION |
|
535 | ||
536 |
L<Mojo::UserAgent> is a full featured non-blocking I/O HTTP and WebSocket user |
|
537 |
agent, with IPv6, TLS, SNI, IDNA, Comet (long polling), keep-alive, connection |
|
538 |
pooling, timeout, cookie, multipart, proxy, gzip compression and multiple |
|
539 |
event loop support. |
|
540 | ||
541 |
All connections will be reset automatically if a new process has been forked, |
|
542 |
this allows multiple processes to share the same L<Mojo::UserAgent> object |
|
543 |
safely. |
|
544 | ||
545 |
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS |
|
546 |
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.16+) and |
|
547 |
L<IO::Socket::SSL> (1.75+) will be used automatically by L<Mojo::IOLoop> if |
|
548 |
they are installed. Individual features can also be disabled with the |
|
549 |
MOJO_NO_IPV6 and MOJO_NO_TLS environment variables. |
|
550 | ||
551 |
See L<Mojolicious::Guides::Cookbook> for more. |
|
552 | ||
553 |
=head1 EVENTS |
|
554 | ||
555 |
L<Mojo::UserAgent> inherits all events from L<Mojo::EventEmitter> and can emit |
|
556 |
the following new ones. |
|
557 | ||
558 |
=head2 error |
|
559 | ||
560 |
$ua->on(error => sub { |
|
561 |
my ($ua, $err) = @_; |
|
562 |
... |
|
563 |
}); |
|
564 | ||
565 |
Emitted if an error occurs that can't be associated with a transaction, fatal |
|
566 |
if unhandled. |
|
567 | ||
568 |
$ua->on(error => sub { |
|
569 |
my ($ua, $err) = @_; |
|
570 |
say "This looks bad: $err"; |
|
571 |
}); |
|
572 | ||
573 |
=head2 start |
|
574 | ||
575 |
$ua->on(start => sub { |
|
576 |
my ($ua, $tx) = @_; |
|
577 |
... |
|
578 |
}); |
|
579 | ||
580 |
Emitted whenever a new transaction is about to start, this includes |
|
581 |
automatically prepared proxy C<CONNECT> requests and followed redirects. |
|
582 | ||
583 |
$ua->on(start => sub { |
|
584 |
my ($ua, $tx) = @_; |
|
585 |
$tx->req->headers->header('X-Bender' => 'Bite my shiny metal ass!'); |
|
586 |
}); |
|
587 | ||
588 |
=head1 ATTRIBUTES |
|
589 | ||
590 |
L<Mojo::UserAgent> implements the following attributes. |
|
591 | ||
592 |
=head2 ca |
|
593 | ||
594 |
my $ca = $ua->ca; |
|
595 |
$ua = $ua->ca('/etc/tls/ca.crt'); |
|
596 | ||
597 |
Path to TLS certificate authority file, defaults to the value of the |
|
598 |
MOJO_CA_FILE environment variable. Also activates hostname verification. |
|
599 | ||
600 |
# Show certificate authorities for debugging |
|
601 |
IO::Socket::SSL::set_defaults( |
|
602 |
SSL_verify_callback => sub { say "Authority: $_[2]" and return $_[0] }); |
|
603 | ||
604 |
=head2 cert |
|
605 | ||
606 |
my $cert = $ua->cert; |
|
607 |
$ua = $ua->cert('/etc/tls/client.crt'); |
|
608 | ||
609 |
Path to TLS certificate file, defaults to the value of the MOJO_CERT_FILE |
|
610 |
environment variable. |
|
611 | ||
612 |
=head2 connect_timeout |
|
613 | ||
614 |
my $timeout = $ua->connect_timeout; |
|
615 |
$ua = $ua->connect_timeout(5); |
|
616 | ||
617 |
Maximum amount of time in seconds establishing a connection may take before |
|
618 |
getting canceled, defaults to the value of the MOJO_CONNECT_TIMEOUT |
|
619 |
environment variable or C<10>. |
|
620 | ||
621 |
=head2 cookie_jar |
|
622 | ||
623 |
my $cookie_jar = $ua->cookie_jar; |
|
624 |
$ua = $ua->cookie_jar(Mojo::UserAgent::CookieJar->new); |
|
625 | ||
626 |
Cookie jar to use for this user agents requests, defaults to a |
|
627 |
L<Mojo::UserAgent::CookieJar> object. |
|
628 | ||
629 |
# Disable cookie jar |
|
630 |
$ua->cookie_jar(0); |
|
631 | ||
632 |
=head2 inactivity_timeout |
|
633 | ||
634 |
my $timeout = $ua->inactivity_timeout; |
|
635 |
$ua = $ua->inactivity_timeout(15); |
|
636 | ||
637 |
Maximum amount of time in seconds a connection can be inactive before getting |
|
638 |
closed, defaults to the value of the MOJO_INACTIVITY_TIMEOUT environment |
|
639 |
variable or C<20>. Setting the value to C<0> will allow connections to be |
|
640 |
inactive indefinitely. |
|
641 | ||
642 |
=head2 ioloop |
|
643 | ||
644 |
my $loop = $ua->ioloop; |
|
645 |
$ua = $ua->ioloop(Mojo::IOLoop->new); |
|
646 | ||
647 |
Event loop object to use for blocking I/O operations, defaults to a |
|
648 |
L<Mojo::IOLoop> object. |
|
649 | ||
650 |
=head2 key |
|
651 | ||
652 |
my $key = $ua->key; |
|
653 |
$ua = $ua->key('/etc/tls/client.crt'); |
|
654 | ||
655 |
Path to TLS key file, defaults to the value of the MOJO_KEY_FILE environment |
|
656 |
variable. |
|
657 | ||
658 |
=head2 local_address |
|
659 | ||
660 |
my $address = $ua->local_address; |
|
661 |
$ua = $ua->local_address('127.0.0.1'); |
|
662 | ||
663 |
Local address to bind to. |
|
664 | ||
665 |
=head2 max_connections |
|
666 | ||
667 |
my $max = $ua->max_connections; |
|
668 |
$ua = $ua->max_connections(5); |
|
669 | ||
670 |
Maximum number of keep-alive connections that the user agent will retain |
|
671 |
before it starts closing the oldest ones, defaults to C<5>. |
|
672 | ||
673 |
=head2 max_redirects |
|
674 | ||
675 |
my $max = $ua->max_redirects; |
|
676 |
$ua = $ua->max_redirects(3); |
|
677 | ||
678 |
Maximum number of redirects the user agent will follow before it fails, |
|
679 |
defaults to the value of the MOJO_MAX_REDIRECTS environment variable or C<0>. |
|
680 | ||
681 |
=head2 proxy |
|
682 | ||
683 |
my $proxy = $ua->proxy; |
|
684 |
$ua = $ua->proxy(Mojo::UserAgent::Proxy->new); |
|
685 | ||
686 |
Proxy manager, defaults to a L<Mojo::UserAgent::Proxy> object. |
|
687 | ||
688 |
# Detect proxy servers from environment |
|
689 |
$ua->proxy->detect; |
|
690 | ||
691 |
=head2 request_timeout |
|
692 | ||
693 |
my $timeout = $ua->request_timeout; |
|
694 |
$ua = $ua->request_timeout(5); |
|
695 | ||
696 |
Maximum amount of time in seconds establishing a connection, sending the |
|
697 |
request and receiving a whole response may take before getting canceled, |
|
698 |
defaults to the value of the MOJO_REQUEST_TIMEOUT environment variable or |
|
699 |
C<0>. Setting the value to C<0> will allow the user agent to wait |
|
700 |
indefinitely. The timeout will reset for every followed redirect. |
|
701 | ||
702 |
# Total limit of 5 seconds, of which 3 seconds may be spent connecting |
|
703 |
$ua->max_redirects(0)->connect_timeout(3)->request_timeout(5); |
|
704 | ||
705 |
=head2 server |
|
706 | ||
707 |
my $server = $ua->server; |
|
708 |
$ua = $ua->server(Mojo::UserAgent::Server->new); |
|
709 | ||
710 |
Application server relative URLs will be processed with, defaults to a |
|
711 |
L<Mojo::UserAgent::Server> object. |
|
712 | ||
713 |
# Introspect |
|
714 |
say $ua->server->app->secret; |
|
715 | ||
716 |
# Change log level |
|
717 |
$ua->server->app->log->level('fatal'); |
|
718 | ||
719 |
# Port currently used for processing relative URLs |
|
720 |
say $ua->server->url->port; |
|
721 | ||
722 |
=head2 transactor |
|
723 | ||
724 |
my $t = $ua->transactor; |
|
725 |
$ua = $ua->transactor(Mojo::UserAgent::Transactor->new); |
|
726 | ||
727 |
Transaction builder, defaults to a L<Mojo::UserAgent::Transactor> object. |
|
728 | ||
729 |
# Change name of user agent |
|
730 |
$ua->transactor->name('MyUA 1.0'); |
|
731 | ||
732 |
=head1 METHODS |
|
733 | ||
734 |
L<Mojo::UserAgent> inherits all methods from L<Mojo::EventEmitter> and |
|
735 |
implements the following new ones. |
|
736 | ||
737 |
=head2 build_tx |
|
738 | ||
739 |
my $tx = $ua->build_tx(GET => 'example.com'); |
|
740 |
my $tx = $ua->build_tx(PUT => 'http://example.com' => {DNT => 1} => 'Hi!'); |
|
741 |
my $tx = $ua->build_tx( |
|
742 |
PUT => 'http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
743 |
my $tx = $ua->build_tx( |
|
744 |
PUT => 'http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
745 | ||
746 |
Generate L<Mojo::Transaction::HTTP> object with |
|
747 |
L<Mojo::UserAgent::Transactor/"tx">. |
|
748 | ||
749 |
# Request with cookie |
|
750 |
my $tx = $ua->build_tx(GET => 'example.com'); |
|
751 |
$tx->req->cookies({name => 'foo', value => 'bar'}); |
|
752 |
$ua->start($tx); |
|
753 | ||
754 |
=head2 build_websocket_tx |
|
755 | ||
756 |
my $tx = $ua->build_websocket_tx('ws://example.com'); |
|
757 |
my $tx = |
|
758 |
$ua->build_websocket_tx('ws://example.com' => {DNT => 1} => ['v1.proto']); |
|
759 | ||
760 |
Generate L<Mojo::Transaction::HTTP> object with |
|
761 |
L<Mojo::UserAgent::Transactor/"websocket">. |
|
762 | ||
763 |
=head2 delete |
|
764 | ||
765 |
my $tx = $ua->delete('example.com'); |
|
766 |
my $tx = $ua->delete('http://example.com' => {DNT => 1} => 'Hi!'); |
|
767 |
my $tx = $ua->delete( |
|
768 |
'http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
769 |
my $tx = $ua->delete( |
|
770 |
'http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
771 | ||
772 |
Perform blocking DELETE request and return resulting |
|
773 |
L<Mojo::Transaction::HTTP> object, takes the same arguments as |
|
774 |
L<Mojo::UserAgent::Transactor/"tx"> (except for the method). You can also |
|
775 |
append a callback to perform requests non-blocking. |
|
776 | ||
777 |
$ua->delete('http://example.com' => sub { |
|
778 |
my ($ua, $tx) = @_; |
|
779 |
say $tx->res->body; |
|
780 |
}); |
|
781 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
782 | ||
783 |
=head2 get |
|
784 | ||
785 |
my $tx = $ua->get('example.com'); |
|
786 |
my $tx = $ua->get('http://example.com' => {DNT => 1} => 'Hi!'); |
|
787 |
my $tx = $ua->get('http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
788 |
my $tx = $ua->get('http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
789 | ||
790 |
Perform blocking GET request and return resulting L<Mojo::Transaction::HTTP> |
|
791 |
object, takes the same arguments as L<Mojo::UserAgent::Transactor/"tx"> |
|
792 |
(except for the method). You can also append a callback to perform requests |
|
793 |
non-blocking. |
|
794 | ||
795 |
$ua->get('http://example.com' => sub { |
|
796 |
my ($ua, $tx) = @_; |
|
797 |
say $tx->res->body; |
|
798 |
}); |
|
799 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
800 | ||
801 |
=head2 head |
|
802 | ||
803 |
my $tx = $ua->head('example.com'); |
|
804 |
my $tx = $ua->head('http://example.com' => {DNT => 1} => 'Hi!'); |
|
805 |
my $tx = $ua->head( |
|
806 |
'http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
807 |
my $tx = $ua->head( |
|
808 |
'http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
809 | ||
810 |
Perform blocking HEAD request and return resulting L<Mojo::Transaction::HTTP> |
|
811 |
object, takes the same arguments as L<Mojo::UserAgent::Transactor/"tx"> |
|
812 |
(except for the method). You can also append a callback to perform requests |
|
813 |
non-blocking. |
|
814 | ||
815 |
$ua->head('http://example.com' => sub { |
|
816 |
my ($ua, $tx) = @_; |
|
817 |
say $tx->res->body; |
|
818 |
}); |
|
819 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
820 | ||
821 |
=head2 options |
|
822 | ||
823 |
my $tx = $ua->options('example.com'); |
|
824 |
my $tx = $ua->options('http://example.com' => {DNT => 1} => 'Hi!'); |
|
825 |
my $tx = $ua->options( |
|
826 |
'http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
827 |
my $tx = $ua->options( |
|
828 |
'http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
829 | ||
830 |
Perform blocking OPTIONS request and return resulting |
|
831 |
L<Mojo::Transaction::HTTP> object, takes the same arguments as |
|
832 |
L<Mojo::UserAgent::Transactor/"tx"> (except for the method). You can also |
|
833 |
append a callback to perform requests non-blocking. |
|
834 | ||
835 |
$ua->options('http://example.com' => sub { |
|
836 |
my ($ua, $tx) = @_; |
|
837 |
say $tx->res->body; |
|
838 |
}); |
|
839 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
840 | ||
841 |
=head2 patch |
|
842 | ||
843 |
my $tx = $ua->patch('example.com'); |
|
844 |
my $tx = $ua->patch('http://example.com' => {DNT => 1} => 'Hi!'); |
|
845 |
my $tx = $ua->patch( |
|
846 |
'http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
847 |
my $tx = $ua->patch( |
|
848 |
'http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
849 | ||
850 |
Perform blocking PATCH request and return resulting L<Mojo::Transaction::HTTP> |
|
851 |
object, takes the same arguments as L<Mojo::UserAgent::Transactor/"tx"> |
|
852 |
(except for the method). You can also append a callback to perform requests |
|
853 |
non-blocking. |
|
854 | ||
855 |
$ua->patch('http://example.com' => sub { |
|
856 |
my ($ua, $tx) = @_; |
|
857 |
say $tx->res->body; |
|
858 |
}); |
|
859 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
860 | ||
861 |
=head2 post |
|
862 | ||
863 |
my $tx = $ua->post('example.com'); |
|
864 |
my $tx = $ua->post('http://example.com' => {DNT => 1} => 'Hi!'); |
|
865 |
my $tx = $ua->post( |
|
866 |
'http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
867 |
my $tx = $ua->post( |
|
868 |
'http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
869 | ||
870 |
Perform blocking POST request and return resulting L<Mojo::Transaction::HTTP> |
|
871 |
object, takes the same arguments as L<Mojo::UserAgent::Transactor/"tx"> |
|
872 |
(except for the method). You can also append a callback to perform requests |
|
873 |
non-blocking. |
|
874 | ||
875 |
$ua->post('http://example.com' => sub { |
|
876 |
my ($ua, $tx) = @_; |
|
877 |
say $tx->res->body; |
|
878 |
}); |
|
879 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
880 | ||
881 |
=head2 put |
|
882 | ||
883 |
my $tx = $ua->put('example.com'); |
|
884 |
my $tx = $ua->put('http://example.com' => {DNT => 1} => 'Hi!'); |
|
885 |
my $tx = $ua->put('http://example.com' => {DNT => 1} => form => {a => 'b'}); |
|
886 |
my $tx = $ua->put('http://example.com' => {DNT => 1} => json => {a => 'b'}); |
|
887 | ||
888 |
Perform blocking PUT request and return resulting L<Mojo::Transaction::HTTP> |
|
889 |
object, takes the same arguments as L<Mojo::UserAgent::Transactor/"tx"> |
|
890 |
(except for the method). You can also append a callback to perform requests |
|
891 |
non-blocking. |
|
892 | ||
893 |
$ua->put('http://example.com' => sub { |
|
894 |
my ($ua, $tx) = @_; |
|
895 |
say $tx->res->body; |
|
896 |
}); |
|
897 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
898 | ||
899 |
=head2 start |
|
900 | ||
901 |
my $tx = $ua->start(Mojo::Transaction::HTTP->new); |
|
902 | ||
903 |
Perform blocking request. You can also append a callback to perform requests |
|
904 |
non-blocking. |
|
905 | ||
906 |
my $tx = $ua->build_tx(GET => 'http://example.com'); |
|
907 |
$ua->start($tx => sub { |
|
908 |
my ($ua, $tx) = @_; |
|
909 |
say $tx->res->body; |
|
910 |
}); |
|
911 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
912 | ||
913 |
=head2 websocket |
|
914 | ||
915 |
$ua->websocket('ws://example.com' => sub {...}); |
|
916 |
$ua->websocket( |
|
917 |
'ws://example.com' => {DNT => 1} => ['v1.proto'] => sub {...}); |
|
918 | ||
919 |
Open a non-blocking WebSocket connection with transparent handshake, takes the |
|
920 |
same arguments as L<Mojo::UserAgent::Transactor/"websocket">. The callback |
|
921 |
will receive either a L<Mojo::Transaction::WebSocket> or |
|
922 |
L<Mojo::Transaction::HTTP> object. |
|
923 | ||
924 |
$ua->websocket('ws://example.com/echo' => sub { |
|
925 |
my ($ua, $tx) = @_; |
|
926 |
say 'WebSocket handshake failed!' and return unless $tx->is_websocket; |
|
927 |
$tx->on(finish => sub { |
|
928 |
my ($tx, $code, $reason) = @_; |
|
929 |
say "WebSocket closed with status $code."; |
|
930 |
}); |
|
931 |
$tx->on(message => sub { |
|
932 |
my ($tx, $msg) = @_; |
|
933 |
say "WebSocket message: $msg"; |
|
934 |
$tx->finish; |
|
935 |
}); |
|
936 |
$tx->send('Hi!'); |
|
937 |
}); |
|
938 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
939 | ||
940 |
=head1 DEBUGGING |
|
941 | ||
942 |
You can set the MOJO_USERAGENT_DEBUG environment variable to get some advanced |
|
943 |
diagnostics information printed to C<STDERR>. |
|
944 | ||
945 |
MOJO_USERAGENT_DEBUG=1 |
|
946 | ||
947 |
=head1 SEE ALSO |
|
948 | ||
949 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
950 | ||
951 |
=cut |