swiftiply 0.5.0 → 0.5.1

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/CONTRIBUTORS ADDED
@@ -0,0 +1,4 @@
1
+ Swiftiply is written by Kirk Haines (wyhaines@gmail.com).
2
+
3
+ Ezra Zygmuntowicz contributed swiftiply_mongrel_rails and edits to
4
+ mongrel_rails.
@@ -0,0 +1,90 @@
1
+ /* def add_frontend_client clnt
2
+ clnt.create_time = @ctime
3
+ unless match_client_to_server_now(clnt)
4
+ if clnt.uri =~ /\w+-\w+-\w+\.\w+\.[\w\.]+-(\w+)?$/
5
+ @demanding_clients[$1].unshift clnt
6
+ else
7
+ @client_q[clnt.name].unshift(clnt)
8
+ end
9
+ end
10
+ end
11
+
12
+ def match_client_to_server_now(client)
13
+ sq = @server_q[@incoming_map[client.name]]
14
+ if client.uri =~ /\w+-\w+-\w+\.\w+\.[\w\.]+-(\w+)?$/ and sidx = sq.index(@reverse_id_map[$1])
15
+ server = sq.delete_at(sidx)
16
+ server.associate = client
17
+ client.associate = server
18
+ client.push
19
+ true
20
+ elsif server = sq.pop
21
+ server.associate = client
22
+ client.associate = server
23
+ client.push
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+ */
30
+
31
+ /*
32
+ def receive_data data
33
+ @data.unshift data
34
+ if @name
35
+ push
36
+ else
37
+ data =~ /\s([^\s\?]*)/
38
+ @uri ||= $1
39
+ if data =~ /^Host:\s*([^\r\n:]*)/
40
+ @name = $1
41
+ ProxyBag.add_frontend_client self
42
+ push
43
+ elsif data.index(/\r\n\r\n/)
44
+ @name = ProxyBag.default_name
45
+ ProxyBag.add_frontend_client self
46
+ push
47
+ end
48
+ end
49
+ end
50
+ */
51
+
52
+ /*
53
+
54
+
55
+ def receive_data data
56
+ unless @initialized
57
+ @id = data.slice!(0..11)
58
+ ProxyBag.add_id(self,@id)
59
+ @initialized = true
60
+ end
61
+ unless @headers_completed
62
+ if data.index(Crnrn)
63
+ @headers_completed = true
64
+ h,d = data.split(Rrnrn)
65
+ @headers << h << Crnrn
66
+ @headers =~ /Content-Length:\s*([^\r\n]+)/
67
+ @content_length = $1.to_i
68
+ @associate.send_data @headers
69
+ data = d
70
+ else
71
+ @headers << data
72
+ end
73
+ end
74
+
75
+ if @headers_completed
76
+ @associate.send_data data
77
+ @content_sent += data.length
78
+ if @content_sent >= @content_length
79
+ @associate.close_connection_after_writing
80
+ @associate = nil
81
+ setup
82
+ ProxyBag.add_server self
83
+ end
84
+ end
85
+ rescue
86
+ @associate.close_connection_after_writing
87
+ @associate = nil
88
+ setup
89
+ ProxyBag.add_server self
90
+ end
data/setup.rb CHANGED
@@ -18,22 +18,13 @@ Package.setup("1.0") {
18
18
  lib(*Dir["src/swiftcore/**/*.rb"])
19
19
  lib("src/swiftcore/evented_mongrel.rb")
20
20
  lib("src/swiftcore/swiftiplied_mongrel.rb")
21
+ lib(*Dir["src/ramaze/adapter/*.rb"])
21
22
  ri(*Dir["src/swiftcore/**/*.rb"])
22
23
  bin "bin/swiftiply"
23
24
  bin "bin/swiftiply_mongrel_rails"
24
25
  #File.rename("#{Config::CONFIG["bindir"]}/mongrel_rails","#{Config::CONFIG["bindir"]}/mongrel_rails.orig")
25
26
  bin "bin/mongrel_rails"
26
27
 
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
28
  # unit_test "test/TC_Swiftiply.rb"
38
29
 
39
30
  true
@@ -12,6 +12,8 @@ end
12
12
 
13
13
  module Swiftcore
14
14
  module Swiftiply
15
+ Version = '0.5.1'
16
+
15
17
  Ccluster_address = 'cluster_address'.freeze
16
18
  Ccluster_port = 'cluster_port'.freeze
17
19
  CBackendAddress = 'BackendAddress'.freeze
@@ -33,7 +33,6 @@ module Mongrel
33
33
  @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
34
34
  if @parser.finished?
35
35
  if @request_len.nil?
36
- #@request_len = @nparsed + @params[::Mongrel::Const::CONTENT_LENGTH].to_i
37
36
  @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
38
37
  script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
39
38
  if handlers
@@ -49,14 +48,24 @@ module Mongrel
49
48
  @linebuffer = new_buffer
50
49
  else
51
50
  @linebuffer = StringIO.new(@linebuffer[@nparsed..-1])
51
+ @linebuffer.pos = @linebuffer.length
52
52
  end
53
53
  end
54
54
  if @linebuffer.length >= @request_len
55
+ @linebuffer.rewind
55
56
  ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
57
+ @linebuffer.delete if Tempfile === @linebuffer
56
58
  end
57
59
  elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
60
+ close_connection
58
61
  raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.")
59
62
  end
63
+ rescue ::Mongrel::HttpParserError
64
+ if $mongrel_debug_client
65
+ STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
66
+ STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
67
+ end
68
+ close_connection
60
69
  rescue Exception => e
61
70
  close_connection
62
71
  raise e
@@ -1,6 +1,6 @@
1
1
  # This module rewrites pieces of the very good Mongrel web server in
2
2
  # order to change it from a threaded application to an event based
3
- # application running since an EventMachine event loop. It should
3
+ # application running inside an EventMachine event loop. It should
4
4
  # be compatible with the existing Mongrel handlers for Rails,
5
5
  # Camping, Nitro, etc....
6
6
 
@@ -62,7 +62,6 @@ module Mongrel
62
62
  @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
63
63
  if @parser.finished?
64
64
  if @request_len.nil?
65
- #@request_len = @nparsed + @params[::Mongrel::Const::CONTENT_LENGTH].to_i
66
65
  @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
67
66
  script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
68
67
  if handlers
@@ -75,20 +74,28 @@ module Mongrel
75
74
  new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE)
76
75
  new_buffer.binmode
77
76
  new_buffer << @linebuffer[@nparsed..-1]
78
- #new_buffer << @linebuffer
79
77
  @linebuffer = new_buffer
80
78
  else
81
79
  @linebuffer = StringIO.new(@linebuffer[@nparsed..-1])
82
- #@linebuffer = StringIO.new(@linebuffer)
80
+ @linebuffer.pos = @linebuffer.length
83
81
  end
84
82
  end
85
83
  if @linebuffer.length >= @request_len
84
+ @linebuffer.rewind
86
85
  ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
86
+ @linebuffer.delete if Tempfile === @linebuffer
87
87
  post_init
88
88
  end
89
89
  elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
90
+ close_connection
90
91
  raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.")
91
92
  end
93
+ rescue ::Mongrel::HttpParserError
94
+ if $mongrel_debug_client
95
+ STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
96
+ STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
97
+ end
98
+ close_connection
92
99
  rescue Exception => e
93
100
  close_connection
94
101
  raise e
data/swiftiply.gemspec CHANGED
@@ -11,7 +11,7 @@ spec = Gem::Specification.new do |s|
11
11
  s.name = 'swiftiply'
12
12
  s.author = %q(Kirk Haines)
13
13
  s.email = %q(wyhaines@gmail.com)
14
- s.version = '0.5.0'
14
+ s.version = '0.5.1'
15
15
  s.summary = %q(A fast clustering proxy for web applications.)
16
16
  s.platform = Gem::Platform::RUBY
17
17
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: swiftiply
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.5.0
7
- date: 2007-05-12 00:00:00 +02:00
6
+ version: 0.5.1
7
+ date: 2007-05-15 00:00:00 +02:00
8
8
  summary: A fast clustering proxy for web applications.
9
9
  require_paths:
10
10
  - src
@@ -29,12 +29,14 @@ post_install_message:
29
29
  authors:
30
30
  - Kirk Haines
31
31
  files:
32
- - README
33
32
  - bin
34
33
  - external
35
- - setup.rb
36
34
  - src
37
35
  - test
36
+ - CONTRIBUTORS
37
+ - README
38
+ - setup.rb
39
+ - ext
38
40
  - swiftiply.gemspec
39
41
  - bin/swiftiply
40
42
  - bin/mongrel_rails
@@ -43,7 +45,6 @@ files:
43
45
  - external/package.rb
44
46
  - src/swiftcore
45
47
  - src/ramaze
46
- - src/swiftcore/Swiftiply.rb.orig
47
48
  - src/swiftcore/Swiftiply.rb
48
49
  - src/swiftcore/swiftiplied_mongrel.rb
49
50
  - src/swiftcore/evented_mongrel.rb
@@ -159,6 +160,8 @@ files:
159
160
  - test/ramaze/src/element/page.rb
160
161
  - test/ramaze/src/controller/main.rb
161
162
  - test/ramaze/template/index.xhtml
163
+ - ext/swiftiply_parse
164
+ - ext/swiftiply_parse/parse.rl
162
165
  test_files: []
163
166
 
164
167
  rdoc_options:
@@ -1,390 +0,0 @@
1
- begin
2
- load_attempted ||= false
3
- require 'eventmachine'
4
- rescue LoadError => e
5
- unless load_attempted
6
- load_attempted = true
7
- require 'rubygems'
8
- end
9
- raise e
10
- end
11
-
12
- module Swiftcore
13
- module Swiftiply
14
- Ccluster_address = 'cluster_address'.freeze
15
- Ccluster_port = 'cluster_port'.freeze
16
- CBackendAddress = 'BackendAddress'.freeze
17
- CBackendPort = 'BackendPort'.freeze
18
- Cmap = 'map'.freeze
19
- Cincoming = 'incoming'.freeze
20
- Ckeepalive = 'keepalive'.freeze
21
- Cdaemonize = 'daemonize'.freeze
22
- Curl = 'url'.freeze
23
- Chost = 'host'.freeze
24
- Cport = 'port'.freeze
25
- Coutgoing = 'outgoing'.freeze
26
- Ctimeout = 'timeout'.freeze
27
- Cdefault = 'default'.freeze
28
-
29
- # The ProxyBag is a class that holds the client and the server queues,
30
- # and that is responsible for managing them, matching them, and expiring
31
- # them, if necessary.
32
-
33
- class ProxyBag
34
- @client_q = Hash.new {|h,k| h[k] = []}
35
- @server_q = Hash.new {|h,k| h[k] = []}
36
- @ctime = Time.now
37
- @server_unavailable_timeout = 6
38
-
39
- class << self
40
-
41
- def now
42
- @ctime
43
- end
44
-
45
- # Returns the access key. If an access key is set, then all new backend
46
- # connections must send the correct access key before being added to
47
- # the cluster as a valid backend.
48
-
49
- def key
50
- @key
51
- end
52
-
53
- # Sets the access key.
54
-
55
- def key=(val)
56
- @key = val
57
- end
58
-
59
- def default_name
60
- @default_name
61
- end
62
-
63
- def default_name=(val)
64
- @default_name = val
65
- end
66
-
67
- # This timeout is the amount of time a connection will sit in queue
68
- # waiting for a backend to process it.
69
-
70
- def server_unavailable_timeout
71
- @server_unavailable_timeout
72
- end
73
-
74
- # Sets the server unavailable timeout value.
75
-
76
- def server_unavailable_timeout=(val)
77
- @server_unavailable_timeout = val
78
- end
79
-
80
- # Pushes a front end client (web browser) into the queue of clients
81
- # waiting to be serviced if there's no server available to handle
82
- # it right now.
83
-
84
- def add_frontend_client clnt
85
- clnt.create_time = @ctime
86
- @client_q[clnt.name].unshift(clnt) unless match_client_to_server_now(clnt)
87
- end
88
-
89
- # Pushes a backend server into the queue of servers waiting for a
90
- # client to service if there are no clients waiting to be serviced.
91
-
92
- def add_server srvr
93
- @server_q[srvr.name].unshift(srvr) unless match_server_to_client_now(srvr)
94
- end
95
-
96
- # Deletes the provided server from the server queue.
97
-
98
- def remove_server srvr
99
- @server_q[srvr.name].delete srvr
100
- end
101
-
102
- # Removes the named client from the client queue.
103
- # TODO: Try replacing this with a linked list. Performance
104
- # here has to suck when the list is long.
105
-
106
- def remove_client clnt
107
- @client_q[clnt.name].delete clnt
108
- end
109
-
110
- # Walks through the client and server queues, matching
111
- # waiting clients with waiting servers until the queue
112
- # runs out of one or the other. DEPRECATED
113
-
114
- def match_clients_to_servers
115
- while @server_q.first && @client_q.first
116
- server = @server_q.pop
117
- client = @client_q.pop
118
- server.associate = client
119
- client.associate = server
120
- client.push
121
- end
122
- end
123
-
124
- # Tries to match the client passed as an argument to a
125
- # server.
126
-
127
- def match_client_to_server_now(client)
128
- if server = @server_q[client.name].pop
129
- #server = @server_q[client.name].pop
130
- server.associate = client
131
- client.associate = server
132
- client.push
133
- true
134
- else
135
- false
136
- end
137
- end
138
-
139
- # Tries to match the server passed as an argument to a
140
- # client.
141
-
142
- def match_server_to_client_now(server)
143
- if client = @client_q[server.name].pop
144
- #client = @client_q[server.name].pop
145
- server.associate = client
146
- client.associate = server
147
- client.push
148
- true
149
- else
150
- false
151
- end
152
- end
153
-
154
- # Walk through the waiting clients if there is no server
155
- # available to process clients and expire any clients that
156
- # have been waiting longer than @server_unavailable_timeout
157
- # seconds. Clients which are expired will receive a 503
158
- # response.
159
-
160
- def expire_clients
161
- now = Time.now
162
- @server_q.each_key do |name|
163
- unless @server_q[name].first
164
- while c = @client_q[name].pop
165
- if (now - c.create_time) >= @server_unavailable_timeout
166
- c.send_503_response
167
- else
168
- @client_q[name].push c
169
- break
170
- end
171
- end
172
- end
173
- end
174
- end
175
-
176
- # This is called by a periodic timer once a second to update
177
- # the time.
178
-
179
- def update_ctime
180
- @ctime = Time.now
181
- end
182
-
183
- end
184
- end
185
-
186
- # The ClusterProtocol is the subclass of EventMachine::Connection used
187
- # to communicate between Swiftiply and the web browser clients.
188
-
189
- class ClusterProtocol < EventMachine::Connection
190
- attr_accessor :create_time, :associate, :name
191
-
192
- Crnrn = "\r\n\r\n".freeze
193
- Rrnrn = /\r\n\r\n/
194
- R_colon = /:/
195
-
196
- # Initialize the @data array, which is the temporary storage for blocks
197
- # of data received from the web browser client, then invoke the superclass
198
- # initialization.
199
-
200
- def initialize *args
201
- @data = []
202
- @name = nil
203
- super
204
- end
205
-
206
- # Receive data from the web browser client, then immediately try to
207
- # push it to a backend.
208
-
209
- def receive_data data
210
- @data.unshift data
211
- if @name
212
- push
213
- else
214
- # if data =~ /^Host:\s*(.*)\s*$/
215
- # @name = $1.chomp.split(R_colon,2).first
216
- if data =~ /^Host:\s*([^\r\n:]*)/
217
- @name = $1
218
- ProxyBag.add_frontend_client self
219
- push
220
- elsif data =~ /\r\n\r\n/
221
- @name = ProxyBag.default_name
222
- ProxyBag.add_frontend_client self
223
- push
224
- end
225
- end
226
- end
227
-
228
- # Hardcoded 503 response that is sent if a connection is timed out while
229
- # waiting for a backend to handle it.
230
-
231
- def send_503_response
232
- send_data [
233
- "HTTP/1.0 503 Server Unavailable\r\n",
234
- "Content-type: text/plain\r\n",
235
- "Connection: close\r\n",
236
- "\r\n",
237
- "Server Unavailable"
238
- ].join
239
- close_connection_after_writing
240
- end
241
-
242
- # Push data from the web browser client to the backend server process.
243
-
244
- def push
245
- if @associate
246
- while data = @data.pop
247
- @associate.send_data data
248
- end
249
- end
250
- end
251
-
252
- # The connection with the web browser client has been closed, so the
253
- # object must be removed from the ProxyBag's queue if it is has not
254
- # been associated with a backend. If it has already been associated
255
- # with a backend, then it will not be in the queue and need not be
256
- # removed.
257
-
258
- def unbind
259
- ProxyBag.remove_client(self) unless @associate
260
- end
261
- end
262
-
263
-
264
- # The BackendProtocol is the EventMachine::Connection subclass that
265
- # handles the communications between Swiftiply and the backend process
266
- # it is proxying to.
267
-
268
- class BackendProtocol < EventMachine::Connection
269
- attr_accessor :associate
270
-
271
- Crnrn = "\r\n\r\n".freeze
272
- Rrnrn = /\r\n\r\n/
273
-
274
- def initialize *args
275
- @name = self.class.bname
276
- super
277
- end
278
-
279
- def name
280
- @name
281
- end
282
-
283
- # Call setup() and add the backend to the ProxyBag queue.
284
-
285
- def post_init
286
- setup
287
- @initialized = nil
288
- ProxyBag.add_server self
289
- end
290
-
291
- # Setup the initial variables for receiving headers and content.
292
-
293
- def setup
294
- @headers = ''
295
- @headers_completed = false
296
- @content_length = nil
297
- @content_sent = 0
298
- end
299
-
300
- # Receive data from the backend process. Headers are parsed from
301
- # the rest of the content, and the Content-Length header used to
302
- # determine when the complete response has been read. The proxy
303
- # will attempt to maintain a persistent connection with the backend,
304
- # allowing for greater throughput.
305
-
306
- def receive_data data
307
- # unless @initialized
308
- # id = data.slice!(0..3).to_i(16)
309
- # ProxyBag.add_id(self,id)
310
- # @initialized = true
311
- # end
312
- unless @headers_completed
313
- if data.index(Crnrn)
314
- @headers_completed = true
315
- h,d = data.split(Rrnrn)
316
- @headers << h << Crnrn
317
- @headers =~ /Content-Length:\s*([^\r\n]+)/
318
- @content_length = $1.to_i
319
- @associate.send_data @headers
320
- @associate.send_data d
321
- @content_sent += d.length
322
- else
323
- @headers << data
324
- end
325
- end
326
-
327
- if @headers_completed
328
- if @content_sent < @content_length
329
- @associate.send_data data
330
- @content_sent += data.length
331
- else
332
- @associate.close_connection_after_writing
333
- @associate = nil
334
- setup
335
- ProxyBag.add_server self
336
- end
337
- end
338
- end
339
-
340
- # This is called when the backend disconnects from the proxy.
341
- # If the backend is currently associated with a web browser client,
342
- # that connection will be closed. Otherwise, the backend will be
343
- # removed from the ProxyBag's backend queue.
344
-
345
- def unbind
346
- if @associate
347
- @associate.close_connection_after_writing
348
- else
349
- ProxyBag.remove_server(self)
350
- end
351
- end
352
-
353
- def self.bname=(val)
354
- @bname = val
355
- end
356
-
357
- def self.bname
358
- @bname
359
- end
360
- end
361
-
362
- # Start the EventMachine event loop and create the front end and backend
363
- # handlers, then create the timers that are used to expire unserviced
364
- # clients and to update the Proxy's clock.
365
-
366
- def self.run(config, key = nil)
367
- EventMachine.run do
368
- EventMachine.start_server(config[Ccluster_address], config[Ccluster_port], ClusterProtocol)
369
- config[Cmap].each do |m|
370
- if m[Ckeepalive]
371
- m[Cincoming].each do |p|
372
- m[Coutgoing].each do |o|
373
- backend_class = Class.new(BackendProtocol)
374
- backend_class.bname = p
375
- ProxyBag.default_name = p if m[Cdefault]
376
- host, port = o.split(/:/,2)
377
- EventMachine.start_server(host, port.to_i, backend_class)
378
- end
379
- end
380
- end
381
- end
382
- ProxyBag.server_unavailable_timeout ||= config[Ctimeout]
383
- ProxyBag.key = key
384
- EventMachine.add_periodic_timer(2) { ProxyBag.expire_clients }
385
- EventMachine.add_periodic_timer(1) { ProxyBag.update_ctime }
386
- end
387
- end
388
- end
389
- end
390
-