2012-10-20 10 views
6

Muszę zadzwonić do usługi sieciowej, która daje mi zawartość pliku binarnego. Ja po prostu chcę dać tę samą zawartość z powrotem do wywołującego mojego kontrolera:Odtwarzanie: Odpowiedź Binary Webservice

val blobPromise = WS.url("http://url/to/webservice/file.txt").get() 
Async { 
    blobPromise.map(f => Ok(f.body)) 
} 

To działa dla plików tekstowych, ale pliki binarne zostanie uszkodzona. Co robię źle tutaj? (Może to jest kod f.body, który koduje wynik binarny z usługi sieci Web do łańcucha znaków ?, ale jak mogę uzyskać surowe dane?)

Wiem, to nie jest dobry sposób na duże pliki - czytałem w dokumentach Play około Streaming HTTP responses, ale wydaje mi się to skomplikowane dla mnie jako początkującego ze strukturą Play.

Odpowiedz

8

Możesz uzyskać surowe dane za pomocą f.ahcResponse.gerResponseBodyAsBytes. Ale myślę, że to załaduje całą odpowiedź do pamięci, co jest nieefektywne.

Możesz użyć funkcji przesyłania strumieniowego Play! zapewnia dość łatwo tak:

Async { 
    WS.url("http://url/to/webservice/file.txt").get().map(response => { 
    val asStream: InputStream = response.ahcResponse.getResponseBodyAsStream 
    Ok.stream(Enumerator.fromStream(asStream)) 
    }) 
} 
+0

Dziękuję bardzo, oba rozwiązania z 'f.ahcResponse.getResponseBodyAsBytes' działają teraz również z danymi binarnymi. Funkcja przesyłania strumieniowego wydaje się łatwiejsza niż myślałem ... :-) – Sonson123

+3

Czy to nie blokuje? InputStreams zwykle robią .... –

+0

W rzeczywistości nie blokuje się, gdy czytasz z niego ... ale tylko dlatego, że najpierw jest on wczytany do pamięci. Aby tego uniknąć, musiałbyś użyć przeciążonej formy 'get()', która przyjmuje argument funkcji konsumenta: 'get [A] (konsument: (ResponseHeaders) ⇒ Iteratee [Array [Byte], A])' –

3

Jeśli chcesz przesyłać strumieniowo zawartość:

def streamFromWS = Action.async { request => 
    import play.api.libs.iteratee.Concurrent.joined 

    val resultPromise = Promise[SimpleResult] 

    val consumer = { rs: ResponseHeaders => 
    val (wsConsumer, stream) = joined[Array[Byte]] 
    val contentLength = rs.headers.get("Content-Length").map(_.head).get 
    val contentType = rs.headers.get("Content-Type").map(_.head).getOrElse("binary/octet-stream") 
    resultPromise.success(
     SimpleResult(
     header = ResponseHeader(
      status = OK, 
      headers = Map(
      CONTENT_LENGTH -> contentLength, 
      CONTENT_DISPOSITION -> s"""attachment; filename="file.txt"""", 
      CONTENT_TYPE -> contentType 
     )), 
     body = stream 
    )) 
    wsConsumer 
    } 

    WS.url("http://url/to/webservice/file.txt").get(consumer).map(_.run) 

    resultPromise.future 
} 
+0

dziękuję bardzo, wydaje się, że "złączone" zezwolenia na pracę –

1

podstawie Yann Simon odpowiedź, oto prosta implementacja proxy CORS który umożliwia strumieniowe przesyłanie pobranych plików zdalnych i przesyłać je do klienta. Nie ładuje całego pliku w pamięci.

import play.api.libs.iteratee._ 

    private def getAndForwardStream(requestHolder: WSRequestHolder)(computeHeaders: ResponseHeaders => ResponseHeader): Future[SimpleResult] = { 
    val resultPromise = scala.concurrent.Promise[SimpleResult] 
    requestHolder.get { wsResponseHeaders: ResponseHeaders => 
     val (wsResponseIteratee, wsResponseEnumerator) = Concurrent.joined[Array[Byte]] 
     val result = SimpleResult(
     header = computeHeaders(wsResponseHeaders), 
     body = wsResponseEnumerator 
    ) 
     resultPromise.success(result) 
     wsResponseIteratee 
    } 
    resultPromise.future 
    } 

    def corsProxy(url: URL) = Action.async { implicit request => 
    val requestHolder = WS.url(url.toString).withRequestTimeout(10000) 
    getAndForwardStream(requestHolder) { wsResponseHeaders: ResponseHeaders => 
     // We use the WS response headers and transmit them unchanged to the client, except we add the CORS header... 
     val originToAllow = request.headers.get("Origin").getOrElse("*") 
     val headers = wsResponseHeaders.headers.mapValues(_.head) + ("Access-Control-Allow-Origin" -> originToAllow) 
     ResponseHeader(
     status = wsResponseHeaders.status, 
     headers = headers 
    ) 
    } 
    } 

Ważną częścią jest tutaj użycie play.api.libs.iteratee.Concurrent.joined[Array[Byte]]. Pozwala utworzyć parę Iteratee/Enumerator tak, że ilekroć dodasz bajty do iteracji, te bajty będą wyliczane przez moduł wyliczający.

Był to brakujące ogniwo, ponieważ:

  • Państwo potrzebować Iteratee spożywać odpowiedź WS.
  • Potrzebujesz modułu wyliczającego, aby wygenerować odpowiedź ramową gry.
+0

zauważyć nowy strumień API() nadchodzi z gry 2.3: http://www.playframework.com/documentation/2.3.x/api/scala/ index.html # play.api.libs.ws.WSRequestHolder z tym, nie potrzebujesz już złączonego [] więcej –

+0

dzięki @YannSimon thx;) –