swiftiply 0.6.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTORS +2 -0
- data/README.md +62 -0
- data/bin/{mongrel_rails → evented_mongrel_rails} +6 -14
- data/bin/swiftiplied_mongrel_rails +246 -0
- data/bin/swiftiply +136 -116
- data/bin/swiftiply_mongrel_rails +2 -2
- data/bin/swiftiplyctl +283 -0
- data/cleanup.sh +5 -0
- data/ext/deque/extconf.rb +162 -0
- data/ext/deque/swiftcore/rubymain.cpp +435 -0
- data/ext/fastfilereader/extconf.rb +2 -2
- data/ext/fastfilereader/mapper.cpp +2 -0
- data/ext/map/extconf.rb +161 -0
- data/ext/map/rubymain.cpp +500 -0
- data/ext/splaytree/extconf.rb +161 -0
- data/ext/splaytree/swiftcore/rubymain.cpp +580 -0
- data/ext/splaytree/swiftcore/splay_map.h +635 -0
- data/ext/splaytree/swiftcore/splay_set.h +575 -0
- data/ext/splaytree/swiftcore/splay_tree.h +1127 -0
- data/external/httpclient.rb +231 -0
- data/external/package.rb +13 -13
- data/setup.rb +18 -2
- data/src/swiftcore/Swiftiply.rb +417 -773
- data/src/swiftcore/Swiftiply/backend_protocol.rb +213 -0
- data/src/swiftcore/Swiftiply/cache_base.rb +49 -0
- data/src/swiftcore/Swiftiply/cache_base_mixin.rb +52 -0
- data/src/swiftcore/Swiftiply/cluster_managers/rest_based_cluster_manager.rb +9 -0
- data/src/swiftcore/Swiftiply/cluster_protocol.rb +70 -0
- data/src/swiftcore/Swiftiply/config.rb +370 -0
- data/src/swiftcore/Swiftiply/config/rest_updater.rb +26 -0
- data/src/swiftcore/Swiftiply/constants.rb +101 -0
- data/src/swiftcore/Swiftiply/content_cache_entry.rb +44 -0
- data/src/swiftcore/Swiftiply/content_response.rb +45 -0
- data/src/swiftcore/Swiftiply/control_protocol.rb +49 -0
- data/src/swiftcore/Swiftiply/dynamic_request_cache.rb +41 -0
- data/src/swiftcore/Swiftiply/etag_cache.rb +64 -0
- data/src/swiftcore/Swiftiply/file_cache.rb +46 -0
- data/src/swiftcore/Swiftiply/hash_cache_base.rb +22 -0
- data/src/swiftcore/Swiftiply/http_recognizer.rb +267 -0
- data/src/swiftcore/Swiftiply/loggers/Analogger.rb +21 -0
- data/src/swiftcore/Swiftiply/loggers/stderror.rb +13 -0
- data/src/swiftcore/Swiftiply/mocklog.rb +10 -0
- data/src/swiftcore/Swiftiply/proxy.rb +15 -0
- data/src/swiftcore/Swiftiply/proxy_backends/keepalive.rb +286 -0
- data/src/swiftcore/Swiftiply/proxy_backends/traditional.rb +286 -0
- data/src/swiftcore/Swiftiply/proxy_backends/traditional/redis_directory.rb +87 -0
- data/src/swiftcore/Swiftiply/proxy_backends/traditional/static_directory.rb +69 -0
- data/src/swiftcore/Swiftiply/proxy_bag.rb +716 -0
- data/src/swiftcore/Swiftiply/rest_based_cluster_manager.rb +15 -0
- data/src/swiftcore/Swiftiply/splay_cache_base.rb +21 -0
- data/src/swiftcore/Swiftiply/support_pagecache.rb +6 -3
- data/src/swiftcore/Swiftiply/swiftiply_2_http_proxy.rb +7 -0
- data/src/swiftcore/Swiftiply/swiftiply_client.rb +20 -5
- data/src/swiftcore/Swiftiply/version.rb +5 -0
- data/src/swiftcore/evented_mongrel.rb +26 -8
- data/src/swiftcore/hash.rb +43 -0
- data/src/swiftcore/method_builder.rb +28 -0
- data/src/swiftcore/streamer.rb +46 -0
- data/src/swiftcore/swiftiplied_mongrel.rb +91 -23
- data/src/swiftcore/types.rb +20 -3
- data/swiftiply.gemspec +14 -8
- data/test/TC_Deque.rb +152 -0
- data/test/TC_ProxyBag.rb +147 -166
- data/test/TC_Swiftiply.rb +576 -169
- data/test/TC_Swiftiply/mongrel/evented_hello.rb +1 -1
- data/test/TC_Swiftiply/mongrel/swiftiplied_hello.rb +1 -1
- data/test/TC_Swiftiply/test_serve_static_file_xsendfile/sendfile_client.rb +27 -0
- data/test/TC_Swiftiply/test_ssl/bin/validate_ssl_capability.rb +21 -0
- data/test/TC_Swiftiply/test_ssl/test.cert +16 -0
- data/test/TC_Swiftiply/test_ssl/test.key +15 -0
- data/{bin → test/bin}/echo_client +0 -0
- metadata +136 -94
- data/README +0 -126
- 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
|
-
|
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
|
-
|
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
|
@@ -1,6 +1,20 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
87
|
+
send_data C400Header
|
88
|
+
close_connection_after_writing
|
74
89
|
rescue Exception => e
|
75
|
-
|
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
|
-
|
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.
|
161
|
+
params[Const::REQUEST_PATH] = uri.path
|
133
162
|
end
|
134
163
|
|
135
|
-
|
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
|
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
|
-
|
208
|
-
|
209
|
-
|
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)
|