【问题标题】:Jersey client upload progress泽西客户端上传进度
【发布时间】:2012-07-20 01:14:02
【问题描述】:

我有一个 jersey 客户端,它需要上传一个足够大的文件,需要一个进度条。
问题是,对于需要几分钟的上传,我看到传输的字节数达到 100%应用程序启动后。然后打印“on finished”字符串需要几分钟时间。
就好像字节被发送到缓冲区一样,我正在读取传输到缓冲区的速度而不是实际的上传速度。这使得进度条无用。

这是非常简单的代码:

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

为了获得进度状态,我添加了一个 ContainerListener 过滤器,显然是在调用 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));

【问题讨论】:

    标签: java upload jersey


    【解决方案1】:

    为 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) - 禁用缓冲,但对流式传输有一些限制:必须发送确切的字节数

    所以我建议您的问题的最终解决方案使用第一个选项,看起来像这样:

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

    【讨论】:

    • 感谢 Tomasz,这个答案非常好。您提供了两种配置客户端的方法,这一事实确实令人钦佩和解释。不幸的是,问题仍然存在。我只是在 entityStream.write 之后放了一个 System.out.println(...),但结果是我在几分之一秒内写入了大文件 (>10MB),然后在“真实”上传发生时冻结。这种解决方案也会发生的事实意味着问题出在其他地方。对于您的回答,我不能接受,但我可以开始另一个具体问题,我很乐意将其标记为正确。 :-)
    • 我也尝试添加一个 entityStream.flush();在 entityStream.write (...) 之后,以强制实际写入套接字,而不仅仅是写入缓冲区。结果相同:-(
    • 好的,很好的答案,它有效。在这两种方式中,即与侦听器和自定义文件提供程序一起使用。也许应该强调解决方案是第二部分,也许将它移到顶部。 File Provider 作为侦听器的替代品很有趣。此外,它有助于澄清球衣架构,所以我会保留它,但不能直接回答这个问题。
    【解决方案2】:

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

    然后我向客户端注册了此拦截器,或者向您要使用拦截器的特定目标注册。

    【讨论】:

      【解决方案3】:

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

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

      CountingOutputStreamafterWrite 然后可以设置进度:

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

      也许对大卫的回答最重要的补充以及我决定发布自己的原因是以下代码:

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

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

      使用WriterInterceptor 方法也必须禁用缓冲。上面的代码是使用 Jersey 2.x 执行此操作的简单方法

      【讨论】:

        猜你喜欢
        • 2015-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-28
        • 2015-01-12
        • 2013-08-02
        • 1970-01-01
        • 2017-01-26
        相关资源
        最近更新 更多