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);
>  }
>
> }
>
>