2013-06-09 15 views
5

Próbuję utworzyć żądanie przesyłania strumieniowego HTTP "full-duplex" przy użyciu Apache HTTPClient.Apache HTTPClient Streaming żądanie HTTP POST?

W mojej pierwszej próbie, próbowałem za pomocą następującego kodu żądanie:

URL url=new URL(/* code goes here */); 

HttpPost request=new HttpPost(url.toString()); 

request.addHeader("Connection", "close"); 

PipedOutputStream requestOutput=new PipedOutputStream(); 
PipedInputStream requestInput=new PipedInputStream(requestOutput, DEFAULT_PIPE_SIZE); 
ContentType requestContentType=getContentType(); 
InputStreamEntity requestEntity=new InputStreamEntity(requestInput, -1, requestContentType); 
request.setEntity(requestEntity); 

HttpEntity responseEntity=null; 
HttpResponse response=getHttpClient().execute(request); // <-- Hanging here 
try { 
    if(response.getStatusLine().getStatusCode() != 200) 
     throw new IOException("Unexpected status code: "+response.getStatusLine().getStatusCode()); 

    responseEntity = response.getEntity(); 
} 
finally { 
    if(responseEntity == null) 
     request.abort(); 
} 

InputStream responseInput=responseEntity.getContent(); 
ContentType responseContentType; 
if(responseEntity.getContentType() != null) 
    responseContentType = ContentType.parse(responseEntity.getContentType().getValue()); 
else 
    responseContentType = DEFAULT_CONTENT_TYPE; 

Reader responseStream=decode(responseInput, responseContentType); 
Writer requestStream=encode(requestOutput, getContentType()); 

Żądanie wisi na linii wskazanej powyżej. Wygląda na to, że kod próbuje wysłać całą prośbę, zanim otrzyma odpowiedź. Z perspektywy czasu ma to sens. Jednak nie tego oczekiwałem. :)

Zamiast tego, miałem nadzieję, aby wysłać nagłówki żądania z Transfer-Encoding: chunked, otrzyma nagłówek reakcji HTTP/1.1 200 OK z Transfer-Encoding: chunked nagłówku własną rękę, a potem będę mieć pełny dupleks połączenie strumieniowe HTTP do pracy.

Na szczęście mój klient HTTPClient ma innego asynchronicznego klienta opartego na systemie NIO z dobrymi przykładami użycia (np. this one). Moje pytania to:

  1. Czy moja interpretacja synchronicznego zachowania HTTPClient jest poprawna? Czy jest coś, co mogę zrobić, aby nadal używać (prostszego) synchronicznego HTTPClient w opisany sposób?
  2. Czy klient oparty na NIO czeka na wysłanie całej prośby przed uzyskaniem odpowiedzi? Czy będę w stanie przesłać żądanie narastająco i otrzymywać odpowiedź narastająco w tym samym czasie?

Jeśli HTTPClient nie obsługuje tej modalności, czy istnieje inna biblioteka klienta HTTP, która będzie? Czy powinienem planować napisanie (minimalnego) klienta HTTP, który będzie obsługiwał tę modalność?

Odpowiedz

1

Oto mój pogląd na skim odczytaniu kodu:

  1. nie mogę całkowicie zgadza się z faktem, że odpowiedź non-200 oznacza awarii. Wszystkie odpowiedzi 2XX są w większości poprawne. Aby uzyskać więcej informacji, sprawdź:

  2. Dla każdego żądania TCP, polecam otrzymać całą odpowiedź, aby potwierdzić, że jest ważna. Mówię to, ponieważ częściowa odpowiedź może być najczęściej traktowana jako zła odpowiedź, ponieważ większość wdrożeń klienta nie może jej użyć. (Wyobraźmy sobie sytuację, w której serwer odpowiada z 2MB danych i idzie w dół w tym czasie)

+0

To są dobre punkty. Akceptuję tylko 200, ponieważ jestem w trybie testowym; masz rację, że powinienem zaakceptować 2XX, by odnieść sukces. W tym drugim przypadku celem całego wdrożenia jest otrzymanie i przetworzenie odpowiedzi w czasie. – sigpwned

+0

To nie jest powód zawieszenia programu. Wynika to z potokowego strumienia wejściowego. –

0

Odrębny wątek musi być pisemnie do OutputStream dla kodu pracy.

  • powyżej kod oferuje HTTPClient z PipedInputStream.
  • PipedInputStream udostępnia bajty, ponieważ są one zapisywane w odpowiednim strumieniu wyjściowym.
  • Powyższy kod nie pisze do OutputStream (który musi być wykonane za pomocą oddzielnego wątku.
  • Dlatego kod wisi dokładnie gdzie Twój komentarz jest.
  • Pod maską, klient Apache mówi „InputStream. read() ", który w przypadku strumieni potokowych wymaga wcześniejszego wywołania metody outputStream.write (bytes) (przez osobny wątek).
  • Ponieważ nie pompujesz bajtów do powiązanego strumienia wyjściowego z oddzielnego wątku, parametr InputStream po prostu siedzi i czeka, aż OutputStream zostanie zapisany przez "inny wątek".

Z Javadocs:

rurami strumień wejściowy powinien być połączony z rurami strumienia wyjściowego; Potokowany strumień wejściowy zapewnia, że ​​wszystkie bajty danych są zapisywane jako do potokowego strumienia wyjściowego.

Zazwyczaj dane są odczytywane z obiektu PipedInputStream przez jeden wątek , a dane są zapisywane w odpowiednim obiekcie PipedOutputStream przez inny wątek o numerze .

Próba użycia obu obiektów z jednego wątku nie jest zalecana, ponieważ może zakleić wątek.

Strumień wejściowy potokowy zawiera bufor, oddzielając operacje odczytu od operacji zapisu, w ramach limitów. Mówi się, że potok został "uszkodzony" , jeśli wątek, który dostarczał bajty danych do podłączonego potoku wyjściowego , nie jest już aktywny.

Uwaga: Wydaje mi się, ponieważ piped strumieni i współbieżności nie zostały wymienione w wyciągu z problemem, że nie jest to konieczne. Spróbuj najpierw zawinąć obiekt ByteArrayInputStream() za pomocą obiektu Entity, aby uzyskać kontrolę poprawności ... Powinno to pomóc w zawężeniu problemu.

Aktualizacja

Nawiasem mówiąc, pisałem inwersję Apache HTTP Client API [PipedApacheClientOutputStream] która zapewnia interfejs OutputStream dla HTTP POST przy użyciu klienta HTTP Apache Commons 4.3.4. Może to być blisko tego, co szukasz ...

wywołującej kod wygląda następująco:

// Calling-code manages thread-pool 
ExecutorService es = Executors.newCachedThreadPool(
    new ThreadFactoryBuilder() 
    .setNameFormat("apache-client-executor-thread-%d") 
    .build()); 


// Build configuration 
PipedApacheClientOutputStreamConfig config = new  
    PipedApacheClientOutputStreamConfig(); 
config.setUrl("http://localhost:3000"); 
config.setPipeBufferSizeBytes(1024); 
config.setThreadPool(es); 
config.setHttpClient(HttpClientBuilder.create().build()); 

// Instantiate OutputStream 
PipedApacheClientOutputStream os = new  
PipedApacheClientOutputStream(config); 

// Write to OutputStream 
os.write(...); 

try { 
    os.close(); 
} catch (IOException e) { 
    logger.error(e.getLocalizedMessage(), e); 
} 

// Do stuff with HTTP response 
... 

// Close the HTTP response 
os.getResponse().close(); 

// Finally, shut down thread pool 
// This must occur after retrieving response (after is) if interested 
// in POST result 
es.shutdown(); 

Uwaga - W praktyce ten sam klient, obsługa wykonawca, a config prawdopodobnie będzie ponownie użyty przez cały czas trwania aplikacji, więc zewnętrzny kod prep i close w powyższym przykładzie będzie prawdopodobnie żył w bootstrap/init i kodzie finalizacyjnym, a nie bezpośrednio w inline z instancją OutputStream.