swiftiply 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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
-