Jersey client upload progress

一个人想着一个人 提交于 2019-11-30 09:17:33

it should be enough to provide you own MessageBodyWriter for java.io.File which fires some events or notifies some listeners as progress changes

@Provider()
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public class MyFileProvider implements MessageBodyWriter<File> {

    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return File.class.isAssignableFrom(type);
    }

    public void writeTo(File t, Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
        InputStream in = new FileInputStream(t);
        try {
            int read;
            final byte[] data = new byte[ReaderWriter.BUFFER_SIZE];
            while ((read = in.read(data)) != -1) {
                entityStream.write(data, 0, read);
                // fire some event as progress changes
            }
        } finally {
            in.close();
        }
    }

    @Override
    public long getSize(File t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return t.length();
    }
}

and to make your client application uses this new provider simply:

ClientConfig config = new DefaultClientConfig();
config.getClasses().add(MyFileProvider.class);

or

ClientConfig config = new DefaultClientConfig();
MyFileProvider myProvider = new MyFileProvider ();
cc.getSingletons().add(myProvider);

You would have to also include some algorithm to recognize which file is transfered when receiving progress events.

Edited:

I just found that by default HTTPUrlConnection uses buffering. And to disable buffering you could do couple of things:

  1. httpUrlConnection.setChunkedStreamingMode(chunklength) - disables buffering and uses chunked transfer encoding to send request
  2. httpUrlConnection.setFixedLengthStreamingMode(contentLength) - disables buffering and but ads some constraints to streaming: exact number of bytes must be sent

So I suggest the final solution to your problem uses 1st option and would look like this:

ClientConfig config = new DefaultClientConfig();
config.getClasses().add(MyFileProvider.class);
URLConnectionClientHandler clientHandler = new URLConnectionClientHandler(new HttpURLConnectionFactory() {
     @Override
     public HttpURLConnection getHttpURLConnection(URL url) throws IOException {
           HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setChunkedStreamingMode(1024);
                return connection;
            }
});
Client client = new Client(clientHandler, config);

In Jersey 2.X, i've used a WriterInterceptor to wrap the output stream with a subclass of Apache Commons IO CountingOutputStream that tracks the writing and notify my upload progress code (not shown).

public class UploadMonitorInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {

        // the original outputstream jersey writes with
        final OutputStream os = context.getOutputStream();

        // you can use Jersey's target/builder properties or 
        // special headers to set identifiers of the source of the stream
        // and other info needed for progress monitoring
        String id = (String) context.getProperty("id");
        long fileSize = (long) context.getProperty("fileSize");

        // subclass of counting stream which will notify my progress
        // indicators.
        context.setOutputStream(new MyCountingOutputStream(os, id, fileSize));

        // proceed with any other interceptors
        context.proceed();
    }

}

I then registered this interceptor with the client, or with specific targets where you want to use the interceptor.

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!