swiftiply 0.6.1.1 → 1.0.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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTORS +2 -0
  3. data/README.md +62 -0
  4. data/bin/{mongrel_rails → evented_mongrel_rails} +6 -14
  5. data/bin/swiftiplied_mongrel_rails +246 -0
  6. data/bin/swiftiply +136 -116
  7. data/bin/swiftiply_mongrel_rails +2 -2
  8. data/bin/swiftiplyctl +283 -0
  9. data/cleanup.sh +5 -0
  10. data/ext/deque/extconf.rb +162 -0
  11. data/ext/deque/swiftcore/rubymain.cpp +435 -0
  12. data/ext/fastfilereader/extconf.rb +2 -2
  13. data/ext/fastfilereader/mapper.cpp +2 -0
  14. data/ext/map/extconf.rb +161 -0
  15. data/ext/map/rubymain.cpp +500 -0
  16. data/ext/splaytree/extconf.rb +161 -0
  17. data/ext/splaytree/swiftcore/rubymain.cpp +580 -0
  18. data/ext/splaytree/swiftcore/splay_map.h +635 -0
  19. data/ext/splaytree/swiftcore/splay_set.h +575 -0
  20. data/ext/splaytree/swiftcore/splay_tree.h +1127 -0
  21. data/external/httpclient.rb +231 -0
  22. data/external/package.rb +13 -13
  23. data/setup.rb +18 -2
  24. data/src/swiftcore/Swiftiply.rb +417 -773
  25. data/src/swiftcore/Swiftiply/backend_protocol.rb +213 -0
  26. data/src/swiftcore/Swiftiply/cache_base.rb +49 -0
  27. data/src/swiftcore/Swiftiply/cache_base_mixin.rb +52 -0
  28. data/src/swiftcore/Swiftiply/cluster_managers/rest_based_cluster_manager.rb +9 -0
  29. data/src/swiftcore/Swiftiply/cluster_protocol.rb +70 -0
  30. data/src/swiftcore/Swiftiply/config.rb +370 -0
  31. data/src/swiftcore/Swiftiply/config/rest_updater.rb +26 -0
  32. data/src/swiftcore/Swiftiply/constants.rb +101 -0
  33. data/src/swiftcore/Swiftiply/content_cache_entry.rb +44 -0
  34. data/src/swiftcore/Swiftiply/content_response.rb +45 -0
  35. data/src/swiftcore/Swiftiply/control_protocol.rb +49 -0
  36. data/src/swiftcore/Swiftiply/dynamic_request_cache.rb +41 -0
  37. data/src/swiftcore/Swiftiply/etag_cache.rb +64 -0
  38. data/src/swiftcore/Swiftiply/file_cache.rb +46 -0
  39. data/src/swiftcore/Swiftiply/hash_cache_base.rb +22 -0
  40. data/src/swiftcore/Swiftiply/http_recognizer.rb +267 -0
  41. data/src/swiftcore/Swiftiply/loggers/Analogger.rb +21 -0
  42. data/src/swiftcore/Swiftiply/loggers/stderror.rb +13 -0
  43. data/src/swiftcore/Swiftiply/mocklog.rb +10 -0
  44. data/src/swiftcore/Swiftiply/proxy.rb +15 -0
  45. data/src/swiftcore/Swiftiply/proxy_backends/keepalive.rb +286 -0
  46. data/src/swiftcore/Swiftiply/proxy_backends/traditional.rb +286 -0
  47. data/src/swiftcore/Swiftiply/proxy_backends/traditional/redis_directory.rb +87 -0
  48. data/src/swiftcore/Swiftiply/proxy_backends/traditional/static_directory.rb +69 -0
  49. data/src/swiftcore/Swiftiply/proxy_bag.rb +716 -0
  50. data/src/swiftcore/Swiftiply/rest_based_cluster_manager.rb +15 -0
  51. data/src/swiftcore/Swiftiply/splay_cache_base.rb +21 -0
  52. data/src/swiftcore/Swiftiply/support_pagecache.rb +6 -3
  53. data/src/swiftcore/Swiftiply/swiftiply_2_http_proxy.rb +7 -0
  54. data/src/swiftcore/Swiftiply/swiftiply_client.rb +20 -5
  55. data/src/swiftcore/Swiftiply/version.rb +5 -0
  56. data/src/swiftcore/evented_mongrel.rb +26 -8
  57. data/src/swiftcore/hash.rb +43 -0
  58. data/src/swiftcore/method_builder.rb +28 -0
  59. data/src/swiftcore/streamer.rb +46 -0
  60. data/src/swiftcore/swiftiplied_mongrel.rb +91 -23
  61. data/src/swiftcore/types.rb +20 -3
  62. data/swiftiply.gemspec +14 -8
  63. data/test/TC_Deque.rb +152 -0
  64. data/test/TC_ProxyBag.rb +147 -166
  65. data/test/TC_Swiftiply.rb +576 -169
  66. data/test/TC_Swiftiply/mongrel/evented_hello.rb +1 -1
  67. data/test/TC_Swiftiply/mongrel/swiftiplied_hello.rb +1 -1
  68. data/test/TC_Swiftiply/test_serve_static_file_xsendfile/sendfile_client.rb +27 -0
  69. data/test/TC_Swiftiply/test_ssl/bin/validate_ssl_capability.rb +21 -0
  70. data/test/TC_Swiftiply/test_ssl/test.cert +16 -0
  71. data/test/TC_Swiftiply/test_ssl/test.key +15 -0
  72. data/{bin → test/bin}/echo_client +0 -0
  73. metadata +136 -94
  74. data/README +0 -126
  75. data/ext/swiftiply_parse/parse.rl +0 -90
@@ -0,0 +1,15 @@
1
+ require 'em-http'
2
+ module Swiftcore
3
+ module Swiftiply
4
+ module ManagerProtocols
5
+ class RestBasedClusterManager
6
+ def self.call(callsite, params)
7
+ EventMachine::HttpRequest.new(callsite).get
8
+ true
9
+ rescue Exception
10
+ false
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ require 'swiftcore/Swiftiply/cache_base_mixin'
2
+
3
+ module Swiftcore
4
+ # Use Array instead of Deque, if Deque wasn't available.
5
+ Deque = Array unless HasDeque or const_defined?(:Deque)
6
+
7
+ module Swiftiply
8
+ class CacheBase < Swiftcore::SplayTreeMap
9
+ include CacheBaseMixin
10
+ def initialize(vw = 900, time_limit = 0.05, maxsize = 100)
11
+ @vw = vw
12
+ @tl = time_limit
13
+ @vwtl = vw * time_limit
14
+ @old_vql = 0
15
+ @vq = Deque.new
16
+ super()
17
+ self.max_size = maxsize
18
+ end
19
+ end
20
+ end
21
+ end
@@ -33,16 +33,19 @@ module Swiftcore
33
33
 
34
34
  def find_static_file(dr,path_info,client_name)
35
35
  path = File.join(dr,path_info)
36
- if FileTest.exist?(path) and FileTest.file?(path)
36
+ puts path
37
+ if FileTest.exist?(path) and FileTest.file?(path) and File.expand_path(path).index(dr) == 0 and !(x = static_mask(client_name) and path =~ x)
37
38
  path
38
39
  elsif @suffix_list.has_key?(client_name)
39
40
  path = File.join(dr,@cache_dir[client_name],path_info)
40
- if FileTest.exist?(path) and FileTest.file?(path)
41
+ if FileTest.exist?(path) and FileTest.file?(path) and File.expand_path(path).index(dr) == 0 and !(x = static_mask(client_name) and path =~ x)
41
42
  path
42
43
  else
43
44
  for suffix in @suffix_list[client_name] do
44
45
  p = "#{path}.#{suffix}"
45
- return p if FileTest.exist?(p) and FileTest.file?(p)
46
+ if FileTest.exist?(p) and FileTest.file?(p) and File.expand_path(p).index(dr) == 0 and !(x = static_mask(client_name) and p =~ x)
47
+ return p
48
+ end
46
49
  end
47
50
  nil
48
51
  end
@@ -0,0 +1,7 @@
1
+ module Swiftiply
2
+ class Swiftiply2HttpProxy
3
+ def self.run(config)
4
+ end
5
+
6
+ end
7
+ end
@@ -1,6 +1,20 @@
1
- # -*- coding: ISO-8859-1 -*-
2
- require 'eventmachine'
3
- require 'socket'
1
+ # Encoding:ascii-8bit
2
+
3
+ begin
4
+ load_attempted ||= false
5
+ require 'eventmachine'
6
+ require 'socket'
7
+ rescue LoadError => e
8
+ unless load_attempted
9
+ load_attempted = true
10
+ # Ugh. Everything gets slower once rubygems are used. So, for the
11
+ # best speed possible, don't install EventMachine or Swiftiply via
12
+ # gems.
13
+ require 'rubygems'
14
+ retry
15
+ end
16
+ raise e
17
+ end
4
18
 
5
19
  # This is a basic Swiftiply client implementation.
6
20
 
@@ -22,7 +36,8 @@ class SwiftiplyClientProtocol < EventMachine::Connection
22
36
  conn.port = port
23
37
  conn.key = key
24
38
  ip = conn.ip = conn.__get_ip(hostname)
25
- conn.id = 'swiftclient' << ip.collect {|x| sprintf('%02x',x.to_i)}.join << sprintf('%04x',port.to_i)<< sprintf('%02x',key.length) << key
39
+ #conn.id = 'swiftclient' << ip.collect {|x| sprintf('%02x',x.to_i)}.join << sprintf('%04x',port.to_i)<< sprintf('%02x',key.length) << key
40
+ conn.id = 'swiftclient' << ip.collect {|x| sprintf('%02x',x.to_i)}.join << sprintf('%04x',$$)<< sprintf('%02x',key.length) << key
26
41
  conn.set_comm_inactivity_timeout inactivity_timeout
27
42
  end
28
43
  end
@@ -54,4 +69,4 @@ class SwiftiplyClientProtocol < EventMachine::Connection
54
69
  def __get_ip hostname
55
70
  Socket.gethostbyname(hostname)[3].unpack(CCCCC) rescue ip = C0s
56
71
  end
57
- end
72
+ end
@@ -0,0 +1,5 @@
1
+ module Swiftcore
2
+ module Swiftiply
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
@@ -19,6 +19,10 @@ require 'mongrel'
19
19
 
20
20
  module Mongrel
21
21
  class MongrelProtocol < EventMachine::Connection
22
+
23
+ Cblank = ''.freeze
24
+ C400Header = "HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\nServer: Swiftiplied Mongrel 0.6.5\r\nConnection: close\r\n\r\n"
25
+
22
26
  def post_init
23
27
  @parser = HttpParser.new
24
28
  @params = HttpParams.new
@@ -34,11 +38,16 @@ module Mongrel
34
38
  if @parser.finished?
35
39
  if @request_len.nil?
36
40
  @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
37
- script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
41
+ script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH] || Cblank)
38
42
  if handlers
39
43
  @params[::Mongrel::Const::PATH_INFO] = path_info
40
44
  @params[::Mongrel::Const::SCRIPT_NAME] = script_name
41
- @params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] || ::Socket.unpack_sockaddr_in(get_peername)[1]
45
+ # The previous behavior of this line set REMOTE_ADDR equal to HTTP_X_FORWARDED_FOR
46
+ # if it was defined. This behavior seems inconsistent with the intention of
47
+ # http://www.ietf.org/rfc/rfc3875 so it has been changed. REMOTE_ADDR now always
48
+ # contains the address of the immediate source of the connection. Check
49
+ # @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] if you need that information.
50
+ @params[::Mongrel::Const::REMOTE_ADDR] = ::Socket.unpack_sockaddr_in(get_peername)[1] rescue nil
42
51
  @notifiers = handlers.select { |h| h.request_notify }
43
52
  end
44
53
  if @request_len > ::Mongrel::Const::MAX_BODY
@@ -65,10 +74,12 @@ module Mongrel
65
74
  STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
66
75
  STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
67
76
  end
68
- close_connection
77
+ send_data C400Header
78
+ close_connection_after_writing
69
79
  rescue Exception => e
70
- close_connection
71
80
  raise e
81
+ send_data C400Header
82
+ close_connection_after_writing
72
83
  end
73
84
 
74
85
  def write data
@@ -102,23 +113,28 @@ module Mongrel
102
113
  def run
103
114
  trap('INT') { raise StopServer }
104
115
  trap('TERM') { raise StopServer }
105
- @acceptor = Thread.new do
116
+ #@acceptor = Thread.new do
117
+ @acceptor = Thread.current
118
+ # SHOULD NOT DO THIS AUTOMATICALLY.
119
+ # There either needs to be a way to configure this, or to detect
120
+ # when it is safe or when kqueue needs to run.
106
121
  EventMachine.epoll
107
122
  EventMachine.set_descriptor_table_size(4096)
108
123
  EventMachine.run do
124
+ EM.set_timer_quantum(5)
109
125
  begin
110
- EventMachine.start_server(@host,@port,MongrelProtocol)
126
+ EventMachine.start_server(@host,@port.to_i,MongrelProtocol)
111
127
  rescue StopServer
112
128
  EventMachine.stop_event_loop
113
129
  end
114
130
  end
115
- end
131
+ #end
116
132
  end
117
133
 
118
134
  def process_http_request(params,linebuffer,client)
119
135
  if not params[Const::REQUEST_PATH]
120
136
  uri = URI.parse(params[Const::REQUEST_URI])
121
- params[Const::REQUEST_PATH] = uri.request_uri
137
+ params[Const::REQUEST_PATH] = uri.path
122
138
  end
123
139
 
124
140
  raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
@@ -129,6 +145,8 @@ module Mongrel
129
145
  notifiers = handlers.select { |h| h.request_notify }
130
146
  request = HttpRequest.new(params, linebuffer, notifiers)
131
147
 
148
+ # TODO: Add Keep-Alive support
149
+
132
150
  # request is good so far, continue processing the response
133
151
  response = HttpResponse.new(client)
134
152
 
@@ -0,0 +1,43 @@
1
+ class Hash
2
+
3
+ # Hash#merge doesn't work right if any of the values in the hash are themselves
4
+ # hashes. This creates frowns on my normally smiling face. This method
5
+ # will walk a hash, merging it, while preserving default_proc's that may
6
+ # exist on the hashes involved. The code was adopted from IOWA. It's also
7
+ # quite likely that it sucks. I wrote it years ago, and have left it alone
8
+ # since then. If you need similar functionality in your code somewhere,
9
+ # though, feel free to use this. If you make it better, I'd appreciate it
10
+ # if you would send me your patches, though.
11
+
12
+ def rmerge!(h)
13
+ h.each do |k,v|
14
+ if v.kind_of?(::Hash)
15
+ if self[k].kind_of?(::Hash)
16
+ unless self[k].respond_to?(:rmerge!)
17
+ if dp = self[k].default_proc
18
+ self[k] = Hash.new {|h,k| dp.call(h,k)}.rmerge!(self[k])
19
+ else
20
+ osk = self[k]
21
+ self[k] = Hash.new
22
+ self[k].rmerge!(osk)
23
+ end
24
+ end
25
+ self[k].rmerge!(v)
26
+ else
27
+ if self.default_proc
28
+ self.delete k
29
+ self[k]
30
+ end
31
+ unless self[k].kind_of?(::Hash)
32
+ self.delete k
33
+ self[k] = Hash.new
34
+ end
35
+ self[k].rmerge!(v)
36
+ end
37
+ else
38
+ self[k] = v
39
+ end
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,28 @@
1
+ # Mixin to allow dynamic construction of instance methods.
2
+
3
+ module MethodBuilder
4
+ def method_builder(method_name, method_args, chunks, callback_args = [])
5
+ if Array === method_args
6
+ pma = [] method_args.each do |ma|
7
+ if Array === ma
8
+ pma << "#{ma[0]}=#{ma[1]}"
9
+ else
10
+ pma << ma.to_s
11
+ end
12
+ end
13
+ parsed_method_args = pma.join(',')
14
+ else
15
+ parsed_method_args = method_args.to_s
16
+ end
17
+
18
+ m = "def #{method_name}(#{parsed_method_args})\n" chunks.each do |chunk|
19
+ if chunk.respond_to?(:call)
20
+ m << chunk.call(*args)
21
+ else
22
+ m << chunk.to_s
23
+ end
24
+ end m << "\nend"
25
+
26
+ class_eval m
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ begin
2
+ load_attempted ||= false
3
+ require 'em/streamer'
4
+ rescue LoadError
5
+ unless load_attempted
6
+ load_attempted = true
7
+ require 'rubygems'
8
+ retry
9
+ end
10
+ end
11
+
12
+ module EventMachine
13
+ class FileStreamer
14
+
15
+ def stream_one_chunk
16
+ loop {
17
+ if @position < @size
18
+ if @connection.get_outbound_data_size > BackpressureLevel
19
+ EventMachine::next_tick {stream_one_chunk}
20
+ break
21
+ else
22
+ len = @size - @position
23
+ len = ChunkSize if (len > ChunkSize)
24
+
25
+ #@connection.send_data( "#{format("%x",len)}\r\n" ) if @http_chunks
26
+ if @http_chunks
27
+ @connection.send_data( "#{len.to_s(16)}\r\n" ) if @http_chunks
28
+ @connection.send_data( @mapping.get_chunk( @position, len ))
29
+ @connection.send_data("\r\n") if @http_chunks
30
+ else
31
+ @connection.send_data( @mapping.get_chunk( @position, len ))
32
+ end
33
+
34
+ @position += len
35
+ end
36
+ else
37
+ @connection.send_data "0\r\n\r\n" if @http_chunks
38
+ @mapping.close
39
+ succeed
40
+ break
41
+ end
42
+ }
43
+ end
44
+
45
+ end
46
+ end
@@ -3,7 +3,7 @@
3
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
+
7
7
  begin
8
8
  load_attempted ||= false
9
9
  require 'eventmachine'
@@ -20,6 +20,9 @@ end
20
20
  module Mongrel
21
21
  C0s = [0,0,0,0].freeze unless const_defined?(:C0s)
22
22
  CCCCC = 'CCCC'.freeze unless const_defined?(:CCCCC)
23
+ Cblank = ''.freeze
24
+
25
+ C400Header = "HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\nServer: Swiftiplied Mongrel 0.6.5\r\nConnection: close\r\n\r\n"
23
26
 
24
27
  class MongrelProtocol < SwiftiplyClientProtocol
25
28
 
@@ -34,15 +37,26 @@ module Mongrel
34
37
 
35
38
  def receive_data data
36
39
  @linebuffer << data
40
+
37
41
  @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
38
42
  if @parser.finished?
39
- if @request_len.nil?
43
+
44
+ unless @params[::Mongrel::Const::REQUEST_PATH]
45
+ params[::Mongrel::Const::REQUEST_PATH] = URI.parse(params[::Mongrel::Const::REQUEST_URI]).path
46
+ end
47
+
48
+ unless @request_len
40
49
  @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
41
50
  script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
42
51
  if handlers
43
- @params[::Mongrel::Const::PATH_INFO] = path_info
52
+ @params[::Mongrel::Const::PATH_INFO] = path_info || Cblank # path_info shouldn't be nil, but just in case it somehow is, let's make sure we don't crash later because of it.
44
53
  @params[::Mongrel::Const::SCRIPT_NAME] = script_name
45
- @params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] || ::Socket.unpack_sockaddr_in(get_peername)[1]
54
+ # The previous behavior of this line set REMOTE_ADDR equal to HTTP_X_FORWARDED_FOR
55
+ # if it was defined. This behavior seems inconsistent with the intention of
56
+ # http://www.ietf.org/rfc/rfc3875 so it has been changed. REMOTE_ADDR now always
57
+ # contains the address of the immediate source of the connection. Check
58
+ # @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] if you need that information.
59
+ @params[::Mongrel::Const::REMOTE_ADDR] = ::Socket.unpack_sockaddr_in(get_peername)[1] rescue nil
46
60
  @notifiers = handlers.select { |h| h.request_notify }
47
61
  end
48
62
  if @request_len > ::Mongrel::Const::MAX_BODY
@@ -58,7 +72,7 @@ module Mongrel
58
72
  if @linebuffer.length >= @request_len
59
73
  @linebuffer.rewind
60
74
  ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
61
- @linebuffer.delete if Tempfile === @linebuffer
75
+ @linebuffer.delete if Tempfile === @linebuffer && FileTest.exist?(@linebuffer.path.to_s)
62
76
  post_init
63
77
  end
64
78
  elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
@@ -70,10 +84,17 @@ module Mongrel
70
84
  STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
71
85
  STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
72
86
  end
73
- close_connection
87
+ send_data C400Header
88
+ close_connection_after_writing
74
89
  rescue Exception => e
75
- close_connection
90
+ # This isn't a parse error; if this rescue caught an exception, then something
91
+ # significant happened.
76
92
  raise e
93
+ send_data C400Header
94
+ close_connection_after_writing
95
+ ensure
96
+ # Make sure to cleanup the Tempfile.
97
+ @linebuffer.delete if Tempfile === @linebuffer && FileTest.exist?(@linebuffer.path.to_s)
77
98
  end
78
99
 
79
100
  def write data
@@ -84,6 +105,7 @@ module Mongrel
84
105
  false
85
106
  end
86
107
 
108
+ def flush; end # EM handles flushing, not us, so this is just here for compatibility.
87
109
  end
88
110
 
89
111
  class HttpServer
@@ -93,7 +115,12 @@ module Mongrel
93
115
  # The support is here, for swiftiplied_mongrels which are secured by
94
116
  # a key. If someone want to donate any patches. Otherwise, this won't
95
117
  # really be useful to most people until 0.7.0.
96
-
118
+
119
+ CHTTP_CONNECTION = 'HTTP_CONNECTION'.freeze
120
+ CHTTP_VERSION = 'HTTP_VERSION'.freeze
121
+ CKEEP_ALIVE = 'KEEP_ALIVE'.freeze
122
+ C1_1 = '1.1'.freeze
123
+
97
124
  def initialize(host, port, num_processors=950, x=0, y=nil,key='') # Deal with Mongrel 1.0.1 or earlier, as well as later.
98
125
  @socket = nil
99
126
  @classifier = URIClassifier.new
@@ -115,24 +142,26 @@ module Mongrel
115
142
  def run
116
143
  trap('INT') { EventMachine.stop_event_loop }
117
144
  trap('TERM') { EventMachine.stop_event_loop }
118
- @acceptor = Thread.new do
145
+ #@acceptor = Thread.new do
146
+ @acceptor = Thread.current
119
147
  EventMachine.run do
148
+ EM.set_timer_quantum(5)
120
149
  begin
121
- MongrelProtocol.connect(@host,@port,@key)
150
+ MongrelProtocol.connect(@host,@port.to_i,@key)
122
151
  rescue StopServer
123
152
  EventMachine.stop_event_loop
124
153
  end
125
154
  end
126
- end
155
+ #end
127
156
  end
128
157
 
129
158
  def process_http_request(params,linebuffer,client)
130
159
  if not params[Const::REQUEST_PATH]
131
160
  uri = URI.parse(params[Const::REQUEST_URI])
132
- params[Const::REQUEST_PATH] = uri.request_uri
161
+ params[Const::REQUEST_PATH] = uri.path
133
162
  end
134
163
 
135
- raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
164
+ raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
136
165
 
137
166
  script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
138
167
 
@@ -140,8 +169,15 @@ module Mongrel
140
169
  notifiers = handlers.select { |h| h.request_notify }
141
170
  request = HttpRequest.new(params, linebuffer, notifiers)
142
171
 
172
+ http_version = params[CHTTP_VERSION]
173
+ if http_version == C1_1
174
+ keep_alive = params[CHTTP_CONNECTION] =~ /close/i ? false : true
175
+ else
176
+ keep_alive = params[CHTTP_CONNECTION] =~ /alive/i ? true : false
177
+ end
178
+
143
179
  # request is good so far, continue processing the response
144
- response = HttpResponse.new(client)
180
+ response = HttpResponse.new(client,http_version,keep_alive)
145
181
 
146
182
  # Process each handler in registered order until we run out or one finalizes the response.
147
183
  dispatch_to_handlers(handlers,request,response)
@@ -156,19 +192,20 @@ module Mongrel
156
192
  # header MUST accompany all HTTP responses that go into a swiftiply
157
193
  # keepalive connection, so just use the Response object to construct the
158
194
  # 404 response.
195
+ response = HttpResponse.new(client)
159
196
  response.status = 404
160
- response.body = "#{params[Const::REQUEST_PATH]} not found"
197
+ response.body << "#{params[Const::REQUEST_PATH]} not found"
161
198
  response.finished
162
199
  end
163
200
  end
164
-
201
+
165
202
  def dispatch_to_handlers(handlers,request,response)
166
203
  handlers.each do |handler|
167
204
  handler.process(request, response)
168
205
  break if response.done
169
206
  end
170
207
  end
171
-
208
+
172
209
  end
173
210
 
174
211
  class HttpRequest
@@ -180,6 +217,25 @@ module Mongrel
180
217
  end
181
218
 
182
219
  class HttpResponse
220
+
221
+ CContentLength = 'Content-Length'.freeze
222
+ C1_1 = '1.1'.freeze
223
+
224
+ def initialize(socket,http_version = C1_1, keepalive = false)
225
+ @socket = socket
226
+ @body = StringIO.new
227
+ @status = 404
228
+ @reason = nil
229
+ @header = HeaderOut.new(StringIO.new)
230
+ @header[Const::DATE] = Time.now.httpdate
231
+ @body_sent = false
232
+ @header_sent = false
233
+ @status_sent = false
234
+ @http_version = http_version
235
+ #@keepalive = keepalive
236
+ @keepalive = false ## DISABLE ALL KEEPALIVE
237
+ end
238
+
183
239
  def send_file(path, small_file = false)
184
240
  File.open(path, "rb") do |f|
185
241
  while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
@@ -193,6 +249,18 @@ module Mongrel
193
249
  @body_sent = true
194
250
  end
195
251
 
252
+ def send_status(content_length=@body.length)
253
+ unless @status_sent
254
+ @header[CContentLength] = content_length if content_length && @status != 304
255
+ if @keepalive
256
+ write("HTTP/1.1 #{@status} #{@reason || HTTP_STATUS_CODES[@status]}\r\nConnection: Keep-Alive\r\n")
257
+ else
258
+ write("HTTP/1.1 #{@status} #{@reason || HTTP_STATUS_CODES[@status]}\r\nConnection: Close\r\n")
259
+ end
260
+ @status_sent = true
261
+ end
262
+ end
263
+
196
264
  def write(data)
197
265
  @socket.send_data data
198
266
  end
@@ -204,12 +272,12 @@ module Mongrel
204
272
  end
205
273
 
206
274
  def finished
207
- send_status
208
- send_header
209
- send_body
275
+ send_status
276
+ send_header
277
+ send_body
210
278
  end
211
279
  end
212
-
280
+
213
281
  class Configurator
214
282
  # This version fixes a bug in the regular Mongrel version by adding
215
283
  # initialization of groups.
@@ -218,12 +286,12 @@ module Mongrel
218
286
  log "Initializing groups for {#user}:{#group}."
219
287
  Process.initgroups(user,Etc.getgrnam(group).gid)
220
288
  end
221
-
289
+
222
290
  if group
223
291
  log "Changing group to #{group}."
224
292
  Process::GID.change_privilege(Etc.getgrnam(group).gid)
225
293
  end
226
-
294
+
227
295
  if user
228
296
  log "Changing user to #{user}."
229
297
  Process::UID.change_privilege(Etc.getpwnam(user).uid)