HTTP1.1のTransfer-Encoding: chunkedを試してみる
RFC2616 Transfer-Encoding: chunked を試すために、
curlとかnetcatとかrubyで色々やってみます。
仕様
とりあえず仕様を確認してみます。
Chunked-Body = *chunk last-chunk trailer CRLF chunk = chunk-size [ chunk-extension ] CRLF chunk-data CRLF chunk-size = 1*HEX last-chunk = 1*("0") [ chunk-extension ] CRLF chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) chunk-ext-name = token chunk-ext-val = token | quoted-string chunk-data = chunk-size(OCTET) trailer = *(entity-header CRLF)
curl
curlからデータを送ってみます。
server
netcatでサーバを起動
$ nc -l 8888
curl client
headerにchunkedやkeepaliveの情報を付加すればchunkedになると聞いてやってみます。
$ vim hoge.txt hogehoge fugafuga $ curl -X POST -T "hoge.txt" -H 'Content-type: text/plain' --header "Transfer-Encoding: chunked" --header "Connection: Keep-Alive" http://localhost:8888/resource/test
request
server側にrequestが来ました。
bodyに16進数でsizeが入っています。0CRLFCRLFで1chunkと見るようです。
ただ、bodyの後に余計にCRLFが1つ入ってしまっています。仕様を見た限りでは必要無さそうです。
また、host.txtの内容を増やしていくと、どこかのタイミングでchunkが分割されるようです。
POST /resource/test HTTP/1.1 User-Agent: curl/7.28.0 Host: localhost:8888 Accept: */* Content-type: text/plain Transfer-Encoding: chunked Connection: Keep-Alive Expect: 100-continue 12 hogehoge fugafuga 0
netcat
clientにもnetcatを使ってみます。
server
netcatでサーバを起動
$ nc -l 8888
client
netcatクライアント
curlのrequestを真似しました。
$ host=localhost $ port=8888 $ body=hogehoge\\nfugafuga $ size=`expr ${#body} | xargs -I{} printf '%x\n' {}` $ echo "POST /resource/test HTTP/1.1\r\nHost: ${host}:${port}\r\nContent-type: text/plain\r\nTransfer-Encoding: chunked\r\nConnection: Keep-Alive\r\nExpect: 100-continue\r\n\r\n${size}\r\n${body}\r\n0\r\n\r\n" | nc $host $port
request
server側にrequestが来ました。
netcatでは、標準入力が閉じられるとコネクションが閉じられるようです。
POST /resource/test HTTP/1.1 Host: localhost:8888 Content-type: text/plain Transfer-Encoding: chunked Connection: Keep-Alive Expect: 100-continue 12 hogehoge fugafuga 0
ruby
最後にrubyからもデータを送ってみます。
といってもクライアントはnetcatをpipeで開いて送っています。
tcp server
request内容を出力するTCP Server
$ vim tcpserver.rb require "socket" c = TCPServer.new(7777) while true sock = c.accept while buf = sock.gets puts buf end sock.close end c.close $ ruby tcpserver.rb
client
Rubyからnetcatをパイプで開いてデータを送ってみます。
1度headerを投げたあとに10回bodyを送ってコネクションを切ります。
require 'open3' class ChunkTest def initialize(host, port) @host = host @port = port @cmd = "nc" end def run Open3.popen3("#{@cmd} #{@host} #{@port}") do |stdin, stdout, stderr, wait_thr| begin i = 0 loop do body = "hogehoge#{i}\nfugafuga#{i}" size = body.size head = \ "POST /resource/test HTTP/1.1\r\nUser-Agent: curl/7.28.0\r\nHost: #{@host}:#{@port}\r\nContent-type: text/plain\r\nTransfer-Encoding: chunked\r\nConnection: Keep-Alive\r\nExpect: 100-continue\r\n\r\n" if i == 0 stdin << head elsif i > 10 stdin << "0\r\n\r\n" break else chunk = "#{size.to_s(16)}\r\n#{body}\r\n" stdin << chunk end i += 1 end ensure stdin.close end end end end chunk = ChunkTest.new('localhost', 8888) chunk.run
request
POST /resource/test HTTP/1.1 User-Agent: curl/7.28.0 Host: localhost:8888 Content-type: text/plain Transfer-Encoding: chunked Connection: Keep-Alive Expect: 100-continue 13 hogehoge1 fugafuga1 13 hogehoge2 fugafuga2 13 hogehoge3 fugafuga3 13 hogehoge4 fugafuga4 13 hogehoge5 fugafuga5 13 hogehoge6 fugafuga6 13 hogehoge7 fugafuga7 13 hogehoge8 fugafuga8 13 hogehoge9 fugafuga9 15 hogehoge10 fugafuga10 0
chunked server
以上の実験でchunkedでのデータのやり取りが分かってきました。
サーバーにcool.ioを使用してchunkedなrequestを処理してみます。
受け取ったrequest bodyを画面出力します。
require 'cool.io' require 'http_parser' ADDR = '127.0.0.1' PORT = 8888 class EchoServerConnection < Cool.io::TCPSocket def on_connect puts "#{remote_addr}:#{remote_port} connected" @parser = Http::Parser.new(self) end def on_message_begin @headers = nil end def on_close puts "#{remote_addr}:#{remote_port} disconnected" end def on_headers_complete(headers) @headers = headers end def on_read(data) @parser << data end def on_body(data) puts data end end event_loop = Cool.io::Loop.default Cool.io::TCPServer.new(ADDR, PORT, EchoServerConnection).attach(event_loop) puts "Echo server listening on #{ADDR}:#{PORT}" event_loop.run
request
無事にbodyを取り出せました。
$ ruby coolio.rb Echo server listening on 127.0.0.1:8888 127.0.0.1:49895 connected hogehoge1 fugafuga1 hogehoge2 fugafuga2 hogehoge3 fugafuga3 hogehoge4 fugafuga4 hogehoge5 fugafuga5 hogehoge6 fugafuga6 hogehoge7 fugafuga7 hogehoge8 fugafuga8 hogehoge9 fugafuga9 hogehoge10 fugafuga10 127.0.0.1:49895 disconnected
終わりに
HTTP1.1のTransfer-Encoding: chunkedがなかなか分からず苦労しました。
間違っている部分があれば、ご指摘ご助言等頂けると幸いです。