class QReplay::HttpExtractor
Public Instance Methods
finalize()
click to toggle source
# File lib/qreplay/httpextractor.rb, line 39 def finalize end
process_stream(stream)
click to toggle source
# File lib/qreplay/httpextractor.rb, line 10 def process_stream stream calls = [] i = 0 last_req = nil req = nil while i + 1 < stream[:data].size if last_req req = parse_request(last_req, stream[:data][i][:data]) last_req = nil req['Expect'] = nil else req = parse_request(stream[:data][i]) end resp = parse_response(stream[:data][i + 1]) if req && req['Expect'] && req['Expect'].strip == '100-continue' last_req = stream[:data][i] elsif !req.nil? && !resp.nil? calls << [stream[:index], req, resp] end i += 2 end calls end
Private Instance Methods
add_headers(o, headers)
click to toggle source
# File lib/qreplay/httpextractor.rb, line 102 def add_headers o, headers headers.each do |line| m = /\A([^:]+):\s*/.match(line) or raise "Unable to parse header line [#{line}]" o[m[1]] = m.post_match end end
parse_request(stream, separate_body = nil)
click to toggle source
# File lib/qreplay/httpextractor.rb, line 44 def parse_request stream, separate_body = nil headers, body = split_headers(stream[:data]) return nil unless headers body = separate_body if separate_body line0 = headers.shift m = /(\S+)\s+(\S+)\s+(\S+)/.match(line0) or raise "Unable to parse first line of http request #{line0}" clazz = { 'POST' => Net::HTTP::Post, 'HEAD' => Net::HTTP::Head, 'GET' => Net::HTTP::Get, 'PUT' => Net::HTTP::Put, 'DELETE' => Net::HTTP::Delete }[m[1]] or raise "Unknown http request type [#{m[1]}]" req = clazz.new m[2] req['Pcap-Src'] = stream[:from] req['Pcap-Src-Port'] = stream[:from_port] req['Pcap-Dst'] = stream[:to] req['Pcap-Dst-Port'] = stream[:to_port] req.time = stream[:time] req.body = body req['user-agent'] = nil req['accept'] = nil add_headers req, headers if req['Content-Length'] if req.body.bytesize != req['Content-Length'].to_i && (req['Expect'].nil? || req['Expect'].strip != '100-continue') puts "Wrong content-length for http request, header say [#{req['Content-Length'].chomp}], found #{req.body.size}" return nil end end req end
parse_response(stream)
click to toggle source
# File lib/qreplay/httpextractor.rb, line 76 def parse_response stream headers, body = split_headers(stream[:data]) line0 = headers.shift m = /^(\S+)\s+(\S+)\s+(.*)$/.match(line0) or raise "Unable to parse first line of http response [#{line0}]" resp = Net::HTTPResponse.send(:response_class, m[2]).new(m[1], m[2], m[3]) resp.time = stream[:time] add_headers resp, headers if resp.chunked? resp.body = read_chunked("\r\n" + body) else resp.body = body if resp['Content-Length'] unless resp.body.size == resp['Content-Length'].to_i puts "Wrong content-length for http response, header say [#{resp['Content-Length'].chomp}], found #{resp.body.size}" return nil end end end begin resp.body = Zlib::GzipReader.new(StringIO.new(resp.body)).read if resp['Content-Encoding'] == 'gzip' rescue Zlib::GzipFile::Error warn "Response body is not in gzip: [#{resp.body}]" end resp end
read_chunked(str)
click to toggle source
# File lib/qreplay/httpextractor.rb, line 115 def read_chunked str if str.nil? || (str == "\r\n") return '' end m = /\r\n([0-9a-fA-F]+)\r\n/.match(str) or raise "Unable to read chunked body in #{str.split("\r\n")[0]}" len = m[1].hex return '' if len == 0 m.post_match[0..len - 1] + read_chunked(m.post_match[len .. -1]) end
split_headers(str)
click to toggle source
# File lib/qreplay/httpextractor.rb, line 109 def split_headers str index = str.index("\r\n\r\n") return nil, nil if index.nil? return str[0 .. index].split("\r\n"), str[index + 4 .. -1] end