Everything You Wanted to
Know
About Writing Async,
Concurrent HTTP Apps in Java
Agenda
• Mostly this:
Agenda
• And this:
Agenda
• And this:
About your speaker
linkd.in/jbaruch
stackoverflow.com/users/402053/jbaruch
What Frog?
What Frog?
What Frog?
What Frog?
Requirements
– parallel file Downloads
– Parallel file parts
– interrupt/pause/resume
– Progress events
– Checksums caching
First Association for “concurrent
downloader”
Lucky day: Download manager
written in Java!
Let’s look if we can use it!
1. No traceable license
2. No website or docs
3. No traceable sources
4. It’s an app, not a lib
Java.net.urlconnection
1. Memory wasteful (buffering)
2. Minimal API
3. Blocking streams
What we’re looking for
1. Async/non-blocking
2. Event callbacks
What is IT going to take
1. Reactor
2. nio
Welcome to the reactor
– pattern for lightweight concurrency
– Event driven
– Threads reuse
– Uses non-blocking Io
Original pattern
http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf
Guess the author by the
diagram
http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
In Java, Reactor
means NIO
Selector as a multiplexer
Java version - Registering
SocketChannel channel= SocketChannel.open();
socketChannel.connect(new
InetSocketAddress("http://remote.com", 80));
...
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey k = channel.register(selector,
SelectionKey.OP_READ);
k.attach(handler);
Java version - Dispatcher
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext())
SelectionKey k = (SelectionKey)(it.next();
((Runnable)(k.attachment())).run();
selected.clear();
}
Handling reactor events is complex
– Need to maintain state
– Buffering – assembling chunks
– Coordinating async events
Nio libraries
– Most of them are servers
–Netty, grizzly, etc.
– Apache Mina
– Apache HTTP components asyncclient
– Ning http client
Nio libraries
– Most of them are servers
–Netty, grizzly, etc.
– Apache Mina
– Apache HTTP components asyncclient
– Ning http client
– Client and server nio library
– Evolved from netty
– Latest release October 2012
Nio libraries
– Most of them are servers
–Netty, grizzly, etc
– Apache Mina
– Apache HTTP components asyncclient
– Ning http client
Ning’s async http client
Here it is!
try (AsyncHttpClient asyncHttpClient = new AsyncHttpClient()) {
ListenableFuture<Response> future = asyncHttpClient.prepareGet(
"http://oss.jfrog.org/api/system/ping").execute(
new AsyncCompletionHandler<Response>() {
@Override
public Response onCompleted(Response response) {
System.out.println(response.getResponseBody());
return response;
}
@Override
public void onThrowable(Throwable t) {
t.printStackTrace();
}
});
Response response = future.get();
}
HAC Concepts
– Request producer
– Response consumer
try (CloseableHttpAsyncClient asyncHttpClient = HttpAsyncClients.createDefault()) {
asyncHttpClient.start();
Future<HttpResponse> future = asyncHttpClient.execute(
HttpAsyncMethods.createGet("http://oss.jfrog.org/api/system/ping"),
new AsyncByteConsumer<HttpResponse>() {
@Override
protected void onResponseReceived(final HttpResponse response) {
System.out.println(response.getStatusLine().getReasonPhrase());
}
@Override
protected void onByteReceived(final CharBuffer buf, final IOControl ioctrl) { }
@Override
protected void releaseResources() { }
@Override
protected HttpResponse buildResult(final HttpContext context) {
return (HttpResponse) context.getAttribute("http.response");
}
}, null);
HttpResponse response = future.get();
}
Choosing between ning and http
asyncclient
"All problems in computer science can
be solved by another level of
indirection"
David
Wheeler
public interface HttpProviderDownloadHandler {
void onResponseReceived(int statusCode, Map<String, List<String>> headers);
boolean onBytesReceived(ByteBuffer buf);
void onFailed(Throwable error);
void onCanceled();
void onCompleted();
}
Head to head
Feature/Library Ning client Http Async Client
Maturity Good Very new (early 2014)
Download cancelation Easy With bugs
Progress hooks Events not granular
enough
Just use onByteReceived()
Documentation A bit sparse Minimal
Server-side counterpart None, client only org.apache.httpcomponents
httpcore-nio
Performance?
0
100
200
300
400
500
600
700
800
900
Small file Medium file Large file
Ning
AHAC
http://blogs.atlassian.com/2013/07/http-client-performance-io/
Rfc2616: a universe of its own
Confused?
Just read some stackoverflow
(and improve your rep as you go)
And that one for
discovering that
range header is
lost on redirect
Question!
What should be
content-length
when using
compression?
https://github.com/http2/http2-spec/issues/46
Question!
Why when redirected to CDN
all the chunks start from zero?
HttpAsyncClientBuilder builder = HttpAsyncClients.custom();
// add redirect strategy that copies "range" headers, if exist
builder.setRedirectStrategy(new DefaultRedirectStrategy() {
@Override
public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response,
HttpContext context)
HttpUriRequest redirectRequest = super.getRedirect(request, response, context);
// copy "Range" headers, if exist
Header[] rangeHeaders = request.getHeaders(HttpHeaders.RANGE);
if (rangeHeaders != null) {
for (Header header : rangeHeaders) {
redirectRequest.addHeader(header);
}
}
return redirectRequest;
}});
Question!
How many simultaneous
connections should I open?
Question!
What’s wrong
with the
following
code?
public static String encodeUrl(String urlStr) {
URLEncoder.encode(urlStr, "UTF-8");
...
}
Decoded URLs cannot be
re-encoded to the same form
http://example.com/?query=a&b==c
Cannot be decoded back after it was
encoded:
http://example.com/?query=a%26b==c
Don’t use java.net.URLEncoder
“Utility class for HTML form encoding.
This class contains static methods for
converting a String to the
application/x-www-form-
urlencoded MIME format.
For more information about HTML
form encoding, consult the HTML
specification.”
AHC Alternatives
Question!
How do I
close a
socket
correctly?
How hard can it be to close a
socket?
The art of socket closing
http://www.safaribooksonline.com/library/view/http-the-definitive/1565925092/ch04s07.html
Half-closed: no new customers
Never block in socket close()
• The other side expects you
to clean up nicely
• It will give up on time out
• You will wait (forever)
Remember?
Question!
How can I write
file parts
concurrently?
– Write to separate files, combine on finish
– Write to same file, seeking to the right
position
Use FileChannel
• Implements SeekableByteChannel
java.nio.channels.FileChannel#write(
java.nio.ByteBuffer src, long position)
Download progress tracking
• PersistentFileProgressInfo
– Save the total size, sha1, number of parts
– State of each part (offset, size, completed...)
FileProgressInfo FilePartProgressInfo
*
File Locking
File locking Levels
– VM level
– OS level
OS level File locking
• Multiple downloader instances writing to
the same file
• Needed for writing:
– Partial download file
– Persistent download progress
FileLock lock = fileChannel.tryLock();
//Non-shared: (0L, Long.MAX_VALUE, false)
if (lock == null) {
throw new OverlappingFileLockException();
}
return lock;
}
OS Level File Locking -
Exclusive
private FileLock lock(FileChannel fileChannel) throws IOException {
FileLock lock = fileChannel.tryLock(Long.MAX_VALUE - 1, 1, false);
if (lock == null) {
throw new OverlappingFileLockException();
}
return lock;
}
OS Level File Locking –
Advisory exclusive
WTF?!
VM Level File Locking
VM Level File Locking
– Prevent same VM threads writing to the file when we
started closing it
– Closing sequence:
– Release file locks
– Close channels
– Rename a file to it's final name (remove .part)
– Erase progress info
VM Level File Locking
ReentrantReadWriteLock.ReadLock writeToFileLock = rwl.readLock();
ReentrantReadWriteLock.WriteLock closeFileLock = rwl.writeLock();
public void close() throws IOException {
this.closeFileLock.lock();
}
public int write(int partIndex, ByteBuffer buf) {
if (!this.writeToFileLock.tryLock()) {
throw new IllegalStateException("File is being closed");
}
...
}
What’s next?
http/2
– Mostly standardizing Google's spdy
– Header compression
– multiplexing
– Prioritization
– Server push
– On the way clear some stuff
– E.g. compressed content length
Ease the load
Links!
• RTFM: RFC 2616
• Ultimate book: HTTP: The Definitive
Guide
– Amazon
– Safari
• Reactor pattern
• Doug Lea on NIO
No, Thank you!

Presentation: Everything you wanted to know about writing async, high-concurrency HTTP applications in Java, but were afraid to ask

  • 1.
    Everything You Wantedto Know About Writing Async, Concurrent HTTP Apps in Java
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 12.
    Requirements – parallel fileDownloads – Parallel file parts – interrupt/pause/resume – Progress events – Checksums caching
  • 13.
    First Association for“concurrent downloader”
  • 15.
    Lucky day: Downloadmanager written in Java!
  • 17.
    Let’s look ifwe can use it! 1. No traceable license 2. No website or docs 3. No traceable sources 4. It’s an app, not a lib
  • 20.
    Java.net.urlconnection 1. Memory wasteful(buffering) 2. Minimal API 3. Blocking streams
  • 22.
    What we’re lookingfor 1. Async/non-blocking 2. Event callbacks
  • 23.
    What is ITgoing to take 1. Reactor 2. nio
  • 24.
  • 25.
    – pattern forlightweight concurrency – Event driven – Threads reuse – Uses non-blocking Io
  • 26.
  • 27.
    Guess the authorby the diagram http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
  • 28.
  • 29.
    Selector as amultiplexer
  • 30.
    Java version -Registering SocketChannel channel= SocketChannel.open(); socketChannel.connect(new InetSocketAddress("http://remote.com", 80)); ... Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey k = channel.register(selector, SelectionKey.OP_READ); k.attach(handler);
  • 31.
    Java version -Dispatcher while (!Thread.interrupted()) { selector.select(); Set selected = selector.selectedKeys(); Iterator it = selected.iterator(); while (it.hasNext()) SelectionKey k = (SelectionKey)(it.next(); ((Runnable)(k.attachment())).run(); selected.clear(); }
  • 32.
    Handling reactor eventsis complex – Need to maintain state – Buffering – assembling chunks – Coordinating async events
  • 34.
    Nio libraries – Mostof them are servers –Netty, grizzly, etc. – Apache Mina – Apache HTTP components asyncclient – Ning http client
  • 35.
    Nio libraries – Mostof them are servers –Netty, grizzly, etc. – Apache Mina – Apache HTTP components asyncclient – Ning http client
  • 36.
    – Client andserver nio library – Evolved from netty – Latest release October 2012
  • 38.
    Nio libraries – Mostof them are servers –Netty, grizzly, etc – Apache Mina – Apache HTTP components asyncclient – Ning http client
  • 40.
  • 43.
  • 44.
    try (AsyncHttpClient asyncHttpClient= new AsyncHttpClient()) { ListenableFuture<Response> future = asyncHttpClient.prepareGet( "http://oss.jfrog.org/api/system/ping").execute( new AsyncCompletionHandler<Response>() { @Override public Response onCompleted(Response response) { System.out.println(response.getResponseBody()); return response; } @Override public void onThrowable(Throwable t) { t.printStackTrace(); } }); Response response = future.get(); }
  • 46.
    HAC Concepts – Requestproducer – Response consumer
  • 47.
    try (CloseableHttpAsyncClient asyncHttpClient= HttpAsyncClients.createDefault()) { asyncHttpClient.start(); Future<HttpResponse> future = asyncHttpClient.execute( HttpAsyncMethods.createGet("http://oss.jfrog.org/api/system/ping"), new AsyncByteConsumer<HttpResponse>() { @Override protected void onResponseReceived(final HttpResponse response) { System.out.println(response.getStatusLine().getReasonPhrase()); } @Override protected void onByteReceived(final CharBuffer buf, final IOControl ioctrl) { } @Override protected void releaseResources() { } @Override protected HttpResponse buildResult(final HttpContext context) { return (HttpResponse) context.getAttribute("http.response"); } }, null); HttpResponse response = future.get(); }
  • 49.
    Choosing between ningand http asyncclient
  • 50.
    "All problems incomputer science can be solved by another level of indirection" David Wheeler
  • 51.
    public interface HttpProviderDownloadHandler{ void onResponseReceived(int statusCode, Map<String, List<String>> headers); boolean onBytesReceived(ByteBuffer buf); void onFailed(Throwable error); void onCanceled(); void onCompleted(); }
  • 52.
    Head to head Feature/LibraryNing client Http Async Client Maturity Good Very new (early 2014) Download cancelation Easy With bugs Progress hooks Events not granular enough Just use onByteReceived() Documentation A bit sparse Minimal Server-side counterpart None, client only org.apache.httpcomponents httpcore-nio
  • 53.
    Performance? 0 100 200 300 400 500 600 700 800 900 Small file Mediumfile Large file Ning AHAC http://blogs.atlassian.com/2013/07/http-client-performance-io/
  • 54.
  • 56.
  • 57.
    Just read somestackoverflow (and improve your rep as you go)
  • 58.
    And that onefor discovering that range header is lost on redirect
  • 59.
  • 62.
  • 63.
    Question! Why when redirectedto CDN all the chunks start from zero?
  • 64.
    HttpAsyncClientBuilder builder =HttpAsyncClients.custom(); // add redirect strategy that copies "range" headers, if exist builder.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) HttpUriRequest redirectRequest = super.getRedirect(request, response, context); // copy "Range" headers, if exist Header[] rangeHeaders = request.getHeaders(HttpHeaders.RANGE); if (rangeHeaders != null) { for (Header header : rangeHeaders) { redirectRequest.addHeader(header); } } return redirectRequest; }});
  • 65.
  • 70.
  • 71.
    public static StringencodeUrl(String urlStr) { URLEncoder.encode(urlStr, "UTF-8"); ... }
  • 72.
    Decoded URLs cannotbe re-encoded to the same form http://example.com/?query=a&b==c Cannot be decoded back after it was encoded: http://example.com/?query=a%26b==c
  • 73.
    Don’t use java.net.URLEncoder “Utilityclass for HTML form encoding. This class contains static methods for converting a String to the application/x-www-form- urlencoded MIME format. For more information about HTML form encoding, consult the HTML specification.”
  • 74.
  • 76.
    Question! How do I closea socket correctly?
  • 77.
    How hard canit be to close a socket?
  • 78.
    The art ofsocket closing http://www.safaribooksonline.com/library/view/http-the-definitive/1565925092/ch04s07.html
  • 79.
  • 80.
    Never block insocket close() • The other side expects you to clean up nicely • It will give up on time out • You will wait (forever)
  • 82.
  • 83.
    Question! How can Iwrite file parts concurrently?
  • 84.
    – Write toseparate files, combine on finish – Write to same file, seeking to the right position
  • 87.
    Use FileChannel • ImplementsSeekableByteChannel java.nio.channels.FileChannel#write( java.nio.ByteBuffer src, long position)
  • 88.
    Download progress tracking •PersistentFileProgressInfo – Save the total size, sha1, number of parts – State of each part (offset, size, completed...) FileProgressInfo FilePartProgressInfo *
  • 89.
  • 90.
    File locking Levels –VM level – OS level
  • 91.
    OS level Filelocking • Multiple downloader instances writing to the same file • Needed for writing: – Partial download file – Persistent download progress
  • 92.
    FileLock lock =fileChannel.tryLock(); //Non-shared: (0L, Long.MAX_VALUE, false) if (lock == null) { throw new OverlappingFileLockException(); } return lock; } OS Level File Locking - Exclusive
  • 93.
    private FileLock lock(FileChannelfileChannel) throws IOException { FileLock lock = fileChannel.tryLock(Long.MAX_VALUE - 1, 1, false); if (lock == null) { throw new OverlappingFileLockException(); } return lock; } OS Level File Locking – Advisory exclusive WTF?!
  • 94.
  • 95.
    VM Level FileLocking – Prevent same VM threads writing to the file when we started closing it – Closing sequence: – Release file locks – Close channels – Rename a file to it's final name (remove .part) – Erase progress info
  • 96.
    VM Level FileLocking ReentrantReadWriteLock.ReadLock writeToFileLock = rwl.readLock(); ReentrantReadWriteLock.WriteLock closeFileLock = rwl.writeLock(); public void close() throws IOException { this.closeFileLock.lock(); } public int write(int partIndex, ByteBuffer buf) { if (!this.writeToFileLock.tryLock()) { throw new IllegalStateException("File is being closed"); } ... }
  • 97.
  • 98.
    http/2 – Mostly standardizingGoogle's spdy – Header compression – multiplexing – Prioritization – Server push – On the way clear some stuff – E.g. compressed content length
  • 99.
  • 100.
    Links! • RTFM: RFC2616 • Ultimate book: HTTP: The Definitive Guide – Amazon – Safari • Reactor pattern • Doug Lea on NIO
  • 101.

Editor's Notes

  • #3 Things that are not simple
  • #4 Why you dont’s
  • #5 Just avoid
  • #6 CURRENT – JB NEXT - YOAV
  • #27 Events are multiplexed by a single thread dispatcher
  • #53 TODO send table to back!!!
  • #55 Hypertext Transfer Protocol -- HTTP/1.1
  • #67 RFC2616 8.1.4
  • #68 Browsers go like:
  • #87 RAC is not concurrent!
  • #94 lock beyond the end of file, so we can write while others are reading avoid OS-dependent no support for shared lock (for concurrent r/w) and fallback to exclusive + avoid OS-dependent access effect of exclusive locks (Windoze...) Allows writing and reading by knowing if someone else is writing https://community.oracle.com/message/8781694