2012-07-20 18 views
7

Tengo un cliente de jersey que necesita cargar un archivo lo suficientemente grande como para requerir una barra de progreso.
El problema es que, para una carga que requiere algunos minutos, veo los bytes transferidos para ir al 100% tan pronto como la aplicación ha comenzado. Luego, toma algunos minutos imprimir la secuencia "en acabado".
Es como si los bytes se hubieran enviado a un búfer, y estaba leyendo la velocidad de transferencia al búfer en lugar de la velocidad de carga real. Esto hace que la barra de progreso sea inútil.progreso de carga del cliente de Jersey

El código muy simple:

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

Para conseguir el progreso del estado He añadido un filtro ContainerListener, obviouslt antes de llamar 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)); 

Respuesta

3

debería ser suficiente para proporcionar es dueño de MessageBodyWriter para java.io.File que activa algunos eventos o notifica a algunos oyentes a medida que el progreso cambia

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

y para hacer que la aplicación cliente utiliza este nuevo proveedor simplemente:

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

o

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

Usted tendría que incluir también algún algoritmo para reconocer qué archivo se transfiere al recibir eventos de progreso.

Editado:

acabo de encontrar que por defecto HttpURLConnection utiliza el almacenamiento en búfer. Y para desactivar almacenamiento en búfer que podría hacer dos cosas:

  1. httpUrlConnection.setChunkedStreamingMode (chunklength) - deshabilita el almacenamiento en búfer y utiliza codificación de transferencia en enviar la petición
  2. httpUrlConnection.setFixedLengthStreamingMode (contentLength) - deshabilita el almacenamiento en búfer y anuncios, pero algunos limitaciones para el streaming: número exacto de bytes se debe enviar

Así que sugieren la solución final a su problema utiliza primera opción y se vería así:

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

gracias Tomasz, esta respuesta es muy buena. El hecho de que haya proporcionado dos formas de configurar al cliente es realmente admirable y explicativo. desafortunadamente, el problema persiste. Acabo de poner un System.out.println (...) después de entityStream.write, pero el resultado es que escribo archivos grandes (> 10MB) en una fracción de segundo, y luego se congela mientras ocurre la carga "real".El hecho de que esto ocurra también con esta solución significa que el problema está en otra parte. Por su respuesta, no puedo aceptarlo pero puedo comenzar otra pregunta específica en la que me complacerá marcarla como correcta. :-) – AgostinoX

+0

Intenté también agregar un entityStream.flush(); after entityStream.write (...) para forzar una escritura real en socket en lugar de simplemente escribir en el búfer. Mismo resultado :-( – AgostinoX

+0

ok, gran respuesta, funciona de ambas maneras, es decir, con oyentes y con un proveedor de archivos personalizado. Tal vez debería enfatizarse que la solución es la segunda parte, tal vez moviéndola a la cima. El proveedor es interesante como una alternativa para los oyentes. Además, ayuda a aclarar la arquitectura jersey, por lo que la mantendría, pero no como una respuesta directa a la pregunta. – AgostinoX

3

En Jersey 2.X, he utilizado un WriterInterceptor para envolver el flujo de salida con una subclase de Apache Commons IO CountingOutputStream que rastrea la escritura y notificar mi código de progreso de carga (no se muestra).

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

} 

Luego registré este interceptor con el cliente o con objetivos específicos en los que desea utilizar el interceptor.

Cuestiones relacionadas