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
12 I want to talk to you about HTTP user agents.
15 class: img-map, center, middle
17 ![WebService modules on CPAN](img/webservice-on-cpan.png)
21 There is a whole class of so-called "web service" modules on CPAN that provide a nice perly interface for interacting
24 All kinds of things, from...
27 class: img-map, center, middle
29 ![Twilio module](img/twilio.png)
35 class: img-map, center, middle
37 ![Ontario Power Generation module](img/opg.png)
40 the Ontario Power Generation website.
45 class: img-map, center, middle
47 ![WebService modules on CPAN](img/webservice-on-cpan-circled.png)
50 Most of these modules congregate here (or at least they should).
58 In the `WebService` namespace.
64 There are other common namespaces for this sort of thing...
73 ##
<strike>`Net`
</strike>
74 ##
<strike>`WWW`
</strike>
77 PSA: For new stuff, prefer the `WebService` namespace for such modules.
82 ## `WebService` modules are useful.
85 Even though a lot of APIs nowadays are RESTful which may be easy to use with just your favorite user agent, these
86 modules often take care of some of the tricky or boring details, like:
91 Details that are important but you don't want to read through the API documentation to figure it out.
100 And here's the problem...
103 class: center, middle
105 ## These modules are .major-em[**tightly-coupled**] to specific .major-em[user agents].
110 class: center, middle
112 ![Dependencies](img/deps1.png)
113 ![Dependencies](img/deps2.png)
114 ![Dependencies](img/deps3.png)
115 ![Dependencies](img/deps4.png)
116 ![Dependencies](img/deps5.png)
119 Most of them use `LWP` or `HTTP::Tiny`.
122 class: center, middle
124 ## This... could be better.
127 Now I'm going to try to convince you that this is a problem.
130 class: center, middle
134 ### How to configure the user agent...
137 User agents typically have defaults and so may not need to be configured, but what if the user needs the user agent to
148 use WebService::Whatever;
150 my $ws = WebService::Whatever-
>new(verify_SSL =
> 1);
154 my $resp = $ws-
>account_info;
160 So, one way this has been solved is for the webservice to expose all the attributes and knobs needed to configure the
161 internal agent and just pass them through.
163 But this kinda bad because now every webservice module has to do this,
165 ...and every module will probably do it slightly differently.
172 use WebService::Whatever;
174 my $ua = HTTP::Tiny-
>new(verify_SSL =
> 1);
176 *my $ws = WebService::Whatever-
>new(ua =
> $ua);
180 my $resp = $ws-
>account_info;
186 Then someone remembered that dependency injection was a thing, so now we have modules that let you pass in your own user
189 This is great because it lets the user set up the user agent however they want, and webservice modules writers don't
190 need to do any of that boring work.
198 *use Mojo::UserAgent;
199 use WebService::Whatever;
201 *my $ua = Mojo::UserAgent-
>new(insecure =
> 0);
203 my $ws = WebService::Whatever-
>new(ua =
> $ua);
205 $ua-
>connect_timeout(
10);
207 my $resp = $ws-
>account_info; # ;-(
213 But I can't just plug in any user agent I want!
215 If the webservice module was written for `HTTP::Tiny` or any other user agent, it's expecting that I'm going to pass it
216 the kind of user agent it wants.
221 class: center, middle
223 ## Let the user decide.
226 I think the user writing a program should decide which user agent to use.
228 After all, they're the ones who know the requirements of their program.
230 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
231 with a *not* tiny user agent, then I'm out of luck.
233 Or if somebody wrote a great webservice module using a blocking interface like `HTTP::Tiny` or `LWP` that I want to use
234 but my program is event-driven and so can't block, then I'm out of luck again.
237 ## [`Mail::SendGrid`](https://metacpan.org/pod/Mail::SendGrid) -
> [`HTTP::Tiny`](https://metacpan.org/pod/HTTP::Tiny)
238 ## [`Mojo::Sendgrid`](https://metacpan.org/pod/Mojo::Sendgrid) -
> [`Mojo::UserAgent`](https://metacpan.org/pod/Mojo::UserAgent)
239 ## [`WebService::SendGrid`](https://metacpan.org/pod/WebService::SendGrid) -
> [`Net::Curl::Simple`](https://metacpan.org/pod/Net::Curl::Simple)
243 The solution we've come up with so far is to just implement a new webservice module for each type of user agent that
249 class: center, middle
251 ## There's a better way.
254 What we need is user agent **adapter**.
256 As in, the **adapter pattern**, which is the same pattern we generally use for the myriad "Any" modules.
258 We need something that has an common inteface that module writers can code against, and then adapters to transform the
259 request and response appropriately for whatever real user agent is wanted.
261 So yeah, this isn't an original idea of mine.
264 ## [`HTTP::Any`](https://metacpan.org/pod/HTTP::Any)
267 I searched CPAN and found just such a thing!
270 #### But it has some .major-em[fatal flaws]...
273 in my opinion. (No offense to this module's author.)
276 ###
1. It provides its own *new* interface.
279 - Okay, this one's not fatal.
280 - Nobody wants to learn yet another user agent interface.
281 - And it's a callback interface in order to support non-blocking user agents.
283 But having to set callback functions if you're not actually using a non-blocking user agent is kinda clunky.
286 ###
2. It doesn't support many user agents.
294 ###
3. It doesn't actually provide a common interface.
297 so it's not really usable as an adapter.
299 There were a couple other potential solutions on CPAN I found, but none of them overcome all of these problems.
301 Some of the modules that look like they might work at face value, actually are aiming to solve the opposite problem;
302 that is, when the user doesn't care what user agent is used, just find one and use it.
305 class: center, middle
310 class: center, middle
312 ## [`HTTP::AnyUA`](https://metacpan.org/pod/HTTP::AnyUA)
315 This one is different because it has a "UA" at the end (for "user agent").
317 It also solves the problems I had with the other module.
320 ## [`HTTP::AnyUA`](https://metacpan.org/pod/HTTP::AnyUA)
322 ###
1. Uses the `HTTP::Tiny` interface.
325 - So not much new to learn.
326 - And it doesn't make you use a callback interface if your user agent is non-blocking.
328 If your webservice module already uses `HTTP::Tiny`, this is *almost* a drop-in replacement.
331 ###
2. Supports at least six user agents.
334 - [`AnyEvent::HTTP`](https://metacpan.org/pod/AnyEvent::HTTP)
335 - [`Furl`](https://metacpan.org/pod/Furl)
336 - [`HTTP::Tiny`](https://metacpan.org/pod/HTTP::Tiny)
339 - [`LWP::UserAgent`](https://metacpan.org/pod/LWP::UserAgent)
340 - [`Mojo::UserAgent`](https://metacpan.org/pod/Mojo::UserAgent)
341 - [`Net::Curl::Easy`](https://metacpan.org/pod/Net::Curl::Easy)
345 Plus any user agent that inherits from any of these in a well-behaved manner should also work.
347 It's pretty easy to support for other user agents.
350 ###
3. Provides a *common* interface.
353 The interface, like I said, is the `HTTP::Tiny` interface.
359 has ua =
> ( # <-- user agent
369 HTTP::AnyUA-
>new(ua =
> $self-
>ua);
375 A webservice module implementing this looks something like this.
376 - Allow the user to pass in a useragent. You could also default to `HTTP::Tiny` or something if you wanted the attribute
378 - Then you construct an `HTTP::AnyUA` instance and pass it the useragent.
387 * my $resp = $self-
>any_ua-
>get(
388 $self-
>base_url . '/account',
391 authorization =
> $self-
>auth,
401 The webservice methods then use the `HTTP::AnyUA` to make requests using the same args and response that `HTTP::Tiny`
408 my $ua = HTTP::Tiny-
>new;
410 my $ws = WebService::Whatever-
>new(ua =
> $ua);
412 my $resp = $ws-
>account_info;
416 A **user** of the webservice module would look like this.
418 You just provide the useragent to the webservice.
424 my $ua = LWP::UserAgent-
>new;
426 my $ws = WebService::Whatever-
>new(ua =
> $ua);
428 my $resp = $ws-
>account_info;
435 my $ua = Mojo::UserAgent-
>new;
437 my $ws = WebService::Whatever-
>new(ua =
> $ua);
439 my $resp = $ws-
>account_info;
446 my $ua = 'AnyEvent::HTTP';
448 my $ws = WebService::Whatever-
>new(ua =
> $ua);
450 my $resp = $ws-
>account_info;
457 my $ua = 'AnyEvent::HTTP';
459 my $ws = WebService::Whatever-
>new(ua =
> $ua);
461 my $resp = $ws-
>account_info;
465 # url =
> "https://whatever/account"
468 # content =
> "{...}",
474 The response from `HTTP::AnyUA` always looks like an `HTTP::Tiny` response, regardless of which user agent the user
477 In this case, my "whatever" webservice is just passing the raw response back to the user, but a more useful service will
478 decode the response content.
480 And, in the case that the user provides a non-blocking user agent, then instead of returning a hashref with the normal
481 `HTTP::Tiny` response, it returns a `Future` object that resolves to a hashref with the normal `HTTP::Tiny` response. So
482 you know what to expect.
485 class: center, middle
487 ![HTTP::AnyUA diagram](img/http-anyua-diagram.svg)
490 I think this is pretty cool already, but I'll show you one more thing before I get kicked off that's even cooler...
492 When you have a request response pipeline that is shaped like this, it begs to have...
495 class: center, middle
497 ![HTTP::AnyUA with middleware diagram](img/http-anyua-middleware-diagram.svg)
500 Just like in PSGI where you can have middleware components between whatever server handler and the app, you can do the
501 same sort of thing for the client.
503 You can write components that work for any user agent and plug them in.
505 I've written only a couple components,
506 - one to time or profile each request and
507 - another to ensure a proper 'content-length' header is set.
509 Middleware components can do anything, even short-circuit and not actually call the user agent.
511 I started writing a caching component, but it's taking me awhile to write because I do want it to be
512 `RFC-
7234`-compliant (and my interest jumps around), but it will be cool because not every user agent has a decent
515 With HTTP::AnyUA, I just need to implement the cache once and it works for all user agents that can be plugged into this
519 class: center, middle
524 If you're writing a module that needs to *use* an HTTP user agent but otherwise has no reason to care what the user
525 agent actually is, consider using `HTTP::AnyUA` or something like it (instead of coupling directly with a user agent).
527 It will make your webservice module usable by more people.
530 class: center, middle
535 </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>
536 <!-- vim: set ts=4 sts=4 sw=4 tw=120 et ft=markdown nowrap: -->