2 <html><head><meta charset=
"utf-8"><title>Bring Your Own User-Agent
</title><link rel=
"stylesheet" href=
"css/common.css"><link rel=
"stylesheet" href=
"css/slides.css"></head><body><textarea id=
"source">
7 # Bring Your Own User-Agent
14 I want to talk to you about HTTP user agents.
17 class: img-map, center, middle
19 ![WebService modules on CPAN](img/webservice-on-cpan.png)
23 There is a whole class of so-called "web service" modules on CPAN that provide a nice perly interface for interacting
26 All kinds of things, from...
29 class: img-map, center, middle
31 ![Twilio module](img/twilio.png)
37 class: img-map, center, middle
39 ![Ontario Power Generation module](img/opg.png)
42 the Ontario Power Generation website.
47 class: img-map, center, middle
49 ![WebService modules on CPAN](img/webservice-on-cpan-circled.png)
52 Most of these modules congregate here (or at least they should).
60 In the `WebService` namespace.
66 There are other common namespaces for this sort of thing...
75 ##
<strike>`Net`
</strike>
76 ##
<strike>`WWW`
</strike>
79 PSA: For new stuff, prefer the `WebService` namespace for such modules.
84 ## `WebService` modules are useful.
87 Even though a lot of APIs nowadays are RESTful which may be easy to use with just your favorite user agent, these
88 modules often take care of some of the tricky or boring details, like:
93 Details that are important but you don't want to read through the API documentation to figure it out.
102 And here's the problem...
105 class: center, middle
107 ## These modules are .major-em[**tightly-coupled**] to specific .major-em[user agents].
112 class: center, middle
114 ![Dependencies](img/deps1.png)
115 ![Dependencies](img/deps2.png)
116 ![Dependencies](img/deps3.png)
117 ![Dependencies](img/deps4.png)
118 ![Dependencies](img/deps5.png)
121 Most of them use `LWP` or `HTTP::Tiny`.
124 class: center, middle
126 ## This... could be better.
129 Now I'm going to try to convince you that this is a problem.
132 class: center, middle
136 ### How to configure the user agent...
139 User agents typically have defaults and so may not need to be configured, but what if the user needs the user agent to
150 use WebService::Whatever;
152 my $ws = WebService::Whatever-
>new(verify_SSL =
> 1);
156 my $resp = $ws-
>account_info;
162 So, one way this has been solved is for the webservice to expose all the attributes and knobs needed to configure the
163 internal agent and just pass them through.
165 But this kinda bad because now every webservice module has to do this,
167 ...and every module will probably do it slightly differently.
174 use WebService::Whatever;
176 my $ua = HTTP::Tiny-
>new(verify_SSL =
> 1);
178 *my $ws = WebService::Whatever-
>new(ua =
> $ua);
182 my $resp = $ws-
>account_info;
188 Then someone remembered that dependency injection was a thing, so now we have modules that let you pass in your own user
191 This is great because it lets the user set up the user agent however they want, and webservice modules writers don't
192 need to do any of that boring work.
200 *use Mojo::UserAgent;
201 use WebService::Whatever;
203 *my $ua = Mojo::UserAgent-
>new(insecure =
> 0);
205 my $ws = WebService::Whatever-
>new(ua =
> $ua);
207 $ua-
>connect_timeout(
10);
209 my $resp = $ws-
>account_info; # ;-(
215 But I can't just plug in any user agent I want!
217 If the webservice module was written for `HTTP::Tiny` or any other user agent, it's expecting that I'm going to pass it
218 the kind of user agent it wants.
223 class: center, middle
225 ## Let the user decide.
228 I think the user writing a program should decide which user agent to use.
230 After all, they're the ones who know the requirements of their program.
232 If I'm writing a program that needs to use the least amount of resources, and I want to use a webservice that is coupled
233 with a *not* tiny user agent, then I'm out of luck.
235 Or if somebody wrote a great webservice module using a blocking interface like `HTTP::Tiny` or `LWP` that I want to use
236 but my program is event-driven and so can't block, then I'm out of luck again.
239 ## [`Mail::SendGrid`](https://metacpan.org/pod/Mail::SendGrid) -
> [`HTTP::Tiny`](https://metacpan.org/pod/HTTP::Tiny)
240 ## [`Mojo::Sendgrid`](https://metacpan.org/pod/Mojo::Sendgrid) -
> [`Mojo::UserAgent`](https://metacpan.org/pod/Mojo::UserAgent)
241 ## [`WebService::SendGrid`](https://metacpan.org/pod/WebService::SendGrid) -
> [`Net::Curl::Simple`](https://metacpan.org/pod/Net::Curl::Simple)
245 The solution we've come up with so far is to just implement a new webservice module for each type of user agent that
251 class: center, middle
253 ## There's a better way.
256 What we need is user agent **adapter**.
258 As in, the **adapter pattern**, which is the same pattern we generally use for the myriad "Any" modules.
260 We need something that has an common inteface that module writers can code against, and then adapters to transform the
261 request and response appropriately for whatever real user agent is wanted.
263 So yeah, this isn't an original idea of mine.
266 ## [`HTTP::Any`](https://metacpan.org/pod/HTTP::Any)
269 I searched CPAN and found just such a thing!
272 #### But it has some .major-em[fatal flaws]...
275 in my opinion. (No offense to this module's author.)
278 ###
1. It provides its own *new* interface.
281 - Okay, this one's not fatal.
282 - Nobody wants to learn yet another user agent interface.
283 - And it's a callback interface in order to support non-blocking user agents.
285 But having to set callback functions if you're not actually using a non-blocking user agent is kinda clunky.
288 ###
2. It doesn't support many user agents.
296 ###
3. It doesn't actually provide a common interface.
299 so it's not really usable as an adapter.
301 There were a couple other potential solutions on CPAN I found, but none of them overcome all of these problems.
303 Some of the modules that look like they might work at face value, actually are aiming to solve the opposite problem;
304 that is, when the user doesn't care what user agent is used, just find one and use it.
307 class: center, middle
312 class: center, middle
314 ## [`HTTP::AnyUA`](https://metacpan.org/pod/HTTP::AnyUA)
317 This one is different because it has a "UA" at the end (for "user agent").
319 It also solves the problems I had with the other module.
322 ## [`HTTP::AnyUA`](https://metacpan.org/pod/HTTP::AnyUA)
324 ###
1. Uses the `HTTP::Tiny` interface.
327 - So not much new to learn.
328 - And it doesn't make you use a callback interface if your user agent is non-blocking.
330 If your webservice module already uses `HTTP::Tiny`, this is *almost* a drop-in replacement.
333 ###
2. Supports at least six user agents.
336 - [`AnyEvent::HTTP`](https://metacpan.org/pod/AnyEvent::HTTP)
337 - [`Furl`](https://metacpan.org/pod/Furl)
338 - [`HTTP::Tiny`](https://metacpan.org/pod/HTTP::Tiny)
341 - [`LWP::UserAgent`](https://metacpan.org/pod/LWP::UserAgent)
342 - [`Mojo::UserAgent`](https://metacpan.org/pod/Mojo::UserAgent)
343 - [`Net::Curl::Easy`](https://metacpan.org/pod/Net::Curl::Easy)
347 Plus any user agent that inherits from any of these in a well-behaved manner should also work.
349 It's pretty easy to support for other user agents.
352 ###
3. Provides a *common* interface.
355 The interface, like I said, is the `HTTP::Tiny` interface.
361 has ua =
> ( # <-- user agent
371 HTTP::AnyUA-
>new(ua =
> $self-
>ua);
377 A webservice module implementing this looks something like this.
378 - Allow the user to pass in a useragent. You could also default to `HTTP::Tiny` or something if you wanted the attribute
380 - Then you construct an `HTTP::AnyUA` instance and pass it the useragent.
389 * my $resp = $self-
>any_ua-
>get(
390 $self-
>base_url . '/account',
393 authorization =
> $self-
>auth,
403 The webservice methods then use the `HTTP::AnyUA` to make requests using the same args and response that `HTTP::Tiny`
410 my $ua = HTTP::Tiny-
>new;
412 my $ws = WebService::Whatever-
>new(ua =
> $ua);
414 my $resp = $ws-
>account_info;
418 A **user** of the webservice module would look like this.
420 You just provide the useragent to the webservice.
426 my $ua = LWP::UserAgent-
>new;
428 my $ws = WebService::Whatever-
>new(ua =
> $ua);
430 my $resp = $ws-
>account_info;
437 my $ua = Mojo::UserAgent-
>new;
439 my $ws = WebService::Whatever-
>new(ua =
> $ua);
441 my $resp = $ws-
>account_info;
448 my $ua = 'AnyEvent::HTTP';
450 my $ws = WebService::Whatever-
>new(ua =
> $ua);
452 my $resp = $ws-
>account_info;
459 my $ua = 'AnyEvent::HTTP';
461 my $ws = WebService::Whatever-
>new(ua =
> $ua);
463 my $resp = $ws-
>account_info;
467 # url =
> "https://whatever/account"
470 # content =
> "{...}",
476 The response from `HTTP::AnyUA` always looks like an `HTTP::Tiny` response, regardless of which user agent the user
479 In this case, my "whatever" webservice is just passing the raw response back to the user, but a more useful service will
480 decode the response content.
482 And, in the case that the user provides a non-blocking user agent, then instead of returning a hashref with the normal
483 `HTTP::Tiny` response, it returns a `Future` object that resolves to a hashref with the normal `HTTP::Tiny` response. So
484 you know what to expect.
487 class: center, middle
489 ![HTTP::AnyUA diagram](img/http-anyua-diagram.svg)
492 I think this is pretty cool already, but I'll show you one more thing before I get kicked off that's even cooler...
494 When you have a request response pipeline that is shaped like this, it begs to have...
497 class: center, middle
499 ![HTTP::AnyUA with middleware diagram](img/http-anyua-middleware-diagram.svg)
502 Just like in PSGI where you can have middleware components between whatever server handler and the app, you can do the
503 same sort of thing for the client.
505 You can write components that work for any user agent and plug them in.
507 I've written only a couple components,
508 - one to time or profile each request and
509 - another to ensure a proper 'content-length' header is set.
511 Middleware components can do anything, even short-circuit and not actually call the user agent.
513 I started writing a caching component, but it's taking me awhile to write because I do want it to be
514 `RFC-
7234`-compliant (and my interest jumps around), but it will be cool because not every user agent has a decent
517 With HTTP::AnyUA, I just need to implement the cache once and it works for all user agents that can be plugged into this
521 class: center, middle
526 If you're writing a module that needs to *use* an HTTP user agent but otherwise has no reason to care what the user
527 agent actually is, consider using `HTTP::AnyUA` or something like it (instead of coupling directly with a user agent).
529 It will make your webservice module usable by more people.
532 class: center, middle
537 </textarea><script src=
"https://gnab.github.io/remark/downloads/remark-latest.min.js"></script><script>var slideshow = remark.create({countIncrementalSlides: true, highlightLanguage: '', highlightLines: true, highlightStyle: 'hybrid', ratio: '
16:
9', /*slideNumberFormat: '',*/ navigation: {scroll: false, touch: false, click: false}})
</script><script src=
"js/common.js"></script><script src=
"js/slides.js"></script></body></html>
538 <!-- vim: set ts=4 sts=4 sw=4 tw=120 et ft=markdown nowrap: -->