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.
- 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,213 @@
|
|
1
|
+
module Swiftcore
|
2
|
+
module Swiftiply
|
3
|
+
|
4
|
+
# The BackendProtocol is the EventMachine::Connection subclass that
|
5
|
+
# handles the communications between Swiftiply and the backend process
|
6
|
+
# it is proxying to.
|
7
|
+
|
8
|
+
class BackendProtocol < EventMachine::Connection
|
9
|
+
attr_accessor :associate, :id
|
10
|
+
|
11
|
+
C0rnrn = "0\r\n\r\n".freeze
|
12
|
+
Crnrn = "\r\n\r\n".freeze
|
13
|
+
|
14
|
+
def initialize *args
|
15
|
+
@name = self.class.bname
|
16
|
+
@permit_xsendfile = self.class.xsendfile
|
17
|
+
@enable_sendfile_404 = self.class.enable_sendfile_404
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
@name
|
23
|
+
end
|
24
|
+
|
25
|
+
# Call setup() and add the backend to the ProxyBag queue.
|
26
|
+
|
27
|
+
def post_init
|
28
|
+
setup
|
29
|
+
@initialized = nil
|
30
|
+
ProxyBag.add_server self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Setup the initial variables for receiving headers and content.
|
34
|
+
|
35
|
+
def setup
|
36
|
+
@headers = ''
|
37
|
+
@headers_completed = @dont_send_data = false
|
38
|
+
#@content_length = nil
|
39
|
+
@content_sent = 0
|
40
|
+
@filter = self.class.filter
|
41
|
+
end
|
42
|
+
|
43
|
+
# Receive data from the backend process. Headers are parsed from
|
44
|
+
# the rest of the content. If a Content-Length header is present,
|
45
|
+
# that is used to determine how much data to expect. Otherwise,
|
46
|
+
# if 'Transfer-encoding: chunked' is present, assume chunked
|
47
|
+
# encoding. Otherwise be paranoid; something isn't the way we like
|
48
|
+
# it to be.
|
49
|
+
|
50
|
+
def receive_data data
|
51
|
+
unless @initialized
|
52
|
+
# preamble = data.slice!(0..24)
|
53
|
+
preamble = data[0..24]
|
54
|
+
data = data[25..-1] || C_empty
|
55
|
+
keylen = preamble[23..24].to_i(16)
|
56
|
+
keylen = 0 if keylen < 0
|
57
|
+
key = keylen > 0 ? data.slice!(0..(keylen - 1)) : C_empty
|
58
|
+
#if preamble[0..10] == Cswiftclient and key == ProxyBag.get_key(@name)
|
59
|
+
if preamble.index(Cswiftclient) == 0 and key == ProxyBag.get_key(@name)
|
60
|
+
@id = preamble[11..22]
|
61
|
+
ProxyBag.add_id(self,@id)
|
62
|
+
@initialized = true
|
63
|
+
else
|
64
|
+
# The worker that connected did not present the proper authentication,
|
65
|
+
# so something is fishy; time to cut bait.
|
66
|
+
close_connection
|
67
|
+
return
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
unless @headers_completed
|
72
|
+
if data.include?(Crnrn)
|
73
|
+
@headers_completed = true
|
74
|
+
h,data = data.split(/\r\n\r\n/,2)
|
75
|
+
#@headers << h << Crnrn
|
76
|
+
if @headers.length > 0
|
77
|
+
@headers << h
|
78
|
+
else
|
79
|
+
@headers = h
|
80
|
+
end
|
81
|
+
|
82
|
+
if @headers =~ /Content-[Ll]ength: *([^\r]+)/
|
83
|
+
@content_length = $1.to_i
|
84
|
+
elsif @headers =~ /Transfer-encoding:\s*chunked/
|
85
|
+
@content_length = nil
|
86
|
+
else
|
87
|
+
@content_length = 0
|
88
|
+
end
|
89
|
+
|
90
|
+
if @permit_xsendfile && @headers =~ /X-[Ss]endfile: *([^\r]+)/
|
91
|
+
@associate.uri = $1
|
92
|
+
if ProxyBag.serve_static_file(@associate,ProxyBag.get_sendfileroot(@associate.name))
|
93
|
+
@dont_send_data = true
|
94
|
+
else
|
95
|
+
if @enable_sendfile_404
|
96
|
+
msg = "#{@associate.uri} could not be found."
|
97
|
+
@associate.send_data "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\nContent-Type: text/html\r\nContent-Length: #{msg.length}\r\n\r\n#{msg}"
|
98
|
+
@associate.close_connection_after_writing
|
99
|
+
@dont_send_data = true
|
100
|
+
else
|
101
|
+
@associate.send_data @headers + Crnrn
|
102
|
+
end
|
103
|
+
end
|
104
|
+
else
|
105
|
+
@associate.send_data @headers + Crnrn
|
106
|
+
end
|
107
|
+
|
108
|
+
# If keepalive is turned on, the assumption is that it will stay
|
109
|
+
# on, unless the headers being returned indicate that the connection
|
110
|
+
# should be closed.
|
111
|
+
# So, check for a 'Connection: Closed' header.
|
112
|
+
if keepalive = @associate.keepalive
|
113
|
+
keepalive = false if @headers =~ /Connection: [Cc]lose/
|
114
|
+
if @associate_http_version == C1_0
|
115
|
+
keepalive = false unless @headers == /Connection: Keep-Alive/i
|
116
|
+
end
|
117
|
+
end
|
118
|
+
else
|
119
|
+
@headers << data
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if @headers_completed
|
124
|
+
@associate.send_data data unless @dont_send_data
|
125
|
+
@content_sent += data.length
|
126
|
+
if @content_length and @content_sent >= @content_length or data[-6..-1] == C0rnrn
|
127
|
+
# If @dont_send_data is set, then the connection is going to be closed elsewhere.
|
128
|
+
unless @dont_send_data
|
129
|
+
# Check to see if keepalive is enabled.
|
130
|
+
if keepalive
|
131
|
+
@associate.reset_state
|
132
|
+
ProxyBag.remove_client(self) unless @associate
|
133
|
+
else
|
134
|
+
@associate.close_connection_after_writing
|
135
|
+
end
|
136
|
+
end
|
137
|
+
@associate = @headers_completed = @dont_send_data = nil
|
138
|
+
@headers = ''
|
139
|
+
#@headers_completed = false
|
140
|
+
#@content_length = nil
|
141
|
+
@content_sent = 0
|
142
|
+
#setup
|
143
|
+
ProxyBag.add_server self
|
144
|
+
end
|
145
|
+
end
|
146
|
+
# TODO: Log these errors!
|
147
|
+
rescue Exception => e
|
148
|
+
puts "Kaboom: #{e} -- #{e.backtrace.inspect}"
|
149
|
+
@associate.close_connection_after_writing if @associate
|
150
|
+
@associate = nil
|
151
|
+
setup
|
152
|
+
ProxyBag.add_server self
|
153
|
+
end
|
154
|
+
|
155
|
+
# This is called when the backend disconnects from the proxy.
|
156
|
+
# If the backend is currently associated with a web browser client,
|
157
|
+
# that connection will be closed. Otherwise, the backend will be
|
158
|
+
# removed from the ProxyBag's backend queue.
|
159
|
+
|
160
|
+
def unbind
|
161
|
+
if @associate
|
162
|
+
if !@associate.redeployable or @content_length
|
163
|
+
@associate.close_connection_after_writing
|
164
|
+
else
|
165
|
+
@associate.associate = nil
|
166
|
+
@associate.setup_for_redeployment
|
167
|
+
ProxyBag.rebind_frontend_client(@associate)
|
168
|
+
end
|
169
|
+
else
|
170
|
+
ProxyBag.remove_server(self)
|
171
|
+
end
|
172
|
+
ProxyBag.remove_id(self)
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.bname=(val)
|
176
|
+
@bname = val
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.bname
|
180
|
+
@bname
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.xsendfile=(val)
|
184
|
+
@xsendfile = val
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.xsendfile
|
188
|
+
@xsendfile
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.enable_sendfile_404=(val)
|
192
|
+
@enable_sendfile_404 = val
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.enable_sendfile_404
|
196
|
+
@enable_sendfile_404
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.filter=(val)
|
200
|
+
@filter = val
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.filter
|
204
|
+
@filter
|
205
|
+
end
|
206
|
+
|
207
|
+
def filter
|
208
|
+
@filter
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Swiftcore
|
2
|
+
begin
|
3
|
+
# Attempt to load the SplayTreeMap and Deque. If it is not found, rubygems
|
4
|
+
# will be required, and SplayTreeMap will be checked for again. If it is
|
5
|
+
# still not found, then it will be recorded as unavailable, and the
|
6
|
+
# remainder of the requires will be performed. If any of them are not
|
7
|
+
# found, and rubygems has not been required, it will be required and the
|
8
|
+
# code will retry, once.
|
9
|
+
load_state ||= :start
|
10
|
+
rubygems_loaded ||= false
|
11
|
+
load_state = :splaytreemap
|
12
|
+
require 'swiftcore/splaytreemap' unless const_defined?(:HasSplayTree)
|
13
|
+
HasSplayTree = true unless const_defined?(:HasSplayTree)
|
14
|
+
|
15
|
+
load_state = :deque
|
16
|
+
require 'swiftcore/deque' unless const_defined?(:HasDeque)
|
17
|
+
HasDeque = true unless const_defined?(:HasDeque)
|
18
|
+
|
19
|
+
load_state = :remainder
|
20
|
+
rescue LoadError => e
|
21
|
+
if !rubygems_loaded
|
22
|
+
begin
|
23
|
+
require 'rubygems'
|
24
|
+
rubygems_loaded = true
|
25
|
+
rescue LoadError
|
26
|
+
raise e
|
27
|
+
end
|
28
|
+
retry
|
29
|
+
end
|
30
|
+
|
31
|
+
case load_state
|
32
|
+
when :deque
|
33
|
+
HasDeque = false
|
34
|
+
retry
|
35
|
+
when :splaytreemap
|
36
|
+
HasSplayTreeMap = false
|
37
|
+
retry
|
38
|
+
end
|
39
|
+
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
if HasSplayTree
|
45
|
+
require 'swiftcore/Swiftiply/splay_cache_base'
|
46
|
+
else
|
47
|
+
require 'swiftcore/Swiftiply/hash_cache_base'
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Swiftcore
|
2
|
+
module Swiftiply
|
3
|
+
module CacheBaseMixin
|
4
|
+
attr_accessor :vw, :owners, :owner_hash, :name
|
5
|
+
|
6
|
+
def add_to_verification_queue(path)
|
7
|
+
@vq.unshift(path)
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def vqlength
|
12
|
+
@vq.length
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_verification_queue
|
16
|
+
start = Time.now
|
17
|
+
count = 0
|
18
|
+
@push_to_vq = {}
|
19
|
+
vql = @vq.length
|
20
|
+
qg = vql - @old_vql
|
21
|
+
while Time.now < start + @tl && !@vq.empty?
|
22
|
+
count += 1
|
23
|
+
path = @vq.pop
|
24
|
+
verify(path) ? @push_to_vq[path] = 1 : delete(path)
|
25
|
+
end
|
26
|
+
@push_to_vq.each_key {|x| add_to_verification_queue(x)}
|
27
|
+
@old_vql = @vq.length
|
28
|
+
|
29
|
+
rt = Time.now - start
|
30
|
+
|
31
|
+
# This algorithm is self adaptive based on the amount of work
|
32
|
+
# completed in the time slice, and the amount of remaining work
|
33
|
+
# in the queue.
|
34
|
+
# (verification_window / (verification_queue_length / count)) * (real_time / time_limit)
|
35
|
+
|
36
|
+
# If the queue growth in the last time period exceeded the count of items consumed this time,
|
37
|
+
# use the ratio of the two to reduce the count number. This will result in a shorter period of
|
38
|
+
# of time before the next check cycle. This lets the system stay on top of things when there
|
39
|
+
# are bursts.
|
40
|
+
if qg > count
|
41
|
+
count *= count/qg
|
42
|
+
end
|
43
|
+
if vql == 0
|
44
|
+
@vw / 2
|
45
|
+
else
|
46
|
+
wait_time = (@vwtl * count) / (vql * rt)
|
47
|
+
wait_time < rt ? rt * 2.0 : wait_time > @vw ? @vw : wait_time
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Encoding:ascii-8bit
|
2
|
+
|
3
|
+
require "swiftcore/Swiftiply/http_recognizer"
|
4
|
+
|
5
|
+
module Swiftcore
|
6
|
+
module Swiftiply
|
7
|
+
|
8
|
+
# The ClusterProtocol is the subclass of EventMachine::Connection used
|
9
|
+
# to communicate between Swiftiply and the web browser clients.
|
10
|
+
|
11
|
+
class ClusterProtocol < HttpRecognizer
|
12
|
+
|
13
|
+
proxy_bag_class_is Swiftcore::Swiftiply::ProxyBag
|
14
|
+
|
15
|
+
C503Header = "HTTP/1.1 503 Server Unavailable\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
|
16
|
+
|
17
|
+
def self.init_class_variables
|
18
|
+
@count_503 = 0
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.increment_503_count
|
23
|
+
@count_503 += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
# Hardcoded 503 response that is sent if a connection is timed out while
|
27
|
+
# waiting for a backend to handle it.
|
28
|
+
|
29
|
+
def send_503_response
|
30
|
+
ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host
|
31
|
+
error = "The request (#{@uri} --> #{@name}), received on #{create_time.asctime} from #{ip} timed out before being deployed to a server for processing."
|
32
|
+
send_data "#{C503Header}Server Unavailable\n\n#{error}"
|
33
|
+
ProxyBag.logger.log(Cinfo,"Server Unavailable -- #{error}")
|
34
|
+
close_connection_after_writing
|
35
|
+
increment_503_count
|
36
|
+
end
|
37
|
+
|
38
|
+
def increment_503_count
|
39
|
+
@klass.increment_503_count
|
40
|
+
end
|
41
|
+
|
42
|
+
def push
|
43
|
+
if @associate
|
44
|
+
unless @redeployable
|
45
|
+
# normal data push
|
46
|
+
data = nil
|
47
|
+
@associate.send_data data while data = @data.pop
|
48
|
+
else
|
49
|
+
# redeployable data push; just send the stuff that has
|
50
|
+
# not already been sent.
|
51
|
+
(@data.length - 1 - @data_pos).downto(0) do |p|
|
52
|
+
d = @data[p]
|
53
|
+
@associate.send_data d
|
54
|
+
@data_len += d.length
|
55
|
+
end
|
56
|
+
@data_pos = @data.length
|
57
|
+
|
58
|
+
# If the request size crosses the size limit, then
|
59
|
+
# disallow redeployent of this request.
|
60
|
+
if @data_len > @redeployable
|
61
|
+
@redeployable = false
|
62
|
+
@data.clear
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,370 @@
|
|
1
|
+
module Swiftcore
|
2
|
+
module Swiftiply
|
3
|
+
class Config
|
4
|
+
def self.configure_logging(config, data)
|
5
|
+
ProxyBag.remove_log(data[:hash])
|
6
|
+
if config['logger'] and (!ProxyBag.log(data[:hash]) or MockLog === ProxyBag.log(data[:hash]))
|
7
|
+
new_log = ::Swiftcore::Swiftiply::handle_logger_config(config['logger'])
|
8
|
+
|
9
|
+
ProxyBag.add_log(new_log[:logger],data[:hash])
|
10
|
+
ProxyBag.set_level(new_log[:log_level],data[:hash])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configure_file_cache(config, data)
|
15
|
+
# The File Cache defaults to a max size of 100 elements, with a refresh
|
16
|
+
# window of five minues, and a time slice of a hundredth of a second.
|
17
|
+
sz = 100
|
18
|
+
vw = 300
|
19
|
+
ts = 0.01
|
20
|
+
|
21
|
+
if config.has_key?(Cfile_cache)
|
22
|
+
sz = config[Cfile_cache][Csize] || 100
|
23
|
+
sz = 100 if sz < 0
|
24
|
+
vw = config[Cfile_cache][Cwindow] || 900
|
25
|
+
vw = 900 if vw < 0
|
26
|
+
ts = config[Cfile_cache][Ctimeslice] || 0.01
|
27
|
+
ts = 0.01 if ts < 0
|
28
|
+
end
|
29
|
+
|
30
|
+
ProxyBag.logger.log('debug',"Creating File Cache; size=#{sz}, window=#{vw}, timeslice=#{ts}") if ProxyBag.log_level > 2
|
31
|
+
file_cache = Swiftcore::Swiftiply::FileCache.new(vw,ts,sz)
|
32
|
+
file_cache.owners = data[:owners]
|
33
|
+
file_cache.owner_hash = data[:hash]
|
34
|
+
EventMachine.add_timer(vw/2) {ProxyBag.verify_cache(file_cache)} unless RunningConfig[:initialized]
|
35
|
+
file_cache
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.configure_dynamic_request_cache(config, data)
|
39
|
+
# The Dynamic Request Cache defaults to a max size of 100, with a 15 minute
|
40
|
+
# refresh window, and a time slice of a hundredth of a second.
|
41
|
+
sz = 100
|
42
|
+
vw = 900
|
43
|
+
ts = 0.01
|
44
|
+
if config.has_key?(Cdynamic_request_cache)
|
45
|
+
sz = config[Cdynamic_request_cache][Csize] || 100
|
46
|
+
sz = 100 if sz < 0
|
47
|
+
vw = config[Cdynamic_request_cache][Cwindow] || 900
|
48
|
+
vw = 900 if vw < 0
|
49
|
+
ts = config[Cdynamic_request_cache][Ctimeslice] || 0.01
|
50
|
+
ts = 0.01 if ts < 0
|
51
|
+
end
|
52
|
+
ProxyBag.logger.log('debug',"Creating Dynamic Request Cache; size=#{sz}, window=#{vw}, timeslice=#{ts}") if ProxyBag.log_level > 2
|
53
|
+
dynamic_request_cache = Swiftcore::Swiftiply::DynamicRequestCache.new(config[Cdocroot],vw,ts,sz)
|
54
|
+
dynamic_request_cache.owners = data[:owners]
|
55
|
+
dynamic_request_cache.owner_hash = data[:hash]
|
56
|
+
EventMachine.add_timer(vw/2) {ProxyBag.verify_cache(dynamic_request_cache)} unless Swiftcore::Swiftiply::RunningConfig[:initialized]
|
57
|
+
dynamic_request_cache
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.configure_etag_cache(config, data)
|
61
|
+
# The ETag Cache defaults to a max size of 10000 (it doesn't take a lot
|
62
|
+
# of RAM to hold an etag), with a 5 minute refresh window and a time
|
63
|
+
# slice of a hundredth of a second.
|
64
|
+
sz = 10000
|
65
|
+
vw = 300
|
66
|
+
ts = 0.01
|
67
|
+
if config.has_key?(Cetag_cache)
|
68
|
+
sz = config[Cetag_cache][Csize] || 100
|
69
|
+
sz = 100 if sz < 0
|
70
|
+
vw = config[Cetag_cache][Cwindow] || 900
|
71
|
+
vw = 900 if vw < 0
|
72
|
+
ts = config[Cetag_cache][Ctimeslice] || 0.01
|
73
|
+
ts = 0.01 if ts < 0
|
74
|
+
end
|
75
|
+
ProxyBag.logger.log('debug',"Creating ETag Cache; size=#{sz}, window=#{vw}, timeslice=#{ts}") if ProxyBag.log_level > 2
|
76
|
+
etag_cache = Swiftcore::Swiftiply::EtagCache.new(vw,ts,sz)
|
77
|
+
etag_cache.owners = data[:owners]
|
78
|
+
etag_cache.owner_hash = data[:hash]
|
79
|
+
EventMachine.add_timer(vw/2) {ProxyBag.verify_cache(etag_cache)} unless Swiftcore::Swiftiply::RunningConfig[:initialized]
|
80
|
+
etag_cache
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.setup_caches(new_config,data)
|
84
|
+
# The dynamic request cache may need to know a valid client name.
|
85
|
+
data[:dynamic_request_cache].one_client_name ||= data[:p]
|
86
|
+
|
87
|
+
new_config[Cincoming][data[:p]] = {}
|
88
|
+
ProxyBag.add_incoming_mapping(data[:hash],data[:p])
|
89
|
+
ProxyBag.add_file_cache(data[:file_cache],data[:p])
|
90
|
+
ProxyBag.add_dynamic_request_cache(data[:dynamic_request_cache],data[:p])
|
91
|
+
ProxyBag.add_etag_cache(data[:etag_cache],data[:p])
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.configure_docroot(config, p)
|
95
|
+
if config.has_key?(Cdocroot)
|
96
|
+
ProxyBag.add_docroot(config[Cdocroot],p)
|
97
|
+
else
|
98
|
+
ProxyBag.remove_docroot(p)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.configure_sendfileroot(config, p)
|
103
|
+
if config.has_key?(Csendfileroot)
|
104
|
+
ProxyBag.add_sendfileroot(config[Csendfileroot],p)
|
105
|
+
true
|
106
|
+
else
|
107
|
+
ProxyBag.remove_sendfileroot(p)
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.configure_xforwardedfor(config, p)
|
113
|
+
if config[Cxforwardedfor]
|
114
|
+
ProxyBag.set_x_forwarded_for(p)
|
115
|
+
else
|
116
|
+
ProxyBag.unset_x_forwarded_for(p)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.configure_redeployable(config, p)
|
121
|
+
if config[Credeployable]
|
122
|
+
ProxyBag.add_redeployable(config[Credeployment_sizelimit] || 16384,p)
|
123
|
+
else
|
124
|
+
ProxyBag.remove_redeployable(p)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.configure_key(config, p, data)
|
129
|
+
if config.has_key?(Ckey)
|
130
|
+
ProxyBag.set_key(data[:hash],config[Ckey])
|
131
|
+
else
|
132
|
+
ProxyBag.set_key(data[:hash],C_empty)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.configure_staticmask(config, p)
|
137
|
+
if config.has_key?(Cstaticmask)
|
138
|
+
ProxyBag.add_static_mask(Regexp.new(config[Cstaticmask]),p)
|
139
|
+
else
|
140
|
+
ProxyBag.remove_static_mask(p)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.configure_cache_extensions(config, p)
|
145
|
+
if config.has_key?(Ccache_extensions) or config.has_key?(Ccache_directory)
|
146
|
+
require 'swiftcore/Swiftiply/support_pagecache'
|
147
|
+
ProxyBag.add_suffix_list((config[Ccache_extensions] || ProxyBag.const_get(:DefaultSuffixes)),p)
|
148
|
+
ProxyBag.add_cache_dir((config[Ccache_directory] || ProxyBag.const_get(:DefaultCacheDir)),p)
|
149
|
+
else
|
150
|
+
ProxyBag.remove_suffix_list(p) if ProxyBag.respond_to?(:remove_suffix_list)
|
151
|
+
ProxyBag.remove_cache_dir(p) if ProxyBag.respond_to?(:remove_cache_dir)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.configure_cluster_manager(config, p)
|
156
|
+
# Check for a cluster management section and do setup.
|
157
|
+
# manager: URL
|
158
|
+
#
|
159
|
+
# manager:
|
160
|
+
# callsite: URL
|
161
|
+
#
|
162
|
+
# manager:
|
163
|
+
# require: FILENAME
|
164
|
+
# class: CLASSNAME
|
165
|
+
# callsite: CLASS specific destination
|
166
|
+
# params: param list to pass to the class
|
167
|
+
#
|
168
|
+
# If a filename is not given, cluster management will default to the
|
169
|
+
# URL triggered system, which requires a URL
|
170
|
+
|
171
|
+
if config.has_key?(Cmanager)
|
172
|
+
config[Cmanager].each do |manager|
|
173
|
+
cluster_manager_params = {}
|
174
|
+
if Hash === manager
|
175
|
+
cluster_manager_params[:callsite] = manager[Ccallsite]
|
176
|
+
require manager[Crequire] || "swiftcore/Swiftiply/rest_based_cluster_manager"
|
177
|
+
cluster_manager_params[:class] = get_const_from_name(manager[Cclassname] || "RestBasedClusterManager", ::Swiftcore::Swiftiply::ManagerProtocols)
|
178
|
+
cluster_manager_params[:params] = manager[Cparams] || []
|
179
|
+
else
|
180
|
+
cluster_manager_params[:callsite] = manager
|
181
|
+
require "swiftcore/Swiftiply/rest_based_cluster_manager"
|
182
|
+
cluster_manager_params[:class] = ::Swiftcore::Swiftiply::ManagerProtocols::RestBasedClusterManager
|
183
|
+
cluster_manager_params[:params] = []
|
184
|
+
end
|
185
|
+
ProxyBag.add_cluster_manager(cluster_manager_params, hash)
|
186
|
+
end
|
187
|
+
else
|
188
|
+
ProxyBag.remove_cluster_manager(hash)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.configure_backends(k,args)
|
193
|
+
config = args[:config]
|
194
|
+
p = args[:p]
|
195
|
+
config_data = args[:config_data]
|
196
|
+
new_config = args[:new_config]
|
197
|
+
klass = args[:self]
|
198
|
+
directory_class = args[:directory_class]
|
199
|
+
directory_args = args[:directory_args]
|
200
|
+
|
201
|
+
ProxyBag.remove_filters(p)
|
202
|
+
|
203
|
+
is_a_server = klass.respond_to?(:is_a_server?) && klass.is_a_server?
|
204
|
+
|
205
|
+
if config[k]
|
206
|
+
#
|
207
|
+
# outgoing: 127.0.0.1:12340
|
208
|
+
# outgoing:
|
209
|
+
# to: 127.0.0.1:12340
|
210
|
+
#
|
211
|
+
# outgoing:
|
212
|
+
# match: php$
|
213
|
+
# to: 127.0.0.1:12342
|
214
|
+
#
|
215
|
+
# outgoing:
|
216
|
+
# prefix: /blah
|
217
|
+
# to: 127.0.0.1:12345
|
218
|
+
#
|
219
|
+
#####
|
220
|
+
#
|
221
|
+
# If the outgoing is a simple host:port, then all
|
222
|
+
# requests go striaght to a backend connected to
|
223
|
+
# that socket location.
|
224
|
+
#
|
225
|
+
# If the outgoing is a hash, and the hash only has
|
226
|
+
# a 'to' key, then the behavior is the same as if
|
227
|
+
# it were a simple host:port.
|
228
|
+
#
|
229
|
+
# If the outgoing hash has a 'to' and a 'match',
|
230
|
+
# then the incoming request's uri will be compared
|
231
|
+
# to the regular expression contained in the
|
232
|
+
# 'match' parameter.
|
233
|
+
#
|
234
|
+
# If the outgoing hash has a 'to' and a 'prefix',
|
235
|
+
# then the incoming request's uri will be compared
|
236
|
+
# with the prefix using a trie classifier.
|
237
|
+
# THE PREFIX OPTION IS NOT FULLY IMPLEMENTED!!!
|
238
|
+
config[k].each do |o|
|
239
|
+
# Directory classes have a lot of power to customize what's happening. So, make a new instance right away.
|
240
|
+
new_directory_class = generate_new_directory_class(directory_class, o, new_config)
|
241
|
+
|
242
|
+
if is_a_server
|
243
|
+
if klass.respond_to?(:parse_connection_params)
|
244
|
+
params = klass.parse_connection_params(o, new_directory_class) # Provide the directory class, as it might have an opinion on how to parse this information.
|
245
|
+
out = params[:out]
|
246
|
+
host = params[:host]
|
247
|
+
port = params[:port]
|
248
|
+
filter = params[:filter]
|
249
|
+
else
|
250
|
+
if Hash === o
|
251
|
+
out = [o['to'],o['match'],o['prefix']].compact.join('::')
|
252
|
+
host, port = o['to'].split(/:/,2)
|
253
|
+
filter = Regexp.new(o['match'])
|
254
|
+
else
|
255
|
+
out = o
|
256
|
+
host, port = out.split(/:/,2)
|
257
|
+
filter = nil
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
ProxyBag.logger.log(Cinfo," Configuring outgoing server #{out}") if ::Swiftcore::Swiftiply::log_level > 0
|
262
|
+
ProxyBag.default_name = p if config[Cdefault]
|
263
|
+
if Swiftcore::Swiftiply::existing_backends.has_key?(out)
|
264
|
+
ProxyBag.logger.log(Cinfo,' Already running; skipping') if ::Swiftcore::Swiftiply::log_level > 2
|
265
|
+
new_config[Coutgoing][out] ||= Swiftcore::Swiftiply::RunningConfig[Coutgoing][out]
|
266
|
+
next
|
267
|
+
else
|
268
|
+
# TODO: Add ability to create filters for outgoing destinations, so one can send different path patterns to different outgoing hosts/ports.
|
269
|
+
Swiftcore::Swiftiply::existing_backends[out] = true
|
270
|
+
backend_class = setup_backends(args.dup.merge(:directory_class => new_directory_class), filter)
|
271
|
+
|
272
|
+
begin
|
273
|
+
new_config[Coutgoing][out] = EventMachine.start_server(host, port.to_i, backend_class)
|
274
|
+
rescue RuntimeError => e
|
275
|
+
puts e.inspect
|
276
|
+
advice = ''
|
277
|
+
if port.to_i < 1024
|
278
|
+
advice << 'Make sure you have the correct permissions to use that port, and make sure there is nothing else running on that port.'
|
279
|
+
else
|
280
|
+
advice << 'Make sure there is nothing else running on that port.'
|
281
|
+
end
|
282
|
+
advice << " The original error was: #{e}\n"
|
283
|
+
raise EMStartServerError.new("The listener on #{host}:#{port} could not be started.\n#{advice}")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
else # it's not a server
|
287
|
+
if klass.respond_to?(:parse_connection_params)
|
288
|
+
params = klass.parse_connection_params(o, new_directory_class) # Provide the directory class, as it might have an opinion on how to parse this information.
|
289
|
+
out = params[:out] # this should be some sort of identifier, just for logging purposes.
|
290
|
+
filter = params[:out]
|
291
|
+
else
|
292
|
+
if Hash === o
|
293
|
+
filter = Regexp.new(o['match'])
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
out ||= p
|
298
|
+
ProxyBag.logger.log(Cinfo," Configuring outgoing protocol for #{out}") if ::Swiftcore::Swiftiply::log_level > 0
|
299
|
+
ProxyBag.default_name = p if config[Cdefault]
|
300
|
+
|
301
|
+
setup_backends(args.dup.merge(:directory_class => new_directory_class), filter)
|
302
|
+
end
|
303
|
+
end # done iterating on config
|
304
|
+
else
|
305
|
+
new_directory_class = generate_new_directory_class(directory_class, config, new_config)
|
306
|
+
|
307
|
+
if klass.respond_to?(:parse_connection_params)
|
308
|
+
params = klass.parse_connection_params({}, new_directory_class) # Provide the directory class, as it might have an opinion on how to parse this information.
|
309
|
+
out = params[:out] # this should be some sort of identifier, just for logging purposes.
|
310
|
+
filter = params[:out]
|
311
|
+
end
|
312
|
+
|
313
|
+
out ||= p
|
314
|
+
ProxyBag.logger.log(Cinfo," Configuring outgoing protocol for #{out}") if ::Swiftcore::Swiftiply::log_level > 0
|
315
|
+
ProxyBag.default_name = p if config[Cdefault]
|
316
|
+
|
317
|
+
setup_backends(args.dup.merge(:directory_class => new_directory_class), filter)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.stop_unused_servers(new_config)
|
322
|
+
# Now stop everything that is still running but which isn't needed.
|
323
|
+
if Swiftcore::Swiftiply::RunningConfig.has_key?(Coutgoing)
|
324
|
+
(Swiftcore::Swiftiply::RunningConfig[Coutgoing].keys - new_config[Coutgoing].keys).each do |unneeded_server_key|
|
325
|
+
EventMachine.stop_server(Swiftcore::Swiftiply::RunningConfig[Coutgoing][unneeded_server_key])
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def self.set_server_queue(data, klass, config)
|
331
|
+
klass ||= ::Swiftcore::Deque
|
332
|
+
ProxyBag.set_server_queue(data[:hash], klass, config)
|
333
|
+
end
|
334
|
+
|
335
|
+
def self.generate_new_directory_class(directory_class, config, new_config)
|
336
|
+
new_directory_class = Class.new(directory_class)
|
337
|
+
new_directory_class.config(config, new_config) if new_directory_class.respond_to? :config
|
338
|
+
new_directory_class
|
339
|
+
end
|
340
|
+
|
341
|
+
def self.setup_backends(args, filter)
|
342
|
+
config = args[:config]
|
343
|
+
p = args[:p]
|
344
|
+
config_data = args[:config_data]
|
345
|
+
new_config = args[:new_config]
|
346
|
+
klass = args[:self]
|
347
|
+
directory_class = args[:directory_class]
|
348
|
+
directory_args = args[:directory_args]
|
349
|
+
|
350
|
+
backend_class = Class.new(klass)
|
351
|
+
backend_class.bname = config_data[:hash]
|
352
|
+
ProxyBag.logger.log(Cinfo," Do Caching") if config['caching'] and ::Swiftcore::Swiftiply::log_level > 0
|
353
|
+
backend_class.caching = config['caching'] if backend_class.respond_to? :caching
|
354
|
+
ProxyBag.logger.log(Cinfo," Permit X-Sendfile") if config_data[:permit_xsendfile] and ::Swiftcore::Swiftiply::log_level > 0
|
355
|
+
backend_class.xsendfile = config_data[:permit_xsendfile]
|
356
|
+
ProxyBag.logger.log(Cinfo," Enable 404 on missing Sendfile resource") if config[Cenable_sendfile_404] and ::Swiftcore::Swiftiply::log_level > 0
|
357
|
+
backend_class.enable_sendfile_404 = true if config[Cenable_sendfile_404]
|
358
|
+
backend_class.filter = !filter.nil?
|
359
|
+
|
360
|
+
directory_class.backend_class = backend_class if directory_class.respond_to? :backend_class
|
361
|
+
Config.set_server_queue(config_data, directory_class, directory_args)
|
362
|
+
|
363
|
+
ProxyBag.add_filter(filter,config_data[:hash]) if filter
|
364
|
+
ProxyBag.set_server_queue_as_filter_queue(config_data[:hash],backend_class) if filter
|
365
|
+
backend_class
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|