You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@hc.apache.org by ching <ch...@gmail.com> on 2010/04/29 11:51:31 UTC
Can HttpCore NIO work on Android?
I'm implementing a download application that works like android's DownloadProvider but based HttpCore NIO.
A fatal problem I encountered is this:
04-29 09:26:05.419: ERROR/AndroidRuntime(332): java.nio.channels.IllegalSelectorException
04-29 09:26:05.419: ERROR/AndroidRuntime(332): at java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:158)
04-29 09:26:05.419: ERROR/AndroidRuntime(332): at java.nio.channels.SelectableChannel.register(SelectableChannel.java:158)
04-29 09:26:05.419: ERROR/AndroidRuntime(332): at org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processSessionRequests(DefaultConnectingIOReactor.java:244)
04-29 09:26:05.419: ERROR/AndroidRuntime(332): at org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processEvents(DefaultConnectingIOReactor.java:97)
04-29 09:26:05.419: ERROR/AndroidRuntime(332): at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor.execute(AbstractMultiworkerIOReactor.java:317)
04-29 09:26:05.419: ERROR/AndroidRuntime(332): at io.DownloadClient.run(DownloadClient.java:168)
04-29 09:26:05.419: ERROR/AndroidRuntime(332): at java.lang.Thread.run(Thread.java:1096)
however it can work on real j2se6, is that unsupported on android at all?
And a minor problem: Will "ioReactor.execute(ioEventDispatch)" really throw out a InterruptedIOException?
I found it will be never even when I interrupt the thread explicitly. shutdown can't be done gracefully without this.
Thanks for help, my raw codes for testing:
//********** io.DownloadClient.java **********
package io;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.nio.DefaultClientIOEventDispatch;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.nio.ContentDecoder;
import org.apache.http.nio.FileContentDecoder;
import org.apache.http.nio.NHttpClientConnection;
import org.apache.http.nio.NHttpClientHandler;
import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.protocol.AsyncNHttpClientHandler;
import org.apache.http.nio.protocol.NHttpRequestExecutionHandler;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.util.HeapByteBufferAllocator;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
public class DownloadClient implements Runnable {
public static final String TAG = "nioDownload";
Thread dispatcher;
ExecutorService workers;
ConnectingIOReactor reactor;
IOEventDispatch eventDispatch;
public DownloadClient() throws IOException {
BasicHttpProcessor httpproc = new BasicHttpProcessor();
httpproc.addInterceptor(new RequestContent());
httpproc.addInterceptor(new RequestTargetHost());
httpproc.addInterceptor(new RequestConnControl());
httpproc.addInterceptor(new RequestUserAgent());
httpproc.addInterceptor(new RequestExpectContinue());
HttpParams params = new BasicHttpParams();
params.setParameter(CoreProtocolPNames.USER_AGENT, "HttpComponents/1.1");
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000);
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024);
params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false);
params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);
NHttpRequestExecutionHandler execHandler = new NHttpRequestExecutionHandler() {
HttpRequest request = null;
@Override
public void initalizeContext(HttpContext context, Object attachment) {
if(attachment != null && attachment instanceof DownloadCourse) {
DownloadCourse course = (DownloadCourse)attachment;
request = course.getRequest();
context.setAttribute(TAG, attachment);
}
}
@Override
public HttpRequest submitRequest(HttpContext context) {
HttpRequest result = this.request;
if(result != null) {
this.request = null;
}
return result;
}
@Override
public ConsumingNHttpEntity responseEntity(HttpResponse response, HttpContext context)
throws IOException {
//nothing, handle in NHttpClientHandler.inputReady
return null;
}
@Override
public void handleResponse(HttpResponse response, HttpContext context)
throws IOException {
//nothing, handle in NHttpClientHandler.inputReady
}
@Override
public void finalizeContext(HttpContext context) {
context.removeAttribute(TAG);
}
};
NHttpClientHandler handler = new AsyncNHttpClientHandler(
httpproc, execHandler, new DefaultConnectionReuseStrategy(),
new HeapByteBufferAllocator(), params) {
@Override
public void closed(NHttpClientConnection conn) {
super.closed(conn);
Object attachment = conn.getContext().getAttribute(TAG);
if(attachment != null && attachment instanceof DownloadCourse) {
DownloadCourse course = (DownloadCourse)attachment;
course.interrupt();
}
}
@Override
public void exception(NHttpClientConnection conn, HttpException ex) {
super.exception(conn, ex);
Object attachment = conn.getContext().getAttribute(TAG);
if(attachment != null && attachment instanceof DownloadCourse) {
DownloadCourse course = (DownloadCourse)attachment;
course.error(ex);
}
}
@Override
public void exception(NHttpClientConnection conn, IOException ex) {
super.exception(conn, ex);
Object attachment = conn.getContext().getAttribute(TAG);
if(attachment != null && attachment instanceof DownloadCourse) {
DownloadCourse course = (DownloadCourse)attachment;
course.error(ex);
}
}
@Override
public void inputReady(NHttpClientConnection conn, ContentDecoder content) {
//don't invoke super, handle by self
log("inputReady: %s", content);
Object attachment = conn.getContext().getAttribute(TAG);
if(attachment != null && attachment instanceof DownloadCourse) {
DownloadCourse course = (DownloadCourse)attachment;
course.update((FileContentDecoder)content);
if(content.isCompleted()) {
course.complete(conn.getHttpResponse());
}
}
}
@Override
public void responseReceived(NHttpClientConnection conn) {
super.responseReceived(conn);
Object attachment = conn.getContext().getAttribute(TAG);
if(attachment != null && attachment instanceof DownloadCourse) {
DownloadCourse course = (DownloadCourse)attachment;
course.prepare(conn.getHttpResponse());
}
}
@Override
public void timeout(NHttpClientConnection conn) {
super.timeout(conn);
Object attachment = conn.getContext().getAttribute(TAG);
if(attachment != null && attachment instanceof DownloadCourse) {
DownloadCourse course = (DownloadCourse)attachment;
course.interrupt();
}
}
};
eventDispatch = new DefaultClientIOEventDispatch(handler, params);
reactor = new DefaultConnectingIOReactor(1, params);
}
@Override
public void run() {
log("DownloadClient run at %s", new Date());
try {
reactor.execute(eventDispatch);
} catch(InterruptedIOException e) {
//never thrown? even when being interrupted?
log("DownloadClient interrupted");
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
reactor.shutdown();
} catch(IOException e) {
e.printStackTrace();
}
}
}
public void execute(DownloadCourse course) {
HttpHost host = course.getHost();
reactor.connect(
new InetSocketAddress(host.getHostName(), host.getPort() > 0 ? host.getPort() : 80), null,
course, null);
}
public void startup() {
dispatcher = new Thread(this, "DownloadClient Thread");
dispatcher.setDaemon(true);
dispatcher.start();
workers = Executors.newFixedThreadPool(2);
}
public void shutdown() {
dispatcher.interrupt();
workers.shutdown();
}
public void download(URI uri, File saveFile) throws IOException {
DownloadCourse course = new DownloadCourse(this, uri, saveFile);
workers.submit(course);
}
public static void log(Object s) {
System.out.println(s);
}
public static void log(String format, Object... a) {
System.out.println(String.format(format, a));
}
public static void main(String[] args) throws Exception {
//startup client
DownloadClient client = new DownloadClient();
client.startup();
//submit request
File saveFile = new File("/sdcard/download.txt");
if(!saveFile.exists()) saveFile.createNewFile();
client.download(new URI("http://www.google.com/"), saveFile);
//for a while...
Thread.sleep(10000);
//clear for end
client.shutdown();
}
}
//********** io.DownloadCourse.java **********
package io;
import static io.DownloadClient.log;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URI;
import java.nio.channels.FileChannel;
import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.nio.FileContentDecoder;
public class DownloadCourse implements Runnable {
public static final int maxRetryTimes = 5;
public static final byte QUEUING_STATUS = 0;
public static final byte DOWNLOADING = 1;
public static final byte PAUSED = 15;
public static final byte FINISHED = 127;
public static final byte VIOLATED = -1;
public static final byte TERMINATED = -128;
DownloadClient client;
URI uri;
File file;
RandomAccessFile randomAccessFile;
FileChannel fileChannel;
long contentLength;
long position;
int status;
int retryTimes;
public DownloadCourse(DownloadClient client, URI uri, File file) {
this.client = client;
this.uri = uri;
this.file = file;
status = QUEUING_STATUS;
}
@Override
public void run() {
log("DownloadCourse run at %s", new Date());
status = QUEUING_STATUS;
client.execute(this);
}
public HttpHost getHost() {
return uri.getPort() > 0 ?
new HttpHost(uri.getHost(), uri.getPort()) :
new HttpHost(uri.getHost());
}
public HttpRequest getRequest() {
HttpRequest request = uri.getQuery() != null ?
new BasicHttpRequest("GET", uri.getPath() + "?" + uri.getQuery()) :
new BasicHttpRequest("GET", uri.getPath());
request.setHeader("Host", getHost().toHostString());
return request;
}
public void prepare(HttpResponse response) {
Header contentLengthHeader = response.getFirstHeader("Content-Length");
if(contentLengthHeader != null) {
contentLength = Integer.parseInt(contentLengthHeader.getValue().trim());
}
try {
randomAccessFile = new RandomAccessFile(file, "rw");
if(contentLength > 0) {
randomAccessFile.setLength(contentLength);
}
fileChannel = randomAccessFile.getChannel();
} catch(IOException e) {
throw new RuntimeException(e);
}
status = DOWNLOADING;
}
public void update(FileContentDecoder content) {
long length = 0;
try {
length = content.transfer(fileChannel, position, contentLength);
} catch(IOException e) {
throw new RuntimeException(e);
}
position += length;
log("download progress: %d/%d", position, contentLength);
}
public void complete(HttpResponse response) {
try {
randomAccessFile.close();
log(response.getStatusLine().toString());
log(new String(FileUtils.readFileToByteArray(file)));
} catch(IOException e) {
throw new RuntimeException(e);
}
status = FINISHED;
}
public void error(Exception ex) {
try {
randomAccessFile.close();
} catch(IOException e) {
throw new RuntimeException(e);
}
if(++retryTimes < maxRetryTimes) {
status = VIOLATED;
} else {
status = TERMINATED;
}
}
public void interrupt() {
try {
randomAccessFile.close();
} catch(IOException e) {
throw new RuntimeException(e);
}
status = PAUSED;
}
public String toString() {
return String.format("%s\n\turi: %s\n\tfile: %s\n\tstatus: %d\n\tcontentLength: %d\n\tposition:%d", super.toString(), uri, file, status, contentLength, position);
}
}
Re: Can HttpCore NIO work on Android?
Posted by Oleg Kalnichevski <ol...@apache.org>.
On Thu, 2010-04-29 at 17:51 +0800, ching wrote:
> I'm implementing a download application that works like android's DownloadProvider but based HttpCore NIO.
>
> A fatal problem I encountered is this:
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): java.nio.channels.IllegalSelectorException
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:158)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at java.nio.channels.SelectableChannel.register(SelectableChannel.java:158)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processSessionRequests(DefaultConnectingIOReactor.java:244)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processEvents(DefaultConnectingIOReactor.java:97)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor.execute(AbstractMultiworkerIOReactor.java:317)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at io.DownloadClient.run(DownloadClient.java:168)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at java.lang.Thread.run(Thread.java:1096)
>
> however it can work on real j2se6, is that unsupported on android at all?
>
HttpCore NIO has not been tested for compatibility with Android, so
problems like this can happen. If you are willing to work with SVN
snapshots, I will happily put together patches you could try out.
As James pointed out, raising a JIRA would be the right way to go about
the problem.
> And a minor problem: Will "ioReactor.execute(ioEventDispatch)" really throw out a InterruptedIOException?
> I found it will be never even when I interrupt the thread explicitly.
I think InterruptedIOException will be thrown only if the selector is
blocked in a select operation. Why do not you just use the #shutdown
method to gracefully shut down the I/O reactor?
Oleg
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org
Re: Can HttpCore NIO work on Android?
Posted by James Abley <ja...@gmail.com>.
Hi,
Which version of HttpCore NIO are you using?
That looks like it may be a bug which would need raising in JIRA. The
Android docs [1] say that 1, 4, 8 and 16 are valid values for the bitmask
ops parameter (or use the constants defined on SelectionKey); the code [2]
is currently passing in 0. The JavaDoc says that may throw an
IllegalArgumentException in that case [3].
I'm not familiar enough with the code to know what the intent of the code is
at that point.
Cheers,
James
[1]
http://developer.android.com/reference/java/nio/channels/SelectionKey.html#OP_ACCEPT
[2]
http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/reactor/DefaultConnectingIOReactor.java
[3]
http://java.sun.com/j2se/1.5.0/docs/api/java/nio/channels/SelectableChannel.html#register(java.nio.channels.Selector,
int)
On 29 April 2010 10:51, ching <ch...@gmail.com> wrote:
> I'm implementing a download application that works like android's
> DownloadProvider but based HttpCore NIO.
>
> A fatal problem I encountered is this:
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332):
> java.nio.channels.IllegalSelectorException
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at
> java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:158)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at
> java.nio.channels.SelectableChannel.register(SelectableChannel.java:158)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at
> org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processSessionRequests(DefaultConnectingIOReactor.java:244)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at
> org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processEvents(DefaultConnectingIOReactor.java:97)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at
> org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor.execute(AbstractMultiworkerIOReactor.java:317)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at
> io.DownloadClient.run(DownloadClient.java:168)
> 04-29 09:26:05.419: ERROR/AndroidRuntime(332): at
> java.lang.Thread.run(Thread.java:1096)
>
> however it can work on real j2se6, is that unsupported on android at all?
>
> And a minor problem: Will "ioReactor.execute(ioEventDispatch)" really throw
> out a InterruptedIOException?
> I found it will be never even when I interrupt the thread explicitly.
> shutdown can't be done gracefully without this.
>
>
> Thanks for help, my raw codes for testing:
> //********** io.DownloadClient.java **********
> package io;
>
> import java.io.File;
> import java.io.IOException;
> import java.io.InterruptedIOException;
> import java.net.InetSocketAddress;
> import java.net.URI;
> import java.util.Date;
> import java.util.concurrent.ExecutorService;
> import java.util.concurrent.Executors;
>
> import org.apache.http.HttpException;
> import org.apache.http.HttpHost;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.nio.DefaultClientIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
> import org.apache.http.nio.ContentDecoder;
> import org.apache.http.nio.FileContentDecoder;
> import org.apache.http.nio.NHttpClientConnection;
> import org.apache.http.nio.NHttpClientHandler;
> import org.apache.http.nio.entity.ConsumingNHttpEntity;
> import org.apache.http.nio.protocol.AsyncNHttpClientHandler;
> import org.apache.http.nio.protocol.NHttpRequestExecutionHandler;
> import org.apache.http.nio.reactor.ConnectingIOReactor;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.util.HeapByteBufferAllocator;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.RequestConnControl;
> import org.apache.http.protocol.RequestContent;
> import org.apache.http.protocol.RequestExpectContinue;
> import org.apache.http.protocol.RequestTargetHost;
> import org.apache.http.protocol.RequestUserAgent;
>
> public class DownloadClient implements Runnable {
> public static final String TAG = "nioDownload";
>
> Thread dispatcher;
> ExecutorService workers;
>
> ConnectingIOReactor reactor;
> IOEventDispatch eventDispatch;
>
> public DownloadClient() throws IOException {
> BasicHttpProcessor httpproc = new BasicHttpProcessor();
> httpproc.addInterceptor(new RequestContent());
> httpproc.addInterceptor(new RequestTargetHost());
> httpproc.addInterceptor(new RequestConnControl());
> httpproc.addInterceptor(new RequestUserAgent());
> httpproc.addInterceptor(new RequestExpectContinue());
> HttpParams params = new BasicHttpParams();
> params.setParameter(CoreProtocolPNames.USER_AGENT,
> "HttpComponents/1.1");
> params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000);
> params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
> params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 *
> 1024);
> params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,
> false);
> params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);
> NHttpRequestExecutionHandler execHandler = new
> NHttpRequestExecutionHandler() {
> HttpRequest request = null;
> @Override
> public void initalizeContext(HttpContext context, Object attachment) {
> if(attachment != null && attachment instanceof DownloadCourse) {
> DownloadCourse course = (DownloadCourse)attachment;
> request = course.getRequest();
> context.setAttribute(TAG, attachment);
> }
> }
> @Override
> public HttpRequest submitRequest(HttpContext context) {
> HttpRequest result = this.request;
> if(result != null) {
> this.request = null;
> }
> return result;
> }
> @Override
> public ConsumingNHttpEntity responseEntity(HttpResponse response,
> HttpContext context)
> throws IOException {
> //nothing, handle in NHttpClientHandler.inputReady
> return null;
> }
> @Override
> public void handleResponse(HttpResponse response, HttpContext context)
> throws IOException {
> //nothing, handle in NHttpClientHandler.inputReady
> }
> @Override
> public void finalizeContext(HttpContext context) {
> context.removeAttribute(TAG);
> }
> };
> NHttpClientHandler handler = new AsyncNHttpClientHandler(
> httpproc, execHandler, new DefaultConnectionReuseStrategy(),
> new HeapByteBufferAllocator(), params) {
> @Override
> public void closed(NHttpClientConnection conn) {
> super.closed(conn);
> Object attachment = conn.getContext().getAttribute(TAG);
> if(attachment != null && attachment instanceof DownloadCourse) {
> DownloadCourse course = (DownloadCourse)attachment;
> course.interrupt();
> }
> }
> @Override
> public void exception(NHttpClientConnection conn, HttpException ex) {
> super.exception(conn, ex);
> Object attachment = conn.getContext().getAttribute(TAG);
> if(attachment != null && attachment instanceof DownloadCourse) {
> DownloadCourse course = (DownloadCourse)attachment;
> course.error(ex);
> }
> }
> @Override
> public void exception(NHttpClientConnection conn, IOException ex) {
> super.exception(conn, ex);
> Object attachment = conn.getContext().getAttribute(TAG);
> if(attachment != null && attachment instanceof DownloadCourse) {
> DownloadCourse course = (DownloadCourse)attachment;
> course.error(ex);
> }
> }
> @Override
> public void inputReady(NHttpClientConnection conn, ContentDecoder
> content) {
> //don't invoke super, handle by self
> log("inputReady: %s", content);
> Object attachment = conn.getContext().getAttribute(TAG);
> if(attachment != null && attachment instanceof DownloadCourse) {
> DownloadCourse course = (DownloadCourse)attachment;
> course.update((FileContentDecoder)content);
> if(content.isCompleted()) {
> course.complete(conn.getHttpResponse());
> }
> }
> }
> @Override
> public void responseReceived(NHttpClientConnection conn) {
> super.responseReceived(conn);
> Object attachment = conn.getContext().getAttribute(TAG);
> if(attachment != null && attachment instanceof DownloadCourse) {
> DownloadCourse course = (DownloadCourse)attachment;
> course.prepare(conn.getHttpResponse());
> }
> }
> @Override
> public void timeout(NHttpClientConnection conn) {
> super.timeout(conn);
> Object attachment = conn.getContext().getAttribute(TAG);
> if(attachment != null && attachment instanceof DownloadCourse) {
> DownloadCourse course = (DownloadCourse)attachment;
> course.interrupt();
> }
> }
> };
> eventDispatch = new DefaultClientIOEventDispatch(handler, params);
> reactor = new DefaultConnectingIOReactor(1, params);
> }
>
> @Override
> public void run() {
> log("DownloadClient run at %s", new Date());
> try {
> reactor.execute(eventDispatch);
> } catch(InterruptedIOException e) {
> //never thrown? even when being interrupted?
> log("DownloadClient interrupted");
> } catch(IOException e) {
> e.printStackTrace();
> } finally {
> try {
> reactor.shutdown();
> } catch(IOException e) {
> e.printStackTrace();
> }
> }
> }
>
> public void execute(DownloadCourse course) {
> HttpHost host = course.getHost();
> reactor.connect(
> new InetSocketAddress(host.getHostName(), host.getPort() > 0 ?
> host.getPort() : 80), null,
> course, null);
> }
>
> public void startup() {
> dispatcher = new Thread(this, "DownloadClient Thread");
> dispatcher.setDaemon(true);
> dispatcher.start();
> workers = Executors.newFixedThreadPool(2);
> }
>
> public void shutdown() {
> dispatcher.interrupt();
> workers.shutdown();
> }
>
> public void download(URI uri, File saveFile) throws IOException {
> DownloadCourse course = new DownloadCourse(this, uri, saveFile);
> workers.submit(course);
> }
>
> public static void log(Object s) {
> System.out.println(s);
> }
>
> public static void log(String format, Object... a) {
> System.out.println(String.format(format, a));
> }
>
> public static void main(String[] args) throws Exception {
> //startup client
> DownloadClient client = new DownloadClient();
> client.startup();
>
> //submit request
> File saveFile = new File("/sdcard/download.txt");
> if(!saveFile.exists()) saveFile.createNewFile();
> client.download(new URI("http://www.google.com/"), saveFile);
>
> //for a while...
> Thread.sleep(10000);
>
> //clear for end
> client.shutdown();
> }
>
> }
>
>
> //********** io.DownloadCourse.java **********
> package io;
>
> import static io.DownloadClient.log;
>
> import java.io.File;
> import java.io.IOException;
> import java.io.RandomAccessFile;
> import java.net.URI;
> import java.nio.channels.FileChannel;
> import java.util.Date;
>
> import org.apache.commons.io.FileUtils;
> import org.apache.http.Header;
> import org.apache.http.HttpHost;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.message.BasicHttpRequest;
> import org.apache.http.nio.FileContentDecoder;
>
> public class DownloadCourse implements Runnable {
>
> public static final int maxRetryTimes = 5;
> public static final byte QUEUING_STATUS = 0;
> public static final byte DOWNLOADING = 1;
> public static final byte PAUSED = 15;
> public static final byte FINISHED = 127;
> public static final byte VIOLATED = -1;
> public static final byte TERMINATED = -128;
>
> DownloadClient client;
> URI uri;
> File file;
> RandomAccessFile randomAccessFile;
> FileChannel fileChannel;
> long contentLength;
> long position;
> int status;
> int retryTimes;
>
> public DownloadCourse(DownloadClient client, URI uri, File file) {
> this.client = client;
> this.uri = uri;
> this.file = file;
> status = QUEUING_STATUS;
> }
>
> @Override
> public void run() {
> log("DownloadCourse run at %s", new Date());
> status = QUEUING_STATUS;
> client.execute(this);
> }
>
> public HttpHost getHost() {
> return uri.getPort() > 0 ?
> new HttpHost(uri.getHost(), uri.getPort()) :
> new HttpHost(uri.getHost());
> }
>
> public HttpRequest getRequest() {
> HttpRequest request = uri.getQuery() != null ?
> new BasicHttpRequest("GET", uri.getPath() + "?" + uri.getQuery()) :
> new BasicHttpRequest("GET", uri.getPath());
> request.setHeader("Host", getHost().toHostString());
> return request;
> }
>
> public void prepare(HttpResponse response) {
> Header contentLengthHeader = response.getFirstHeader("Content-Length");
> if(contentLengthHeader != null) {
> contentLength = Integer.parseInt(contentLengthHeader.getValue().trim());
> }
> try {
> randomAccessFile = new RandomAccessFile(file, "rw");
> if(contentLength > 0) {
> randomAccessFile.setLength(contentLength);
> }
> fileChannel = randomAccessFile.getChannel();
> } catch(IOException e) {
> throw new RuntimeException(e);
> }
> status = DOWNLOADING;
> }
>
> public void update(FileContentDecoder content) {
> long length = 0;
> try {
> length = content.transfer(fileChannel, position, contentLength);
> } catch(IOException e) {
> throw new RuntimeException(e);
> }
> position += length;
> log("download progress: %d/%d", position, contentLength);
> }
>
> public void complete(HttpResponse response) {
> try {
> randomAccessFile.close();
> log(response.getStatusLine().toString());
> log(new String(FileUtils.readFileToByteArray(file)));
> } catch(IOException e) {
> throw new RuntimeException(e);
> }
> status = FINISHED;
> }
>
> public void error(Exception ex) {
> try {
> randomAccessFile.close();
> } catch(IOException e) {
> throw new RuntimeException(e);
> }
> if(++retryTimes < maxRetryTimes) {
> status = VIOLATED;
> } else {
> status = TERMINATED;
> }
> }
>
> public void interrupt() {
> try {
> randomAccessFile.close();
> } catch(IOException e) {
> throw new RuntimeException(e);
> }
> status = PAUSED;
> }
>
> public String toString() {
> return String.format("%s\n\turi: %s\n\tfile: %s\n\tstatus:
> %d\n\tcontentLength: %d\n\tposition:%d", super.toString(), uri, file,
> status, contentLength, position);
> }
>
> }
>
>