Jersey client upload progress

Viewed 4079

I have a jersey client that need to upload a file big enough to require a progress bar.
The problem is that, for an upload that requires some minutes, i see the bytes transfered to go to 100% as soon as the application has started. Then it takes some minutes to print the "on finished" string.
It is as if the bytes were sent to a buffer, and i was reading the transfert-to-the buffer speed instead of the actual upload speed. This makes the progress bar useless.

This is the very simple code:

ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource resource = client.resource("www.myrestserver.com/uploads");
WebResource.Builder builder = resource.type(MediaType.MULTIPART_FORM_DATA_TYPE);

FormDataMultiPart multiPart = new FormDataMultiPart();
FileDataBodyPart fdbp = new FileDataBodyPart("data.zip", new File("data.zip"));
BodyPart bp = multiPart.bodyPart(fdbp);
String response = builder.post(String.class, multiPart);

To get progress state i've added a ContainerListener filter, obviouslt before calling builder.post:

final ContainerListener containerListener = new ContainerListener() {

        @Override
        public void onSent(long delta, long bytes) {
            System.out.println(delta + " : " + long);
        }

        @Override
        public void onFinish() {
            super.onFinish();
            System.out.println("on finish");
        }

    };

    OnStartConnectionListener connectionListenerFactory = new OnStartConnectionListener() {
        @Override
        public ContainerListener onStart(ClientRequest cr) {
            return containerListener;
        }

    };

    resource.addFilter(new ConnectionListenerFilter(connectionListenerFactory));
3 Answers

I have successfully used David's answer. However, I would like to extend on it:

The following aroundWriteTo implementation of my WriterInterceptor shows how a panel (or similar) can also be passed to the CountingOutputStream:

@Override
public void aroundWriteTo(WriterInterceptorContext context)
    throws IOException, WebApplicationException
{
  final OutputStream outputStream = context.getOutputStream();

  long fileSize = (long) context.getProperty(FILE_SIZE_PROPERTY_NAME);

  context.setOutputStream(new ProgressFileUploadStream(outputStream, fileSize,
      (progressPanel) context
          .getProperty(PROGRESS_PANEL_PROPERTY_NAME)));

  context.proceed();
}

The afterWrite of the CountingOutputStream can then set the progress:

@Override
protected void afterWrite(int n)
{
  double percent = ((double) getByteCount() / fileSize);
  progressPanel.setValue((int) (percent * 100));
}

The properties can be set on the Invocation.Builder object:

Invocation.Builder invocationBuilder = webTarget.request();
invocationBuilder.property(
    UploadMonitorInterceptor.FILE_SIZE_PROPERTY_NAME, newFile.length());
invocationBuilder.property(
    UploadMonitorInterceptor.PROGRESS_PANEL_PROPERTY_NAME,      
    progressPanel);

Perhaps the most important addition to David's answer and the reason why I decided to post my own is the following code:

client.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024);
client.property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED");

The client object is the the javax.ws.rs.client.Client.

It is essential to disable buffering also with the WriterInterceptor approach. Above code is a straightforward way to do this with Jersey 2.x

Related