泽西客户端上传进度

时间:2012-07-20 18:13:12

标签: java upload jersey

我有一个球衣客户端,需要上传足够大的文件才能要求进度条 问题是,对于需要几分钟的上传,我看到一旦应用程序启动就转移到100%的字节。然后打印“on finished”字符串需要几分钟。
就好像将字节发送到缓冲区,我正在读取transfert到缓冲区的速度,而不是实际上传速度。这使得进度条无用。

这是非常简单的代码:

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

为了获得进度状态,我在调用builder.post之前添加了一个ContainerListener过滤器,其中显示了:

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 个答案:

答案 0 :(得分:4)

在Jersey 2.X中,我使用 WriterInterceptor 将输出流包装为Apache Commons IO CountingOutputStream的子类,该子类跟踪写入并通知我的上传进度代码(未显示) )。

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

}

然后我在客户端注册了这个拦截器,或者在你想要使用拦截器的特定目标上注册了这个拦截器。

答案 1 :(得分:3)

它应该足以为java.io.File提供自己的MessageBodyWriter,它会触发某些事件或通知某些侦听器进度发生变化

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

并使您的客户端应用程序简单地使用此新提供程序:

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

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

您还必须包含一些算法来识别接收进度事件时传输的文件。

<强>编辑:

我刚发现HTTPUrlConnection默认使用缓冲。要禁用缓冲,你可以做几件事:

  1. httpUrlConnection.setChunkedStreamingMode(chunklength) - 禁用缓冲并使用分块传输编码发送请求
  2. httpUrlConnection.setFixedLengthStreamingMode(contentLength) - 禁用缓冲,但会对流式传输一些约束:必须发送确切的字节数
  3. 所以我建议你的问题的最终解决方案使用第一个选项,看起来像这样:

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

答案 2 :(得分:1)

我已经成功使用了大卫的答案。但是,我想对此进行扩展:

我的aroundWriteTo的以下WriterInterceptor实现显示了如何将面板(或类似面板)传递给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();
}

然后afterWrite中的CountingOutputStream可以设置进度:

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

可以在Invocation.Builder对象上设置属性:

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

也许是David回答中最重要的补充,而我决定发布自己的原因可能是以下代码:

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

client对象是javax.ws.rs.client.Client

使用WriterInterceptor方法也必须禁用缓冲。上面的代码是使用Jersey 2.x做到这一点的直接方法