]> Dogcows Code - chaz/talk-bring-your-own-user-agent/blob - slides.html
37678e64d19c9d667f18c8027717a6fa5b13af78
[chaz/talk-bring-your-own-user-agent] / slides.html
1 <!DOCTYPE html>
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">
3
4 class: center, middle
5 name: title
6
7 # Bring Your Own User-Agent
8
9 ???
10 Hi, I'm Chaz.
11
12 I want to talk to you about HTTP user agents.
13
14 Some time ago I had an idea and I wrote a module and put it up on CPAN.
15
16 I don't think anyone knows it exists because I didn't promote it at all. I didn't even really talk to anyone about it.
17 I'm not a very social person.
18
19 Anyway, I think it *might* actually be a good idea so I'm going to share it now.
20
21 ---
22 class: center, middle
23
24 ## The problem.
25
26 ???
27 So here's the problem that I wanted to solve.
28
29 ---
30 class: img-map, center, middle
31
32 ![WebService modules on CPAN](img/webservice-on-cpan.png)
33
34 ???
35
36 There is a whole class of so-called "web service" modules on CPAN that provide a nice perly interface for interacting
37 with... web services.
38
39 All kinds of things, from...
40
41 ---
42 class: img-map, center, middle
43
44 ![Twilio module](img/twilio.png)
45
46 ???
47 to
48
49 ---
50 class: img-map, center, middle
51
52 ![Ontario Power Generation module](img/opg.png)
53
54 ???
55 the Ontario Power Generation website.
56
57 ---
58 class: img-map, center, middle
59
60 ![WebService modules on CPAN](img/webservice-on-cpan-circled.png)
61
62 ???
63 Most of these modules congregate here.
64
65 ---
66 class: center, middle
67
68 ## `WebService`
69
70 --
71 ## `Net`
72
73 --
74 ## `WWW`
75
76 ---
77 class: center, middle
78
79 ## `WebService`
80 ## <strike>`Net`</strike>
81 ## <strike>`WWW`</strike>
82
83 ???
84 PSA: For new stuff, prefer the `WebService` namespace for such modules.
85
86 ---
87 class: center, middle
88
89 ## `WebService` modules are useful.
90
91 ???
92 Even though a lot of APIs nowadays are RESTful which may be easy to use with just your favorite user agent, these
93 modules often take care of some of the tricky or boring details, like:
94
95 - authentication
96 - paging
97
98 Details that are important but you don't want to read through the API documentation to figure it out.
99
100 ---
101 class: center, middle
102
103 ## But
104
105 ???
106 And here's the problem...
107
108 ---
109 class: center, middle
110
111 ## These modules are **tightly-coupled** to specific user agents.
112
113 ### ;-(
114
115 ---
116 class: center, middle
117
118 ![Dependencies](img/deps1.png)
119 ![Dependencies](img/deps2.png)
120 ![Dependencies](img/deps3.png)
121 ![Dependencies](img/deps4.png)
122 ![Dependencies](img/deps5.png)
123
124 ???
125 Most of them use `LWP` or `HTTP::Tiny`.
126
127 ---
128 class: center, middle
129
130 ## This has problems.
131
132 ???
133 Now I'm going to try to convince you that this is a problem.
134
135 ---
136 class: center, middle
137
138 ## Problem #1
139
140 ### How to configure the user agent...
141
142 ???
143 User agents typically have defaults and so may not need to be configured, but what if the user needs the user agent to
144 support proxying, caching, TLS, or shorter timeouts?
145
146 If the webservice package is *composing* (or wrapping) a user agent, then the webservice package needs to expose all of
147 the ways that the user agent can be configured.
148
149 ---
150 class: ex-code
151
152 ```perl
153 use WebService::Whatever;
154
155 my $ws = WebService::Whatever->new(verify_SSL => 1);
156
157 $ws->timeout(10);
158
159 my $resp = $ws->account_info;
160
161 ...
162 ```
163
164 ???
165 So, one way this has been solved is for the webservice to expose all the attributes and knobs needed to configure the
166 internal agent.
167
168 But that's terrible.
169
170 ---
171 class: ex-code
172
173 ```perl
174 use HTTP::Tiny;
175 use WebService::Whatever;
176
177 my $ua = HTTP::Tiny->new(verify_SSL => 1);
178
179 *my $ws = WebService::Whatever->new(ua => $ua);
180
181 $ua->timeout(10);
182
183 my $resp = $ws->account_info;
184
185 ...
186 ```
187
188 ???
189 So someone remembered that dependency injection was a thing, so now we have modules that let you pass in your own user
190 agent.
191
192 Big improvement!
193
194 ---
195 class: ex-code
196
197 ```perl
198 *use Mojo::UserAgent;
199 use WebService::Whatever;
200
201 *my $ua = Mojo::UserAgent->new(insecure => 0);
202
203 my $ws = WebService::Whatever->new(ua => $ua);
204
205 $ua->connect_timeout(10);
206
207 my $resp = $ws->account_info; # ;-(
208
209 ...
210 ```
211
212 ???
213 But I can't just plug in any user agent I want! If the webservice module was written for `HTTP::Tiny` or any other user
214 agent, it's expecting that I'm going to pass it the kind of user agent it wants.
215
216 This makes me sad.
217
218 ---
219 class: center, middle
220
221 ## Let the user decide.
222
223 ???
224 I think the user writing a program should decide which user agent to use.
225
226 After all, they're the ones who know what the requirements of their app are.
227
228 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
229 with a *not* tiny user agent, then I'm out of luck.
230
231 Or if somebody wrote a great webservice module using a blocking interface like `HTTP::Tiny` or `LWP` that I want to use
232 but my program is event-driven and so can't block, then I'm out of luck again.
233
234 So then what, are we just going to write separate webservice modules for each user agent?
235
236 ---
237
238 ## [`Mail::SendGrid`](https://metacpan.org/pod/Mail::SendGrid) -> [`HTTP::Tiny`](https://metacpan.org/pod/HTTP::Tiny)
239 ## [`Mojo::Sendgrid`](https://metacpan.org/pod/Mojo::Sendgrid) -> [`Mojo::UserAgent`](https://metacpan.org/pod/Mojo::UserAgent)
240 ## [`WebService::SendGrid`](https://metacpan.org/pod/WebService::SendGrid) -> [`Net::Curl::Simple`](https://metacpan.org/pod/Net::Curl::Simple)
241
242 ???
243 Yeah, that's exactly what's up.
244
245 What we need is user agent adapter (as in the adapter pattern).
246
247 Something that has an inteface that module writers can code against and then translates the request and response
248 appropriately for whatever real user agent is provided.
249
250 ---
251
252 ## [`HTTP::Any`](https://metacpan.org/pod/HTTP::Any)
253
254 ???
255 I searched CPAN and found just such a thing!
256
257 --
258 #### But it has fatal flaws...
259
260 ???
261 in my opinion. (No offense to this module's author.)
262
263 --
264 ### 1. It provides its own *new* interface.
265
266 ???
267 - And nobody wants to learn yet another user agent interface.
268 - And it's a callback interface in order to support non-blocking user agents,
269
270 But having to set callback functions if you're not actually using a non-blocking user agent is kinda clunky.
271
272 --
273 ### 2. It doesn't support many user agents.
274
275 ???
276 only `LWP`, `AnyEvent`, and `Curl`.
277
278 --
279 ### 3. It doesn't actually provide a common interface.
280
281 ???
282 so it's not really usable as an adapter.
283
284 ---
285 class: center, middle
286
287 ## I wrote a module to fix these problems.
288
289 ---
290 class: center, middle
291
292 ## [`HTTP::AnyUA`](https://metacpan.org/pod/HTTP::AnyUA)
293
294 ???
295 This one is different because it has a "UA" at the end.
296
297 It's also a true HTTP user agent adapter providing a common interface.
298
299 ---
300 ## [`HTTP::AnyUA`](https://metacpan.org/pod/HTTP::AnyUA)
301
302 ### 1. Uses the `HTTP::Tiny` interface.
303
304 ???
305 - So not much new to learn.
306 - And you don't have to use callbacks if your user agent is non-blocking.
307
308 --
309 ### 2. Supports at least six user agents.
310
311 .col[
312 - [`AnyEvent::HTTP`](https://metacpan.org/pod/AnyEvent::HTTP)
313 - [`Furl`](https://metacpan.org/pod/Furl)
314 - [`HTTP::Tiny`](https://metacpan.org/pod/HTTP::Tiny)
315 ]
316 .col[
317 - [`LWP::UserAgent`](https://metacpan.org/pod/LWP::UserAgent)
318 - [`Mojo::UserAgent`](https://metacpan.org/pod/Mojo::UserAgent)
319 - [`Net::Curl::Easy`](https://metacpan.org/pod/Net::Curl::Easy)
320 ]
321
322 ???
323 Plus any user agent that inherits from any of these in a well-behaved manner should also work.
324
325 --
326 ### 3. Provides a *common* interface.
327
328 ???
329 which, like I said, is the `HTTP::Tiny` interface.
330
331 ---
332 class: ex-code
333
334 ```perl
335 has ua => (
336 is => 'ro',
337 required => 1,
338 );
339
340 has any_ua => (
341 is => 'lazy',
342 default => sub {
343 my $self = shift;
344 require HTTP::AnyUA;
345 HTTP::AnyUA->new(ua => $self->ua);
346 },
347 );
348 ```
349
350 ???
351 A webservice module implementing this looks something like this.
352 - Allow the user to pass in a useragent. You could also default to `HTTP::Tiny` or something if you wanted the attribute
353 to be optional.
354 - Then you construct an `HTTP::AnyUA` instance and pass it the useragent.
355
356 ---
357 class: ex-code
358
359 ```perl
360 sub account_info {
361 my $self = shift;
362
363 * my $resp = $self->any_ua->get(
364 $self->base_url . '/account',
365 {
366 headers => {
367 authorization => $self->auth,
368 },
369 },
370 );
371
372 return $resp;
373 }
374 ```
375
376 ???
377 The webservice methods then use the `HTTP::AnyUA` to make requests using the same args and response that `HTTP::Tiny`
378 has.
379
380 ---
381 class: ex-code
382
383 ```perl
384 my $ua = HTTP::Tiny->new;
385
386 my $ws = WebService::Whatever->new(ua => $ua);
387
388 my $resp = $ws->account_info;
389 ```
390
391 ???
392 A **user** of the webservice module would look like this.
393
394 You just provide the useragent to the webservice.
395
396 ---
397 class: ex-code
398
399 ```perl
400 my $ua = LWP::UserAgent->new;
401
402 my $ws = WebService::Whatever->new(ua => $ua);
403
404 my $resp = $ws->account_info;
405 ```
406
407 ---
408 class: ex-code
409
410 ```perl
411 my $ua = Mojo::UserAgent->new;
412
413 my $ws = WebService::Whatever->new(ua => $ua);
414
415 my $resp = $ws->account_info;
416 ```
417
418 ---
419 class: ex-code
420
421 ```perl
422 my $ua = 'AnyEvent::HTTP';
423
424 my $ws = WebService::Whatever->new(ua => $ua);
425
426 my $resp = $ws->account_info;
427 ```
428
429 ---
430 class: ex-code
431
432 ```perl
433 my $ua = 'AnyEvent::HTTP';
434
435 my $ws = WebService::Whatever->new(ua => $ua);
436
437 my $resp = $ws->account_info;
438
439 # {
440 # success => 1,
441 # url => "https://whatever/account"
442 # status => 200,
443 # reason => "OK",
444 # content => "{...}",
445 # headers => {...},
446 # }
447 ```
448
449 ???
450 The response from `HTTP::AnyUA` always looks like an `HTTP::Tiny` response, regardless of which user agent the user
451 provides.
452
453 In this case, my "whatever" webservice is just passing the raw response back to the user, but a more useful service will
454 decode the response content.
455
456 And, in the case that the user provides a non-blocking user agent, then instead of returning a hashref with the normal
457 `HTTP::Tiny` response, it returns a `Future` object that resolves to a hashref with the normal `HTTP::Tiny` response. So
458 you know what to expect.
459
460 ---
461 class: center, middle
462
463 ![HTTP::AnyUA diagram](img/http-anyua-diagram.svg)
464
465 ???
466 I think this is pretty cool already, but I'll show you one more thing before I get kicked off that's even cooler...
467
468 ---
469 class: center, middle
470
471 ![HTTP::AnyUA with middleware diagram](img/http-anyua-middleware-diagram.svg)
472
473 ???
474 You can write components that work for any user agent and plug them in. I've written only a couple such components, one
475 to time the request takes and another to ensure a proper 'content-length' header is set.
476
477 Middleware components can do anything, even short-circuit and not actually call the user agent.
478
479 I started writing a caching component. This middleware is taking me awhile to write because I want it to be
480 `RFC-7234`-compliant (and my interest jumps around), but it would be cool because not every user agent has a decent
481 cache.
482
483 With HTTP::AnyUA, I just need to implement the cache once and it works for all of them.
484
485 ---
486 class: center, middle
487
488 ## Conclusion
489
490 ???
491 If you're writing a module that needs to *use* an HTTP user agent but otherwise has no reason to prefer one over
492 another, consider using `HTTP::AnyUA` or something like it.
493
494 ---
495 class: center, middle
496 name: last
497
498 ### Thanks.
499
500 </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>
501 <!-- vim: set ts=4 sts=4 sw=4 tw=120 et ft=markdown nowrap: -->
This page took 0.050986 seconds and 3 git commands to generate.