Home > Async Http client > Going Asynchronous using AsyncHttpClient: The Basic

Going Asynchronous using AsyncHttpClient: The Basic

The Async Http Client library purpose is to allow Java applications to easily execute HTTP requests and asynchronously process the HTTP responses. In this blog I will explain how to use the library and what features are supported.

Executing request synchronously or asynchronously.

The first thing to decide when using the library is if your application can handle asynchronous response or not. If not, the library has been designed using the Future API, hence you can always execute synchronous call by blocking on the Future.get() method:

   AsyncHttpClient client = new AsyncHttpClient();
   Response response = client.prepareGet(("http://sonatype.com")
     .execute().get();

The above means the request will block until the full Response has been received. It also made your application’s blocking, waiting for the response to comes back. This could be potentially an issue to block for every request, specially when doing POST or PUT operations where you don’t necessarily need to wait for the response. A simple way consists of not calling the Future.get()

AsyncHttpClient client = new AsyncHttpClient();
Response response =
   client.preparePut(("http://sonatype.com/myFile.avi").execute();

A better way than above would consist of using an AsyncHandler. The AynchHandler API is fairly simple and just consist of 5 methods to implements:

public interface AsyncHandler<T>  {
    void onThrowable(Throwable t);

    STATE onBodyPartReceived(HttpResponseBodyPart bodyPart)
      throws Exception;

    STATE onStatusReceived(HttpResponseStatus responseStatus)
      throws Exception;

    STATE onHeadersReceived(HttpResponseHeaders headers)
      throws Exception;

    T onCompleted() throws Exception;
}

The method’s order of invocation when the response start arriving consist of:

  1. onStatusReceived: The status line has been processed.
  2. onHeadersReceived: All response’s headers has been processed.
  3. onBodyPartReceived: A body parts has been received. This method can be invoked many time depending of the response’s bytes body.
  4. onCompleted: Invoked when the full response has been read, or if the processing get aborted (more on this below)
  5. onThrowable: Invoked if something wrong happened inside the previous methods or when an I/O exception occurs.

Note that for all methods onXXXReceived, the return value is an enum which can take the value of CONTINUE or ABORT. Returning CONTINUE tells the library to continue processing the response, where ABORT means stop processing the response and automatically invoke the onCompleted(). This is particularly helpful if your application just need to looks for the response’s status or headers, without the need to process the entire response’s body. An implementation would looks like (T can be anything):

AsyncHttpClient client = new AsyncHttpClient();
client.prepareGet("http://sonatype.com")
  .execute(new AsyncHandler<T>() {

     void onThrowable(Throwable t) {
     }
     public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart)
       throws Exception{
        return STATE.CONTINUE;
     }
     public STATE onStatusReceived(HttpResponseStatus responseStatus)
       throws Exception {
        return STATE.CONTINUE;
     }
     public STATE onHeadersReceived(HttpResponseHeaders headers)
       throws Exception {
        return STATE.CONTINUE;
     }
     T onCompleted() throws Exception {
       return T;
   }
});

Creating a Request object

The AsynHttpClient uses the builder pattern when it is time to create Request object. The simplest way consist of:

    RequestBuilder builder = new RequestBuilder("PUT");
    Request request = builder..setUrl("http://")
     .addHeader("name", "value")
     .setBody(new File("myUpload.avi"))
     .build();
    AsyncHttpClient client = new AsyncHttpClient();
    client.execute(request, new AsyncHandler&lt;...&gt;() {
         .....
    } );

If you need to work with File, the library supports the zero copy in memory concept, e.g the File can be uploaded or downloaded without loading its associated bytes in memory, preventing out of memory errors in case you need to upload or download many large files. Although the library support the following:

    Request request = builder..setUrl("http://")
      .addHeader("name", "value")
      .setBody(myInputStream))
      .build();

it is discouraged to use InputStream as the library will need to buffer bytes in memory in order to determine the length of the stream, and instead highly recommended to either use a File or the BodyGenerator API to avoid loading unnecessary bytes in memory:

 public interface BodyGenerator {
    Body createBody() throws IOException;
}

where a Body is defined as:

public interface Body {
    long getContentLength();
    long read(ByteBuffer buffer)
      throws IOException;
    void close() throws IOException;
}

This way the library will never read unnecessary bytes in memory, which could significantly improve the performance your application.

The RequestBuilder can also be used to create per Request configuration, like setting a Proxy or request timeout:

    PerRequestConfig requestConfig = new PerRequestConfig();
    requestConfig.setRequestTimeoutInMs(5 * 1000);
    requestConfig.setProxy(new ProxyServer(...));
    Future responseFuture =
     client.prepareGet("http://").setPerRequestConfig(requestConfig)
        .execute();

Creating a Response object

The AsyncHandler is typed, e.g you can return any object from the AsyncHandler.onCompleted(). One useful object of the library is the Response object and it’s associate builder. You can incrementally create a Response object using the ResponseBuilder.accumulate() method:

MyAsyncHandler<Response> asyncHandler = new MyAsyncHanfler<Response>() {
  private final Response.ResponseBuilder builder =
          new Response.ResponseBuilder();

  public STATE onBodyPartReceived(final HttpResponseBodyPart content)
    throws Exception {
      builder.accumulate(content);
      return STATE.CONTINUE;
  }

  public STATE onStatusReceived(final HttpResponseStatus status)
     throws Exception {
      builder.accumulate(status);
      return STATE.CONTINUE;
  }

  public STATE onHeadersReceived(final HttpResponseHeaders headers)
     throws Exception {
      builder.accumulate(headers);
      return STATE.CONTINUE;
  }

  public Response onCompleted() throws Exception {
      return builder.build();
  }
}

Response response = client.prepareGet("http://sonatype.com")
     .execute(asyncHandler).get();

One thing to consider when creating a Response object is the size of the response body. By default, a Response object will accumulate all response’s bytes in memory, and that could potentially create an out of memory error. If you are planning to use the API for downloading large files, it is not recommended to accumulate bytes in memory and instead flush the bytes on disk as soon as they are available. Note that you can still use the Response object, except you don’t accumulate the response’s bytes as demonstrated below:

MyAsyncHandler<Response> asyncHandler = new MyAsyncHanfler<Response>() {
   private final Response.ResponseBuilder builder =
      new Response.ResponseBuilder();

   public STATE onBodyPartReceived(final HttpResponseBodyPart content)
     throws Exception {
       content.write(myOutputStream);
       return STATE.CONTINUE;
   }

   public STATE onStatusReceived(final HttpResponseStatus status)
     throws Exception {
       builder.accumulate(status);
       return STATE.CONTINUE;
   }

   public STATE onHeadersReceived(final HttpResponseHeaders headers)
      throws Exception {
       builder.accumulate(headers);
       return STATE.CONTINUE;
   }

   public Response onCompleted() throws Exception {
       return builder.build();
   }
}

Response response = client.prepareGet("http://sonatype.com")
   .execute(asyncHandler).get();

Note that in the above scenario invoking Response.getResponseBodyAsStream() or getResponseBody() will return an IllegalStateException because the body wasn’t accumulated by the Response object.

Configuring the AsyncHttpClient: Compression, Connection Pool, Proxy, Times out, Thread Pools, Security, etc.

You can configure the AsyncHttpClient class using the AsyncHttpClientConfig’s Builder:

    Builder builder = new AsyncHttpClientConfig.Builder();
    builder.setCompressionEnabled(true)
        .setAllowPoolingConnection(true)
        .setRequestTimesout(30000)
        .build();
    AsyncHttpClient client = new AsyncHttpClient(builder.build());

You can set the ExecutorServices as well if you don’t want to use the default, which is a cached threads pool:

    Builder builder = new AsyncHttpClientConfig.Builder();
    builder.setExecutorService(myOwnThreadPool);
    AsyncHttpClient client = new AsyncHttpClient(builder.build());

You can also configure the connection pool the library is using and implement your own polling strategy:

    Builder builder = new AsyncHttpClientConfig.Builder();
    builder.setConnectionsPool(new ConnectionsPoo<U,V>() {
          public boolean offer(U uri, V connection) {...}
          public V poll(U uri)  {...}
          public boolean removeAll(V connection)  {...}
          public boolean canCacheConnection()  {...}
          public void destroy()  {...}
     });
    AsyncHttpClient client = new AsyncHttpClient(builder.build());

It is recommended to use the default connections pool for performance reason, but you are always free to design a better one.
You can also set the SSL information, Filters, etc. Those topics will be covered inside their own section.

Configuring SSL

Configuring the library to support SSL is simple. By default you don’t have to configure anything if you don’t need to use your own certificates etc.

   AsyncHttpClient client = new AsyncHttpClient();
   Response response = client.prepareGet(("https://sonatype.com")
     .execute().get();

The library will detect it’s an SSL request and appropriately locate the key store, trust store etc. If you need to configure those objects, all you need to do is to create an SSLContext and set it using the AsyncHttpClient’s Builder as showed below:

  InputStream keyStoreStream = ....
  char[] keyStorePassword = "changeit".toCharArray();
  KeyStore ks = KeyStore.getInstance("JKS");
  ks.load(keyStoreStream, keyStorePassword);

  char[] certificatePassword = "changeit".toCharArray();
  KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
  kmf.init(ks, certificatePassword);

  KeyManager[] keyManagers = kmf.getKeyManagers();
  TrustManager[] trustManagers = new TrustManager[]{DUMMY_TRUST_MANAGER};
  SecureRandom secureRandom = new SecureRandom();

  SSLContext sslContext = SSLContext.getInstance("TLS");
  sslContext.init(keyManagers, trustManagers, secureRandom);
  Builder builder = new AsyncHttpClientConfig.Builder();
  builder.setSSLContext(myOwnThreadPool);
  AsyncHttpClient client = new AsyncHttpClient(builder.build());

Using Filters

The library supports three types of Filter who can intercept, transform, decorate and replay transactions: Request, Response and IOException.

Request Filter

Request Filters are useful if you need to manipulate the Request or AsyncHandler object before the request is made. As an example, you can throttle requests using the following RequestFilter implementation:

public class ThrottleRequestFilter implements RequestFilter {
    private final int maxConnections;
    private final Semaphore available;
    private final int maxWait;

    public ThrottleRequestFilter(int maxConnections) {
        this.maxConnections = maxConnections;
        this.maxWait = Integer.MAX_VALUE;
        available = new Semaphore(maxConnections, true);
    }

    public ThrottleRequestFilter(int maxConnections, int maxWait) {
        this.maxConnections = maxConnections;
        this.maxWait = maxWait;
        available = new Semaphore(maxConnections, true);
    }

    public FilterContext filter(FilterContext ctx) throws FilterException {

        try {
            if (!available.tryAcquire(maxWait, TimeUnit.MILLISECONDS)) {
                throw new FilterException(
                    String.format("No slot available for Request %s "
                            "with AsyncHandler %s",
                            ctx.getRequest(), ctx.getAsyncHandler()));
            };
        } catch (InterruptedException e) {
            throw new FilterException(
                    String.format("Interrupted Request %s" +
                         "with AsyncHandler %s",
                            ctx.getRequest(), ctx.getAsyncHandler()));
        }

        return new FilterContext(
             new AsyncHandlerWrapper(ctx.getAsyncHandler()), ctx.getRequest());
    }

    private class AsyncHandlerWrapper implements AsyncHandler<T> {

        private final AsyncHandler asyncHandler;

        public AsyncHandlerWrapper(AsyncHandler asyncHandler) {
            this.asyncHandler = asyncHandler;
        }

        public void onThrowable(Throwable t) {
            asyncHandler.onThrowable(t);
        }

        public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart)
                throws Exception {
            return asyncHandler.onBodyPartReceived(bodyPart);
        }

        public STATE onStatusReceived(HttpResponseStatus responseStatus)
              throws Exception {
            return asyncHandler.onStatusReceived(responseStatus);
        }

        public STATE onHeadersReceived(HttpResponseHeaders headers)
              throws Exception {
            return asyncHandler.onHeadersReceived(headers);
        }

        public T onCompleted() throws Exception {
            available.release();
            return asyncHandler.onCompleted();
        }
    }

In the above, we decorate the original AsyncHandler and use semaphore to throttle requests. To add RequestFilter, all you need to do is to configure it on the AsyncHttpClientConfig:

 AsyncHttpClientConfig.Builder b =
                 new AsyncHttpClientConfig.Builder();
 b.addRequestFilter(new ThrottleRequestFilter(100));
 AsyncHttpClient c = new AsyncHttpClient(b.build());

Response Filter

Like with Request, you can also filter the Response’s bytes before an AsyncHandler gets called. Response Filters are always invoked before the library executes the logic for authentication, proxy challenging, redirection etc. That means an application can takes control of those operations at any moment using a Response Filter. As an example, the following Response Filter redirect request from google.ca to google.com in case .ca is not responding:

 AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder();
 b.addResponseFilter(new ResponseFilter() {

   public FilterContext filter(FilterContext ctx) throws FilterException {

      if ( ctx.getResponseStatus().getStatusCode() == 503 ) {
          return new FilterContext.FilterContextBuilder(ctx)
                       .request(new RequestBuilder("GET")
                       .setUrl("http://google.com").build())
                       .build();
                 }
            }});

 AsyncHttpClient c = new AsyncHttpClient(b.build());

IOException Filter

The AsyncHttpClient library support IOExceptionFilter that can be used to replay a request in case server a server goes down or unresponsive, a network outage occurs, or nay kind of I/O abnormal situation. In those cases, the library will catch the IOException and delegate the IOException handling to the Filter. As an example, the following filter will resume an interrupted download instead of restarting downloading the file from the beginning:

    AsyncHttpClient c = new AsyncHttpClient(
          new AsyncHttpClientConfig.Builder()
            .addIOExceptionFilter(
              new ResumableIOExceptionFilter()).build());

    Response r = c.prepareGet("http://host:port/LargeFile.avi")
       .execute(new AsyncHandler(){...}).get();

The IOExceptionFilter is defined as

public class ResumableIOExceptionFilter implements IOExceptionFilter {
    public FilterContext filter(FilterContext ctx) throws FilterException {
        if (ctx.getIOException() != null ) {
            Request request = new RequestBuilder(ctx.getRequest())
                .setRangeOffset(file.length());
            return new FilterContext.FilterContextBuilder(ctx)
               .request(request)
               .replayRequest(true)
               .build();
        }
        return ctx;
    }
}

In the above we just catch any IOException and replay the request using the Range header to tell the remote server to restart sending bytes at that position. This way we don’t need to re download the entire file.

Uploading file: Progress Listener

When uploading bytes, an application might need to take some action depending on where the upload status is. The AsyncHttpClient library support a special AsyncHandler called ProgressAsyncHandler that can be used to track the upload operation:

public interface ProgressAsyncHandler<T> extends AsyncHandler<T> {
    STATE onHeaderWriteCompleted();
    STATE onContentWriteCompleted();
    STATE onContentWriteProgress(long amount, long current, long total);
}

The methods are called in the following order:

  • onHeaderWriteCompleted: invoked when the headers has been flushed to the remote server
  • onContentWriteProgress: as soon as some response’s body bytes are written. Might be invoked many times.
  • onContentWriteCompleted: invoked when the response has been sent or aborted.

Like with AsyncHandler, you can always always abort the processing at any moment in the upload process

Configuring Authentication: BASIC, DIGEST or NTLM

Configuring authentication with AsyncHttpClient is simple. You can configure it at the Request level using the RealmBuilder:

    AsyncHttpClient client = new AsyncHttpClient();
    Realm realm = new Realm.RealmBuilder()
               .setPrincipal(user)
               .setPassword(admin)
               .setUsePreemptiveAuth(true)
               .setScheme(AuthScheme.BASIC)
               .build();
    client.prepareGet("http://...").setRealm(realm).execute();

You can also set the realm at the AsyncHttpClientConfig level:

    Builder builder = new AsyncHttpClientConfig.Builder();
    Realm realm = new Realm.RealmBuilder()
               .setPrincipal(user)
               .setPassword(admin)
               .setUsePreemptiveAuth(true)
               .setScheme(AuthScheme.BASIC)
               .build();
    builder.setRealm(realm).build();
    AsyncHttpClient client = new AsyncHttpClient(builder.build());

The authentication type supported are BASIC, DIGEST and NTLM. You can also customize your own authentication mechanism by using the Response Filter.

Configuring a Proxy

The AsyncHttpClient library supports proxy, proxy authentication and proxy tunneling. Just need to create a ProxyServer instance:

AsyncHttpClient client = new AsyncHttpClient();
        Future<Response> f = client
                .prepareGet("http://....)
                .setProxyServer(new ProxyServer("127.0.0.1", 8080))
                .execute();

If you need to use an SSL tunnel, all you need to do is:

 ProxyServer ps =
          new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", 8080);
 AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
 RequestBuilder rb = new RequestBuilder("GET")
             .setProxyServer(ps)
             .setUrl("https://twitpic.com:443");

  Future responseFuture = asyncHttpClient
               .executeRequest(rb.build(), new AsyncCompletionHandlerBase() {
    @Override
    public void onThrowable(Throwable t) {}

    @Override
    public Response onCompleted(Response response) throws Exception {
      return response;
    }});

   Response r = responseFuture.get();

You can also set the authentication token on the ProxyServer instance

 ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS,
                                  "127.0.0.1",
                                  8080,
                                  "admin",
                                  "password");
  AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
  RequestBuilder rb = new RequestBuilder("GET")
        .setProxyServer(ps).setUrl("https://twitpic.com:443");

  Future responseFuture = asyncHttpClient
               .executeRequest(rb.build(), new AsyncCompletionHandlerBase() {
    @Override
    public void onThrowable(Throwable t) {}

    @Override
    public Response onCompleted(Response response) throws Exception {
      return response;
    }});

  Response r = responseFuture.get();

You can also set the ProxyServer at the AsyncHttpClientConfig level. In that case, all request will share the same proxy information.

Switching Provider

By default, the AsyncHttpClient is using the powerful Netty’s framework as the HTTP processor. There might be environment where you can’t use Netty. Fortunately, the AsyncHttpClient library supports two other http runtime: the JDKAsyncHttpProvider, which build around the  URLConnection, and ApacheAsyncHttpProvider which build on top of the Apache HttpClient. To change provider, all you need to do is:

 AsyncHttpClient client = new AsyncHttpClient(
    new ApacheAsyncHttpProvider(new AsyncHttpClientConfig.Builder().build()));

Same for the JDK:

 AsyncHttpclient client = new AsyncHttpClient(
    new JDKAsyncHttpProvider(new AsyncHttpClientConfig.Builder().build()));

Also every AsyncHttpClientProvider can be configured with their native functionality. As an example, you can switch the NettyAsyncHttpProvider to use blocking I/O instead of NIO:

 NettyAsyncHttpProviderConfig config = new NettyAsyncHttpProviderConfig();
 config.setProperty(NettyAsyncHttpProviderConfig.USE_BLOCKING_IO, "true");

 AsyncHttpClientConfig c = 
    new AsyncHttpClientConfig()
      .setAsyncHttpClientProviderConfig(config).build();

 AsyncHttpClient client = new AsyncHttpClient(
    new NettyAsyncHttpProvider(config));

Using the WebDav protocol

The AsyncHttpClient has build in support for the WebDav protocol. The API can be used the same way normal HTTP request are made, and everything discussed in this blog works with WebDAV as well:

 AsyncHttpClient c = new AsyncHttpClient();
 Request mkcolRequest = new RequestBuilder("MKCOL")
      .setUrl("http://host:port/folder1").build();
 Response response = c.executeRequest(mkcolRequest).get();

or

  AsyncHttpClient c = new AsyncHttpClient();
  Request propFindRequest = new RequestBuilder("PROPFIND")
     .setUrl("http://host:port).build();
  Response response = c.executeRequest(propFindRequest, new AsyncHandler(){...}).get();

Using the ready to go AsyncHandler

The framework is shipping with ready to go AsyncHandler:

  • The AsyncCompletionHandlerBase is an implementation of the AsyncCompletionHandler&lt;Response&gt;. That means Future.get() will always return a instance of Response.
  • The WebDavCompletionHandlerBase can be used to parse the XML response of a PROPFIND request. As an example, response.getStatusCode() will return 200 instead of 207 if you don’t use that AsyncHandler.
  • The ResumableAsyncHandler can be used to survive interrupted download, either produced by an IOException or because the JVM went down. This handler will be explained in details in the next blog about AsyncHttpClient.

What’s Next

In the next part of this article (here)  I will explain more complex operations that can be done with the AsyncHttpClient library like:

  • Configuring resumable download using the ResumableAsyncHandler, ResumableProcessor and ResumableListener
  • How to efficiently uses the TransferListener
  • How to efficiently uses the zero bytes copy mechanism
  • How to configure the AsyncHttpClient for performance
  • How to manage 100-Continue server response
  • OAuth build in support
  • Supporting the WebSocket protocol.

For any questions you can use our Google Group, irc.freenode.net #asynchttpclient or use Twitter to reach me! You can checkout the code on Github, download the jars from Maven Central or use Maven:

<dependency>
    <groupId>com.ning</groupId>
    <artifactId>async-http-client</artifactId>
    <version>1.4.1</version>
</dependency>

About these ads
  1. Usama Ahmed
    January 4, 2011 at 11:21 am | #1

    AsyncHttpClient. Is this api better than apache http client coz i have been poking my head with apache http client and it sucks.

    • January 4, 2011 at 3:13 pm | #2

      Just take a look at this blog or checkout the code and look at the unit test. API is MUCH cleaner than Apache IMO :-)

  2. bflueras
    February 3, 2011 at 4:04 pm | #3

    @jfarcand since you contribute on both asynchttp and atmosphere frameworks, can you post a pros/cons comparison for the 2? I’m a little bit confused whether to use one or another framework and an expert opinion might help! [Of course it depends a lot of what I'm after, but anyway...]

    • February 3, 2011 at 4:11 pm | #4

      @bflueras AHC is a java client that can be used with Atmosphere….Atmosphere is a server side framework for doing websocket and comet application. Atmosphere has a javascript client, but you can also use Atmosphere with AHC…if you checkout Atmosphere, all our unit tests rely on AHC for testing Atmosphere. So you have plenty on example there oh how AHC can be used with long polling…..Hope that help.

      – Jeanfrancois

  3. Cem Koc
    February 17, 2011 at 9:25 am | #5

    @jfarcand is there any road map to integrate to other projects such as Jersey Client, Spring etc..

    • February 17, 2011 at 1:28 pm | #6

      @Cem A company is supposed to contribute the Jersey Client but I don’t know when….For Sprint I guess someone will have to do it :-)
      A+
      — Jeanfrancois

  4. emanuele
    February 25, 2011 at 8:49 pm | #7

    @jfarcand thanks for the project. Is there any howto to import it in eclipse?

  5. June 9, 2011 at 12:10 pm | #8

    Great work.
    A question in the
    Configuring the AsyncHttpClient: Compression, Connection Pool, Proxy, Times out, Thread Pools, Security, etc. example :

    Builder builder = new AsyncHttpClientConfig.Builder();
    builder.setCompressionEnabled(true)
    .setAllowPoolingConnection(true)
    .setRequestTimesout(30000)
    .build(); //I Think this build is not required.
    AsyncHttpClient client = new AsyncHttpClient(builder.build());

  6. Sainath
    July 6, 2011 at 8:07 am | #10

    Hey

    I am using the AsyncHttpClient with a performance tool , to make the calls async.I collect the response times through the async handler you have provided.

    I see that I cannot fire more than 500 req/sec and when I try to fire more requests/sec the latency increases and the client chokes.

    The client I am using is RHEL box
    hwconfig :

    Summary: Dell DCS CS24-SC, 2 x Xeon L5420 2.50GHz, 15.7GB, 1 x 250GB SATA
    System: Dell DCS CS24-SC, Config 3
    Processors: 2 x Xeon L5420 2.50GHz (8 cores) – Harpertown C0, 64-bit, quad-core, 45nm, L2: 12MB
    Memory: 15.7GB

    My AsyncHttpClientConfig currently configured as

    builder.setMaximumConnectionsPerHost(100);
    builder.setMaximumConnectionsTotal(100);

    Also , To what number can this connections be increased without impacting the performance of the client?

    Thanks
    Sainath

  7. July 13, 2012 at 5:03 am | #11

    @jfarcand AsyncHttpClient supports resumable downloads. Does it support resumable uploads as well? Say a file upload is interrupted at “n” bytes, is it possible to resume that upload without starting all over?

  1. December 24, 2010 at 12:02 pm | #1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 50 other followers

%d bloggers like this: