em-http-monitor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/README.rdoc +31 -0
- data/Rakefile +10 -0
- data/em-http-monitor.gemspec +22 -0
- data/lib/em-http-monitor.rb +244 -0
- data/lib/em-http-monitor/version.rb +7 -0
- data/spec/em_http_monitor_spec.rb +97 -0
- data/spec/fixtures/monitor_dump +4 -0
- metadata +93 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
= Eventmachine HTTP monitor
|
2
|
+
|
3
|
+
This does four things.
|
4
|
+
|
5
|
+
== Print out debug info
|
6
|
+
|
7
|
+
EM::Http::Monitor.debug(STDOUT)
|
8
|
+
# ...
|
9
|
+
EM::Http::Monitor.disconnect
|
10
|
+
|
11
|
+
And you'll get useful debug info
|
12
|
+
|
13
|
+
== Dump requests to disk
|
14
|
+
|
15
|
+
EM::Http::Monitor.dump("/path/to/directory")
|
16
|
+
# ...
|
17
|
+
EM::Http::Monitor.disconnect
|
18
|
+
|
19
|
+
This will dump requests to disk.
|
20
|
+
|
21
|
+
== Defer to disk or dump new
|
22
|
+
|
23
|
+
EM::Http::Monitor.use_and_dump("/path/to/directory")
|
24
|
+
|
25
|
+
This will use fixtures if found, or, dump new requests to the file.
|
26
|
+
|
27
|
+
== Only use dumped requests
|
28
|
+
|
29
|
+
EM::Http::Monitor.use("/path/to/directory")
|
30
|
+
|
31
|
+
This will only use fixtures if found, or, errback.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/em-http-monitor/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "em-http-monitor"
|
6
|
+
s.version = EM::Http::MonitorFactory::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = []
|
9
|
+
s.email = []
|
10
|
+
s.homepage = "http://rubygems.org/gems/em-http-monitor"
|
11
|
+
s.summary = "Monitor, request recording and playback for em-http-request"
|
12
|
+
s.description = "Monitor, request recording and playback for em-http-request"
|
13
|
+
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
s.rubyforge_project = "em-http-monitor"
|
16
|
+
|
17
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
21
|
+
s.require_path = 'lib'
|
22
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'em-http'
|
3
|
+
require 'json'
|
4
|
+
require 'ftools'
|
5
|
+
|
6
|
+
module EventMachine
|
7
|
+
module Http
|
8
|
+
class MonitorFactory
|
9
|
+
|
10
|
+
MockNotFoundError = Class.new(RuntimeError)
|
11
|
+
|
12
|
+
OriginalHttpRequest = EM::HttpRequest unless const_defined?(:OriginalHttpRequest)
|
13
|
+
MockHttpRequest = EM::MockHttpRequest unless const_defined?(:MockHttpRequest)
|
14
|
+
|
15
|
+
class FakeHttpClient < EM::HttpClient
|
16
|
+
attr_writer :response
|
17
|
+
attr_reader :data
|
18
|
+
def setup(response, uri)
|
19
|
+
@uri = uri
|
20
|
+
if response == :fail
|
21
|
+
fail(self)
|
22
|
+
else
|
23
|
+
original_receive_data(response)
|
24
|
+
succeed(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def unbind
|
29
|
+
end
|
30
|
+
|
31
|
+
def close_connection
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug(out = STDERR)
|
36
|
+
activate
|
37
|
+
install StreamOutputter.new(out)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dump(path)
|
41
|
+
activate
|
42
|
+
install FileOutputter.new(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def clean_and_dump(path)
|
46
|
+
activate
|
47
|
+
outputter = FileOutputter.new(path)
|
48
|
+
outputter.reset
|
49
|
+
install outputter
|
50
|
+
end
|
51
|
+
|
52
|
+
def use_and_dump(path)
|
53
|
+
activate
|
54
|
+
install MockingFileOutputter.new(path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def use(path)
|
58
|
+
activate
|
59
|
+
install FailingMockingFileOutputter.new(path)
|
60
|
+
end
|
61
|
+
|
62
|
+
def deactivate
|
63
|
+
decativate_mocks
|
64
|
+
deactivate_methods
|
65
|
+
end
|
66
|
+
|
67
|
+
def inform_receive(client, data)
|
68
|
+
@app.receive(client, data)
|
69
|
+
end
|
70
|
+
|
71
|
+
def inform_send(client, data)
|
72
|
+
@app.send(client, data)
|
73
|
+
end
|
74
|
+
|
75
|
+
def install(app)
|
76
|
+
@app = app
|
77
|
+
end
|
78
|
+
|
79
|
+
def installed
|
80
|
+
@app
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def decativate_mocks
|
86
|
+
end
|
87
|
+
|
88
|
+
def activate
|
89
|
+
EM::HttpClient.send(:class_eval, "
|
90
|
+
unless method_defined?(:original_receive_data)
|
91
|
+
alias_method :original_receive_data, :receive_data
|
92
|
+
alias_method :original_send_data, :send_data
|
93
|
+
|
94
|
+
def receive_data(data)
|
95
|
+
EM::Http::Monitor.inform_receive(self, data) and original_receive_data(data)
|
96
|
+
end
|
97
|
+
|
98
|
+
def send_data(data)
|
99
|
+
EM::Http::Monitor.inform_send(self, data) and original_send_data(data)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
", __FILE__, __LINE__)
|
103
|
+
|
104
|
+
EM::HttpRequest.send(:class_eval, <<-HERE_DOC, __FILE__, __LINE__)
|
105
|
+
unless method_defined?(:original_send_request)
|
106
|
+
include HttpEncoding
|
107
|
+
|
108
|
+
alias_method :original_send_request, :send_request
|
109
|
+
def send_request(&blk)
|
110
|
+
uri = encode_query(@req.uri, @req.options[:query])
|
111
|
+
verb = @req.method
|
112
|
+
host = @req.uri.host
|
113
|
+
if EM::Http::Monitor.installed.respond_to?(:find_mock) and data = EM::Http::Monitor.installed.find_mock(verb, uri, host)
|
114
|
+
client = FakeHttpClient.new(nil)
|
115
|
+
client.setup(data, @req.uri)
|
116
|
+
client
|
117
|
+
else
|
118
|
+
original_send_request(&blk)
|
119
|
+
end
|
120
|
+
rescue MockNotFoundError
|
121
|
+
client = FakeHttpClient.new(nil)
|
122
|
+
client.setup(:fail, @req.uri)
|
123
|
+
client
|
124
|
+
end
|
125
|
+
end
|
126
|
+
HERE_DOC
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
def deactivate_methods
|
131
|
+
EM::HttpClient.send(:class_eval, "
|
132
|
+
if method_defined?(:original_receive_data)
|
133
|
+
alias_method :receive_data, :original_receive_data
|
134
|
+
alias_method :send_data, :original_send_data
|
135
|
+
undef_method :original_receive_data
|
136
|
+
undef_method :original_send_data
|
137
|
+
end
|
138
|
+
", __FILE__, __LINE__)
|
139
|
+
EM::HttpRequest.send(:class_eval, "
|
140
|
+
if method_defined?(:original_send_request)
|
141
|
+
alias_method :send_request, :original_send_request
|
142
|
+
undef_method :original_send_request
|
143
|
+
end
|
144
|
+
", __FILE__, __LINE__)
|
145
|
+
end
|
146
|
+
|
147
|
+
class StreamOutputter
|
148
|
+
def initialize(stream)
|
149
|
+
@stream = stream
|
150
|
+
@stream.sync = true
|
151
|
+
end
|
152
|
+
|
153
|
+
def send(client, data)
|
154
|
+
@stream << "#{client.object_id} >> #{data}"
|
155
|
+
@stream << "\n" unless data[-1] == 13 or data[-1] == 10
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
def receive(client, data)
|
160
|
+
@stream << "#{client.object_id} << #{data}"
|
161
|
+
@stream << "\n" unless data[-1] == 13 or data[-1] == 10
|
162
|
+
true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class FileOutputter
|
167
|
+
def initialize(file)
|
168
|
+
@file = file
|
169
|
+
@fh = File.new(file, 'a')
|
170
|
+
@fh.sync = true
|
171
|
+
end
|
172
|
+
|
173
|
+
def reset
|
174
|
+
File.truncate(@file, 0)
|
175
|
+
end
|
176
|
+
|
177
|
+
def send(client, data)
|
178
|
+
@fh.puts({:mode => 'send', :id => client.object_id, :data => data}.to_json)
|
179
|
+
@fh.fsync
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
def receive(client, data)
|
184
|
+
@fh.puts({:mode => 'receive', :id => client.object_id, :data => data}.to_json)
|
185
|
+
@fh.fsync
|
186
|
+
true
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class MockingFileOutputter < FileOutputter
|
191
|
+
|
192
|
+
attr_reader :file
|
193
|
+
|
194
|
+
class MockingData
|
195
|
+
attr_reader :sent, :received
|
196
|
+
|
197
|
+
def initialize
|
198
|
+
@sent, @received = '', ''
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
def initialize(file)
|
204
|
+
@file = file
|
205
|
+
if File.exist?(file)
|
206
|
+
@mock_data = File.read(file).each_line.inject([]) do |hash, line|
|
207
|
+
data = JSON.parse(line)
|
208
|
+
if entry = hash.assoc(data['id'])
|
209
|
+
entry = entry.last
|
210
|
+
else
|
211
|
+
entry = MockingData.new
|
212
|
+
hash << [data['id'], entry]
|
213
|
+
end
|
214
|
+
case data['mode']
|
215
|
+
when 'send'
|
216
|
+
entry.sent << data['data']
|
217
|
+
when 'receive'
|
218
|
+
entry.received << data['data']
|
219
|
+
else
|
220
|
+
raise
|
221
|
+
end
|
222
|
+
hash
|
223
|
+
end
|
224
|
+
end
|
225
|
+
super
|
226
|
+
end
|
227
|
+
|
228
|
+
def find_mock(verb, uri, host)
|
229
|
+
if @mock_data and matching_data = @mock_data.find{ |d| d.last.sent[/^#{Regexp.quote(verb.to_s.upcase)} #{Regexp.quote(uri)} HTTP\/1\.[01][\r\n]/] and d.last.sent[/(Host:\s*#{Regexp.quote(host)})[\r\n]/] }
|
230
|
+
@mock_data.delete(matching_data).last.received
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class FailingMockingFileOutputter < MockingFileOutputter
|
236
|
+
def find_mock(verb, uri, host)
|
237
|
+
super or raise(MockNotFoundError, "Cannot find mock for #{verb} #{uri} #{host} in #{file}")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
EM::Http::Monitor = EM::Http::MonitorFactory.new
|
@@ -0,0 +1,97 @@
|
|
1
|
+
$: << "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'em-http-monitor'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
DUMP_FILE = '/tmp/monitor_dump'
|
9
|
+
|
10
|
+
describe "em-http-monitor" do
|
11
|
+
after(:each) do
|
12
|
+
EM::Http::Monitor.deactivate
|
13
|
+
FileUtils.rm_rf(DUMP_FILE)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should print debug info to the stream" do
|
17
|
+
io = StringIO.new
|
18
|
+
EM::Http::Monitor.debug(io)
|
19
|
+
EM.run do
|
20
|
+
http = EventMachine::HttpRequest.new("http://www.google.ca/").get :timeout => 2
|
21
|
+
|
22
|
+
http.callback {
|
23
|
+
io.rewind
|
24
|
+
data = io.read
|
25
|
+
data.should match(/>> GET \/ HTTP\/1\.1\r\n/)
|
26
|
+
data.should match(/<< HTTP\/1\.1 200 OK\r\n/)
|
27
|
+
EM.stop
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should leave methods behind when it cleans up" do
|
33
|
+
EM::Http::Monitor.debug(STDOUT)
|
34
|
+
EM::HttpClient.instance_methods.should include('original_receive_data')
|
35
|
+
EM::Http::Monitor.deactivate
|
36
|
+
EM::HttpClient.instance_methods.should_not include('original_receive_data')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should dump requests to disk" do
|
40
|
+
EM::Http::Monitor.dump(DUMP_FILE)
|
41
|
+
EM.run do
|
42
|
+
http = EventMachine::HttpRequest.new("http://www.google.ca/").get :timeout => 2
|
43
|
+
|
44
|
+
http.callback {
|
45
|
+
lines = File.read(DUMP_FILE).split("\n").map{|line| JSON.parse(line)}
|
46
|
+
received_line = lines.shift
|
47
|
+
received_line['mode'].should == 'send'
|
48
|
+
received_line['data'].should match(/GET \/ HTTP\/1\.1\r\n/)
|
49
|
+
lines.size.should >= 1
|
50
|
+
lines.each {|line| line['mode'].should == 'receive' }
|
51
|
+
lines.first['data'].should match(/HTTP\/1\.1 200 OK\r\n/)
|
52
|
+
EM.stop
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should use dumps to mock requests" do
|
58
|
+
EM::Http::Monitor.use_and_dump("#{File.dirname(__FILE__)}/fixtures/monitor_dump")
|
59
|
+
EM.run do
|
60
|
+
http = EventMachine::HttpRequest.new("http://news.google.com/").get :timeout => 10
|
61
|
+
http.callback {
|
62
|
+
http.response.should == 'yupyupyup'
|
63
|
+
http2 = EventMachine::HttpRequest.new("http://www.google.ca/").get :timeout => 10
|
64
|
+
http2.callback {
|
65
|
+
http2.response.should == 'heyheyhey'
|
66
|
+
EM.stop
|
67
|
+
}
|
68
|
+
http2.errback { |err|
|
69
|
+
p err
|
70
|
+
fail
|
71
|
+
EM.stop
|
72
|
+
}
|
73
|
+
}
|
74
|
+
http.errback { |err|
|
75
|
+
p err
|
76
|
+
fail
|
77
|
+
EM.stop
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should only use dumps to mock requests" do
|
83
|
+
EM::Http::Monitor.use("#{File.dirname(__FILE__)}/fixtures/monitor_dump")
|
84
|
+
EM.run do
|
85
|
+
http = EventMachine::HttpRequest.new("http://www.slashdot.org/").get :timeout => 10
|
86
|
+
http.callback {
|
87
|
+
fail
|
88
|
+
EM.stop
|
89
|
+
}
|
90
|
+
http.errback { |err|
|
91
|
+
1.should == 1
|
92
|
+
EM.stop
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
{"mode":"send","data":"GET / HTTP/1.1\r\nUser-Agent: EventMachine HttpClient\r\nHost: www.google.ca\r\n\r\n","id":2156920860}
|
2
|
+
{"mode":"receive","data":"HTTP/1.1 200 OK\r\nDate: Wed, 22 Sep 2010 06:43:25 GMT\r\nContent-length: 9\r\n\r\nheyheyhey","id":2156920860}
|
3
|
+
{"mode":"send","data":"GET / HTTP/1.1\r\nUser-Agent: EventMachine HttpClient\r\nHost: news.google.com\r\n\r\n","id":2150286760}
|
4
|
+
{"mode":"receive","data":"HTTP/1.1 200 OK\r\nDate: Wed, 22 Sep 2010 06:43:25 GMT\r\nContent-length: 9\r\n\r\nyupyupyup","id":2150286760}
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-http-monitor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors: []
|
13
|
+
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-24 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bundler
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
description: Monitor, request recording and playback for em-http-request
|
38
|
+
email: []
|
39
|
+
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- Gemfile
|
49
|
+
- README.rdoc
|
50
|
+
- Rakefile
|
51
|
+
- em-http-monitor.gemspec
|
52
|
+
- lib/em-http-monitor.rb
|
53
|
+
- lib/em-http-monitor/version.rb
|
54
|
+
- spec/em_http_monitor_spec.rb
|
55
|
+
- spec/fixtures/monitor_dump
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://rubygems.org/gems/em-http-monitor
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 23
|
80
|
+
segments:
|
81
|
+
- 1
|
82
|
+
- 3
|
83
|
+
- 6
|
84
|
+
version: 1.3.6
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project: em-http-monitor
|
88
|
+
rubygems_version: 1.3.7
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Monitor, request recording and playback for em-http-request
|
92
|
+
test_files: []
|
93
|
+
|