Efficient HTTP Apis
A walk through http/2 via okhttp
@adrianfcole
introduction
hello http api!
hello okhttp!
uh oh.. scale!
hello http/2!
wrapping up
introduction
hello http api!
hello okhttp!
uh oh.. scale!
hello http/2!
wrapping up
* Can be blamed for the http/2 defects in okhttp!
@adrianfcole
• engineer at Pivotal on Spring Cloud
• lots of open source work
• focus on cloud computing
okhttp
• HttpURLConnection Apache HttpClient compatible
• designed for java and android
• BDFL: Jesse Wilson from square
https://github.com/square/okhttp
introduction
hello http api!
hello okhttp!
uh oh.. scale!
hello http/2!
wrapping up
$ curl https://apihost/things 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Accept: application/json’
[
{
"id": 1,
"owner_id": 0,
"name": "Able"
},
...
{
"id": 26,
"owner_id": 0,
$ curl https://apihost/things/2 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Accept: application/json’
{
"id": 2,
"owner_id": 0,
"name": "Beatific"
}
$ curl -X POST https://apihost/things 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Content-Type: application/json’ -d
’{
"name": "Open-minded"
}’
$ curl -X DELETE https://apihost/things/2 
-H ‘SecurityToken: b08c85073c1a2d02’
json apis are so simple
$ curl https://apihost/things 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Accept: application/json’
[
{
"id": 1,
"owner_id": 0,
"name": "Able"
},
...
{
"id": 26,
"owner_id": 0,
"name": "Zest"
}
$ curl https://apihost/things/2 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Accept: application/json’
{
"id": 2,
"owner_id": 0,
"name": "Beatific"
}
$ curl -X POST https://apihost/things 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Content-Type: application/json’ -d
’{ "name": "Open-minded" }’
$ curl -X DELETE https://apihost/things/2 
-H ‘SecurityToken: b08c85073c1a2d02’
so many bytes?!
1642!
$ curl --compress https://apihost/things 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Accept: application/json’
-H ‘Accept-encoding: gzip, deflate’
[
{
"id": 1,
"owner_id": 0,
"name": "Able"
},
...
{
"id": 26,
"owner_id": 0,
"name": "Zest"
$ curl https://apihost/things/2 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Accept: application/json’
{
"id": 2,
"owner_id": 0,
"name": "Beatific"
}
$ curl -X POST https://apihost/things 
-H ‘SecurityToken: b08c85073c1a2d02’ 
-H ‘Content-Type: application/json’ -d
’{ "name": "Open-minded" }’
$ curl -X DELETE https://apihost/things/2 
-H ‘SecurityToken: b08c85073c1a2d02’
now with gzip
318
java + gson
url = new URL(“https://apihost/things”);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty(“SecurityToken”, “b08c85073c1a2d02”);
connection.setRequestProperty(“Accept”, “application/json”);
connection.setRequestProperty(“Accept-Encoding”, "gzip, deflate");
is = connection.getInputStream();
isr = new InputStreamReader(new GZIPInputStream(is));
things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){});
okhttp + gson
client = new OkHttpClient();
url = new URL(“https://apihost/things”);
connection = new OkUrlFactory(client).open(url);
connection.setRequestProperty(“SecurityToken”, “b08c85073c1a2d02”);
connection.setRequestProperty(“Accept”, “application/json”);
connection.setRequestProperty(“Accept-Encoding”, "gzip, deflate");
is = connection.getInputStream();
isr = new InputStreamReader(is); // automatic gunzip
things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){});
is.close();
We won!
• List body reduced from 1642 to 318 bytes!
• We saved some lines using OkHttp
• Concession: cpu for compression, curl is a
little verbose.
introduction
hello http api!
hello okhttp!
uh oh.. scale!
hello http/2!
wrapping up
okhttp
• v2.5 has lots of stuff since v1
• buffer, call, cache, interceptor, url
• http/2 and WebSocket support
• Apache adapter
https://github.com/square/okhttp
okhttp
client = new OkHttpClient();
request = new Request.Builder()
.url(“https://apihost/things”)
.header(“SecurityToken”, “b08c85073c1a2d02”)
.header(“Accept”, “application/json”)
.header(“Accept-Encoding”, "gzip, deflate”).build();
// ^^ look, ma.. I’m immutable!
reader = client.newCall(request).execute().body().charStream();
things = new Gson().fromJson(reader, new TypeToken<List<Thing>>(){});
• OkHttp has its own api, which has a
concise way to get bytes or chars
(via okio).
okhttp call
client = new OkHttpClient();
request = new Request.Builder()
.url(“https://apihost/things”)
.header(“SecurityToken”, “b08c85073c1a2d02”)
.header(“Accept”, “application/json”)
.header(“Accept-Encoding”, "gzip, deflate").build();
call = client.newCall(request);
call.enqueue(new ParseThingsCallback());
call.cancel(); // changed my mind
• OkHttp also has an async api,
which (also) supports cancel!
• # Explicity trust certs for your properties
• new CertificatePinner.Builder()
• .add(“my.biz”, publicKeySHA1)
• .add(“*.my.biz”, publicKeySHA1)
• .build()
• # Respond to authentication challenges
• new Authenticator() {
• public Request authenticate(Proxy p, Response rsp) {
• return rsp.request().newBuilder()
• .header(“Authorization", “Bearer …”)
• # Set your own floor for TLS
• new ConnectionSpec.Builder(MODERN_TLS)
okhttp
security
okhttp interceptor
client = new OkHttpClient();
client.interceptors().add((chain) ->

chain.proceed(chain.request().newBuilder()

.addHeader("SecurityToken", securityToken.get())

.build())

);
request = new Request.Builder()
.url(“https://apihost/things”)
.header(“Accept-Encoding”, "gzip, deflate").build();
call = client.newCall(request);
call.enqueue(new ParseThingsCallback());
• Now you can change your
security token!
okio
• ByteString
• Unbounded Buffer
• Source/Sink abstraction
https://github.com/square/okio
// Okio is (more than) a prettier DataInputStream
BufferedSink sink = Okio.buffer(Okio.sink(file));
sink.writeUtf8("Hello, java.io file!”);
sink.writeLong(1000000000000000L);
sink.close();
// Streaming writes with no housekeeping
new RequestBody() {
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbersn");
sink.writeUtf8("-------n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %sn", i, factor(i)));
}
}
—snip—
// Lazy, composable bodies
new MultipartBuilder("123")
.addPart(RequestBody.create(MediaType.parse(“text/plain”, "Quick"))
.addPart(new StreamingBody("Brown"))
okio
samples
samples
• Starters like web crawler
• How-to guide w/ recipes
https://github.com/square/okhttp/tree/master/samples
introduction
hello http api!
hello okhttp!
uh oh.. scale!
hello http/2!
wrapping up
Hey.. List #1 is slow!
368!
Ask Ilya why!
• TCP connections need 3-
way handshake.
• TLS requires up to 2
more round-trips.
• Read High Performance
Browser Networking
http://chimera.labs.oreilly.com/books/1230000000545
HttpUrlConnection
• http.keepAlive - should connections should be
pooled at all? Default is true.
• http.maxConnections - maximum number of idle
connections to each host in the pool. Default is 5.
• get[Input|Error]Stream().close() - recycles the
connection, if fully read.
• disconnect() - removes the connection.
Don’t forget to read!
...
is = tryGetInputStream(connection);
isr = new InputStreamReader(is);
things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){});
...
InputStream tryGetInputStream(HttpUrlConnection connection) throws IOException {
try {
return connection.getInputStream();
} catch (IOException e) {
InputStream err = connection.getErrorStream();
  while (in.read() != -1); // skip
err.close();
throw e;
}
or just use okhttp
try (ResponseBody body = client.newCall(request).execute().body())
{
// connection will be cleaned on close.
}
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Request request, IOException e) {
// body is implicitly closed on failure
}
@Override public void onResponse(Response response) {
// make sure you call response.body().close() when done
}
});
We won!
• Recycled socket requests are much faster
and have less impact on the server.
• Concessions: must read responses,
concurrency is still bounded by sockets.
Let’s make List #2 free
Cache-Control: private, max-age=60, s-maxage=0
Vary: SecurityToken
Step 1: add a little magic to your server response
Step 2: make sure you are using a cache!
client.setCache(new Cache(dir, 10_MB));
Step 3: pray your developers don’t get clever
// TODO: stupid server sends stale data without this
new Request.Builder().url(”https://apihost/things?time=” + currentTimeMillis());
// Limit cache duration
thisMessageWillSelfDestruct = new Request.Builder()
.url(“https://foo.com/weather”)
.cacheControl(new CacheControl.Builder().maxAge(12, HOURS).build())
.build();
// Opt-out of response caching
notStored = new Request.Builder()
.url(“https://foo.com/private”)
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
// Offline mode
offline = new Request.Builder()
.url(“https://foo.com/image”)
.cacheControl(CacheControl.FORCE_CACHE)
.build();
okhttp cache recipes
We won again!
• No time or bandwidth used for cached
responses
• No application-specific cache bugs code.
• Concessions: only supports “safe methods”,
caching needs to be tuned.
introduction
hello http api!
hello okhttp!
uh oh.. scale!
hello http/2!
wrapping up
http/1.1
• rfc2616 - June 1999
• text-based framing
• defined semantics of the web
http://www.w3.org/Protocols/rfc2616/rfc2616.html
FYI: RFC 2616 is dead!
• RFC 7230-5 replaces RFC 2616.
• Checkout details on www.mnot.net/blog
spdy/3.1
http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1
• google - Sep 2013
• binary framing
• retains http/1.1 semantics
• concurrent multiplexed streams
http/2
https://tools.ietf.org/html/rfc7540
• rfc7540 - May 2015
• binary framing
• retains http/1.1 semantics
• concurrent multiplexed streams
multiplexing
header compression
flow control
priority
server push
http/2 headline features
multiplexing
header compression
server push
http/2 headline features
Looking at the whole
message
Request: request line, headers, and body
Response: status line, headers, and body
http/1.1 round-trip
GZIPPED DATA
Content-Length: 318
Cache-Control: private, max-age=60, s-
maxage=0
Vary: SecurityToken
Date: Sun, 02 Feb 2014 20:30:38 GMT
Content-Type: application/json
Content-Encoding: gzip
Host: apihost
SecurityToken: b08c85073c1a2d02
Accept: application/json
Accept-encoding: gzip, deflate
GET /things HTTP/1.1
HTTP/1.1 200 OK
• http/1.1 sends requests and
responses one at a time
over a network connection
http/2 round-trip
GZIPPED DATA
:status: 200
content-length: 318
cache-control: private, max-age=60, s-
maxage=0
vary: SecurityToken
date: Sun, 02 Feb 2014 20:30:38 GMT
content-type: application/json
:method: GET
:authority: apihost
:path: /things
securitytoken: b08c85073c1a2d02
accept: application/json
accept-encoding: gzip, deflate
HEADERS
Stream: 3
Flags: END_HEADERS, END_STREAM
HEADERS
Stream: 3
Flags: END_HEADERS
DATA
Stream: 3
Flags: END_STREAM
• http/2 breaks up messages
into stream-scoped frames
Interleaving
HEADERS
Stream: 5
Flags: END_HEADERS, END_STREAM
HEADERS
Stream: 3
Flags: END_HEADERS
DATA
Stream: 5
Flags:
DATA
Stream: 5
Flags: END_STREAM
HEADERS
Stream: 3
Flags: END_HEADERS, END_STREAM
HEADERS
Stream: 5
Flags: END_HEADERS
DATA
Stream: 3
Flags: END_STREAM
• http/2 multiplexes a network
connection by interleaving
request/response frames
Canceling a Stream
HEADERS
Stream: 5
Flags: END_HEADERS, END_STREAM
HEADERS
Stream: 3
Flags: END_HEADERS
DATA
Stream: 5
Flags:
HEADERS
Stream: 3
Flags: END_HEADERS, END_STREAM
HEADERS
Stream: 5
Flags: END_HEADERS
DATA
Stream: 3
Flags: END_STREAM
RST_STREAM
Stream: 5
ErrorCode: CANCEL
• interrupts a response without
affecting the connection
control frames
HEADERS
Stream: 5
HEADERS
Stream: 3
DATA
Stream: 5
DATA
Stream: 3
HEADERS
Stream: 3
HEADERS
Stream: 5
SETTINGS
Stream: 0
SETTINGS
Stream: 0
DATA
Stream: 5
• connection-scoped frames
allow runtime updates, like
flow-control
OkHttp/2 Architecture
Frame
Reader
Thread
Frame
Writer
Lock
Control
Frame
Queue
FramedConnection
multiplexing
header compression
server push
http/2 headline features
http/1.1 headers
GZIPPED DATA
Content-Length: 318
Cache-Control: private, max-age=60, s-
maxage=0
Vary: SecurityToken
Date: Sun, 02 Feb 2014 20:30:38 GMT
Content-Type: application/json
Content-Encoding: gzip
Host: apihost
SecurityToken: b08c85073c1a2d02
Accept: application/json
Accept-encoding: gzip, deflate
GET /things HTTP/1.1
HTTP/1.1 200 OK
168!
195!
318
• You can gzip
data, but not
headers!
header compression
GZIPPED DATA
:status: 200
content-length: 318
cache-control: private, max-age=60, s-maxage=0
vary: SecurityToken
date: Sun, 02 Feb 2014 20:30:38 GMT
content-type: application/json
content-encoding: gzip
:method: GET
:authority: apihost
:path: /things
securitytoken: b08c85073c1a2d02
accept: application/json
accept-encoding: gzip, deflate
HEADERS
Stream: 3
Flags: END_HEADERS, END_STREAM
HEADERS
Stream: 3
Flags: END_HEADERS
DATA
Stream: 3
Flags: END_STREAM
8 bytes
52 bytes compressed
8 bytes
85 bytes compressed
• 161 byte overhead
instead of 363 8 bytes
indexed headers!
GZIPPED DATA
:status: 200
content-length: 318
cache-control: private, max-age=60, s-maxage=0
vary: SecurityToken
date: Sun, 02 Feb 2014 20:30:39 GMT
content-type: application/json
content-encoding: gzip
:method: GET
:authority: apihost
:path: /things
securitytoken: b08c85073c1a2d02
accept: application/json
accept-encoding: gzip, deflate
HEADERS
Stream: 3
Flags: END_HEADERS, END_STREAM
HEADERS
Stream: 3
Flags: END_HEADERS
DATA
Stream: 3
Flags: END_STREAM
8 bytes
28 bytes compressed
8 bytes
30 bytes compressed
• 82 byte overhead
instead of 363 8 bytes
hpack
http://tools.ietf.org/html/rfc7541
• rfc7540 - May 2015
• static and dynamic table
• huffman encoding
multiplexing
header compression
server push
http/2 headline features
push promise
:method: GET
:path: /things
...
HEADERS
Stream: 3
HEADERS
Stream: 3
DATA
Stream: 4
:method: GET
:path: /users/0
...
PUSH_PROMISE
Stream: 3
Promised-Stream: 4
HEADERS
Stream: 4
push
response
goes into
cache
DATA
Stream: 3
Server guesses a
future request or
indicates a cache
invalidation
okhttp + http/2
• OkHttp 2.3+ supports http/2 on TLS+ALPN connections.
• Works out of the box on Android
• Java needs hacks until JEP 244
For now, add jetty’s ALPN to your bootclasspath.
* Verify version matches your JRE *
$ java -Xbootclasspath/p:/path/to/alpn-boot-8.1.4.v20150727.jar
We won!
• 1 socket for 100+ concurrent streams.
• Advanced features like flow control,
cancellation and cache push.
• Dramatically less header overhead.
okhttp tools
• There are tools that leverage OkHttp natively…
• including its http/2 features.
MockWebServer
SSLSocketFactory factory = SslContextBuilder.localhost().getSocketFactory();
OkHttpClient client = new OkHttpClient()
.setSslSocketFactory(factory);
@Rule public final MockWebServer server = new MockWebServer()
.setSslSocketFactory(factory);
@Test public void evenWithHttp2SlowResponsesKill() throws Exception {
server.enqueue(new MockResponse().setBody(lotsOfThings)
.throttleBody(1024, 1, SECONDS)); // slow connection 1KiB/second
request = new Request.Builder()
.url(server.getUrl(”/”) + ”things”)
—snip—
• MockWebServer uses http/2
by default when using TLS
Retrofit 2
@Headers("Accept-Encoding: gzip, deflate”)
interface ThingService {
@GET("things") Single<List<Things> list(); // RxJava support!
}
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(“https://apihost/“)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
thingsService = retrofit.create(ThingService.class);
subscription = thingsService.iist().subscribe(System.out::println)
• Retrofit 2 extends OkHttp
with api model binding
okcurl
$ java -Xbootclasspath/p:alpn-boot.jar -jar okcurl.jar https://twitter.com
-X HEAD --frames
>> CONNECTION 505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
<< 0x00000000 6 SETTINGS
>> 0x00000000 6 SETTINGS
>> 0x00000000 4 WINDOW_UPDATE
>> 0x00000000 0 SETTINGS ACK
>> 0x00000003 60 HEADERS END_STREAM|END_HEADERS
<< 0x00000000 0 SETTINGS ACK
<< 0x00000003 2097 HEADERS END_STREAM|END_HEADERS
>> 0x00000000 8 GOAWAY
introduction
hello http api!
hello okhttp!
uh oh.. scale!
hello http/2!
wrapping up
Engage!
• Consider http/2 or at least spdy!
• Check-out okhttp and friends
• Test http/2 load balancers like google cloud
https://github.com/square/okhttp/tree/master/mockwebserver
http://square.github.io/retrofit/
https://cloud.google.com/compute/docs/load-balancing/http/
Thank you!
github square/okhttp @adrianfcole

Efficient HTTP Apis

  • 1.
    Efficient HTTP Apis Awalk through http/2 via okhttp @adrianfcole
  • 2.
    introduction hello http api! hellookhttp! uh oh.. scale! hello http/2! wrapping up
  • 3.
    introduction hello http api! hellookhttp! uh oh.. scale! hello http/2! wrapping up
  • 4.
    * Can beblamed for the http/2 defects in okhttp! @adrianfcole • engineer at Pivotal on Spring Cloud • lots of open source work • focus on cloud computing
  • 5.
    okhttp • HttpURLConnection ApacheHttpClient compatible • designed for java and android • BDFL: Jesse Wilson from square https://github.com/square/okhttp
  • 6.
    introduction hello http api! hellookhttp! uh oh.. scale! hello http/2! wrapping up
  • 7.
    $ curl https://apihost/things -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Accept: application/json’ [ { "id": 1, "owner_id": 0, "name": "Able" }, ... { "id": 26, "owner_id": 0, $ curl https://apihost/things/2 -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Accept: application/json’ { "id": 2, "owner_id": 0, "name": "Beatific" } $ curl -X POST https://apihost/things -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Content-Type: application/json’ -d ’{ "name": "Open-minded" }’ $ curl -X DELETE https://apihost/things/2 -H ‘SecurityToken: b08c85073c1a2d02’ json apis are so simple
  • 8.
    $ curl https://apihost/things -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Accept: application/json’ [ { "id": 1, "owner_id": 0, "name": "Able" }, ... { "id": 26, "owner_id": 0, "name": "Zest" } $ curl https://apihost/things/2 -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Accept: application/json’ { "id": 2, "owner_id": 0, "name": "Beatific" } $ curl -X POST https://apihost/things -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Content-Type: application/json’ -d ’{ "name": "Open-minded" }’ $ curl -X DELETE https://apihost/things/2 -H ‘SecurityToken: b08c85073c1a2d02’ so many bytes?! 1642!
  • 9.
    $ curl --compresshttps://apihost/things -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Accept: application/json’ -H ‘Accept-encoding: gzip, deflate’ [ { "id": 1, "owner_id": 0, "name": "Able" }, ... { "id": 26, "owner_id": 0, "name": "Zest" $ curl https://apihost/things/2 -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Accept: application/json’ { "id": 2, "owner_id": 0, "name": "Beatific" } $ curl -X POST https://apihost/things -H ‘SecurityToken: b08c85073c1a2d02’ -H ‘Content-Type: application/json’ -d ’{ "name": "Open-minded" }’ $ curl -X DELETE https://apihost/things/2 -H ‘SecurityToken: b08c85073c1a2d02’ now with gzip 318
  • 10.
    java + gson url= new URL(“https://apihost/things”); connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty(“SecurityToken”, “b08c85073c1a2d02”); connection.setRequestProperty(“Accept”, “application/json”); connection.setRequestProperty(“Accept-Encoding”, "gzip, deflate"); is = connection.getInputStream(); isr = new InputStreamReader(new GZIPInputStream(is)); things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){});
  • 11.
    okhttp + gson client= new OkHttpClient(); url = new URL(“https://apihost/things”); connection = new OkUrlFactory(client).open(url); connection.setRequestProperty(“SecurityToken”, “b08c85073c1a2d02”); connection.setRequestProperty(“Accept”, “application/json”); connection.setRequestProperty(“Accept-Encoding”, "gzip, deflate"); is = connection.getInputStream(); isr = new InputStreamReader(is); // automatic gunzip things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){}); is.close();
  • 12.
    We won! • Listbody reduced from 1642 to 318 bytes! • We saved some lines using OkHttp • Concession: cpu for compression, curl is a little verbose.
  • 13.
    introduction hello http api! hellookhttp! uh oh.. scale! hello http/2! wrapping up
  • 14.
    okhttp • v2.5 haslots of stuff since v1 • buffer, call, cache, interceptor, url • http/2 and WebSocket support • Apache adapter https://github.com/square/okhttp
  • 15.
    okhttp client = newOkHttpClient(); request = new Request.Builder() .url(“https://apihost/things”) .header(“SecurityToken”, “b08c85073c1a2d02”) .header(“Accept”, “application/json”) .header(“Accept-Encoding”, "gzip, deflate”).build(); // ^^ look, ma.. I’m immutable! reader = client.newCall(request).execute().body().charStream(); things = new Gson().fromJson(reader, new TypeToken<List<Thing>>(){}); • OkHttp has its own api, which has a concise way to get bytes or chars (via okio).
  • 16.
    okhttp call client =new OkHttpClient(); request = new Request.Builder() .url(“https://apihost/things”) .header(“SecurityToken”, “b08c85073c1a2d02”) .header(“Accept”, “application/json”) .header(“Accept-Encoding”, "gzip, deflate").build(); call = client.newCall(request); call.enqueue(new ParseThingsCallback()); call.cancel(); // changed my mind • OkHttp also has an async api, which (also) supports cancel!
  • 17.
    • # Explicitytrust certs for your properties • new CertificatePinner.Builder() • .add(“my.biz”, publicKeySHA1) • .add(“*.my.biz”, publicKeySHA1) • .build() • # Respond to authentication challenges • new Authenticator() { • public Request authenticate(Proxy p, Response rsp) { • return rsp.request().newBuilder() • .header(“Authorization", “Bearer …”) • # Set your own floor for TLS • new ConnectionSpec.Builder(MODERN_TLS) okhttp security
  • 18.
    okhttp interceptor client =new OkHttpClient(); client.interceptors().add((chain) ->
 chain.proceed(chain.request().newBuilder()
 .addHeader("SecurityToken", securityToken.get())
 .build())
 ); request = new Request.Builder() .url(“https://apihost/things”) .header(“Accept-Encoding”, "gzip, deflate").build(); call = client.newCall(request); call.enqueue(new ParseThingsCallback()); • Now you can change your security token!
  • 19.
    okio • ByteString • UnboundedBuffer • Source/Sink abstraction https://github.com/square/okio
  • 20.
    // Okio is(more than) a prettier DataInputStream BufferedSink sink = Okio.buffer(Okio.sink(file)); sink.writeUtf8("Hello, java.io file!”); sink.writeLong(1000000000000000L); sink.close(); // Streaming writes with no housekeeping new RequestBody() { @Override public void writeTo(BufferedSink sink) throws IOException { sink.writeUtf8("Numbersn"); sink.writeUtf8("-------n"); for (int i = 2; i <= 997; i++) { sink.writeUtf8(String.format(" * %s = %sn", i, factor(i))); } } —snip— // Lazy, composable bodies new MultipartBuilder("123") .addPart(RequestBody.create(MediaType.parse(“text/plain”, "Quick")) .addPart(new StreamingBody("Brown")) okio samples
  • 21.
    samples • Starters likeweb crawler • How-to guide w/ recipes https://github.com/square/okhttp/tree/master/samples
  • 22.
    introduction hello http api! hellookhttp! uh oh.. scale! hello http/2! wrapping up
  • 23.
    Hey.. List #1is slow! 368!
  • 24.
    Ask Ilya why! •TCP connections need 3- way handshake. • TLS requires up to 2 more round-trips. • Read High Performance Browser Networking http://chimera.labs.oreilly.com/books/1230000000545
  • 25.
    HttpUrlConnection • http.keepAlive -should connections should be pooled at all? Default is true. • http.maxConnections - maximum number of idle connections to each host in the pool. Default is 5. • get[Input|Error]Stream().close() - recycles the connection, if fully read. • disconnect() - removes the connection.
  • 26.
    Don’t forget toread! ... is = tryGetInputStream(connection); isr = new InputStreamReader(is); things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){}); ... InputStream tryGetInputStream(HttpUrlConnection connection) throws IOException { try { return connection.getInputStream(); } catch (IOException e) { InputStream err = connection.getErrorStream();   while (in.read() != -1); // skip err.close(); throw e; }
  • 27.
    or just useokhttp try (ResponseBody body = client.newCall(request).execute().body()) { // connection will be cleaned on close. } client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { // body is implicitly closed on failure } @Override public void onResponse(Response response) { // make sure you call response.body().close() when done } });
  • 28.
    We won! • Recycledsocket requests are much faster and have less impact on the server. • Concessions: must read responses, concurrency is still bounded by sockets.
  • 29.
    Let’s make List#2 free Cache-Control: private, max-age=60, s-maxage=0 Vary: SecurityToken Step 1: add a little magic to your server response Step 2: make sure you are using a cache! client.setCache(new Cache(dir, 10_MB)); Step 3: pray your developers don’t get clever // TODO: stupid server sends stale data without this new Request.Builder().url(”https://apihost/things?time=” + currentTimeMillis());
  • 30.
    // Limit cacheduration thisMessageWillSelfDestruct = new Request.Builder() .url(“https://foo.com/weather”) .cacheControl(new CacheControl.Builder().maxAge(12, HOURS).build()) .build(); // Opt-out of response caching notStored = new Request.Builder() .url(“https://foo.com/private”) .cacheControl(CacheControl.FORCE_NETWORK) .build(); // Offline mode offline = new Request.Builder() .url(“https://foo.com/image”) .cacheControl(CacheControl.FORCE_CACHE) .build(); okhttp cache recipes
  • 31.
    We won again! •No time or bandwidth used for cached responses • No application-specific cache bugs code. • Concessions: only supports “safe methods”, caching needs to be tuned.
  • 32.
    introduction hello http api! hellookhttp! uh oh.. scale! hello http/2! wrapping up
  • 33.
    http/1.1 • rfc2616 -June 1999 • text-based framing • defined semantics of the web http://www.w3.org/Protocols/rfc2616/rfc2616.html
  • 34.
    FYI: RFC 2616is dead! • RFC 7230-5 replaces RFC 2616. • Checkout details on www.mnot.net/blog
  • 35.
    spdy/3.1 http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1 • google -Sep 2013 • binary framing • retains http/1.1 semantics • concurrent multiplexed streams
  • 36.
    http/2 https://tools.ietf.org/html/rfc7540 • rfc7540 -May 2015 • binary framing • retains http/1.1 semantics • concurrent multiplexed streams
  • 37.
  • 38.
  • 39.
    Looking at thewhole message Request: request line, headers, and body Response: status line, headers, and body
  • 40.
    http/1.1 round-trip GZIPPED DATA Content-Length:318 Cache-Control: private, max-age=60, s- maxage=0 Vary: SecurityToken Date: Sun, 02 Feb 2014 20:30:38 GMT Content-Type: application/json Content-Encoding: gzip Host: apihost SecurityToken: b08c85073c1a2d02 Accept: application/json Accept-encoding: gzip, deflate GET /things HTTP/1.1 HTTP/1.1 200 OK • http/1.1 sends requests and responses one at a time over a network connection
  • 41.
    http/2 round-trip GZIPPED DATA :status:200 content-length: 318 cache-control: private, max-age=60, s- maxage=0 vary: SecurityToken date: Sun, 02 Feb 2014 20:30:38 GMT content-type: application/json :method: GET :authority: apihost :path: /things securitytoken: b08c85073c1a2d02 accept: application/json accept-encoding: gzip, deflate HEADERS Stream: 3 Flags: END_HEADERS, END_STREAM HEADERS Stream: 3 Flags: END_HEADERS DATA Stream: 3 Flags: END_STREAM • http/2 breaks up messages into stream-scoped frames
  • 42.
    Interleaving HEADERS Stream: 5 Flags: END_HEADERS,END_STREAM HEADERS Stream: 3 Flags: END_HEADERS DATA Stream: 5 Flags: DATA Stream: 5 Flags: END_STREAM HEADERS Stream: 3 Flags: END_HEADERS, END_STREAM HEADERS Stream: 5 Flags: END_HEADERS DATA Stream: 3 Flags: END_STREAM • http/2 multiplexes a network connection by interleaving request/response frames
  • 43.
    Canceling a Stream HEADERS Stream:5 Flags: END_HEADERS, END_STREAM HEADERS Stream: 3 Flags: END_HEADERS DATA Stream: 5 Flags: HEADERS Stream: 3 Flags: END_HEADERS, END_STREAM HEADERS Stream: 5 Flags: END_HEADERS DATA Stream: 3 Flags: END_STREAM RST_STREAM Stream: 5 ErrorCode: CANCEL • interrupts a response without affecting the connection
  • 44.
    control frames HEADERS Stream: 5 HEADERS Stream:3 DATA Stream: 5 DATA Stream: 3 HEADERS Stream: 3 HEADERS Stream: 5 SETTINGS Stream: 0 SETTINGS Stream: 0 DATA Stream: 5 • connection-scoped frames allow runtime updates, like flow-control
  • 45.
  • 46.
  • 47.
    http/1.1 headers GZIPPED DATA Content-Length:318 Cache-Control: private, max-age=60, s- maxage=0 Vary: SecurityToken Date: Sun, 02 Feb 2014 20:30:38 GMT Content-Type: application/json Content-Encoding: gzip Host: apihost SecurityToken: b08c85073c1a2d02 Accept: application/json Accept-encoding: gzip, deflate GET /things HTTP/1.1 HTTP/1.1 200 OK 168! 195! 318 • You can gzip data, but not headers!
  • 48.
    header compression GZIPPED DATA :status:200 content-length: 318 cache-control: private, max-age=60, s-maxage=0 vary: SecurityToken date: Sun, 02 Feb 2014 20:30:38 GMT content-type: application/json content-encoding: gzip :method: GET :authority: apihost :path: /things securitytoken: b08c85073c1a2d02 accept: application/json accept-encoding: gzip, deflate HEADERS Stream: 3 Flags: END_HEADERS, END_STREAM HEADERS Stream: 3 Flags: END_HEADERS DATA Stream: 3 Flags: END_STREAM 8 bytes 52 bytes compressed 8 bytes 85 bytes compressed • 161 byte overhead instead of 363 8 bytes
  • 49.
    indexed headers! GZIPPED DATA :status:200 content-length: 318 cache-control: private, max-age=60, s-maxage=0 vary: SecurityToken date: Sun, 02 Feb 2014 20:30:39 GMT content-type: application/json content-encoding: gzip :method: GET :authority: apihost :path: /things securitytoken: b08c85073c1a2d02 accept: application/json accept-encoding: gzip, deflate HEADERS Stream: 3 Flags: END_HEADERS, END_STREAM HEADERS Stream: 3 Flags: END_HEADERS DATA Stream: 3 Flags: END_STREAM 8 bytes 28 bytes compressed 8 bytes 30 bytes compressed • 82 byte overhead instead of 363 8 bytes
  • 50.
    hpack http://tools.ietf.org/html/rfc7541 • rfc7540 -May 2015 • static and dynamic table • huffman encoding
  • 51.
  • 52.
    push promise :method: GET :path:/things ... HEADERS Stream: 3 HEADERS Stream: 3 DATA Stream: 4 :method: GET :path: /users/0 ... PUSH_PROMISE Stream: 3 Promised-Stream: 4 HEADERS Stream: 4 push response goes into cache DATA Stream: 3 Server guesses a future request or indicates a cache invalidation
  • 53.
    okhttp + http/2 •OkHttp 2.3+ supports http/2 on TLS+ALPN connections. • Works out of the box on Android • Java needs hacks until JEP 244 For now, add jetty’s ALPN to your bootclasspath. * Verify version matches your JRE * $ java -Xbootclasspath/p:/path/to/alpn-boot-8.1.4.v20150727.jar
  • 54.
    We won! • 1socket for 100+ concurrent streams. • Advanced features like flow control, cancellation and cache push. • Dramatically less header overhead.
  • 55.
    okhttp tools • Thereare tools that leverage OkHttp natively… • including its http/2 features.
  • 56.
    MockWebServer SSLSocketFactory factory =SslContextBuilder.localhost().getSocketFactory(); OkHttpClient client = new OkHttpClient() .setSslSocketFactory(factory); @Rule public final MockWebServer server = new MockWebServer() .setSslSocketFactory(factory); @Test public void evenWithHttp2SlowResponsesKill() throws Exception { server.enqueue(new MockResponse().setBody(lotsOfThings) .throttleBody(1024, 1, SECONDS)); // slow connection 1KiB/second request = new Request.Builder() .url(server.getUrl(”/”) + ”things”) —snip— • MockWebServer uses http/2 by default when using TLS
  • 57.
    Retrofit 2 @Headers("Accept-Encoding: gzip,deflate”) interface ThingService { @GET("things") Single<List<Things> list(); // RxJava support! } retrofit = new Retrofit.Builder() .client(client) .baseUrl(“https://apihost/“) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build(); thingsService = retrofit.create(ThingService.class); subscription = thingsService.iist().subscribe(System.out::println) • Retrofit 2 extends OkHttp with api model binding
  • 58.
    okcurl $ java -Xbootclasspath/p:alpn-boot.jar-jar okcurl.jar https://twitter.com -X HEAD --frames >> CONNECTION 505249202a20485454502f322e300d0a0d0a534d0d0a0d0a << 0x00000000 6 SETTINGS >> 0x00000000 6 SETTINGS >> 0x00000000 4 WINDOW_UPDATE >> 0x00000000 0 SETTINGS ACK >> 0x00000003 60 HEADERS END_STREAM|END_HEADERS << 0x00000000 0 SETTINGS ACK << 0x00000003 2097 HEADERS END_STREAM|END_HEADERS >> 0x00000000 8 GOAWAY
  • 59.
    introduction hello http api! hellookhttp! uh oh.. scale! hello http/2! wrapping up
  • 60.
    Engage! • Consider http/2or at least spdy! • Check-out okhttp and friends • Test http/2 load balancers like google cloud https://github.com/square/okhttp/tree/master/mockwebserver http://square.github.io/retrofit/ https://cloud.google.com/compute/docs/load-balancing/http/
  • 61.