building api client libraries - sdd conference...today’s plan why build them? foundations...

49
BUILDING API CLIENT LIBRARIES THAT DON’T SUCK @darrel_miller Code Monkey

Upload: others

Post on 23-Jan-2021

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

BUILDING API CLIENT

LIBRARIESTHAT DON’T SUCK

@darrel_miller

Code Monkey

Page 2: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

TODAY’S PLAN

Why build them?

Foundations

Structure

Decor

HTTP Client Library

URIs

Requests/Responses

Managing State

Services, Missions and Reactive UI

Page 3: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

WHY CLIENT LIBRARIES?

Increase Developer Adoption

Reduce Developer Effort

Improve Client Application Quality

Improve Client Application Performance

Page 4: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT – COMMON INTERFACE

var response = await httpClient.GetAsync("http://example.org");

var response = await httpClient.PutAsync("http://example.org", content);

var response = await httpClient.PostAsync("http://example.org", content);

var response = await httpClient.DeleteAsync("http://example.org", content);

var response = await httpClient.SendAsync(request);

HttpMethod.HeadHttpMethod.Options

Page 5: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

public async Task<string> GetAllThings(){

using (HttpClient httpClient = new HttpClient()){

var uri = "http://example.org/api/search?key=...";var response = await httpClient.GetAsync(uri);var jdata = await response.Content.ReadAsStringAsync();return jdata;

}}

HTTP CLIENT - LIFETIME

Page 6: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

public class ApiService{

private HttpClient _HttpClient;

public ApiService(){

_HttpClient = new HttpClient(new WebRequestHandler());

_HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("ApiService", "1.0"));

_HttpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en"));

}}

HTTP CLIENT - LIFETIME

Page 7: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT STATE

BaseAddress

Timeout

MaxResponseContentBufferSize

DefaultRequestHeaders

PendingRequestsCancellationTokenSource

Page 8: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT - MIDDLEWARE

Client Application

HttpClient

MessageHandler

MessageHandler

HTTP HandlerHttpClientHandler

WebRequestHandler

WinHttpHandler

NativeMessageHandler

CachingLogging

RedirectionCompression

Cookie HandlingAuthorization

Page 9: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT - FACTORY

var client = HttpClientFactory.Create(new[] {new AuthMiddleware(),new CachingMiddleware(),new RetryMiddleware()

});

https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Client/

Page 10: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT - BUILDER

var builder = new ClientBuilder();

builder.Use(new AuthMiddleware(...));builder.Use(new CachingMiddleware(...));builder.Use(new RetryMiddleware(...));

var client = builder.Build();

Page 11: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT - AUTHENTICATION

client.DefaultRequestHeaders.Authorization= new AuthenticationHeaderValue("bearer", "sometoken");

Page 12: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT - AUTHENTICATION

var builder = new ClientBuilder();

var cache = new HttpCredentialCache();LoadCacheWithCredentials(cache);builder.UseAuthHandler(cache);

var client = builder.Build();

Page 13: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT - AUTHENTICATION

User-agent Origin Server

Unauthorized Request

401 with WWW-Authenticate Header

Authorized Request

200 OK

Auth

Middleware

Credential

Cache

Page 14: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTP CLIENT - CACHING

User Agent

Origin Server

Output Cache

Reverse Proxy Cache

ISP Cache

Corporate Proxy Cache

Private Cache

Page 15: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

WININET PROXY CACHE

HttpClient

WebRequestHandler

HttpWebRequest

WinHttpHandler

WinInet WinHttp

IE, Chrome WebClient

In a Service

Client Cache No Client Cache

Page 16: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTPCLIENT

var builder = new ClientBuilder();

builder.UseAuthHandler(GetCredentialCache(creds));

builder.UseHttpCache(new InMemoryContentStore());

var client = builder.Build();

Page 17: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

HTTPCLIENT - CACHING

Stored Response

HttpCacheHandler

HttpCache

IContentStore

QueryCacheResult(ReturnStored | Revalidate |

CannotUseCache)HttpResponseMessage

Request

Response

Page 18: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

CONDITIONAL REQUESTS

User

Agent

Origin

Server

Private

CacheFresh

Response

User

Agent

Origin

ServerPrivate

CacheFresh

Response

GET

200

CacheControl : Max-age=5

Etag : xyz

GET

200

User

Agent

Origin

Server

Private

Cache

GET

200

GET

304

If-None-Match: xyz

CacheControl : Max-age=5Stale

Response

Fresh

Response

Page 19: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

RESOURCE IDENTIFIERS

Discover Resolve Dereference

https://example.org/my-resource

Page 20: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

URL CONSTRUCTION

Don’t write custom URL construction code

Page 21: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

URI TEMPLATES

Consider using URI Templates RFC 6570

Github does!

Page 22: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

EXTREME TEMPLATES

var url = new UriTemplate("{+baseUrl}{/folder*}/search/code{/language}{?params*}")

.AddParameter("params",new Dictionary<string, string> { {"query", "GetAsync"},

{"sort_order", "desc"}}).AddParameter("baseUrl", "http://api.github.com").AddParameter("folder", new List<string> {"home", "src", "widgets"})

.Resolve();

http://www.bizcoder.com/constructing-urls-the-easy-way

http://api.github.com/home/src/widgets/search/code?query=GetAsync&sort_order=desc

Page 23: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

MAKE CHANGE EASY

Centralize Hardcoded URLs

Page 24: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

LINK TYPES

<link rel="apple-touch-icon image_src“href="//cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png?v=...">

<link rel="search" type="application/opensearchdescription+xml" title="Stack Overflow" href="/opensearch.xml">

<link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/Sites/stackoverflow/all.css?v=3f4c51969762">

<link rel="alternate“type="application/atom+xml" title="Feed of recent questions" href="/feeds">

Page 25: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

LINKS ARE DATA

{"swagger": "2.0","info": {"title": "Forecast API","description": "The Forecast API lets you query for most locations on the globe, and

returns: current conditions, minute-by-minute forecasts out to 1 hour (where available), hour-by-hour forecasts out to 48 hours, and more.",

"version": "1.0"},"host": "api.forecast.io","basePath": "","schemes": ["https"

],"paths": {"/forecast/{apiKey}/{latitude},{longitude}": {"get": {...}

}}

}

Page 26: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

LINK – DISCOVERY DOCUMENT

var discoverLink = new OpenApiLink() {Target = new Uri("http://api.forecast.io/")

};var discoveryResponse = await client.SendAsync(discoverLink.CreateRequest());

_discoveryDoc = OpenApiDocument.Load(await discoveryResponse.Content.ReadAsStreamAsync());

var forecastLink = discoveryDoc.GetOperationLink("getForecast");var forecastResponse = await client.SendAsync(speakersLink.CreateRequest());

Page 27: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

BREAKING REQUEST FROM RESPONSE

Create Request

Process Request

Handle

Response

Page 28: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

CREATING REQUESTS

var link = new SpeakersLink {SpeakerName = "bob"};var request = link.CreateRequest();

var response = await _httpClient.SendAsync(request);

Page 29: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

REQUEST FACTORY

public interface IRequestFactory{

string LinkRelation { get; }HttpRequestMessage CreateRequest();

}

Page 30: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

FOLLOW LINK

var link = new SpeakersLink {SpeakerName = "bob"};

var response = await _httpClient.FollowLinkAsync(request);

Page 31: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

FOLLOW SECRETS

public static class HttpClientExtensions{

public const string PropertyKeyRequestFactory = "tavis.requestfactory";

public static Task<HttpResponseMessage> FollowLinkAsync(this HttpClient httpClient,

IRequestFactory requestFactory,IResponseHandler handler = null)

{var httpRequestMessage = requestFactory.CreateRequest();httpRequestMessage.Properties[PropertyKeyRequestFactory] = requestFactory;

return httpClient.SendAsync(httpRequestMessage).ApplyRepresentationToAsync(handler);

}}

Page 32: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

CENTRALIZED RESPONSE HANDLING

Create Request

Process Request

HTTP Response

Machine

Page 33: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

FIRE AND FORGET

var machine = GetHttpResponseMachine();

await httpClient.FollowLinkAsync(link,machine);

Page 34: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

DISPATCH ON STATUS

bool ok = false;var machine = new HttpResponseMachine();

machine.When(HttpStatusCode.OK).Then(async (l, r) => { ok = true; });

await machine.HandleResponseAsync(null, new HttpResponseMessage(HttpStatusCode.OK));

Assert.True(ok);

Page 35: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

DISPATCH ON MEDIA TYPE

machine.When(HttpStatusCode.OK, null, new MediaTypeHeaderValue("application/json")).Then(async (l, r) =>

{var text = await r.Content.ReadAsStringAsync();root = JToken.Parse(text);

});

machine.When(HttpStatusCode.OK, null, new MediaTypeHeaderValue("application/xml")).Then(async (l, r) => { ... });

Page 36: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

NEED MORE CONTEXT

machine.When(HttpStatusCode.OK, linkRelation: "http://tavis.net/rels/login").Then(LoginSuccessful);

machine.When(HttpStatusCode.Unauthorized, linkRelation:"http://tavis.net/rels/login ").Then(LoginFailed);

machine.When(HttpStatusCode.Forbidden, linkRelation: "http://tavis.net/rels/login").Then(LoginForbidden);

machine.When(HttpStatusCode.BadRequest, linkRelation: "http://tavis.net/rels/login").Then(FailedRequest);

machine.When(HttpStatusCode.OK, linkRelation: " http://tavis.net/rels/reset").Then(ResetForm);

Page 37: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

PARSING MESSAGES

machine.When(HttpStatusCode.OK).Then<Person>((m, l, p) => { aPerson = p; });

Page 38: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

PARSING MESSAGES

var parserStore = new ParserStore();

parserStore.AddMediaTypeParser<JToken>("application/json", async (content) =>{

var stream = await content.ReadAsStreamAsync();return JToken.Load(new JsonTextReader(new StreamReader(stream)));

});

parserStore.AddLinkRelationParser<JToken, Person>(“http://tavis.net/rels/person", (jt) =>{

var person = new Person();var jobject = (JObject)jt;person.FirstName = (string)jobject["FirstName"];person.LastName = (string)jobject["LastName"];return person;

});

var machine = new HttpResponseMachine(parserStore);

Page 39: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

STATE MANAGEMENT

Create Request

Process Request

HTTP Response

Machine

Client State

Model

Client View

Application

Controller

Page 40: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

AFFECT MY STATE

Model<Person> test = new Model<Person>();

var parserStore = LoadParserStore();

var machine = new HttpResponseMachine<Model<Person>>(test,parserStore);

machine.When(HttpStatusCode.OK).Then<Person>((m, l, p) => { m.Value = p; });

Page 41: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

DEATH TO SERIALIZERS

Missing values, unrecognized properties

Does null mean unspecified, or explicitly null?

Non standard data types: datetime, timespan

Empty collection or no collection

Capitalization

Links

Cycles

Changes to behavior in the frameworks

Security Risks

Page 42: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

STREAMING JSON PARSER

public static void ParseStream(Stream stream, object rootSubject, VocabTerm rootTerm){

using (var reader = new JsonTextReader(new StreamReader(stream))){

...

while (reader.Read()){

switch (reader.TokenType){

...}

}}

}

Page 43: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

PARSING VOCABULARIES

var vocab = new VocabTerm<ProblemDocument>();

vocab.MapProperty<string>("type", (s, o) => s.ProblemType = new Uri(o));vocab.MapProperty<string>("title", (s, o) => s.Title = o);vocab.MapProperty<string>("detail", (s, o) => s.Detail = o);vocab.MapProperty<string>("instance", (s, o) => {

s.ProblemInstance = new Uri(o,UriKind.RelativeOrAbsolute);});

var problem = new ProblemDocument();

JsonStreamingParser.ParseStream(stream, problem, vocab);

Page 44: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

PARTIAL PARSING

var opsTerm = new VocabTerm<Operation>();

var pathTerm = new VocabTerm<Path>();pathTerm.MapAnyObject(opsTerm, (s, p) => {

return s.AddOperation(p, Guid.NewGuid().ToString());});

var pathsTerm = new VocabTerm<OpenApiDocument>("paths");

pathsTerm.MapAnyObject(pathTerm,(s,p) => s.AddPath(p));

var rootTerm = new VocabTerm<OpenApiDocument>();rootTerm.MapObject(pathsTerm, (s) => s);

var openAPI = new OpenApiDocument();JsonStreamingParser.ParseStream(stream, openAPI, rootTerm);

Page 45: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

ASSEMBLING THE PIECES

HttpClient

Application Links Response Machine

Client Application

StateMiddleware

Parsers

Page 46: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

SUMMARY

Leverage the developer’s

knowledge of HTTP and

their platform

Use HTTP’s layered

architecture to add

value

Own your message

parsing

Make asynchrony work for

you

Allow HTTP’s uniform

interface to enable

reuse

React don’t assume

Page 47: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

LIBRARIES

http://github.com/tavis-software

Tavis.UriTemplates

Tavis.Link

Tavis.Home

Tavis.Problem

Tavis.JsonPatch

Tavis.HttpCache

Tavis.Auth

Tavis.JsonPointer

Tavis.Hal

Tavis.Status

http://hapikit.github.io

Hapikit.net

Hapikit.py

Hapikit.go

Tooling for building

better HTTP API Client

Libraries

Page 48: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

SAMPLES

https://github.com/Runscope/dotnet-webpack

https://github.com/hapikit/github.net.hapikit

https://github.com/darrelmiller/ForceLinksForNet

https://github.com/hapikit/stormpath.net.hapikit

https://github.com/hapikit/haveibeenpwnd.net.hapikit

Page 49: BUILDING API CLIENT LIBRARIES - SDD Conference...TODAY’S PLAN Why build them? Foundations Structure Decor HTTP Client Library URIs Requests/Responses Managing State Services, Missions

BON APPETIT

Twitter: @darrel_miller

Blog: http://www.bizcoder.com/

Latest Slides: http://bit.ly/darrel-sddconf