Developers choose HTTP for its ubiquity. HTTP's semantics are cherry-picked or embraced in the myriad of apis we develop and consume. Efficiency discussions are commonplace: Does this design imply N+1 requests? Should we denormalize the model? How do consumers discover changes in state? How many connections are needed to effectively use this api?
Meanwhile, HTTP 1.1 is a choice, as opposed to constant. SPDY and HTTP/2 implementations surface, simultaneously retaining semantics and dramatically changing performance implications. We can choose treat these new protocols as more efficient versions HTTP 1.1 or buy into new patterns such as server-side push.
This session walks you through these topics via an open source project from Square called okhttp. You'll understand how okhttp addresses portability so that you can develop against something as familiar as java's HTTPUrlConnection. We'll review how to use new protocol features and constraints to keep in mind along the way. You'll learn how to sandbox ideas with okhttp's mock server, so that you can begin experimenting with SPDY and HTTP/2 today!
4. * 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
5. okhttp
• HttpURLConnection Apache HttpClient compatible
• designed for java and android
• BDFL: Jesse Wilson from square
https://github.com/square/okhttp
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!
• List body reduced from 1642 to 318 bytes!
• We saved some lines using OkHttp
• Concession: cpu for compression, curl is a
little verbose.
14. 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
15. 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).
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. • # 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
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!
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 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;
}
27. 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
}
});
28. 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.
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 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
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.
39. Looking at the whole
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
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!
• 1 socket for 100+ concurrent streams.
• Advanced features like flow control,
cancellation and cache push.
• Dramatically less header overhead.
55. okhttp tools
• There are 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
60. 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/