TCPSocket練習
webrickプロキシに乗せるための試作httpクライアント。
前にNet::BufferedIO(内部用と書いてあるのを勝手に使っている)を流用していた部分がどうにも使い勝手がよろしくないので自作することにした。
TCPSocketを使えば楽ちん。
一挙に全データをreadしないという要件(動画などの巨大データを逐次で送る)のため、httpヘッダを捌く部分がこんなんになってしまった。
#!/usr/bin/ruby # coding: utf-8 require 'uri' require 'socket' module HTTP # HTTPヘッダを行毎に読み込む補助クラス # よろしくないので書き直し # class SocketReader # def initialize(tcp_socket) # @tcp_socket=tcp_socket # @lines=[] # @line_break=false # end # # def getlines # buf=@tcp_socket.read(128) # lines=buf.split("\r\n") # if not @lines.empty? and @line_break then # @lines[@lines.size-1]+=lines.shift # end # @lines+=lines # @line_break=(not buf.end_with?("\r\n")) # end # # def gets # case @lines.length # when 0 # getlines # when 1 # while @lines.length==1 and @line_break do # getlines # end # end # return @lines.shift.rstrip # end # # def flush # #return @lines.join("\n") バイナリデータが壊れる時がある # return @lines.join("\r\n") # end # end class SocketReader def initialize(tcp_socket) @tcp_socket=tcp_socket @buf='' while true buf=@tcp_socket.read(128) if not buf then break end @buf << buf index=@buf.index("\r\n\r\n") if index then @lines=@buf.slice!(0, index+4).split("\r\n") break end end end def gets @lines.shift end def flush return @buf end end class Client attr_accessor :status, :response, :body, :version, :message def get(uri) # open socket ############################################################ begin socket=TCPSocket.open(uri.host, uri.port) rescue SocketError=>e p e return false end # send http request ############################################################ socket << "GET #{uri.path} HTTP/1.0\r\n" socket << "\r\n" # read http response ############################################################ # response reader=SocketReader.new(socket) # header m=reader.gets.match(%r|HTTP/(\d\.\d) (\d+) (.*)|) @version=m[1] @status=m[2].to_i @message=m[3] @response={} while true line=reader.gets if not line then break end if line=="" then break end key, value=line.split(":", 2) @response[key]=value.strip end # body @body=reader.flush while true buf=socket.read(1024) if not buf then break end @body << buf end return true end end end def main(url) c = HTTP::Client.new if not c.get url then puts "fail to get #{url}" return end p c.status, c.message p c.response p c.body.bytesize end if __FILE__==$0 then if(!ARGV[0]) puts "usage: ruby #{__FILE__} URI" exit end main URI.parse(ARGV[0]) end