2012-07-20 28 views
7

Ho un client jersey che deve caricare un file abbastanza grande da richiedere una barra di avanzamento.
Il problema è che, per un caricamento che richiede alcuni minuti, vedo i byte trasferiti per andare al 100% non appena l'applicazione è stata avviata. Quindi sono necessari alcuni minuti per stampare la stringa "on finished".
È come se i byte fossero stati inviati a un buffer e stavo leggendo la velocità del transfert-to-the invece della velocità di upload effettiva. Ciò rende inutile il progresso.Jersey upload progress client

Questo è il codice molto semplice:

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

Per ottenere lo stato di avanzamento Ho aggiunto un filtro ContainerListener, obviouslt prima builder.post chiamare:

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

risposta

3

dovrebbe essere sufficiente a fornire possiedi MessageBodyWriter per java.io.File che attiva alcuni eventi o notifica alcuni listener come modifiche di avanzamento

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

e per rendere l'applicazione client utilizza questo nuovo fornitore semplicemente:

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

o

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

Si dovrebbe includere anche qualche algoritmo per riconoscere quale file viene trasferito durante la ricezione di eventi progress.

Modificato:

ho appena scoperto che di default utilizza HttpURLConnection buffering. E per disabilitare il buffering si poteva fare paio di cose:

  1. httpUrlConnection.setChunkedStreamingMode (chunklength) - disabilita il buffering e utilizza la codifica di trasferimento Chunked di inviare richiesta
  2. httpUrlConnection.setFixedLengthStreamingMode (contentLength) - disabilita buffering e, ma alcuni annunci vincoli per lo streaming: il numero esatto di byte deve essere inviato

Così mi suggeriscono la soluzione finale al problema utilizza prima opzione e sarebbe simile a questa:

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

grazie Tomasz, questa risposta è molto buona. Il fatto che tu abbia fornito due modi per configurare il client è davvero ammirevole e esplicativo. sfortunatamente, il problema persiste. Ho appena messo un System.out.println (...) dopo entityStream.write, ma il risultato è che scrivo file grandi (> 10 MB) in una frazione di secondo, e poi si blocca mentre avviene il caricamento "reale".Il fatto che avvenga anche con questa soluzione significa che il problema è altrove. Per la tua risposta, non posso accettarlo ma posso iniziare un'altra domanda specifica in cui sarò lieto di contrassegnarlo come corretto. :-) – AgostinoX

+0

Ho provato anche ad aggiungere un'entitàStream.flush(); after entityStream.write (...) per forzare una scrittura effettiva in socket invece di limitarsi a scrivere sul buffer. Stesso risultato :-( – AgostinoX

+0

ok, ottima risposta, funziona in entrambi i modi, cioè con gli ascoltatori e con il fornitore di file personalizzato.Probabilmente si dovrebbe sottolineare che la soluzione è la seconda parte, forse spostandola in alto. Il fornitore è interessante come alternativa agli ascoltatori, inoltre aiuta a chiarire l'architettura della jersey, quindi la terrei, ma non come una risposta diretta alla domanda. – AgostinoX

3

In Jersey 2.X, ho utilizzato un WriterInterceptor per avvolgere il flusso di output con una sottoclasse di Apache Commons IO CountingOutputStream che tiene traccia della scrittura e notifica il mio codice di avanzamento caricamento (non mostrato).

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

} 

Ho poi registrati quest'interceptor con il cliente, o con obiettivi specifici in cui si desidera utilizzare l'intercettore.