2012-07-20 32 views
7

Tôi có một ứng dụng khách cần tải lên tệp đủ lớn để yêu cầu thanh tiến trình.
Vấn đề là, đối với tải lên yêu cầu một vài phút, tôi thấy các byte được chuyển đến 100% ngay sau khi ứng dụng đã bắt đầu. Sau đó, phải mất vài phút để in chuỗi "đã hoàn tất".
Dường như các byte được gửi đến bộ đệm, và tôi đã đọc tốc độ bộ đệm chuyển đến tốc độ tải thay vì tốc độ tải lên thực tế. Điều này làm cho thanh tiến trình vô ích.Tiến trình tải lên của khách hàng Jersey

Đây là mã rất đơn giản:

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

Để có được nhà nước tiến bộ tôi đã thêm một bộ lọc ContainerListener, obviouslt trước khi gọi 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)); 

Trả lời

3

nó phải là đủ để cung cấp bạn sở hữu MessageBodyWriter cho java.io.File để kích hoạt một số sự kiện hoặc thông báo cho một số người nghe khi tiến trình thay đổi

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

và để làm cho ứng dụng khách hàng của bạn sử dụng nhà cung cấp mới này chỉ đơn giản là:

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

hoặc

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

Bạn sẽ phải cũng bao gồm một số thuật toán để nhận ra các tập tin được chuyển giao khi nhận được sự kiện tiến bộ.

được sửa đổi:

Tôi chỉ thấy rằng theo mặc định HTTPUrlConnection sử dụng đệm. Và để vô hiệu hóa bộ đệm bạn có thể làm vài điều:

  1. httpUrlConnection.setChunkedStreamingMode (chunklength) - vô hiệu hóa đệm và sử dụng mã hóa chuyển chunked để gửi yêu cầu
  2. httpUrlConnection.setFixedLengthStreamingMode (ContentLength) - vô hiệu hóa đệm và quảng cáo nhưng một số hạn chế để trực tiếp: chính xác số lượng byte phải được gửi

Vì vậy, tôi đề nghị các giải pháp cuối cùng cho vấn đề của bạn sử dụng tùy chọn 1 và sẽ trông như thế này:

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

cảm ơn Tomasz, câu trả lời này rất tốt. Thực tế là bạn đã cung cấp hai cách để cấu hình máy khách thực sự đáng ngưỡng mộ và giải thích. không may, vấn đề vẫn tồn tại. Tôi chỉ cần đặt một System.out.println (...) sau entityStream.write, nhưng kết quả là tôi viết các tệp lớn (> 10MB) trong một phần nhỏ của giây, và sau đó nó đóng băng trong khi tải lên "thực" sẽ xảy ra.Thực tế cũng xảy ra với giải pháp này có nghĩa là vấn đề là ở nơi khác. Đối với câu trả lời của bạn, tôi không thể chấp nhận nó nhưng tôi có thể bắt đầu một câu hỏi cụ thể khác mà tôi sẽ rất vui khi đánh dấu nó là chính xác. :-) – AgostinoX

+0

Tôi cũng cố gắng thêm một entityStream.flush(); sau entityStream.write (...) để ép một văn bản thực tế vào socket thay vì chỉ viết trên bộ đệm. Cùng một kết quả :-( – AgostinoX

+0

ok, câu trả lời tuyệt vời, nó hoạt động theo cả hai cách, đó là với người nghe và với nhà cung cấp tệp tùy chỉnh. Có lẽ cần nhấn mạnh rằng giải pháp là phần thứ hai, có thể di chuyển nó ở trên cùng. Ngoài ra, nó giúp làm rõ kiến ​​trúc áo, vì vậy tôi sẽ giữ nó, nhưng không phải là câu trả lời trực tiếp cho câu hỏi. – AgostinoX

3

Trong Jersey 2.X, tôi đã sử dụng WriterInterceptor để bọc luồng đầu ra với một lớp con của Apache Commons IO CountingOutputStream theo dõi văn bản và thông báo mã tiến trình tải lên của tôi (không được hiển thị).

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

} 

Sau đó, tôi đã đăng ký máy đánh chặn này với máy khách hoặc với các mục tiêu cụ thể nơi bạn muốn sử dụng bộ chặn.

Các vấn đề liên quan