rack_direct 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1 @@
1
+ TBD
@@ -0,0 +1,6 @@
1
+ require 'rack_direct/service'
2
+
3
+ # TODO:
4
+ # when required, run an initializer that plugs into ActiveResource
5
+ # support option to nuke/not nuke the testdb
6
+ # auto-generate name as last element of path if it's not specified?
@@ -0,0 +1,51 @@
1
+ require 'active_resource'
2
+ require 'rack_direct/service'
3
+ require 'rack_direct/direct_response'
4
+
5
+ module RackDirect
6
+
7
+ class ActiveResource::Connection
8
+
9
+ def request_with_filtering_rack_direct(method, path, *arguments)
10
+
11
+ # passthrough anything we don't understand
12
+ return request_without_filtering_rack_direct(method, path, *arguments) unless site.scheme.match(/^rack-direct/)
13
+
14
+ # puts "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
15
+ result = nil
16
+
17
+ headers = arguments.last
18
+ body = arguments.first if arguments.length > 1
19
+
20
+ payload = {
21
+ # Note: We can't pass through a site.scheme of 'rack-direct'
22
+ # because the Rack instance on the receiving end will freak
23
+ # out. So we use http in the URI here.
24
+ "uri" => "http://#{site.host}:#{site.port}#{path}",
25
+ "method" => method.to_s.upcase,
26
+ "body" => body.to_s,
27
+ "CONTENT_TYPE" => headers["Content-Type"] || "text/plain;charset=utf-8",
28
+ }
29
+
30
+ result = JSON.parse(Service.send_request(site.host, payload))
31
+
32
+ result = DirectResponse.new result["status"], result["headers"], result["body"]
33
+
34
+ if Service.verbose_logging
35
+ puts "***** #{result.code} #{result.message}"
36
+ result.each_header { |k,v| puts "***** #{k}: #{v}" }
37
+ puts "***** START BODY"
38
+ puts result.body
39
+ puts "***** END BODY"
40
+ end
41
+
42
+ handle_response(result)
43
+ end
44
+
45
+ # TODO: requiring this more than once will not do the right thing
46
+ alias_method :request_without_filtering_rack_direct, :request
47
+ alias_method :request, :request_with_filtering_rack_direct
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,100 @@
1
+ require 'json'
2
+ require 'pp'
3
+
4
+ module RackDirect
5
+ class DirectHandler
6
+ def self.run(app, options=nil)
7
+ @@verbose = options[:verbose]
8
+ request = []
9
+ while true
10
+ line = STDIN.gets
11
+ if line.blank?
12
+ begin
13
+ break if request[0] == "EXIT" || request[0].blank?
14
+ payload = JSON.parse request[0]
15
+ uri = payload["uri"]
16
+
17
+ #
18
+ # Fix up names in the hash to match what MockRequest is looking for
19
+ payload[:input] = payload["body"]
20
+ payload[:method] = payload["method"]
21
+ payload[:params] = payload["params"]
22
+
23
+ rack_env = Rack::MockRequest.env_for(uri, payload)
24
+ rack_env["rack.errors"] = STDERR
25
+
26
+ self.serve app, rack_env
27
+
28
+ rescue => e
29
+ STDERR.puts "Exception: #{e}"
30
+ e.backtrace.each { |x| STDERR.puts x }
31
+ ensure
32
+ request = []
33
+ end
34
+ else
35
+ request << line.strip
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.serve(app, env)
41
+ if @@verbose
42
+ STDERR.puts "Calling serve"
43
+ STDERR.puts("Rack env:")
44
+ $> = STDERR
45
+ pp rack_env
46
+ $> = STDOUT
47
+ STDERR.puts("Body: #{rack_env["rack.input"].string}")
48
+ end
49
+
50
+ status, headers, body = app.call(env)
51
+
52
+ begin
53
+ body_string = ""
54
+ body.each { |part| body_string += part }
55
+ result = {
56
+ "status" => status,
57
+ "headers" => headers,
58
+ "body" => body_string
59
+ }
60
+
61
+ if @@verbose
62
+ STDERR.puts "Sending result (#{status})"
63
+ STDERR.puts result.to_json
64
+ end
65
+
66
+ unique_id = env["direct_request.unique_id"]
67
+
68
+ STDOUT.puts "BEGIN #{unique_id}"
69
+ STDOUT.puts result.to_json
70
+ STDOUT.puts "END #{unique_id}"
71
+ STDOUT.flush
72
+
73
+ if @@verbose
74
+ send_headers status, headers, STDERR
75
+ send_body body, STDERR
76
+ end
77
+ ensure
78
+ body.close if body.respond_to? :close
79
+ end
80
+ end
81
+
82
+ def self.send_headers(status, headers, file = STDOUT)
83
+ file.print "Status: #{status}\r\n"
84
+ headers.each { |k, vs|
85
+ vs.each { |v|
86
+ file.print "#{k}: #{v}\r\n"
87
+ }
88
+ }
89
+ file.print "\r\n"
90
+ file.flush
91
+ end
92
+
93
+ def self.send_body(body, file = STDOUT)
94
+ body.each { |part|
95
+ file.print part
96
+ file.flush
97
+ }
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ require 'rack'
2
+
3
+ module RackDirect
4
+
5
+ class DirectResponse < Rack::MockResponse
6
+
7
+ include Net::HTTPHeader
8
+
9
+ def initialize(status, headers, body, errors=StringIO.new(""))
10
+ super(status, headers, body, errors)
11
+ # Set up @header to make methods in Net::HTTPHeader work
12
+ @header = {}
13
+ @headers.each do |k,v|
14
+ @header[k.downcase] = [v]
15
+ end
16
+ end
17
+
18
+ def code
19
+ self.status.to_s
20
+ end
21
+
22
+ def message
23
+ if Net::HTTPResponse::CODE_TO_OBJ[self.code]
24
+ Net::HTTPResponse::CODE_TO_OBJ[self.code].to_s.match(/Net::HTTP(.*)/).captures[0].underscore.humanize.titleize
25
+ else
26
+ case self.code
27
+ when /^2/
28
+ 'OK'
29
+ when /^4/
30
+ 'Not Found'
31
+ when /^3/
32
+ 'Redirect'
33
+ else
34
+ 'Error'
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,237 @@
1
+ #
2
+ # Guid - Ruby library for portable GUID/UUID generation.
3
+ #
4
+ # Copyright (c) 2004 David Garamond <davegaramond at icqmail com>
5
+ #
6
+ # This library is free software; you can redistribute it and/or modify it
7
+ # under the same terms as Ruby itself.
8
+ #
9
+
10
+ if RUBY_PLATFORM =~ /mswin32|mingw|cygwin|bccwin32/i
11
+ module Guid_Win32_
12
+ require 'Win32API'
13
+
14
+ PROV_RSA_FULL = 1
15
+ CRYPT_VERIFYCONTEXT = 0xF0000000
16
+ FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
17
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
18
+
19
+ CryptAcquireContext = Win32API.new("advapi32", "CryptAcquireContext",
20
+ 'PPPII', 'L')
21
+ CryptGenRandom = Win32API.new("advapi32", "CryptGenRandom",
22
+ 'LIP', 'L')
23
+ CryptReleaseContext = Win32API.new("advapi32", "CryptReleaseContext",
24
+ 'LI', 'L')
25
+ GetLastError = Win32API.new("kernel32", "GetLastError", '', 'L')
26
+ FormatMessageA = Win32API.new("kernel32", "FormatMessageA",
27
+ 'LPLLPLPPPPPPPP', 'L')
28
+
29
+ def lastErrorMessage
30
+ code = GetLastError.call
31
+ msg = "\0" * 1024
32
+ len = FormatMessageA.call(FORMAT_MESSAGE_IGNORE_INSERTS +
33
+ FORMAT_MESSAGE_FROM_SYSTEM, 0,
34
+ code, 0, msg, 1024, nil, nil,
35
+ nil, nil, nil, nil, nil, nil)
36
+ msg[0, len].tr("\r", '').chomp
37
+ end
38
+
39
+ def initialize
40
+ hProvStr = " " * 4
41
+ if CryptAcquireContext.call(hProvStr, nil, nil, PROV_RSA_FULL,
42
+ CRYPT_VERIFYCONTEXT) == 0
43
+ raise SystemCallError, "CryptAcquireContext failed: #{lastErrorMessage}"
44
+ end
45
+ hProv, = hProvStr.unpack('L')
46
+ @bytes = " " * 16
47
+ if CryptGenRandom.call(hProv, 16, @bytes) == 0
48
+ raise SystemCallError, "CryptGenRandom failed: #{lastErrorMessage}"
49
+ end
50
+ if CryptReleaseContext.call(hProv, 0) == 0
51
+ raise SystemCallError, "CryptReleaseContext failed: #{lastErrorMessage}"
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ module Guid_Unix_
58
+ @@random_device = nil
59
+
60
+ def initialize
61
+ if !@@random_device
62
+ if File.exists? "/dev/urandom"
63
+ @@random_device = File.open "/dev/urandom", "r"
64
+ elsif File.exists? "/dev/random"
65
+ @@random_device = File.open "/dev/random", "r"
66
+ else
67
+ raise RuntimeError, "Can't find random device"
68
+ end
69
+ end
70
+
71
+ @bytes = @@random_device.read(16)
72
+ end
73
+ end
74
+
75
+ class Guid
76
+ if RUBY_PLATFORM =~ /mswin32|mingw|cygwin|bccwin32/
77
+ include Guid_Win32_
78
+ else
79
+ include Guid_Unix_
80
+ end
81
+
82
+ def hexdigest
83
+ @bytes.unpack("h*")[0]
84
+ end
85
+
86
+ alias_method :to_hex , :hexdigest
87
+
88
+ def to_s
89
+ @bytes.unpack("h8 h4 h4 h4 h12").join "-"
90
+ end
91
+
92
+ def inspect
93
+ to_s
94
+ end
95
+
96
+ def raw
97
+ @bytes
98
+ end
99
+
100
+ def self.from_s(s)
101
+ raise ArgumentError, "Invalid GUID hexstring" unless
102
+ s =~ /\A[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}\z/i
103
+ guid = Guid.allocate
104
+ guid.instance_eval { @bytes = [s.gsub(/[^0-9a-f]+/i, '')].pack "h*" }
105
+ guid
106
+ end
107
+
108
+ def self.from_raw(bytes)
109
+ raise ArgumentError, "Invalid GUID raw bytes, length must be 16 bytes" unless
110
+ bytes.length == 16
111
+ guid = Guid.allocate
112
+ guid.instance_eval { @bytes = bytes }
113
+ guid
114
+ end
115
+
116
+ def ==(other)
117
+ @bytes == other.raw
118
+ end
119
+
120
+ # ------------------------------------------------------------------------
121
+ # jambool updates from:
122
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/124607
123
+ @@d36 = ('a'..'z').to_a + ('0'..'9').to_a
124
+ @@rd36 = {}
125
+ @@d36.each_with_index {|d, i| @@rd36[d[0]] = i}
126
+
127
+ def Guid.from_base36(val)
128
+ val = val.downcase
129
+ raise ArgumentError unless val =~ /\A[a-z][a-z0-9]{24}\z/
130
+ n = 0
131
+ mult = 1
132
+ val.reverse.each_byte {|c|
133
+ n += @@rd36[c] * mult
134
+ mult *= 36
135
+ }
136
+ Guid.from_i(n)
137
+ end
138
+
139
+ def Guid.from_hex(s)
140
+ raise ArgumentError, "Invalid GUID hexstring" unless
141
+ s =~ /\A[0-9a-f]{32}\z/i
142
+ guid = Guid.allocate
143
+ guid.instance_eval { @bytes = [s.gsub(/[^0-9a-f]+/i, '')].pack "h*" }
144
+ guid
145
+ end
146
+
147
+
148
+ def Guid.from_i(val)
149
+ bytes = [
150
+ (val & 0xffffffff000000000000000000000000) >> 96,
151
+ (val & 0x00000000ffffffff0000000000000000) >> 64,
152
+ (val & 0x0000000000000000ffffffff00000000) >> 32,
153
+ (val & 0x000000000000000000000000ffffffff)
154
+ ].pack('NNNN')
155
+ guid = Guid.allocate
156
+ guid.instance_eval { @bytes = bytes }
157
+ guid
158
+ end
159
+
160
+ def to_i
161
+ (@bytes[ 0 .. 3].unpack('N')[0] << 96) +
162
+ (@bytes[ 4 .. 7].unpack('N')[0] << 64) +
163
+ (@bytes[ 8 .. 11].unpack('N')[0] << 32) +
164
+ (@bytes[12 .. 15].unpack('N')[0])
165
+ end
166
+
167
+ def to_base36
168
+ self.to_i.to_s(36).tr('0-9a-z', 'a-z0-9').rjust(25, 'a')
169
+ # self.to_s.tr('0-9a-z', 'a-z0-9').rjust(25, 'a')
170
+ end
171
+ # ------------------------------------------------------------------------
172
+ end
173
+
174
+ if __FILE__ == $0
175
+ require 'test/unit'
176
+
177
+ class GuidTest < Test::Unit::TestCase
178
+ def test_new
179
+ g = Guid.new
180
+
181
+ # different representations of guid: hexdigest, hex+dashes, raw bytes
182
+ assert_equal(0, g.to_s =~ /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
183
+ assert_equal(16, g.raw.length)
184
+ assert_equal(0, g.hexdigest =~ /\A[0-9a-f]{32}\z/)
185
+ assert_equal(g.hexdigest, g.to_s.gsub(/-/, ''))
186
+
187
+ # must be different each time we produce (this is just a simple test)
188
+ g2 = Guid.new
189
+ assert_equal(true, g != g2)
190
+ assert_equal(true, g.to_s != g2.to_s)
191
+ assert_equal(true, g.raw != g2.raw)
192
+ assert_equal(true, g.hexdigest != g2.hexdigest)
193
+ assert_equal(1000, (1..1000).select { |i| g != Guid.new }.length)
194
+ end
195
+
196
+ def test_from_s
197
+ g = Guid.new
198
+ g2 = Guid.from_s(g.to_s)
199
+ assert_equal(g, g2)
200
+ end
201
+
202
+ def test_from_raw
203
+ g = Guid.new
204
+ g2 = Guid.from_raw(g.raw)
205
+ assert_equal(g, g2)
206
+ end
207
+
208
+ def test_from_i
209
+ g = Guid.new
210
+ g2 = Guid.from_i(g.to_i)
211
+ assert_equal(g, g2)
212
+ end
213
+
214
+ def test_from_base36
215
+ g = Guid.new
216
+ g2 = Guid.from_base36(g.to_base36)
217
+ assert_equal(g, g2)
218
+ end
219
+
220
+ def test_from_hex
221
+ g = Guid.new
222
+ g2 = Guid.from_hex(g.to_hex)
223
+ assert_equal(g, g2)
224
+ end
225
+
226
+ def test_harder
227
+ g1 = Guid.new
228
+ g2 = g1
229
+ assert_equal(g1, g2)
230
+ g1a = Guid.from_hex(Guid.from_s(Guid.from_i(g1.to_i).to_s).to_hex)
231
+ assert_equal(g1, g1a)
232
+ g2a = Guid.from_base36(Guid.from_i(g1.to_i).to_base36)
233
+ assert_equal(g2, g2a)
234
+ assert_equal(g1a, g2a)
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,103 @@
1
+ require 'rack_direct/guid'
2
+ require 'open3'
3
+ require 'tmpdir'
4
+ require 'rack_direct/active_resource'
5
+
6
+ RACK_DIRECT_ALIAS = 'rack-direct'
7
+
8
+ module RackDirect
9
+
10
+ class Service
11
+
12
+ class << self
13
+ attr_accessor :verbose_logging
14
+ end
15
+
16
+ @@services = {}
17
+ def self.start name, path
18
+ unless @@services[name]
19
+
20
+ tmppath = generate_rackup_file
21
+
22
+ # TODO: check path to make sure a Rails app exists there
23
+ print "Starting service rack-direct://#{name}..."
24
+ cmd = "cd #{path} && rake db:test:prepare && rackup --server #{RACK_DIRECT_ALIAS} #{tmppath} 2>&1"
25
+ # puts cmd
26
+ @@services[name] = IO.popen cmd, "w+"
27
+ puts "done."
28
+
29
+ at_exit do
30
+ RackDirect::Service.stop name
31
+ File.unlink tmppath
32
+ end
33
+ end
34
+ "rack-direct://#{name}"
35
+ end
36
+
37
+ def self.send_request name, rack_request_env
38
+
39
+ if @@services[name]
40
+
41
+ rack_request_env["direct_request.unique_id"] = Guid.new.to_s
42
+
43
+ @@services[name].puts rack_request_env.to_json
44
+ @@services[name].puts ""
45
+
46
+ response = ""
47
+ in_response = false
48
+ while true
49
+ line = @@services[name].gets
50
+ if line.strip == "BEGIN #{rack_request_env["direct_request.unique_id"]}"
51
+ in_response = true
52
+ next
53
+ elsif line.strip == "END #{rack_request_env["direct_request.unique_id"]}"
54
+ break
55
+ elsif in_response
56
+ response += line
57
+ else
58
+ puts "rack-direct://#{name}: #{line.strip}" if self.verbose_logging
59
+ end
60
+ end
61
+ # puts "Final response: #{response}"
62
+ response
63
+ end
64
+
65
+ end
66
+
67
+ def self.stop name
68
+ if @@services[name]
69
+ print "Stopping service rack-direct://#{name}..."
70
+ @@services[name].puts "EXIT"
71
+ @@services[name].puts ""
72
+ @@services[name] = nil
73
+ Process.waitall
74
+ puts "done."
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def self.generate_rackup_file
81
+ rackup_file_contents = <<-EOF
82
+ $: << '~/src/rack-direct/lib'
83
+ require 'rack_direct/direct_handler'
84
+ Rack::Handler.register('#{RACK_DIRECT_ALIAS}', 'RackDirect::DirectHandler')
85
+ # puts "RackDirect::DirectHandler registered"
86
+
87
+ require "config/environment"
88
+ use Rails::Rack::LogTailer
89
+ use Rails::Rack::Static
90
+ run ActionController::Dispatcher.new
91
+ EOF
92
+ tmppath = nil
93
+ Tempfile.open("rack_direct") { |x| tmppath = x.path + ".ru" }
94
+ tmpfile = File.open tmppath, "w+"
95
+ tmpfile.write rackup_file_contents
96
+ tmpfile.close
97
+
98
+ tmppath
99
+ end
100
+
101
+ end
102
+
103
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_direct
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Brian Sharon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-06-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activeresource
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: |
36
+ RackDirect allows you to easily perform integration tests between multiple Rails websites, by launching your ActiveResource services in a standalone process and communicating with them via stdio instead of over a socket.
37
+
38
+ email: brian@floatplane.us
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - README
47
+ - lib/rack_direct/active_resource.rb
48
+ - lib/rack_direct/direct_handler.rb
49
+ - lib/rack_direct/direct_response.rb
50
+ - lib/rack_direct/guid.rb
51
+ - lib/rack_direct/service.rb
52
+ - lib/rack_direct.rb
53
+ has_rdoc: true
54
+ homepage: http://floatplane.us/
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.5
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: RackDirect allows you to easily perform integration tests between multiple Rails websites, by launching your ActiveResource services in a standalone process and communicating with them via stdio instead of over a socket.
81
+ test_files: []
82
+