ドキュメント

§HTTPレスポンスのストリーミング

§標準レスポンスとContent-Lengthヘッダー

HTTP 1.1以降、複数のHTTPリクエストとレスポンスを提供するために単一の接続を開いたままにするには、サーバーはレスポンスと共に適切な `Content-Length` HTTPヘッダーを送信する必要があります。

デフォルトでは、次のような単純な結果を返す場合、`Content-Length` ヘッダーを指定していません。

public Result index() {
  return ok("Hello World");
}

もちろん、送信するコンテンツは既知であるため、Playはコンテンツサイズを計算し、適切なヘッダーを生成できます。

**注**: テキストベースのコンテンツの場合、文字をバイトに変換するために使用される文字エンコーディングに従って `Content-Length` ヘッダーを計算する必要があるため、見た目ほど単純ではありません。

実際、前述したように、レスポンス本文は`play.http.HttpEntity`を使用して指定されます。

public Result index() {
  return new Result(
      new ResponseHeader(200, Collections.emptyMap()),
      new HttpEntity.Strict(ByteString.fromString("Hello World"), Optional.of("text/plain")));
}

これは、`Content-Length` ヘッダーを正しく計算するために、Playがコンテンツ全体を消費し、メモリにロードする必要があることを意味します。

§大量のデータの送信

コンテンツ全体をメモリにロードすることが問題ない場合、大規模なデータセットはどうでしょうか?Webクライアントに大きなファイルを返す場合を考えてみましょう。

まず、ファイルコンテンツの `Source[ByteString, _]` を作成する方法を見てみましょう。

java.io.File file = new java.io.File("/tmp/fileToServe.pdf");
java.nio.file.Path path = file.toPath();
Source<ByteString, ?> source = FileIO.fromPath(path);

これで簡単そうですね。このストリーミングされたHttpEntityを使用してレスポンス本文を指定しましょう。

public Result index() {
  java.io.File file = new java.io.File("/tmp/fileToServe.pdf");
  java.nio.file.Path path = file.toPath();
  Source<ByteString, ?> source = FileIO.fromPath(path);

  return new Result(
      new ResponseHeader(200, Collections.emptyMap()),
      new HttpEntity.Streamed(source, Optional.empty(), Optional.of("text/plain")));
}

実際には、ここに問題があります。ストリーミングされたエンティティで `Content-Length` を指定しないため、Playはそれを自分で計算する必要があり、そのためにはソースコンテンツ全体を消費してメモリにロードし、レスポンスサイズを計算するしかありません。メモリに完全にロードしたくない大きなファイルの場合、これは問題です。これを回避するには、`Content-Length` ヘッダーを自分で指定するだけです。

こうすることで、Playはbodyソースを遅延的に消費し、データの各チャンクが利用可能になり次第、HTTPレスポンスにコピーします。

public Result index() {
  java.io.File file = new java.io.File("/tmp/fileToServe.pdf");
  java.nio.file.Path path = file.toPath();
  Source<ByteString, ?> source = FileIO.fromPath(path);

  Optional<Long> contentLength = null;
  try {
    contentLength = Optional.of(Files.size(path));
  } catch (IOException ioe) {
    throw new RuntimeException(ioe);
  }

  return new Result(
      new ResponseHeader(200, Collections.emptyMap()),
      new HttpEntity.Streamed(source, contentLength, Optional.of("text/plain")));
}

この方法では、Playはボディソースを遅延的に消費し、データの各チャンクが利用可能になり次第HTTPレスポンスにコピーします。

§ファイルの配信

もちろん、Playはローカルファイルを配信するという一般的なタスクのための使いやすいヘルパーを提供しています。

public Result index() {
  return ok(new java.io.File("/tmp/fileToServe.pdf"));
}

このヘルパーは、ファイル名から `Content-Type` ヘッダーも計算し、Webブラウザがこのレスポンスをどのように処理するかを指定するために `Content-Disposition` ヘッダーを追加します。デフォルトでは、HTTPレスポンスに `Content-Disposition: inline; filename=fileToServe.pdf` ヘッダーを追加することで、このファイルを `インライン` で表示します。

独自のファイル名を提供することもできます。

public Result index() {
  return ok(new java.io.File("/tmp/fileToServe.pdf"), Optional.of("fileToServe.pdf"));
}

**注**: 計算されたヘッダーが *正確に* `Content-Disposition: inline` になる場合(ファイル名として `null` を渡す場合)、RFC 6266 Section 4.2 によると、コンテンツをインラインでレンダリングすることがデフォルトであるため、Playによって送信されません。

このファイルを `添付ファイル` として配信する場合

public Result index() {
  return ok(new java.io.File("/tmp/fileToServe.pdf"), /*inline = */ false);
}

Webブラウザはダウンロードしようとせず、Webブラウザウィンドウにファイルの内容を表示するだけなので、ファイル名を指定する必要はありません。これは、テキスト、HTML、画像など、Webブラウザでネイティブにサポートされているコンテンツタイプに役立ちます。

§チャンクレスポンス

今のところ、ストリーミングする前にコンテンツの長さを計算できるため、ストリーミングファイルコンテンツでうまく機能します。しかし、コンテンツサイズがわからない動的に計算されたコンテンツはどうでしょうか?

この種のレスポンスには、**チャンク転送エンコーディング**を使用する必要があります。

**チャンク転送エンコーディング** は、HTTP 1.1バージョンのデータ転送メカニズムであり、Webサーバーは一連のチャンクでコンテンツを提供します。これは、プロトコルがそうでなければ必要とする `Content-Length` ヘッダーの代わりに、`Transfer-Encoding` HTTPレスポンスヘッダーを使用します。`Content-Length` ヘッダーが使用されないため、サーバーはクライアント(通常はWebブラウザ)にレスポンスの送信を開始する前に、コンテンツの長さを知る必要はありません。Webサーバーは、そのコンテンツの合計サイズを知る前に、動的に生成されたコンテンツでレスポンスの送信を開始できます。

各チャンクのサイズは、チャンク自体の直前に送信されるため、クライアントはそのチャンクのデータの受信が完了した時期を判断できます。データ転送は、長さゼロの最後のチャンクによって終了します。

https://en.wikipedia.org/wiki/Chunked_transfer_encoding

利点は、データが利用可能になり次第チャンクを送信できるため、データを**ライブ**で配信できることです。欠点は、Webブラウザはコンテンツサイズを知らないため、適切なダウンロードプログレスバーを表示できないことです。

どこかで動的な `InputStream` を提供してデータを計算するサービスがあるとしましょう。Playにチャンクレスポンスを使用してこのコンテンツを直接ストリーミングするように要求できます。

public Result index() {
  InputStream is = getDynamicStreamSomewhere();
  return ok(is);
}

独自のチャンクレスポンスビルダーを設定することもできます。

public Result index() {
  // Prepare a chunked text stream
  Source<ByteString, ?> source =
      Source.<ByteString>actorRef(256, OverflowStrategy.dropNew())
          .mapMaterializedValue(
              sourceActor -> {
                sourceActor.tell(ByteString.fromString("kiki"), null);
                sourceActor.tell(ByteString.fromString("foo"), null);
                sourceActor.tell(ByteString.fromString("bar"), null);
                sourceActor.tell(new Status.Success(NotUsed.getInstance()), null);
                return NotUsed.getInstance();
              });
  // Serves this stream with 200 OK
  return ok().chunked(source);
}

`Source.actorRef` メソッドは、`ActorRef` に具体化されるAkka Streams `Source` を作成します。その後、アクターにメッセージを送信することで、ストリームに要素をパブリッシュできます。別の方法として、`ActorPublisher` を拡張するアクターを作成し、`Stream.actorPublisher` メソッドを使用して作成する方法があります。

サーバーから送信されたHTTPレスポンスを検査できます。

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked

4
kiki
3
foo
3
bar
0

3つのチャンクと、レスポンスを閉じる最後の空のチャンクが取得されます。

Akka Streamsの使用に関する詳細については、Akka Streamsのドキュメントを参照してください。

**次へ:**Comet


このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインを読んだ後、プルリクエストを送信してください。質問やアドバイスがあれば、コミュニティフォーラムにアクセスして、コミュニティとの会話を始めてください。