はじめに
本記事は、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のサイズは16進数で送る。
- 通信の終わりは0と空行を送る。
- chunkのサイズとchunkが一致しない場合、クライアントはエラーになる
- このChunked transfer encodingはHTTP/1.1の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>