はじめに
本記事は、WebSocketを使わないPUSH通信の一方式である、Chunked transfer encodingによるHTTPStreamingのサンプルプログラムを示す
- サーバ側はRubyで書いたCGIプログラム
- クライアント側はバニラJavaScriptによるfetchAPIを使用したプログラムである
- HTTPStreamingにより、サーバ側で時間のかかる処理をしていても、実行終了を待たずに処理結果をその都度、クライアントに通知可能になる
Chunked transfer encodingによるHTTPStreaming
WebSocketを使わないPUSH通信の一方式。百聞は一見に如かずということで、HTTP Streaming パターン テストというデモサイトを紹介。このサイトの場合は、PHP/jQueryでの実装である。
サーバからのレスポンス通信をどうすればいいのか
- レスポンスヘッダとしてTransfer-encoding: chunkedを送る
- サーバからPUSH通知する塊(chunk)毎に、chunkのサイズとchunkの中身を送る
- 具体的には次の通り。
Content-Type: text/plain Transfer-Encoding: chunked 1 a 2 aa 3 aaa 4 aaaa 5 aaaaa 6 aaaaaa 7 aaaaaaa 8 aaaaaaaa 9 aaa
aaaaaa
a
aaaaaaaaaa
b
aaaaaaaaaaa
c
aaaaaaaaaaaa
d
aaaaaaaaaaaaa
e
aaaaaaaaaaaaaa
0
[CRLF]
* chunkのサイズは<strong><font color="red">16進数</font></strong>で送る。
* 通信の終わりは<font color="red">0と空行</font>を送る。
* chunkのサイズとchunkが一致しない場合、クライアントはエラーになる
* このChunked transfer encodingは[HTTP/1.1の4.1節](https://tools.ietf.org/html/rfc7230#section-4.1)
で定義されている。次の通りである。
>```
chunked-body = *chunk
last-chunk
trailer-part
CRLF
chunk = chunk-size [ chunk-ext ] CRLF
chunk-data CRLF
chunk-size = 1*HEXDIG
last-chunk = 1*("0") [ chunk-ext ] CRLF
chunk-data = 1*OCTET ; a sequence of chunk-size octets
クライアントではどう受け取ればいいのか
-
サーバ側ではRubyによるCGIプログラム
-
クライアントはJavaScriptでfetchAPIを使用する実装を示す
-
一秒おきに、chunkを送るプログラム
streaming.cgi
# !/usr/bin/ruby
puts "Content-Type: text/plain"
puts "Transfer-encoding: chunked"
puts "\n"
15.times{|n|
if(n > 0) then
puts n.to_s(16) # 16進数
puts 'a' * n
STDOUT.flush
end
sleep 1
}
puts "0"
puts # 空行
- クライアントではレスポンス通信の結果をReadStreamから得る
- 毎秒送られてくるchunkを、送られた都度で画面上に出力する
index.js
function startStream(){
const url = "./streaming.cgi"
fetch(url)
.then((res)=>{
if(res.ok){
const reader = res.body.getReader();
let received = 0;
const txt = new TextDecoder();
reader.read().then(function processText({done, value}){
if(done){
console.log("Stream Complete")
return;
}
received += value.length;
const chunk = txt.decode(value);
let listItem = document.createElement('li');
listItem.textContent = 'Read ' + received + ' bytes. Current chunk = ' + chunk;
document.getElementById("result").appendChild(listItem);
return reader.read().then(processText);
})
}else{
console.log(res);
}
})
.catch((err)=>{
console.log(err);
})
}
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>HTTP streaming Test</title>
<script src="./index.js"></script>
<style></style>
</head>
<body>
<h1>HTTP streaming Test</h1>
<button onclick="startStream()">Streaming開始</button>
<ul id="result"></ul>
</body>
</html>