qreplay 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/LICENSE.md +10 -0
- data/README.md +70 -0
- data/bin/qreplay +142 -0
- data/lib/qreplay.rb +4 -0
- data/lib/qreplay/httpextractor.rb +127 -0
- data/lib/qreplay/tcpprocessor.rb +68 -0
- data/lib/qreplay/version.rb +4 -0
- data/qreplay.gemspec +24 -0
- metadata +91 -0
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Copyright (C) 2014 Old School Industries LLC
|
2
|
+
|
3
|
+
(MIT License)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
10
|
+
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# qreplay
|
2
|
+
|
3
|
+
This is a simple tool to drive capturing, saving, and replaying HTTP requests. It depends on `pcap_tools`, `tshark`, `dumpcap`, and `httperf`.
|
4
|
+
|
5
|
+
## Use
|
6
|
+
|
7
|
+
```shell
|
8
|
+
gem install qreplay
|
9
|
+
qreplay [capture|replay|transform|capture_only] [options]
|
10
|
+
```
|
11
|
+
|
12
|
+
Options:
|
13
|
+
```
|
14
|
+
--capture-time, -c <f>: Capture time length in seconds (default: 60.0)
|
15
|
+
--port, -p <i>: Capture/replay traffic to port (default: 80)
|
16
|
+
--host, -h <s>: Replay host (default: 0.0.0.0)
|
17
|
+
--req-sec, -r <i>: Requests per second for replays (default: 20)
|
18
|
+
--total-requests, -t <i>: Total replay requests to send (default: 10000)
|
19
|
+
--capture-file, -a <s>: Output file (default: ./qreplay.sesslog)
|
20
|
+
--tshark-binary, -s <s>: TShark binary file location (default: tshark)
|
21
|
+
--dumpcap-binary, -d <s>: dumpcap binary file location (default: dumpcap)
|
22
|
+
--pcap-file, -f <s>: Temporary intermediate pcap file path (default: ./qreplay.pcap)
|
23
|
+
--httperf-binary, -e <s>: httperf binary file location (default: httperf)
|
24
|
+
--help, -l: Show this message
|
25
|
+
```
|
26
|
+
|
27
|
+
## Example
|
28
|
+
|
29
|
+
You want to capture traffic on a live web server. On the remote machine run:
|
30
|
+
|
31
|
+
```
|
32
|
+
qreplay capture --capture-time 60 --port 80
|
33
|
+
```
|
34
|
+
|
35
|
+
This will use `tshark` to capture TCP traffic to/from port 80 for 60 seconds, stitch together HTTP requests from the TCP traffic, and save requests to `./qreplay.sesslog`. If you observe this file you will notice that each request line contains an HTTP method, path, and body in a format acceptable to `httperf`.
|
36
|
+
|
37
|
+
You can then replay with:
|
38
|
+
|
39
|
+
```
|
40
|
+
qreplay replay --host 127.0.0.1 --port 80 --req-sec 50
|
41
|
+
```
|
42
|
+
|
43
|
+
To replay these requests to the local server at port 80 at a rate of 50 per second.
|
44
|
+
|
45
|
+
## Installing
|
46
|
+
|
47
|
+
Mac OS X (homebrew):
|
48
|
+
```
|
49
|
+
gem install qreplay
|
50
|
+
brew install wireshark httperf
|
51
|
+
```
|
52
|
+
|
53
|
+
Yum package manager:
|
54
|
+
```
|
55
|
+
gem install qreplay
|
56
|
+
yum install wireshark httperf
|
57
|
+
```
|
58
|
+
|
59
|
+
## httperf
|
60
|
+
|
61
|
+
By default, httperf only handles HTTP bodies with 10,000 bytes or less, which is easy to exceed if you're testing an application that POSTs significant amounts of data. We've increased a few of the arbitrary limitations in httperf in our fork, [here](https://github.com/quizlet/httperf), we recommend using it if you're hitting limits in the mainline httperf.
|
62
|
+
|
63
|
+
## License
|
64
|
+
|
65
|
+
The qreplay copyright is owned by Old School Industries LLC. We've licensed it under the MIT License, which can be found in `LICENCE.md`.
|
66
|
+
|
67
|
+
## Thanks
|
68
|
+
|
69
|
+
Thanks to Bertrand Paquet for developing pcap tools and licensing it under the Apache 2 license. You can find more about pcap_tools [here](https://github.com/bpaquet/pcap_tools).
|
70
|
+
|
data/bin/qreplay
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'trollop'
|
4
|
+
require 'pcap_tools'
|
5
|
+
require 'pp'
|
6
|
+
require 'qreplay'
|
7
|
+
|
8
|
+
p = Trollop::Parser.new do
|
9
|
+
version "qreplay #{QReplay::VERSION} - (c) Old School Industries LLC"
|
10
|
+
banner <<-EOS
|
11
|
+
qreplay is a tool for capturing and replaying HTTP traffic.
|
12
|
+
|
13
|
+
qreplay [capture|replay|transform|capture-only] <args>
|
14
|
+
|
15
|
+
capture - Use tshark to capture http packets.
|
16
|
+
replay - Replay HTTP session file with requests to a host/port.
|
17
|
+
transform - Transform a dumpcap file to a sesslog file. This is executed automatically in capture mode.
|
18
|
+
capture-only - Perform a capture without a transform step.
|
19
|
+
|
20
|
+
Capturing TCP traffic requires root privileges on most systems.
|
21
|
+
|
22
|
+
Examples:
|
23
|
+
|
24
|
+
> sudo qreplay capture --capture-time 60 --port 80
|
25
|
+
> qreplay replay --host 127.0.0.1 --port 80 --req-sec 50
|
26
|
+
|
27
|
+
qreplay depends on tshark, dumpcap, and httperf, by default attempting to find them in the environment, with the option of passing the paths to them directly on the command line.
|
28
|
+
|
29
|
+
More info can be found at the gem website at github.com/quizlet/qreplay
|
30
|
+
|
31
|
+
Command Line Options:
|
32
|
+
|
33
|
+
EOS
|
34
|
+
|
35
|
+
opt :capture_time, 'Capture time length in seconds', :default => 60.0
|
36
|
+
opt :capture_interface, 'Traffic capture interface (uses dumpcap default if not specified)', :type => :string
|
37
|
+
opt :port, 'Capture/replay traffic to port', :default => 80
|
38
|
+
opt :host, 'Replay host', :default => '0.0.0.0'
|
39
|
+
opt :req_sec, 'Requests per second for replays', :default => 20
|
40
|
+
opt :total_requests, 'Total replay requests to send', :default => 10000
|
41
|
+
opt :capture_file, 'Output file', :default => './qreplay.sesslog'
|
42
|
+
opt :pcap_file, 'Temporary intermediate pcap file path', :default => './qreplay.pcap'
|
43
|
+
opt :timeout, 'Timeout for replay requests', :default => 10
|
44
|
+
opt :tshark_binary, 'TShark binary file location', :default => 'tshark'
|
45
|
+
opt :dumpcap_binary, 'dumpcap binary file location', :default => 'dumpcap'
|
46
|
+
opt :httperf_binary, 'httperf binary file location', :default => 'httperf'
|
47
|
+
end
|
48
|
+
|
49
|
+
COMMANDS = ['capture', 'replay', 'transform', 'capture-only']
|
50
|
+
|
51
|
+
OPT = Trollop::with_standard_exception_handling p do
|
52
|
+
opt = p.parse ARGV
|
53
|
+
raise Trollop::HelpNeeded if ARGV.size < 1 || !COMMANDS.include?(ARGV[0])
|
54
|
+
opt
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def check_binary(name, binary)
|
59
|
+
r = `which #{binary}`
|
60
|
+
raise "Could not find #{name} binary at path #{binary}" unless r and r.size > 0
|
61
|
+
end
|
62
|
+
|
63
|
+
class Printer
|
64
|
+
def initialize(capture_file)
|
65
|
+
@counter = 0
|
66
|
+
@fhandle = File.open(capture_file, 'w+')
|
67
|
+
end
|
68
|
+
|
69
|
+
def process_stream stream
|
70
|
+
stream.each do |index, req, resp|
|
71
|
+
body = req.body
|
72
|
+
@fhandle.puts "#{req.path} method=#{req.method} contents=#{body.inspect}"
|
73
|
+
@fhandle.puts "\n"
|
74
|
+
@counter += 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def finalize
|
79
|
+
puts "Number of HTTP Requests : #{@counter}"
|
80
|
+
@fhandle.close
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def transform_pcap(tshark_binary, pcap_file, capture_file)
|
85
|
+
puts "Writing HTTP requests with #{tshark_binary} from #{pcap_file} to #{capture_file}"
|
86
|
+
check_binary('tshark', tshark_binary)
|
87
|
+
|
88
|
+
processor = QReplay::TcpProcessor.new
|
89
|
+
processor.add_stream_processor(PcapTools::TcpStreamRebuilder.new)
|
90
|
+
processor.add_stream_processor(QReplay::HttpExtractor.new)
|
91
|
+
processor.add_stream_processor(Printer.new(capture_file))
|
92
|
+
|
93
|
+
begin
|
94
|
+
PcapTools::Loader::load_file(pcap_file, {}) do |index, packet|
|
95
|
+
begin
|
96
|
+
processor.inject index, packet
|
97
|
+
rescue Exception => e
|
98
|
+
puts "Skipping unparseable request:"
|
99
|
+
puts e.message
|
100
|
+
pp e.backtrace
|
101
|
+
end
|
102
|
+
end
|
103
|
+
rescue Exception => e
|
104
|
+
puts "Exception while parsing tshark output, saving current requests and bailing out."
|
105
|
+
puts e.message
|
106
|
+
pp e.backtrace
|
107
|
+
end
|
108
|
+
|
109
|
+
processor.finalize
|
110
|
+
end
|
111
|
+
|
112
|
+
def capture_pcap(port, pcap_file, capture_time, dumpcap_binary, capture_interface)
|
113
|
+
check_binary('dumpcap', dumpcap_binary)
|
114
|
+
|
115
|
+
puts "Capturing HTTP on port #{port} to file #{pcap_file} for #{capture_time} seconds"
|
116
|
+
cmd = "#{dumpcap_binary} -w #{pcap_file} -f 'tcp port #{port}' -a duration:#{capture_time.to_i}"
|
117
|
+
cmd += " -i #{capture_interface}" if capture_interface
|
118
|
+
puts cmd
|
119
|
+
system(cmd)
|
120
|
+
end
|
121
|
+
|
122
|
+
def replay_httperf(binary, req_sec, total_requests, capture_file, host, port, timeout)
|
123
|
+
check_binary('httperf', binary)
|
124
|
+
thinktime = 0.1
|
125
|
+
cmd = "#{binary} --server=#{host} --port=#{port} --rate=#{req_sec} --verbose --wsesslog=#{total_requests},#{thinktime},#{capture_file} --hog --timeout=#{timeout}"
|
126
|
+
puts cmd
|
127
|
+
system(cmd)
|
128
|
+
end
|
129
|
+
|
130
|
+
command = ARGV[0]
|
131
|
+
case command
|
132
|
+
when 'capture'
|
133
|
+
capture_pcap(OPT[:port], OPT[:pcap_file], OPT[:capture_time], OPT[:dumpcap_binary], OPT[:capture_interface])
|
134
|
+
transform_pcap(OPT[:tshark_binary], OPT[:pcap_file], OPT[:capture_file])
|
135
|
+
when 'capture-only'
|
136
|
+
capture_pcap(OPT[:port], OPT[:pcap_file], OPT[:capture_time], OPT[:dumpcap_binary], OPT[:capture_interface])
|
137
|
+
when 'transform'
|
138
|
+
transform_pcap(OPT[:tshark_binary], OPT[:pcap_file], OPT[:capture_file])
|
139
|
+
when 'replay'
|
140
|
+
replay_httperf(OPT[:httperf_binary], OPT[:req_sec], OPT[:total_requests], OPT[:capture_file], OPT[:host], OPT[:port], OPT[:timeout])
|
141
|
+
end
|
142
|
+
|
data/lib/qreplay.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# This is a modified version of the code found in https://github.com/bpaquet/pcap_tools/blob/master/lib/pcap_tools/stream_processors/http.rb
|
2
|
+
# Modified to handle more HTTP traffic cases.
|
3
|
+
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
module QReplay
|
7
|
+
|
8
|
+
class HttpExtractor
|
9
|
+
|
10
|
+
def process_stream stream
|
11
|
+
calls = []
|
12
|
+
i = 0
|
13
|
+
last_req = nil
|
14
|
+
req = nil
|
15
|
+
|
16
|
+
while i + 1 < stream[:data].size
|
17
|
+
if last_req
|
18
|
+
req = parse_request(last_req, stream[:data][i][:data])
|
19
|
+
last_req = nil
|
20
|
+
req['Expect'] = nil
|
21
|
+
else
|
22
|
+
req = parse_request(stream[:data][i])
|
23
|
+
end
|
24
|
+
|
25
|
+
resp = parse_response(stream[:data][i + 1])
|
26
|
+
|
27
|
+
if req && req['Expect'] && req['Expect'].strip == '100-continue'
|
28
|
+
last_req = stream[:data][i]
|
29
|
+
elsif !req.nil? && !resp.nil?
|
30
|
+
calls << [stream[:index], req, resp]
|
31
|
+
end
|
32
|
+
|
33
|
+
i += 2
|
34
|
+
end
|
35
|
+
|
36
|
+
calls
|
37
|
+
end
|
38
|
+
|
39
|
+
def finalize
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def parse_request stream, separate_body = nil
|
45
|
+
headers, body = split_headers(stream[:data])
|
46
|
+
return nil unless headers
|
47
|
+
body = separate_body if separate_body
|
48
|
+
line0 = headers.shift
|
49
|
+
m = /(\S+)\s+(\S+)\s+(\S+)/.match(line0) or raise "Unable to parse first line of http request #{line0}"
|
50
|
+
clazz = {
|
51
|
+
'POST' => Net::HTTP::Post,
|
52
|
+
'HEAD' => Net::HTTP::Head,
|
53
|
+
'GET' => Net::HTTP::Get,
|
54
|
+
'PUT' => Net::HTTP::Put,
|
55
|
+
'DELETE' => Net::HTTP::Delete
|
56
|
+
}[m[1]] or raise "Unknown http request type [#{m[1]}]"
|
57
|
+
req = clazz.new m[2]
|
58
|
+
req['Pcap-Src'] = stream[:from]
|
59
|
+
req['Pcap-Src-Port'] = stream[:from_port]
|
60
|
+
req['Pcap-Dst'] = stream[:to]
|
61
|
+
req['Pcap-Dst-Port'] = stream[:to_port]
|
62
|
+
req.time = stream[:time]
|
63
|
+
req.body = body
|
64
|
+
req['user-agent'] = nil
|
65
|
+
req['accept'] = nil
|
66
|
+
add_headers req, headers
|
67
|
+
if req['Content-Length']
|
68
|
+
if req.body.bytesize != req['Content-Length'].to_i && (req['Expect'].nil? || req['Expect'].strip != '100-continue')
|
69
|
+
puts "Wrong content-length for http request, header say [#{req['Content-Length'].chomp}], found #{req.body.size}"
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
req
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_response stream
|
77
|
+
headers, body = split_headers(stream[:data])
|
78
|
+
line0 = headers.shift
|
79
|
+
m = /^(\S+)\s+(\S+)\s+(.*)$/.match(line0) or raise "Unable to parse first line of http response [#{line0}]"
|
80
|
+
resp = Net::HTTPResponse.send(:response_class, m[2]).new(m[1], m[2], m[3])
|
81
|
+
resp.time = stream[:time]
|
82
|
+
add_headers resp, headers
|
83
|
+
if resp.chunked?
|
84
|
+
resp.body = read_chunked("\r\n" + body)
|
85
|
+
else
|
86
|
+
resp.body = body
|
87
|
+
if resp['Content-Length']
|
88
|
+
unless resp.body.size == resp['Content-Length'].to_i
|
89
|
+
puts "Wrong content-length for http response, header say [#{resp['Content-Length'].chomp}], found #{resp.body.size}"
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
begin
|
95
|
+
resp.body = Zlib::GzipReader.new(StringIO.new(resp.body)).read if resp['Content-Encoding'] == 'gzip'
|
96
|
+
rescue Zlib::GzipFile::Error
|
97
|
+
warn "Response body is not in gzip: [#{resp.body}]"
|
98
|
+
end
|
99
|
+
resp
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_headers o, headers
|
103
|
+
headers.each do |line|
|
104
|
+
m = /\A([^:]+):\s*/.match(line) or raise "Unable to parse header line [#{line}]"
|
105
|
+
o[m[1]] = m.post_match
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def split_headers str
|
110
|
+
index = str.index("\r\n\r\n")
|
111
|
+
return nil, nil if index.nil?
|
112
|
+
return str[0 .. index].split("\r\n"), str[index + 4 .. -1]
|
113
|
+
end
|
114
|
+
|
115
|
+
def read_chunked str
|
116
|
+
if str.nil? || (str == "\r\n")
|
117
|
+
return ''
|
118
|
+
end
|
119
|
+
m = /\r\n([0-9a-fA-F]+)\r\n/.match(str) or raise "Unable to read chunked body in #{str.split("\r\n")[0]}"
|
120
|
+
len = m[1].hex
|
121
|
+
return '' if len == 0
|
122
|
+
m.post_match[0..len - 1] + read_chunked(m.post_match[len .. -1])
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# This is a modified version of the code found in https://github.com/bpaquet/pcap_tools/blob/master/lib/pcap_tools/packet_processors/tcp.rb.
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module QReplay
|
6
|
+
|
7
|
+
class TcpProcessor
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@streams = {}
|
11
|
+
@stream_processors = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_stream_processor processor
|
15
|
+
@stream_processors << processor
|
16
|
+
end
|
17
|
+
|
18
|
+
def inject index, packet
|
19
|
+
stream_index = packet[:stream]
|
20
|
+
if stream_index
|
21
|
+
if packet[:tcp_flags][:syn] && packet[:tcp_flags][:ack] === false
|
22
|
+
@streams[stream_index] = {
|
23
|
+
:first => packet,
|
24
|
+
:data => [],
|
25
|
+
}
|
26
|
+
elsif packet[:tcp_flags][:fin] || packet[:tcp_flags][:rst]
|
27
|
+
if @streams[stream_index]
|
28
|
+
current = {:index => stream_index, :data => @streams[stream_index][:data]}
|
29
|
+
@stream_processors.each do |p|
|
30
|
+
current = p.process_stream current
|
31
|
+
break unless current
|
32
|
+
end
|
33
|
+
@streams.delete stream_index
|
34
|
+
end
|
35
|
+
else
|
36
|
+
unless @streams[stream_index]
|
37
|
+
@streams[stream_index] = {
|
38
|
+
:first => packet,
|
39
|
+
:data => [],
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
packet[:type] = (packet[:from] == @streams[stream_index][:first][:from] && packet[:from_port] == @streams[stream_index][:first][:from_port]) ? :out : :in
|
44
|
+
packet.delete :tcp_flags
|
45
|
+
@streams[stream_index][:data] << packet if packet[:size] > 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def finalize
|
51
|
+
@streams.each do |k, stream|
|
52
|
+
current = {:index => k, :data => stream[:data]}
|
53
|
+
@stream_processors.each do |p|
|
54
|
+
current = p.process_stream current
|
55
|
+
break unless current
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@stream_processors.each do |p|
|
60
|
+
p.finalize
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
data/qreplay.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path("../lib/qreplay.rb", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.add_runtime_dependency 'pcap_tools'
|
6
|
+
gem.add_runtime_dependency 'trollop'
|
7
|
+
|
8
|
+
gem.authors = ["Peter Bakkum"]
|
9
|
+
gem.bindir = 'bin'
|
10
|
+
gem.description = %q{Capture and replay HTTP traffic}
|
11
|
+
gem.email = ['peter@quizlet.com']
|
12
|
+
gem.executables = ['qreplay']
|
13
|
+
gem.extra_rdoc_files = ['LICENSE.md', 'README.md']
|
14
|
+
gem.files = Dir['LICENSE.md', 'README.md', 'qreplay.gemspec', 'Gemfile', 'bin/*', 'lib/**/*']
|
15
|
+
gem.homepage = 'http://github.com/quizlet/qreplay'
|
16
|
+
gem.name = 'qreplay'
|
17
|
+
gem.rdoc_options = ["--charset=UTF-8"]
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
gem.required_rubygems_version = Gem::Requirement.new(">= 1.3.6")
|
20
|
+
gem.summary = %q{Capture and replay HTTP traffic}
|
21
|
+
gem.version = QReplay::VERSION
|
22
|
+
gem.license = 'MIT'
|
23
|
+
end
|
24
|
+
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: qreplay
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Bakkum
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-03-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: pcap_tools
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: trollop
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Capture and replay HTTP traffic
|
47
|
+
email:
|
48
|
+
- peter@quizlet.com
|
49
|
+
executables:
|
50
|
+
- qreplay
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- LICENSE.md
|
54
|
+
- README.md
|
55
|
+
files:
|
56
|
+
- LICENSE.md
|
57
|
+
- README.md
|
58
|
+
- qreplay.gemspec
|
59
|
+
- Gemfile
|
60
|
+
- bin/qreplay
|
61
|
+
- lib/qreplay/httpextractor.rb
|
62
|
+
- lib/qreplay/tcpprocessor.rb
|
63
|
+
- lib/qreplay/version.rb
|
64
|
+
- lib/qreplay.rb
|
65
|
+
homepage: http://github.com/quizlet/qreplay
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options:
|
70
|
+
- --charset=UTF-8
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 1.3.6
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.8.23.2
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Capture and replay HTTP traffic
|
91
|
+
test_files: []
|