swiftiply 0.5.0

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