rack_direct 0.1.2

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/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
+