標(biāo)準(zhǔn)的應(yīng)答和Content-Length頭
由于HTTP 1.1遵守一個(gè)連接為多個(gè)HTTP請(qǐng)求和應(yīng)答服務(wù)開放的規(guī)則,因此服務(wù)器在應(yīng)答時(shí)必須發(fā)送相應(yīng)的Content-Length HTTP頭。
默認(rèn)情況下滞谢,當(dāng)你發(fā)送一個(gè)簡(jiǎn)單的結(jié)果,如:
public Result index() {
return ok("Hello World");
}
你沒有指定 Content-Length頭端幼。是因?yàn)槟惆l(fā)送的內(nèi)容很有名幸斥,因此Play可以為你計(jì)算內(nèi)容的長(zhǎng)度并生產(chǎn)相應(yīng)的頭。
注意:對(duì)于基于文本的內(nèi)容俊庇,這不像看起來那樣簡(jiǎn)單狮暑,由于Content-Length頭必須用根據(jù)把字符轉(zhuǎn)換為字節(jié)的編碼計(jì)算。
為了可以正確的計(jì)算Content-Length頭辉饱,Play必須解析整個(gè)應(yīng)答數(shù)據(jù)并把它的內(nèi)容加載到內(nèi)存搬男。
文件服務(wù)
如果對(duì)于簡(jiǎn)單內(nèi)容來說,可以把整個(gè)內(nèi)容加載進(jìn)內(nèi)容彭沼,那么對(duì)于大數(shù)據(jù)集怎么辦呢缔逛?比如我們想給客戶端發(fā)送一個(gè)大文件。
Play提供了易于使用的助手來提供本地文件服務(wù)這樣的普通任務(wù):
public Result index() {
return ok(new java.io.File("/tmp/fileToServe.pdf"));
}
此外,這個(gè)助手也會(huì)根據(jù)文件名計(jì)算 Content-Type頭褐奴。并且他也添加Content-Disposition頭指定Web瀏覽器應(yīng)該怎樣處理這個(gè)應(yīng)答按脚。默認(rèn)是使用Content-Disposition: attachment; filename=fileToServe.pdf.讓W(xué)eb瀏覽器下載這個(gè)文件。
分塊應(yīng)答
現(xiàn)在敦冬,流文件內(nèi)容的處理還行辅搬,由于我們?cè)诹髦翱梢杂?jì)算內(nèi)容長(zhǎng)度。但是動(dòng)態(tài)計(jì)算沒有可用的內(nèi)容長(zhǎng)度的內(nèi)容會(huì)如何脖旱?
對(duì)于這種類型的應(yīng)答堪遂,我們不得不使用分塊傳輸編碼。
分塊傳輸編碼是HTTP 1.1版在Web服務(wù)器的一系列塊的服務(wù)內(nèi)容的數(shù)據(jù)傳輸機(jī)制萌庆。這使用了Transfer-Encoding HTTP 應(yīng)答頭替代 Content-Length頭溶褪,該協(xié)議將需要另外的需求。由于不使用 Content-Length 頭踊兜,服務(wù)器在開始傳送應(yīng)答到客戶端(通常是Web瀏覽器)之前不需要知道內(nèi)容的長(zhǎng)度竿滨。 Web服務(wù)器可以在知道內(nèi)容的所有長(zhǎng)度之前開始動(dòng)態(tài)的生成傳送應(yīng)答內(nèi)容佳恬。
每一個(gè)塊的大小都是在塊本身發(fā)送之前發(fā)送捏境,因此當(dāng)客戶端完成對(duì)塊數(shù)據(jù)的接受時(shí)可以告知服務(wù)端,數(shù)據(jù)傳輸是由最后一塊長(zhǎng)度是0的塊終止毁葱。
https://en.wikipedia.org/wiki/Chunked_transfer_encoding
好處是我們可以實(shí)時(shí)提供數(shù)據(jù)垫言,意味著我們按照它們的接受能力盡可能快的發(fā)送數(shù)據(jù)塊。缺點(diǎn)是由于Web瀏覽器不知道內(nèi)容的長(zhǎng)度倾剿,因此就不能正確的顯示下載進(jìn)度條。
比如,我們有一個(gè)服務(wù)器截珍,這個(gè)服務(wù)器提供了一個(gè)計(jì)算一些數(shù)據(jù)的動(dòng)態(tài)InputStream 坎拐。我們可以讓Paly直接用分塊相應(yīng)把這個(gè)內(nèi)容轉(zhuǎn)為流:
public Result index() {
InputStream is = getDynamicStreamSomewhere();
return ok(is);
}
你也可以設(shè)置你自己的分塊響應(yīng)構(gòu)建器:
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 null;
});
// Serves this stream with 200 OK
return ok().chunked(source);
}
Source.actorRef 方法創(chuàng)建一個(gè)實(shí)現(xiàn)了Akka Streams Source 的ActorRef。然后你可以通過發(fā)送信息到Actor的方式發(fā)布內(nèi)容到流芹缔。另一種可選的反射是創(chuàng)建一個(gè)ActorPublisher 的Actor并使用Stream.actorPublisher創(chuàng)建它坯癣。
我們可以檢查服務(wù)器發(fā)送的HTTP響應(yīng):
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
4
kiki
3
foo
3
bar
0
我們獲得了三個(gè)塊和一個(gè)最終關(guān)閉響應(yīng)的空塊。
關(guān)于更多使用 Akka Streams的信息最欠,你可以參考Akka Streams文檔.