swiftiply 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/README +126 -0
  2. data/bin/mongrel_rails +254 -0
  3. data/bin/swiftiply +136 -0
  4. data/bin/swiftiply_mongrel_rails +54 -0
  5. data/external/package.rb +672 -0
  6. data/external/test_support.rb +58 -0
  7. data/setup.rb +40 -0
  8. data/src/ramaze/adapter/evented_mongrel.rb +2 -0
  9. data/src/ramaze/adapter/swiftiplied_mongrel.rb +2 -0
  10. data/src/swiftcore/Swiftiply.rb +444 -0
  11. data/src/swiftcore/Swiftiply.rb.orig +390 -0
  12. data/src/swiftcore/evented_mongrel.rb +182 -0
  13. data/src/swiftcore/swiftiplied_mongrel.rb +212 -0
  14. data/swiftiply.gemspec +41 -0
  15. data/test/rails/README +182 -0
  16. data/test/rails/Rakefile +10 -0
  17. data/test/rails/app/controllers/application.rb +6 -0
  18. data/test/rails/app/controllers/tests_controller.rb +15 -0
  19. data/test/rails/app/helpers/application_helper.rb +3 -0
  20. data/test/rails/config/boot.rb +45 -0
  21. data/test/rails/config/database.yml +36 -0
  22. data/test/rails/config/environment.rb +60 -0
  23. data/test/rails/config/environments/development.rb +21 -0
  24. data/test/rails/config/environments/production.rb +18 -0
  25. data/test/rails/config/environments/production_no_caching.rb +18 -0
  26. data/test/rails/config/environments/test.rb +19 -0
  27. data/test/rails/config/routes.rb +23 -0
  28. data/test/rails/doc/README_FOR_APP +2 -0
  29. data/test/rails/observe_ram.rb +10 -0
  30. data/test/rails/public/404.html +30 -0
  31. data/test/rails/public/500.html +30 -0
  32. data/test/rails/public/dispatch.cgi +10 -0
  33. data/test/rails/public/dispatch.fcgi +24 -0
  34. data/test/rails/public/dispatch.rb +10 -0
  35. data/test/rails/public/favicon.ico +0 -0
  36. data/test/rails/public/images/rails.png +0 -0
  37. data/test/rails/public/index.html +277 -0
  38. data/test/rails/public/javascripts/application.js +2 -0
  39. data/test/rails/public/javascripts/controls.js +833 -0
  40. data/test/rails/public/javascripts/dragdrop.js +942 -0
  41. data/test/rails/public/javascripts/effects.js +1088 -0
  42. data/test/rails/public/javascripts/prototype.js +2515 -0
  43. data/test/rails/public/robots.txt +1 -0
  44. data/test/rails/script/about +3 -0
  45. data/test/rails/script/breakpointer +3 -0
  46. data/test/rails/script/console +3 -0
  47. data/test/rails/script/destroy +3 -0
  48. data/test/rails/script/generate +3 -0
  49. data/test/rails/script/performance/benchmarker +3 -0
  50. data/test/rails/script/performance/profiler +3 -0
  51. data/test/rails/script/plugin +3 -0
  52. data/test/rails/script/process/inspector +3 -0
  53. data/test/rails/script/process/reaper +3 -0
  54. data/test/rails/script/process/spawner +3 -0
  55. data/test/rails/script/runner +3 -0
  56. data/test/rails/script/server +3 -0
  57. data/test/rails/test/test_helper.rb +28 -0
  58. data/test/ramaze/conf/benchmark.yaml +35 -0
  59. data/test/ramaze/conf/debug.yaml +34 -0
  60. data/test/ramaze/conf/live.yaml +33 -0
  61. data/test/ramaze/conf/silent.yaml +31 -0
  62. data/test/ramaze/conf/stage.yaml +33 -0
  63. data/test/ramaze/main.rb +18 -0
  64. data/test/ramaze/public/404.jpg +0 -0
  65. data/test/ramaze/public/css/coderay.css +105 -0
  66. data/test/ramaze/public/css/ramaze_error.css +42 -0
  67. data/test/ramaze/public/error.zmr +77 -0
  68. data/test/ramaze/public/favicon.ico +0 -0
  69. data/test/ramaze/public/js/jquery.js +1923 -0
  70. data/test/ramaze/public/ramaze.png +0 -0
  71. data/test/ramaze/src/controller/main.rb +8 -0
  72. data/test/ramaze/src/element/page.rb +17 -0
  73. data/test/ramaze/src/model.rb +6 -0
  74. data/test/ramaze/template/index.xhtml +6 -0
  75. data/test/ramaze/yaml.db +0 -0
  76. metadata +189 -0
@@ -0,0 +1,58 @@
1
+ # A support module for the test suite. This provides a win32 aware
2
+ # mechanism for doing fork/exec operations. It requires win32/process
3
+ # to be installed, however.
4
+ #
5
+ module SwiftcoreTestSupport
6
+ @run_modes = []
7
+
8
+ def self.create_process(args)
9
+ @fork_ok = true unless @fork_ok == false
10
+ pid = nil
11
+ begin
12
+ raise NotImplementedError unless @fork_ok
13
+ unless pid = fork
14
+ Dir.chdir args[:dir]
15
+ exec(*args[:cmd])
16
+ end
17
+ rescue NotImplementedError
18
+ @fork_ok = false
19
+ begin
20
+ require 'rubygems'
21
+ rescue LoadError
22
+ end
23
+
24
+ begin
25
+ require 'win32/process'
26
+ rescue LoadError
27
+ raise "Please install win32-process to run all tests on a Win32 platform. 'gem install win32-process' or http://rubyforge.org/projects/win32utils"
28
+ end
29
+ cwd = Dir.pwd
30
+ Dir.chdir args[:dir]
31
+ pid = Process.create(:app_name => args[:cmd].join(' '))
32
+ Dir.chdir cwd
33
+ end
34
+ pid
35
+ end
36
+
37
+ def self.test_dir(dir)
38
+ File.dirname(File.expand_path(dir))
39
+ end
40
+
41
+ def self.cd_to_test_dir(dir)
42
+ Dir.chdir(File.dirname(File.expand_path(dir)))
43
+ end
44
+
45
+ def self.set_src_dir
46
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../src'))
47
+ end
48
+
49
+ @announcements = {}
50
+ def self.announce(section,msg)
51
+ unless @announcements.has_key?(section)
52
+ puts "\n\n"
53
+ puts msg,"#{'=' * msg.length}\n\n"
54
+ @announcements[section] = true
55
+ end
56
+ end
57
+
58
+ end
data/setup.rb ADDED
@@ -0,0 +1,40 @@
1
+ #!ruby
2
+
3
+ basedir = File.dirname(__FILE__)
4
+ $:.push(basedir)
5
+ require 'external/package'
6
+ require 'rbconfig'
7
+ begin
8
+ require 'rubygems'
9
+ rescue LoadError
10
+ end
11
+
12
+ Dir.chdir(basedir)
13
+ Package.setup("1.0") {
14
+ name "Swiftcore Swiftiply"
15
+
16
+ translate(:lib, 'src/' => '')
17
+ translate(:bin, 'bin/' => '')
18
+ lib(*Dir["src/swiftcore/**/*.rb"])
19
+ lib("src/swiftcore/evented_mongrel.rb")
20
+ lib("src/swiftcore/swiftiplied_mongrel.rb")
21
+ ri(*Dir["src/swiftcore/**/*.rb"])
22
+ bin "bin/swiftiply"
23
+ bin "bin/swiftiply_mongrel_rails"
24
+ #File.rename("#{Config::CONFIG["bindir"]}/mongrel_rails","#{Config::CONFIG["bindir"]}/mongrel_rails.orig")
25
+ bin "bin/mongrel_rails"
26
+
27
+ # Install Ramaze libs if Ramaze is installed.
28
+
29
+ begin
30
+ require 'ramaze'
31
+ lib("src/ramaze/adapter/evented_mongrel.rb")
32
+ lib("src/ramaze/adapter/swiftiplied_mongrel.rb")
33
+ rescue LoadError
34
+ # Ramaze not installed
35
+ end
36
+
37
+ # unit_test "test/TC_Swiftiply.rb"
38
+
39
+ true
40
+ }
@@ -0,0 +1,2 @@
1
+ require 'swiftcore/evented_mongrel'
2
+ require 'ramaze/adapter/mongrel'
@@ -0,0 +1,2 @@
1
+ require 'swiftcore/swiftiplied_mongrel'
2
+ require 'ramaze/adapter/mongrel'
@@ -0,0 +1,444 @@
1
+ begin
2
+ load_attempted ||= false
3
+ require 'digest/sha2'
4
+ require 'eventmachine'
5
+ rescue LoadError => e
6
+ unless load_attempted
7
+ load_attempted = true
8
+ require 'rubygems'
9
+ end
10
+ raise e
11
+ end
12
+
13
+ module Swiftcore
14
+ module Swiftiply
15
+ Ccluster_address = 'cluster_address'.freeze
16
+ Ccluster_port = 'cluster_port'.freeze
17
+ CBackendAddress = 'BackendAddress'.freeze
18
+ CBackendPort = 'BackendPort'.freeze
19
+ Cmap = 'map'.freeze
20
+ Cincoming = 'incoming'.freeze
21
+ Ckeepalive = 'keepalive'.freeze
22
+ Cdaemonize = 'daemonize'.freeze
23
+ Curl = 'url'.freeze
24
+ Chost = 'host'.freeze
25
+ Cport = 'port'.freeze
26
+ Coutgoing = 'outgoing'.freeze
27
+ Ctimeout = 'timeout'.freeze
28
+ Cdefault = 'default'.freeze
29
+
30
+ # The ProxyBag is a class that holds the client and the server queues,
31
+ # and that is responsible for managing them, matching them, and expiring
32
+ # them, if necessary.
33
+
34
+ class ProxyBag
35
+ @client_q = Hash.new {|h,k| h[k] = []}
36
+ @server_q = Hash.new {|h,k| h[k] = []}
37
+ @ctime = Time.now
38
+ @server_unavailable_timeout = 6
39
+ @id_map = {}
40
+ @reverse_id_map = {}
41
+ @incoming_map = {}
42
+ @demanding_clients = Hash.new {|h,k| h[k] = []}
43
+
44
+ class << self
45
+
46
+ def now
47
+ @ctime
48
+ end
49
+
50
+ # Returns the access key. If an access key is set, then all new backend
51
+ # connections must send the correct access key before being added to
52
+ # the cluster as a valid backend.
53
+
54
+ def key
55
+ @key
56
+ end
57
+
58
+ # Sets the access key.
59
+
60
+ def key=(val)
61
+ @key = val
62
+ end
63
+
64
+ def add_id(who,what)
65
+ @id_map[who] = what
66
+ @reverse_id_map[what] = who
67
+ end
68
+
69
+ def remove_id(who)
70
+ what = @id_map.delete(who)
71
+ @reverse_id_map.delete(what)
72
+ end
73
+
74
+ def add_incoming_mapping(hashcode,name)
75
+ @incoming_map[name] = hashcode
76
+ end
77
+
78
+ def default_name
79
+ @default_name
80
+ end
81
+
82
+ def default_name=(val)
83
+ @default_name = val
84
+ end
85
+
86
+ # This timeout is the amount of time a connection will sit in queue
87
+ # waiting for a backend to process it.
88
+
89
+ def server_unavailable_timeout
90
+ @server_unavailable_timeout
91
+ end
92
+
93
+ # Sets the server unavailable timeout value.
94
+
95
+ def server_unavailable_timeout=(val)
96
+ @server_unavailable_timeout = val
97
+ end
98
+
99
+ # Pushes a front end client (web browser) into the queue of clients
100
+ # waiting to be serviced if there's no server available to handle
101
+ # it right now.
102
+
103
+ def add_frontend_client clnt
104
+ clnt.create_time = @ctime
105
+ unless match_client_to_server_now(clnt)
106
+ if clnt.uri =~ /\w+-\w+-\w+\.\w+\.[\w\.]+-(\w+)?$/
107
+ @demanding_clients[$1].unshift clnt
108
+ else
109
+ @client_q[clnt.name].unshift(clnt)
110
+ end
111
+ end
112
+ end
113
+
114
+ # Pushes a backend server into the queue of servers waiting for a
115
+ # client to service if there are no clients waiting to be serviced.
116
+
117
+ def add_server srvr
118
+ @server_q[srvr.name].unshift(srvr) unless match_server_to_client_now(srvr)
119
+ end
120
+
121
+ # Deletes the provided server from the server queue.
122
+
123
+ def remove_server srvr
124
+ @server_q[srvr.name].delete srvr
125
+ end
126
+
127
+ # Removes the named client from the client queue.
128
+ # TODO: Try replacing this with a linked list. Performance
129
+ # here has to suck when the list is long.
130
+
131
+ def remove_client clnt
132
+ @client_q[clnt.name].delete clnt
133
+ end
134
+
135
+ # Walks through the client and server queues, matching
136
+ # waiting clients with waiting servers until the queue
137
+ # runs out of one or the other. DEPRECATED
138
+
139
+ #def match_clients_to_servers
140
+ # while @server_q.first && @client_q.first
141
+ # server = @server_q.pop
142
+ # client = @client_q.pop
143
+ # server.associate = client
144
+ # client.associate = server
145
+ # client.push
146
+ # end
147
+ #end
148
+
149
+ # Tries to match the client passed as an argument to a
150
+ # server.
151
+
152
+ def match_client_to_server_now(client)
153
+ sq = @server_q[@incoming_map[client.name]]
154
+ if client.uri =~ /\w+-\w+-\w+\.\w+\.[\w\.]+-(\w+)?$/ and sidx = sq.index(@reverse_id_map[$1])
155
+ server = sq.delete_at(sidx)
156
+ server.associate = client
157
+ client.associate = server
158
+ client.push
159
+ true
160
+ elsif server = sq.pop
161
+ server.associate = client
162
+ client.associate = server
163
+ client.push
164
+ true
165
+ else
166
+ false
167
+ end
168
+ end
169
+
170
+ # Tries to match the server passed as an argument to a
171
+ # client.
172
+
173
+ def match_server_to_client_now(server)
174
+ if client = @demanding_clients[server.id].pop
175
+ server.associate = client
176
+ client.associate = server
177
+ client.push
178
+ true
179
+ elsif client = @client_q[server.name].pop
180
+ server.associate = client
181
+ client.associate = server
182
+ client.push
183
+ true
184
+ else
185
+ false
186
+ end
187
+ end
188
+
189
+ # Walk through the waiting clients if there is no server
190
+ # available to process clients and expire any clients that
191
+ # have been waiting longer than @server_unavailable_timeout
192
+ # seconds. Clients which are expired will receive a 503
193
+ # response.
194
+
195
+ def expire_clients
196
+ now = Time.now
197
+ @server_q.each_key do |name|
198
+ unless @server_q[name].first
199
+ while c = @client_q[name].pop
200
+ if (now - c.create_time) >= @server_unavailable_timeout
201
+ c.send_503_response
202
+ else
203
+ @client_q[name].push c
204
+ break
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ # This is called by a periodic timer once a second to update
212
+ # the time.
213
+
214
+ def update_ctime
215
+ @ctime = Time.now
216
+ end
217
+
218
+ end
219
+ end
220
+
221
+ # The ClusterProtocol is the subclass of EventMachine::Connection used
222
+ # to communicate between Swiftiply and the web browser clients.
223
+
224
+ class ClusterProtocol < EventMachine::Connection
225
+
226
+ attr_accessor :create_time, :associate, :name
227
+
228
+ Crn = "\r\n".freeze
229
+ Crnrn = "\r\n\r\n".freeze
230
+ Rrnrn = /\r\n\r\n/
231
+ R_colon = /:/
232
+ C_blank = ''.freeze
233
+
234
+ # Initialize the @data array, which is the temporary storage for blocks
235
+ # of data received from the web browser client, then invoke the superclass
236
+ # initialization.
237
+
238
+ def initialize *args
239
+ @data = []
240
+ @name = nil
241
+ @uri = nil
242
+ super
243
+ end
244
+
245
+ def receive_data data
246
+ @data.unshift data
247
+ if @name
248
+ push
249
+ else
250
+ data =~ /\s([^\s\?]*)/
251
+ @uri ||= $1
252
+ if data =~ /^Host:\s*([^\r\n:]*)/
253
+ @name = $1
254
+ ProxyBag.add_frontend_client self
255
+ push
256
+ elsif data.index(/\r\n\r\n/)
257
+ @name = ProxyBag.default_name
258
+ ProxyBag.add_frontend_client self
259
+ push
260
+ end
261
+ end
262
+ end
263
+
264
+ # Hardcoded 503 response that is sent if a connection is timed out while
265
+ # waiting for a backend to handle it.
266
+
267
+ def send_503_response
268
+ send_data [
269
+ "HTTP/1.0 503 Server Unavailable\r\n",
270
+ "Content-type: text/plain\r\n",
271
+ "Connection: close\r\n",
272
+ "\r\n",
273
+ "Server Unavailable"
274
+ ].join
275
+ close_connection_after_writing
276
+ end
277
+
278
+ # Push data from the web browser client to the backend server process.
279
+
280
+ def push
281
+ if @associate
282
+ while data = @data.pop
283
+ @associate.send_data data
284
+ end
285
+ end
286
+ end
287
+
288
+ # The connection with the web browser client has been closed, so the
289
+ # object must be removed from the ProxyBag's queue if it is has not
290
+ # been associated with a backend. If it has already been associated
291
+ # with a backend, then it will not be in the queue and need not be
292
+ # removed.
293
+
294
+ def unbind
295
+ ProxyBag.remove_client(self) unless @associate
296
+ end
297
+
298
+ def uri
299
+ @uri
300
+ end
301
+
302
+ end
303
+
304
+
305
+ # The BackendProtocol is the EventMachine::Connection subclass that
306
+ # handles the communications between Swiftiply and the backend process
307
+ # it is proxying to.
308
+
309
+ class BackendProtocol < EventMachine::Connection
310
+ attr_accessor :associate, :id
311
+
312
+ Crnrn = "\r\n\r\n".freeze
313
+ Rrnrn = /\r\n\r\n/
314
+
315
+ def initialize *args
316
+ @name = self.class.bname
317
+ super
318
+ end
319
+
320
+ def name
321
+ @name
322
+ end
323
+
324
+ # Call setup() and add the backend to the ProxyBag queue.
325
+
326
+ def post_init
327
+ setup
328
+ @initialized = nil
329
+ ProxyBag.add_server self
330
+ end
331
+
332
+ # Setup the initial variables for receiving headers and content.
333
+
334
+ def setup
335
+ @headers = ''
336
+ @headers_completed = false
337
+ @content_length = nil
338
+ @content_sent = 0
339
+ end
340
+
341
+ # Receive data from the backend process. Headers are parsed from
342
+ # the rest of the content, and the Content-Length header used to
343
+ # determine when the complete response has been read. The proxy
344
+ # will attempt to maintain a persistent connection with the backend,
345
+ # allowing for greater throughput.
346
+
347
+ def receive_data data
348
+ unless @initialized
349
+ @id = data.slice!(0..11)
350
+ ProxyBag.add_id(self,@id)
351
+ @initialized = true
352
+ end
353
+ unless @headers_completed
354
+ if data.index(Crnrn)
355
+ @headers_completed = true
356
+ h,d = data.split(Rrnrn)
357
+ @headers << h << Crnrn
358
+ @headers =~ /Content-Length:\s*([^\r\n]+)/
359
+ @content_length = $1.to_i
360
+ @associate.send_data @headers
361
+ data = d
362
+ else
363
+ @headers << data
364
+ end
365
+ end
366
+
367
+ if @headers_completed
368
+ @associate.send_data data
369
+ @content_sent += data.length
370
+ if @content_sent >= @content_length
371
+ @associate.close_connection_after_writing
372
+ @associate = nil
373
+ setup
374
+ ProxyBag.add_server self
375
+ end
376
+ end
377
+ rescue
378
+ @associate.close_connection_after_writing
379
+ @associate = nil
380
+ setup
381
+ ProxyBag.add_server self
382
+ end
383
+
384
+ # This is called when the backend disconnects from the proxy.
385
+ # If the backend is currently associated with a web browser client,
386
+ # that connection will be closed. Otherwise, the backend will be
387
+ # removed from the ProxyBag's backend queue.
388
+
389
+ def unbind
390
+ if @associate
391
+ @associate.close_connection_after_writing
392
+ else
393
+ ProxyBag.remove_server(self)
394
+ end
395
+ ProxyBag.remove_id(self)
396
+ end
397
+
398
+ def self.bname=(val)
399
+ @bname = val
400
+ end
401
+
402
+ def self.bname
403
+ @bname
404
+ end
405
+ end
406
+
407
+ # Start the EventMachine event loop and create the front end and backend
408
+ # handlers, then create the timers that are used to expire unserviced
409
+ # clients and to update the Proxy's clock.
410
+
411
+ def self.run(config, key = nil)
412
+ existing_backends = {}
413
+ EventMachine.run do
414
+ EventMachine.start_server(config[Ccluster_address], config[Ccluster_port], ClusterProtocol)
415
+ config[Cmap].each do |m|
416
+ if m[Ckeepalive]
417
+ incoming_hash = Digest::SHA512.hexdigest(m[Cincoming].join('|'))
418
+ m[Cincoming].each do |p|
419
+ ProxyBag.add_incoming_mapping(incoming_hash,p)
420
+ m[Coutgoing].each do |o|
421
+ ProxyBag.default_name = p if m[Cdefault]
422
+ if existing_backends.has_key?(o)
423
+ # Do we need to do anything here other than skip?
424
+ next
425
+ else
426
+ existing_backends[o] = true
427
+ backend_class = Class.new(BackendProtocol)
428
+ backend_class.bname = incoming_hash
429
+ host, port = o.split(/:/,2)
430
+ EventMachine.start_server(host, port.to_i, backend_class)
431
+ end
432
+ end
433
+ end
434
+ end
435
+ end
436
+ ProxyBag.server_unavailable_timeout ||= config[Ctimeout]
437
+ ProxyBag.key = key
438
+ EventMachine.add_periodic_timer(2) { ProxyBag.expire_clients }
439
+ EventMachine.add_periodic_timer(1) { ProxyBag.update_ctime }
440
+ end
441
+ end
442
+ end
443
+ end
444
+