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 ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in em-http-monitor.gemspec
4
+ gemspec
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,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'spec/rake/spectask'
6
+
7
+ desc "Run all specs"
8
+ Spec::Rake::SpecTask.new('spec') do |t|
9
+ t.spec_files = FileList['spec/**/*.rb']
10
+ end
@@ -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,7 @@
1
+ module EM
2
+ module Http
3
+ class MonitorFactory
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -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
+