em-http-monitor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+