Revでhttpプロキシその2
リファクタリングして、Postメソッドへの対応を実装した。
Proxy用途には、Rev::HttpClientは都合が悪いことが判ったので、素のRev::TCPSocketを使うように変えた。
keep-aliveとssl対応をしてからコンテンツフィルターとキャッシングに使いたいのだがいけるかな。
#!/usr/bin/ruby require 'rev' require 'logger' require 'stringio' require 'strscan' require 'uri' require 'socket' def logger $logger||=Logger.new(STDOUT) end module Proxy class HTTPRequest attr_reader :method, :uri, :major, :minor, :headers, :body def initialize(method, uri, major, minor, headers) @method=method.upcase.to_sym @uri=uri @major=major @minor=minor @headers=headers @body='' end def format http="#@method #@uri HTTP/#@major.#@minor\r\n" # keep-aliveしない @headers['connection']='close' @headers.each do |k, v| case k when /^proxy-/, 'keep-alive' # proxyとkeep-aliveのヘッダとを落とす else http << "#{k}: #{v}\r\n" end end http << "\r\n" end def body_is_end? if @headers.key? 'content-length' then if @headers['content-length'].to_i==@body.bytesize then return true else return false end else if @body.end_with? "\r\n" then return true else return false end end end end class RequestBuilder attr_reader :request def initialize @lines=[''] @request=nil end def push(data) last_byte=nil data.each_byte do |b| if @request then # request body @request.body << b else # request header case b when 0x0d when 0x0a if last_byte==0x0d then if @lines.last=='' then # request complete build_header else # next line @lines << '' end end else @lines.last << b end last_byte=b end end end def build_header # HTTP Request 1st line method, url, major, minor=@lines.shift.match( %r|(\S+)\s+(\S+)\s+HTTP/(\d+)\.(\d+)|).captures # HTTP request headers headers={} @lines.each{|l| if l.size>0 then k, v=l.split(/: */, 2) headers[k.downcase]=v end } # create HTTP request @request=HTTPRequest.new(method, URI.parse(url), major.to_i, minor.to_i, headers) end end class UpStream < Rev::TCPSocket def initialize(uri, down_stream) super TCPSocket.new(uri.host, uri.port) @down_stream=down_stream end def on_read(data) logger.info "UpStream::on_read #{data.bytesize} bytes" @down_stream.write data end def on_close logger.info "UpStream::on_close" @down_stream.close_on_write_complete end end class DownStream < Rev::TCPSocket def initialize(socket) super @requests=[] end def on_read(data) logger.info "DownStream::on_read #{data.bytesize} bytes" @builder||=RequestBuilder.new @builder.push data if @builder.request then case @builder.request.method when :GET, :HEAD proxy_request when :POST if @builder.request.body_is_end? then proxy_request else logger.error @builder.request end else logger.error "unknown method: #{@builder.header.method}" end end end def proxy_request request=@builder.request @requests << request @builder=nil uri=request.uri c=Proxy::UpStream.new(uri, self) c.write request.format if request.method==:POST then c.write request.body end logger.info request.format c.attach(Rev::Loop.default) end def on_write_compilete if @should_close then close end end def close_on_write_complete if output_buffer_size==0 then close else @should_close=true end end end end def start(listen=0, port=8080) logger.info("start.") logger.info("listen: #{listen}.") logger.info("port: #{port}.") server = Rev::TCPServer.new(listen, port, Proxy::DownStream) server.attach(Rev::Loop.default) Rev::Loop.default.run end if $0==__FILE__ then start end