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,231 @@
|
|
1
|
+
# This is a replacement for the regular EventMachine HttpClient library.
|
2
|
+
# It removes a few lines that seek to protect the user against himself.
|
3
|
+
# This makes it a much more useful tool for sending wacky request lines
|
4
|
+
# to a web server.
|
5
|
+
|
6
|
+
|
7
|
+
module EventMachine
|
8
|
+
module Protocols
|
9
|
+
|
10
|
+
class HttpClient < Connection
|
11
|
+
include EventMachine::Deferrable
|
12
|
+
|
13
|
+
remove_const :MaxPostContentLength if MaxPostContentLength
|
14
|
+
MaxPostContentLength = 20 * 1024 * 1024
|
15
|
+
|
16
|
+
# USAGE SAMPLE:
|
17
|
+
#
|
18
|
+
# EventMachine.run {
|
19
|
+
# http = EventMachine::Protocols::HttpClient.request(
|
20
|
+
# :host => server,
|
21
|
+
# :port => 80,
|
22
|
+
# :request => "/index.html",
|
23
|
+
# :query_string => "parm1=value1&parm2=value2"
|
24
|
+
# )
|
25
|
+
# http.callback {|response|
|
26
|
+
# puts response[:status]
|
27
|
+
# puts response[:headers]
|
28
|
+
# puts response[:content]
|
29
|
+
# }
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
|
33
|
+
# TODO:
|
34
|
+
# Add streaming so we can support enormous POSTs. Current max is 20meg.
|
35
|
+
# Timeout for connections that run too long or hang somewhere in the middle.
|
36
|
+
# Persistent connections (HTTP/1.1), may need a associated delegate object.
|
37
|
+
# DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
|
38
|
+
# DNS lookups are unbelievably slow.
|
39
|
+
# HEAD requests.
|
40
|
+
# Chunked transfer encoding.
|
41
|
+
# Convenience methods for requests. get, post, url, etc.
|
42
|
+
# SSL.
|
43
|
+
# Handle status codes like 304, 100, etc.
|
44
|
+
# Refactor this code so that protocol errors all get handled one way (an exception?),
|
45
|
+
# instead of sprinkling set_deferred_status :failed calls everywhere.
|
46
|
+
|
47
|
+
def self.request( args = {} )
|
48
|
+
args[:port] ||= 80
|
49
|
+
EventMachine.connect( args[:host], args[:port], self ) {|c|
|
50
|
+
# According to the docs, we will get here AFTER post_init is called.
|
51
|
+
c.instance_eval {@args = args}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def post_init
|
56
|
+
@start_time = Time.now
|
57
|
+
@data = ""
|
58
|
+
@read_state = :base
|
59
|
+
end
|
60
|
+
|
61
|
+
# We send the request when we get a connection.
|
62
|
+
# AND, we set an instance variable to indicate we passed through here.
|
63
|
+
# That allows #unbind to know whether there was a successful connection.
|
64
|
+
# NB: This naive technique won't work when we have to support multiple
|
65
|
+
# requests on a single connection.
|
66
|
+
def connection_completed
|
67
|
+
@connected = true
|
68
|
+
send_request @args
|
69
|
+
end
|
70
|
+
|
71
|
+
def send_request args
|
72
|
+
args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
|
73
|
+
args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
|
74
|
+
|
75
|
+
verb = args[:verb].to_s.upcase
|
76
|
+
unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
|
77
|
+
set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
|
78
|
+
return # NOTE THE EARLY RETURN, we're not sending any data.
|
79
|
+
end
|
80
|
+
|
81
|
+
request = args[:request] || "/"
|
82
|
+
# unless request[0,1] == "/"
|
83
|
+
# request = "/" + request
|
84
|
+
# end
|
85
|
+
|
86
|
+
qs = args[:query_string] || ""
|
87
|
+
if qs.length > 0 and qs[0,1] != '?'
|
88
|
+
qs = "?" + qs
|
89
|
+
end
|
90
|
+
|
91
|
+
# Allow an override for the host header if it's not the connect-string.
|
92
|
+
host = args[:host_header] || args[:host] || "_"
|
93
|
+
# For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
|
94
|
+
port = args[:port]
|
95
|
+
|
96
|
+
# POST items.
|
97
|
+
postcontenttype = args[:contenttype] || "application/octet-stream"
|
98
|
+
postcontent = args[:content] || ""
|
99
|
+
raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
|
100
|
+
|
101
|
+
# ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
|
102
|
+
# TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
|
103
|
+
req = [
|
104
|
+
"#{verb} #{request}#{qs} HTTP/1.1",
|
105
|
+
"Host: #{host}:#{port}",
|
106
|
+
"User-agent: Ruby EventMachine",
|
107
|
+
]
|
108
|
+
|
109
|
+
if verb == "POST" || verb == "PUT"
|
110
|
+
req << "Content-type: #{postcontenttype}"
|
111
|
+
req << "Content-length: #{postcontent.length}"
|
112
|
+
end
|
113
|
+
|
114
|
+
# TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
|
115
|
+
# Eventually we will want to deal intelligently with arrays and hashes.
|
116
|
+
if args[:cookie]
|
117
|
+
req << "Cookie: #{args[:cookie]}"
|
118
|
+
end
|
119
|
+
|
120
|
+
req << ""
|
121
|
+
reqstring = req.map {|l| "#{l}\r\n"}.join
|
122
|
+
send_data reqstring
|
123
|
+
|
124
|
+
if verb == "POST" || verb == "PUT"
|
125
|
+
send_data postcontent
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def receive_data data
|
131
|
+
while data and data.length > 0
|
132
|
+
case @read_state
|
133
|
+
when :base
|
134
|
+
# Perform any per-request initialization here and don't consume any data.
|
135
|
+
@data = ""
|
136
|
+
@headers = []
|
137
|
+
@content_length = nil # not zero
|
138
|
+
@content = ""
|
139
|
+
@status = nil
|
140
|
+
@read_state = :header
|
141
|
+
when :header
|
142
|
+
ary = data.split( /\r?\n/m, 2 )
|
143
|
+
if ary.length == 2
|
144
|
+
data = ary.last
|
145
|
+
if ary.first == ""
|
146
|
+
if @content_length and @content_length > 0
|
147
|
+
@read_state = :content
|
148
|
+
else
|
149
|
+
dispatch_response
|
150
|
+
@read_state = :base
|
151
|
+
end
|
152
|
+
else
|
153
|
+
@headers << ary.first
|
154
|
+
if @headers.length == 1
|
155
|
+
parse_response_line
|
156
|
+
elsif ary.first =~ /\Acontent-length:\s*/i
|
157
|
+
# Only take the FIRST content-length header that appears,
|
158
|
+
# which we can distinguish because @content_length is nil.
|
159
|
+
# TODO, it's actually a fatal error if there is more than one
|
160
|
+
# content-length header, because the caller is presumptively
|
161
|
+
# a bad guy. (There is an exploit that depends on multiple
|
162
|
+
# content-length headers.)
|
163
|
+
@content_length ||= $'.to_i
|
164
|
+
end
|
165
|
+
end
|
166
|
+
else
|
167
|
+
@data << data
|
168
|
+
data = ""
|
169
|
+
end
|
170
|
+
when :content
|
171
|
+
# If there was no content-length header, we have to wait until the connection
|
172
|
+
# closes. Everything we get until that point is content.
|
173
|
+
# TODO: Must impose a content-size limit, and also must implement chunking.
|
174
|
+
# Also, must support either temporary files for large content, or calling
|
175
|
+
# a content-consumer block supplied by the user.
|
176
|
+
if @content_length
|
177
|
+
bytes_needed = @content_length - @content.length
|
178
|
+
@content += data[0, bytes_needed]
|
179
|
+
data = data[bytes_needed..-1] || ""
|
180
|
+
if @content_length == @content.length
|
181
|
+
dispatch_response
|
182
|
+
@read_state = :base
|
183
|
+
end
|
184
|
+
else
|
185
|
+
@content << data
|
186
|
+
data = ""
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# We get called here when we have received an HTTP response line.
|
194
|
+
# It's an opportunity to throw an exception or trigger other exceptional
|
195
|
+
# handling.
|
196
|
+
def parse_response_line
|
197
|
+
if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
|
198
|
+
@status = $1.to_i
|
199
|
+
else
|
200
|
+
set_deferred_status :failed, {
|
201
|
+
:status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
|
202
|
+
}
|
203
|
+
close_connection
|
204
|
+
end
|
205
|
+
end
|
206
|
+
private :parse_response_line
|
207
|
+
|
208
|
+
def dispatch_response
|
209
|
+
@read_state = :base
|
210
|
+
set_deferred_status :succeeded, {
|
211
|
+
:content => @content,
|
212
|
+
:headers => @headers,
|
213
|
+
:status => @status
|
214
|
+
}
|
215
|
+
# TODO, we close the connection for now, but this is wrong for persistent clients.
|
216
|
+
close_connection
|
217
|
+
end
|
218
|
+
|
219
|
+
def unbind
|
220
|
+
if !@connected
|
221
|
+
set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
|
222
|
+
elsif (@read_state == :content and @content_length == nil)
|
223
|
+
dispatch_response
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
|
data/external/package.rb
CHANGED
@@ -36,9 +36,9 @@ end
|
|
36
36
|
|
37
37
|
def self.config(name)
|
38
38
|
# XXX use pathname
|
39
|
-
prefix = Regexp.quote(
|
40
|
-
exec_prefix = Regexp.quote(
|
41
|
-
|
39
|
+
prefix = Regexp.quote(RbConfig::CONFIG["prefix"])
|
40
|
+
exec_prefix = Regexp.quote(RbConfig::CONFIG["exec_prefix"])
|
41
|
+
RbConfig::CONFIG[name].gsub(/\A\/?(#{prefix}|#{exec_prefix})\/?/, '')
|
42
42
|
end
|
43
43
|
|
44
44
|
SITE_DIRS = {
|
@@ -70,7 +70,7 @@ MODES = {
|
|
70
70
|
|
71
71
|
|
72
72
|
SETUP_OPTIONS = {:parse_cmdline => true, :load_conf => true, :run_tasks => true}
|
73
|
-
RUBY_BIN = File.join(::
|
73
|
+
RUBY_BIN = File.join(::RbConfig::CONFIG['bindir'],::RbConfig::CONFIG['ruby_install_name']) << ::RbConfig::CONFIG['EXEEXT']
|
74
74
|
|
75
75
|
def self.setup(version, options = {}, &instructions)
|
76
76
|
prefixes = dirs = nil
|
@@ -118,7 +118,7 @@ module Actions
|
|
118
118
|
d = File.expand_path(File.join(dirs)).w32
|
119
119
|
FileUtils.install @source, d,
|
120
120
|
{:verbose => @options.verbose,
|
121
|
-
:noop => @options.noop, :mode => @mode }
|
121
|
+
:noop => @options.noop, :mode => @mode } rescue Errno::ENOENT
|
122
122
|
end
|
123
123
|
|
124
124
|
def hash
|
@@ -197,9 +197,9 @@ module Actions
|
|
197
197
|
File.open(tmpfile, 'w', 0755) do |w|
|
198
198
|
first = r.gets
|
199
199
|
return unless SHEBANG_RE =~ first
|
200
|
-
ruby = File.join(::
|
201
|
-
ruby << ::
|
202
|
-
#w.print first.sub(SHEBANG_RE, '#!' +
|
200
|
+
ruby = File.join(::RbConfig::CONFIG['bindir'],::RbConfig::CONFIG['ruby_install_name'])
|
201
|
+
ruby << ::RbConfig::CONFIG['EXEEXT']
|
202
|
+
#w.print first.sub(SHEBANG_RE, '#!' + RbConfig::CONFIG['ruby-prog'])
|
203
203
|
w.print first.sub(SHEBANG_RE, '#!' + ruby)
|
204
204
|
w.write r.read
|
205
205
|
end
|
@@ -308,7 +308,7 @@ class PackageSpecification_1_0
|
|
308
308
|
end
|
309
309
|
#TODO: refactor
|
310
310
|
self.class.declare_file_type(args) do |files, ignore_p, opt_rename_info|
|
311
|
-
files.each do |file|
|
311
|
+
(Array === files ? files : [files]).each do |file|
|
312
312
|
next if ignore_p && IGNORE_FILES.any?{|re| re.match(file)}
|
313
313
|
add_file(kind, file, opt_rename_info, &bin_callback)
|
314
314
|
end
|
@@ -397,7 +397,7 @@ class PackageSpecification_1_0
|
|
397
397
|
end
|
398
398
|
|
399
399
|
def initialize(prefixes = nil, dirs = nil)
|
400
|
-
@prefix =
|
400
|
+
@prefix = RbConfig::CONFIG["prefix"].gsub(/\A\//, '')
|
401
401
|
@translate = {}
|
402
402
|
@prefixes = (prefixes || {}).dup
|
403
403
|
KINDS.each do |kind|
|
@@ -429,7 +429,7 @@ class PackageSpecification_1_0
|
|
429
429
|
__send__ kind, Dir["#{kind}/**/*"]
|
430
430
|
end
|
431
431
|
translate(:ext, "ext/*" => "", :inherit => true)
|
432
|
-
ext Dir["ext/**/*.#{
|
432
|
+
ext Dir["ext/**/*.#{RbConfig::CONFIG['DLEXT']}"]
|
433
433
|
end
|
434
434
|
|
435
435
|
# Builds any needed extensions.
|
@@ -660,7 +660,7 @@ class PackageSpecification_1_0
|
|
660
660
|
end
|
661
661
|
|
662
662
|
def run_tasks
|
663
|
-
@tasks.each { |task| __send__ task }
|
663
|
+
@tasks.each { |task| puts "Doing #{task}"; __send__ task }
|
664
664
|
end
|
665
665
|
end
|
666
666
|
|
@@ -668,5 +668,5 @@ end # module Package
|
|
668
668
|
|
669
669
|
require 'rbconfig'
|
670
670
|
def config(x)
|
671
|
-
|
671
|
+
RbConfig::CONFIG[x]
|
672
672
|
end
|
data/setup.rb
CHANGED
@@ -11,12 +11,25 @@ end
|
|
11
11
|
|
12
12
|
Dir.chdir(basedir)
|
13
13
|
Package.setup("1.0") {
|
14
|
-
|
14
|
+
# TODO pull version right from the code's version.rb.
|
15
|
+
name "Swiftcore Swiftiply v. 0.6.5"
|
15
16
|
|
16
17
|
build_ext "fastfilereader"
|
17
18
|
translate(:ext, 'ext/fastfilereader/' => '/')
|
18
19
|
#translate(:ext, 'ext/http11/' => 'iowa/')
|
20
|
+
|
19
21
|
ext "ext/fastfilereader/fastfilereaderext.so"
|
22
|
+
ext "ext/fastfilereader/fastfilereaderext.bundle"
|
23
|
+
|
24
|
+
build_ext "deque"
|
25
|
+
translate(:ext, 'ext/deque/' => '/swiftcore/')
|
26
|
+
ext "ext/deque/deque.so"
|
27
|
+
ext "ext/deque/deque.bundle"
|
28
|
+
|
29
|
+
build_ext "splaytree"
|
30
|
+
translate(:ext, 'ext/splaytree/' => '/swiftcore/')
|
31
|
+
ext "ext/splaytree/splaytreemap.so"
|
32
|
+
ext "ext/splaytree/splaytreemap.bundle"
|
20
33
|
|
21
34
|
translate(:lib, 'src/' => '')
|
22
35
|
translate(:bin, 'bin/' => '')
|
@@ -28,9 +41,12 @@ Package.setup("1.0") {
|
|
28
41
|
bin "bin/swiftiply"
|
29
42
|
bin "bin/swiftiply_mongrel_rails"
|
30
43
|
#File.rename("#{Config::CONFIG["bindir"]}/mongrel_rails","#{Config::CONFIG["bindir"]}/mongrel_rails.orig")
|
31
|
-
bin "bin/
|
44
|
+
bin "bin/swiftiplied_mongrel_rails"
|
45
|
+
bin "bin/evented_mongrel_rails"
|
46
|
+
bin "bin/swiftiplyctl"
|
32
47
|
|
33
48
|
unit_test "test/TC_ProxyBag.rb"
|
34
49
|
unit_test "test/TC_Swiftiply.rb"
|
50
|
+
unit_test "test/TC_Deque.rb"
|
35
51
|
true
|
36
52
|
}
|
data/src/swiftcore/Swiftiply.rb
CHANGED
@@ -1,776 +1,420 @@
|
|
1
|
-
begin
|
2
|
-
load_attempted ||= false
|
3
|
-
require 'digest/sha2'
|
4
|
-
require 'eventmachine'
|
5
|
-
require 'fastfilereaderext'
|
6
|
-
require 'swiftcore/types'
|
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
|
18
|
-
|
19
1
|
module Swiftcore
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
].join
|
438
|
-
close_connection_after_writing
|
439
|
-
end
|
440
|
-
|
441
|
-
# Push data from the web browser client to the backend server process.
|
442
|
-
|
443
|
-
def push
|
444
|
-
if @associate
|
445
|
-
unless @redeployable
|
446
|
-
# normal data push
|
447
|
-
data = nil
|
448
|
-
@associate.send_data data while data = @data.pop
|
449
|
-
else
|
450
|
-
# redeployable data push; just send the stuff that has
|
451
|
-
# not already been sent.
|
452
|
-
(@data.length - 1 - @data_pos).downto(0) do |p|
|
453
|
-
d = @data[p]
|
454
|
-
@associate.send_data d
|
455
|
-
@data_len += d.length
|
456
|
-
end
|
457
|
-
@data_pos = @data.length
|
458
|
-
|
459
|
-
# If the request size crosses the size limit, then
|
460
|
-
# disallow redeployent of this request.
|
461
|
-
if @data_len > @redeployable
|
462
|
-
@redeployable = false
|
463
|
-
@data.clear
|
464
|
-
end
|
465
|
-
end
|
466
|
-
end
|
467
|
-
end
|
468
|
-
|
469
|
-
# The connection with the web browser client has been closed, so the
|
470
|
-
# object must be removed from the ProxyBag's queue if it is has not
|
471
|
-
# been associated with a backend. If it has already been associated
|
472
|
-
# with a backend, then it will not be in the queue and need not be
|
473
|
-
# removed.
|
474
|
-
|
475
|
-
def unbind
|
476
|
-
ProxyBag.remove_client(self) unless @associate
|
477
|
-
end
|
478
|
-
|
479
|
-
def uri
|
480
|
-
@uri
|
481
|
-
end
|
482
|
-
|
483
|
-
def setup_for_redeployment
|
484
|
-
@data_pos = 0
|
485
|
-
end
|
486
|
-
|
487
|
-
end
|
488
|
-
|
489
|
-
# The BackendProtocol is the EventMachine::Connection subclass that
|
490
|
-
# handles the communications between Swiftiply and the backend process
|
491
|
-
# it is proxying to.
|
492
|
-
|
493
|
-
class BackendProtocol < EventMachine::Connection
|
494
|
-
attr_accessor :associate, :id
|
495
|
-
|
496
|
-
C0rnrn = "0\r\n\r\n".freeze
|
497
|
-
Crnrn = "\r\n\r\n".freeze
|
498
|
-
|
499
|
-
def initialize *args
|
500
|
-
@name = self.class.bname
|
501
|
-
super
|
502
|
-
end
|
503
|
-
|
504
|
-
def name
|
505
|
-
@name
|
506
|
-
end
|
507
|
-
|
508
|
-
# Call setup() and add the backend to the ProxyBag queue.
|
509
|
-
|
510
|
-
def post_init
|
511
|
-
setup
|
512
|
-
@initialized = nil
|
513
|
-
ProxyBag.add_server self
|
514
|
-
end
|
515
|
-
|
516
|
-
# Setup the initial variables for receiving headers and content.
|
517
|
-
|
518
|
-
def setup
|
519
|
-
@headers = ''
|
520
|
-
@headers_completed = false
|
521
|
-
#@content_length = nil
|
522
|
-
@content_sent = 0
|
523
|
-
end
|
524
|
-
|
525
|
-
# Receive data from the backend process. Headers are parsed from
|
526
|
-
# the rest of the content. If a Content-Length header is present,
|
527
|
-
# that is used to determine how much data to expect. Otherwise,
|
528
|
-
# if 'Transfer-encoding: chunked' is present, assume chunked
|
529
|
-
# encoding. Otherwise be paranoid; something isn't the way we like
|
530
|
-
# it to be.
|
531
|
-
|
532
|
-
def receive_data data
|
533
|
-
unless @initialized
|
534
|
-
preamble = data.slice!(0..24)
|
535
|
-
|
536
|
-
keylen = preamble[23..24].to_i(16)
|
537
|
-
keylen = 0 if keylen < 0
|
538
|
-
key = keylen > 0 ? data.slice!(0..(keylen - 1)) : C_empty
|
539
|
-
if preamble[0..10] == Cswiftclient and key == ProxyBag.get_key(@name)
|
540
|
-
@id = preamble[11..22]
|
541
|
-
ProxyBag.add_id(self,@id)
|
542
|
-
@initialized = true
|
543
|
-
else
|
544
|
-
close_connection
|
545
|
-
return
|
546
|
-
end
|
547
|
-
end
|
548
|
-
|
549
|
-
unless @headers_completed
|
550
|
-
if data =~ /\r\n\r\n/
|
551
|
-
@headers_completed = true
|
552
|
-
h,data = data.split(/\r\n\r\n/,2)
|
553
|
-
@headers << h << Crnrn
|
554
|
-
if @headers =~ /Content-[Ll]ength:\s*([^\r]+)/
|
555
|
-
@content_length = $1.to_i
|
556
|
-
elsif @headers =~ /Transfer-encoding:\s*chunked/
|
557
|
-
@content_length = nil
|
558
|
-
else
|
559
|
-
@content_length = 0
|
560
|
-
end
|
561
|
-
@associate.send_data @headers
|
562
|
-
else
|
563
|
-
@headers << data
|
564
|
-
end
|
565
|
-
end
|
566
|
-
|
567
|
-
if @headers_completed
|
568
|
-
@associate.send_data data
|
569
|
-
@content_sent += data.length
|
570
|
-
if @content_length and @content_sent >= @content_length or data[-6..-1] == C0rnrn
|
571
|
-
@associate.close_connection_after_writing
|
572
|
-
@associate = nil
|
573
|
-
@headers = ''
|
574
|
-
@headers_completed = false
|
575
|
-
#@content_length = nil
|
576
|
-
@content_sent = 0
|
577
|
-
#setup
|
578
|
-
ProxyBag.add_server self
|
579
|
-
end
|
580
|
-
end
|
581
|
-
# TODO: Log these errors!
|
582
|
-
rescue
|
583
|
-
@associate.close_connection_after_writing if @associate
|
584
|
-
@associate = nil
|
585
|
-
setup
|
586
|
-
ProxyBag.add_server self
|
587
|
-
end
|
588
|
-
|
589
|
-
# This is called when the backend disconnects from the proxy.
|
590
|
-
# If the backend is currently associated with a web browser client,
|
591
|
-
# that connection will be closed. Otherwise, the backend will be
|
592
|
-
# removed from the ProxyBag's backend queue.
|
593
|
-
|
594
|
-
def unbind
|
595
|
-
if @associate
|
596
|
-
if !@associate.redeployable or @content_length
|
597
|
-
@associate.close_connection_after_writing
|
598
|
-
else
|
599
|
-
@associate.associate = nil
|
600
|
-
@associate.setup_for_redeployment
|
601
|
-
ProxyBag.rebind_frontend_client(@associate)
|
602
|
-
end
|
603
|
-
else
|
604
|
-
ProxyBag.remove_server(self)
|
605
|
-
end
|
606
|
-
ProxyBag.remove_id(self)
|
607
|
-
end
|
608
|
-
|
609
|
-
def self.bname=(val)
|
610
|
-
@bname = val
|
611
|
-
end
|
612
|
-
|
613
|
-
def self.bname
|
614
|
-
@bname
|
615
|
-
end
|
616
|
-
end
|
617
|
-
|
618
|
-
# Start the EventMachine event loop and create the front end and backend
|
619
|
-
# handlers, then create the timers that are used to expire unserviced
|
620
|
-
# clients and to update the Proxy's clock.
|
621
|
-
|
622
|
-
def self.run(config)
|
623
|
-
@existing_backends = {}
|
624
|
-
|
625
|
-
# Default is to assume we want to try to turn epoll support on. EM
|
626
|
-
# ignores this on platforms that don't support it, so this is safe.
|
627
|
-
EventMachine.epoll unless config.has_key?(Cepoll) and !config[Cepoll]
|
628
|
-
EventMachine.set_descriptor_table_size(4096 || config[Cepoll_descriptors]) if config[Cepoll]
|
629
|
-
EventMachine.run do
|
630
|
-
trap("HUP") {em_config(Swiftcore::SwiftiplyExec.parse_options); GC.start}
|
631
|
-
trap("INT") {EventMachine.stop_event_loop}
|
632
|
-
em_config(config)
|
633
|
-
GC.start
|
634
|
-
end
|
635
|
-
end
|
636
|
-
|
637
|
-
def self.em_config(config)
|
638
|
-
new_config = {}
|
639
|
-
if RunningConfig[Ccluster_address] != config[Ccluster_address] or RunningConfig[Ccluster_port] != config[Ccluster_port]
|
640
|
-
begin
|
641
|
-
new_config[Ccluster_server] = EventMachine.start_server(
|
642
|
-
config[Ccluster_address],
|
643
|
-
config[Ccluster_port],
|
644
|
-
ClusterProtocol)
|
645
|
-
rescue RuntimeError => e
|
646
|
-
advice = ''
|
647
|
-
if config[Ccluster_port] < 1024
|
648
|
-
advice << 'Make sure you have the correct permissions to use that port, and make sure there is nothing else running on that port.'
|
649
|
-
else
|
650
|
-
advice << 'Make sure there is nothing else running on that port.'
|
651
|
-
end
|
652
|
-
advice << " The original error was: #{e}\n"
|
653
|
-
raise EMStartServerError.new("The listener on #{config[Ccluster_address]}:#{config[Ccluster_port]} could not be started.\n#{advice}")
|
654
|
-
end
|
655
|
-
new_config[Ccluster_address] = config[Ccluster_address]
|
656
|
-
new_config[Ccluster_port] = config[Ccluster_port]
|
657
|
-
RunningConfig[Ccluster_server].stop_server if RunningConfig.has_key?(Ccluster_server)
|
658
|
-
else
|
659
|
-
new_config[Ccluster_server] = RunningConfig[Ccluster_server]
|
660
|
-
new_config[Ccluster_address] = RunningConfig[Ccluster_address]
|
661
|
-
new_config[Ccluster_port] = RunningConfig[Ccluster_port]
|
662
|
-
end
|
663
|
-
|
664
|
-
new_config[Coutgoing] = {}
|
665
|
-
|
666
|
-
config[Cmap].each do |m|
|
667
|
-
if m[Ckeepalive]
|
668
|
-
# keepalive requests are standard Swiftiply requests.
|
669
|
-
|
670
|
-
# The hash of the "outgoing" config section. It is used to
|
671
|
-
# uniquely identify a section.
|
672
|
-
hash = Digest::SHA256.hexdigest(m[Cincoming].sort.join('|')).intern
|
673
|
-
|
674
|
-
# For each incoming entry, do setup.
|
675
|
-
new_config[Cincoming] = {}
|
676
|
-
m[Cincoming].each do |p_|
|
677
|
-
p = p_.intern
|
678
|
-
new_config[Cincoming][p] = {}
|
679
|
-
ProxyBag.add_incoming_mapping(hash,p)
|
680
|
-
|
681
|
-
if m.has_key?(Cdocroot)
|
682
|
-
ProxyBag.add_incoming_docroot(m[Cdocroot],p)
|
683
|
-
else
|
684
|
-
ProxyBag.remove_incoming_docroot(p)
|
685
|
-
end
|
686
|
-
|
687
|
-
if m[Credeployable]
|
688
|
-
ProxyBag.add_incoming_redeployable(m[Credeployment_sizelimit] || 16384,p)
|
689
|
-
else
|
690
|
-
ProxyBag.remove_incoming_redeployable(p)
|
691
|
-
end
|
692
|
-
|
693
|
-
if m.has_key?(Ckey)
|
694
|
-
ProxyBag.set_key(hash,m[Ckey])
|
695
|
-
else
|
696
|
-
ProxyBag.set_key(hash,C_empty)
|
697
|
-
end
|
698
|
-
|
699
|
-
if m.has_key?(Ccache_extensions) or m.has_key?(Ccache_directory)
|
700
|
-
require 'swiftcore/Swiftiply/support_pagecache'
|
701
|
-
ProxyBag.add_suffix_list((m[Ccache_extensions] || ProxyBag.const_get(:DefaultSuffixes)),p)
|
702
|
-
ProxyBag.add_cache_dir((m[Ccache_directory] || ProxyBag.const_get(:DefaultCacheDir)),p)
|
703
|
-
else
|
704
|
-
ProxyBag.remove_suffix_list(p) if ProxyBag.respond_to?(:remove_suffix_list)
|
705
|
-
ProxyBag.remove_cache_dir(p) if ProxyBag.respond_to?(:remove_cache_dir)
|
706
|
-
end
|
707
|
-
|
708
|
-
m[Coutgoing].each do |o|
|
709
|
-
ProxyBag.default_name = p if m[Cdefault]
|
710
|
-
if @existing_backends.has_key?(o)
|
711
|
-
new_config[Coutgoing][o] ||= RunningConfig[Coutgoing][o]
|
712
|
-
next
|
713
|
-
else
|
714
|
-
@existing_backends[o] = true
|
715
|
-
backend_class = Class.new(BackendProtocol)
|
716
|
-
backend_class.bname = hash
|
717
|
-
host, port = o.split(/:/,2)
|
718
|
-
begin
|
719
|
-
new_config[Coutgoing][o] = EventMachine.start_server(host, port.to_i, backend_class)
|
720
|
-
rescue RuntimeError => e
|
721
|
-
advice = ''
|
722
|
-
if port.to_i < 1024
|
723
|
-
advice << 'Make sure you have the correct permissions to use that port, and make sure there is nothing else running on that port.'
|
724
|
-
else
|
725
|
-
advice << 'Make sure there is nothing else running on that port.'
|
726
|
-
end
|
727
|
-
advice << " The original error was: #{e}\n"
|
728
|
-
raise EMStartServerError.new("The listener on #{host}:#{port} could not be started.\n#{advice}")
|
729
|
-
end
|
730
|
-
end
|
731
|
-
end
|
732
|
-
|
733
|
-
# Now stop everything that is still running but which isn't needed.
|
734
|
-
if RunningConfig.has_key?(Coutgoing)
|
735
|
-
(RunningConfig[Coutgoing].keys - new_config[Coutgoing].keys).each do |unneeded_server_key|
|
736
|
-
RunningConfig[Coutgoing][unneeded_server_key].stop_server
|
737
|
-
end
|
738
|
-
end
|
739
|
-
end
|
740
|
-
else
|
741
|
-
# This is where the code goes that sets up traditional proxy destinations.
|
742
|
-
# This is a future TODO item.
|
743
|
-
end
|
744
|
-
end
|
745
|
-
|
746
|
-
#EventMachine.set_effective_user = config[Cuser] if config[Cuser] and RunningConfig[Cuser] != config[Cuser]
|
747
|
-
run_as(config[Cuser],config[Cgroup]) if (config[Cuser] and RunningConfig[Cuser] != config[Cuser]) or (config[Cgroup] and RunningConfig[Cgroup] != config[Cgroup])
|
748
|
-
new_config[Cuser] = config[Cuser]
|
749
|
-
new_config[Cgroup] = config[Cgroup]
|
750
|
-
|
751
|
-
ProxyBag.server_unavailable_timeout ||= config[Ctimeout]
|
752
|
-
ProxyBag.chunked_encoding_threshold = config[Cchunked_encoding_threshold] || 16384
|
753
|
-
|
754
|
-
unless RunningConfig[:initialized]
|
755
|
-
EventMachine.add_periodic_timer(2) { ProxyBag.expire_clients }
|
756
|
-
EventMachine.add_periodic_timer(1) { ProxyBag.update_ctime }
|
757
|
-
new_config[:initialized] = true
|
758
|
-
end
|
759
|
-
|
760
|
-
RunningConfig.replace new_config
|
761
|
-
end
|
762
|
-
|
763
|
-
|
764
|
-
# This can be used to change the effective user and group that
|
765
|
-
# Swiftiply is running as.
|
766
|
-
|
767
|
-
def self.run_as(user = "nobody", group = "nobody")
|
768
|
-
Process.initgroups(user,Etc.getgrnam(group).gid) if user and group
|
769
|
-
::Process::GID.change_privilege(Etc.getgrnam(group).gid) if group
|
770
|
-
::Process::UID.change_privilege(Etc.getpwnam(user).uid) if user
|
771
|
-
rescue Errno::EPERM
|
772
|
-
raise "Failed to change the effective user to #{user} and the group to #{group}"
|
773
|
-
end
|
774
|
-
end
|
2
|
+
# TODO:
|
3
|
+
#
|
4
|
+
# 1) Basic HTTP Authentication
|
5
|
+
# 2) Stats
|
6
|
+
# Stats will be recorded in aggregate and for each incoming section, and may
|
7
|
+
# accessed through a separate stats port via a RESTful HTTP request which
|
8
|
+
# identifies the section to pull stats for, and the authentication key for
|
9
|
+
# access to those stats.
|
10
|
+
# http://127.0.0.1:8082
|
11
|
+
#
|
12
|
+
# To track:
|
13
|
+
# Total connections
|
14
|
+
# 400s served
|
15
|
+
# 404s served
|
16
|
+
#
|
17
|
+
# Per config section:
|
18
|
+
# backends connected
|
19
|
+
# backends busy
|
20
|
+
# backend disconnects
|
21
|
+
# backend errors
|
22
|
+
# static bytes served
|
23
|
+
# static requests handled
|
24
|
+
# static requests 304'd
|
25
|
+
# cache hits for static files
|
26
|
+
# dynamic bytes returned
|
27
|
+
# dynamic requests handled
|
28
|
+
#
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# 3) Maintenance Page Support
|
32
|
+
# This is a path to a static file which will be returned on a 503 error.
|
33
|
+
# 4) GZip compression
|
34
|
+
# Can be toggled on or off. Configure mime types to compress. Implemented
|
35
|
+
# via an extension.
|
36
|
+
# 5) Make one "SwiftiplyCplusplus" and one "SwiftiplC" extension that,
|
37
|
+
# respectively, encapsulate all of the C++ and C extensions into just
|
38
|
+
# two.
|
39
|
+
|
40
|
+
# A little statemachine for loading requirements. The intention is to
|
41
|
+
# only load rubygems if necessary, and to load the Deque and SplayTreeMap
|
42
|
+
# classes if they are available, setting a constant accordingly so that
|
43
|
+
# the fallbacks (Array and Hash) can be used if they are not.
|
44
|
+
|
45
|
+
begin
|
46
|
+
load_state ||= :start
|
47
|
+
rubygems_loaded ||= false
|
48
|
+
require 'socket'
|
49
|
+
require 'digest/sha2'
|
50
|
+
require 'eventmachine'
|
51
|
+
require 'swiftcore/hash'
|
52
|
+
require 'swiftcore/types'
|
53
|
+
require 'swiftcore/Swiftiply/mocklog'
|
54
|
+
require 'swiftcore/Swiftiply/version'
|
55
|
+
|
56
|
+
load_state = :deque
|
57
|
+
require 'swiftcore/deque' unless const_defined?(:HasDeque)
|
58
|
+
HasDeque = true unless const_defined?(:HasDeque)
|
59
|
+
|
60
|
+
load_state = :splaytreemap
|
61
|
+
require 'swiftcore/splaytreemap' unless const_defined?(:HasSplayTree)
|
62
|
+
HasSplayTree = true unless const_defined?(:HasSplayTree)
|
63
|
+
|
64
|
+
load_state = :helpers
|
65
|
+
require 'swiftcore/streamer'
|
66
|
+
require 'swiftcore/Swiftiply/etag_cache'
|
67
|
+
require 'swiftcore/Swiftiply/file_cache'
|
68
|
+
require 'swiftcore/Swiftiply/dynamic_request_cache'
|
69
|
+
require 'time'
|
70
|
+
|
71
|
+
load_state = :core
|
72
|
+
require 'swiftcore/Swiftiply/constants'
|
73
|
+
require 'swiftcore/Swiftiply/proxy_bag'
|
74
|
+
require 'swiftcore/Swiftiply/cluster_protocol'
|
75
|
+
require 'swiftcore/Swiftiply/proxy'
|
76
|
+
|
77
|
+
rescue LoadError => e
|
78
|
+
unless rubygems_loaded
|
79
|
+
# Everything gets slower once rubygems are used (though this
|
80
|
+
# effect is not so profound as it once was). So, for the
|
81
|
+
# best speed possible, don't install EventMachine or Swiftiply via
|
82
|
+
# gems.
|
83
|
+
begin
|
84
|
+
require 'rubygems'
|
85
|
+
rubygems_loaded = true
|
86
|
+
rescue LoadError
|
87
|
+
raise e
|
88
|
+
end
|
89
|
+
retry
|
90
|
+
end
|
91
|
+
case load_state
|
92
|
+
when :deque
|
93
|
+
HasDeque = false unless const_defined?(:HasDeque)
|
94
|
+
retry
|
95
|
+
when :splaytreemap
|
96
|
+
HasSplayTree = false unless const_defined?(:HasSplayTree)
|
97
|
+
retry
|
98
|
+
end
|
99
|
+
raise e
|
100
|
+
end
|
101
|
+
|
102
|
+
GC.start
|
103
|
+
|
104
|
+
module Swiftiply
|
105
|
+
|
106
|
+
Updaters = {
|
107
|
+
'rest' => ['swiftcore/Swiftiply/config/rest_updater','::Swiftcore::Swiftiply::Config::RestUpdater']
|
108
|
+
}
|
109
|
+
|
110
|
+
def self.existing_backends
|
111
|
+
@existing_backends
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.existing_backends=(val)
|
115
|
+
@existing_backends = val
|
116
|
+
end
|
117
|
+
|
118
|
+
# Start the EventMachine event loop and create the front end and backend
|
119
|
+
# handlers, then create the timers that are used to expire unserviced
|
120
|
+
# clients and to update the Proxy's clock.
|
121
|
+
|
122
|
+
def self.run(config)
|
123
|
+
self.existing_backends = {}
|
124
|
+
|
125
|
+
# Default is to assume we want to try to turn epoll/kqueue support on.
|
126
|
+
EventMachine.epoll unless config.has_key?(Cepoll) and !config[Cepoll] rescue nil
|
127
|
+
EventMachine.kqueue unless config.has_key?(Ckqueue) and !config[Ckqueue] rescue nil
|
128
|
+
EventMachine.set_descriptor_table_size(config[Cepoll_descriptors] || config[Cdescriptors] || 4096) rescue nil
|
129
|
+
|
130
|
+
EventMachine.run do
|
131
|
+
EM.set_timer_quantum(5)
|
132
|
+
trap("HUP") {em_config(Swiftcore::SwiftiplyExec.parse_options); GC.start}
|
133
|
+
trap("INT") {EventMachine.stop_event_loop}
|
134
|
+
GC.start
|
135
|
+
em_config(config)
|
136
|
+
GC.start # We just want to make sure all the junk created during
|
137
|
+
# configuration is purged prior to real work starting.
|
138
|
+
#RubyProf.start
|
139
|
+
end
|
140
|
+
#result = RubyProf.stop
|
141
|
+
|
142
|
+
#printer = RubyProf::TextPrinter.new(result)
|
143
|
+
#File.open('/tmp/swprof','w+') {|fh| printer = printer.print(fh,0)}
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.log_level
|
147
|
+
@log_level
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.log_level=(val)
|
151
|
+
@log_level = val
|
152
|
+
end
|
153
|
+
|
154
|
+
# TODO: This method is absurdly long, and should be refactored.
|
155
|
+
def self.em_config(config)
|
156
|
+
new_config = {Ccluster_address => [],Ccluster_port => [],Ccluster_server => {}}
|
157
|
+
defaults = config['defaults'] || {}
|
158
|
+
|
159
|
+
new_log = _config_loggers(config,defaults)
|
160
|
+
self.log_level = ProxyBag.log_level
|
161
|
+
ssl_addresses = _config_determine_ssl_addresses(config)
|
162
|
+
|
163
|
+
addresses = (Array === config[Ccluster_address]) ? config[Ccluster_address] : [config[Ccluster_address]]
|
164
|
+
ports = (Array === config[Ccluster_port]) ? config[Ccluster_port] : [config[Ccluster_port]]
|
165
|
+
addrports = []
|
166
|
+
|
167
|
+
addresses.each do |address|
|
168
|
+
ports.each do |port|
|
169
|
+
addrport = "#{address}:#{port}"
|
170
|
+
addrports << addrport
|
171
|
+
|
172
|
+
if (!RunningConfig.has_key?(Ccluster_address)) ||
|
173
|
+
(RunningConfig.has_key?(Ccluster_address) && !RunningConfig[Ccluster_address].include?(address)) ||
|
174
|
+
(RunningConfig.has_key?(Ccluster_port) && !RunningConfig[Ccluster_port].include?(port))
|
175
|
+
begin
|
176
|
+
# If this particular address/port runs SSL, check that the certificate and the
|
177
|
+
# key files exist and are readable, then create a special protocol class
|
178
|
+
# that embeds the certificate and key information.
|
179
|
+
|
180
|
+
if ssl_addresses.has_key?(addrport)
|
181
|
+
# TODO: LOG that the certfiles are missing instead of silently ignoring it.
|
182
|
+
next unless exists_and_is_readable(ssl_addresses[addrport][Ccertfile])
|
183
|
+
next unless exists_and_is_readable(ssl_addresses[addrport][Ckeyfile])
|
184
|
+
|
185
|
+
# Create a customized protocol object for each different address/port combination.
|
186
|
+
ssl_protocol = Class.new(ClusterProtocol)
|
187
|
+
ssl_protocol.class_eval <<EOC
|
188
|
+
def post_init
|
189
|
+
start_tls({:cert_chain_file => "#{ssl_addresses[addrport][Ccertfile]}", :private_key_file => "#{ssl_addresses[addrport][Ckeyfile]}"})
|
190
|
+
end
|
191
|
+
EOC
|
192
|
+
ProxyBag.logger.log(Cinfo,"Opening SSL server on #{address}:#{port}") if log_level > 0 and log_level < 3
|
193
|
+
ProxyBag.logger.log(Cinfo,"Opening SSL server on #{address}:#{port} using key at #{ssl_addresses[addrport][Ckeyfile]} and certificate at #{ssl_addresses[addrport][Ccertfile]}")
|
194
|
+
new_config[Ccluster_server][addrport] = EventMachine.start_server(
|
195
|
+
address,
|
196
|
+
port,
|
197
|
+
ssl_protocol)
|
198
|
+
else
|
199
|
+
standard_protocol = Class.new(ClusterProtocol)
|
200
|
+
standard_protocol.init_class_variables
|
201
|
+
ProxyBag.logger.log(Cinfo,"Opening server on #{address}:#{port}") if ProxyBag.log_level > 0
|
202
|
+
new_config[Ccluster_server][addrport] = EventMachine.start_server(
|
203
|
+
address,
|
204
|
+
port,
|
205
|
+
standard_protocol)
|
206
|
+
end
|
207
|
+
rescue RuntimeError => e
|
208
|
+
advice = ''
|
209
|
+
if port < 1024
|
210
|
+
advice << 'Make sure you have the correct permissions to use that port, and make sure there is nothing else running on that port.'
|
211
|
+
else
|
212
|
+
advice << 'Make sure there is nothing else running on that port.'
|
213
|
+
end
|
214
|
+
advice << " The original error was: #{e}\n"
|
215
|
+
msg = "The listener on #{address}:#{port} could not be started.\n#{advice}\n"
|
216
|
+
ProxyBag.logger.log('fatal',msg)
|
217
|
+
raise EMStartServerError.new(msg)
|
218
|
+
end
|
219
|
+
|
220
|
+
new_config[Ccluster_address] << address
|
221
|
+
new_config[Ccluster_port] << port unless new_config[Ccluster_port].include?(port)
|
222
|
+
else
|
223
|
+
new_config[Ccluster_server][addrport] = RunningConfig[Ccluster_server][addrport]
|
224
|
+
new_config[Ccluster_address] << address
|
225
|
+
new_config[Ccluster_port] << port unless new_config[Ccluster_port].include?(port)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Stop anything that is no longer in the config.
|
231
|
+
if RunningConfig.has_key?(Ccluster_server)
|
232
|
+
(RunningConfig[Ccluster_server].keys - addrports).each do |s|
|
233
|
+
ProxyBag.logger.log(Cinfo,"Stopping unused incoming server #{s.inspect} out of #{RunningConfig[Ccluster_server].keys.inspect - RunningConfig[Ccluster_server].keys.inspect}")
|
234
|
+
EventMachine.stop_server(s)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
new_config[Coutgoing] = {}
|
239
|
+
config[Cmap].each do |mm|
|
240
|
+
m = defaults.dup
|
241
|
+
m.rmerge!(mm)
|
242
|
+
Swiftcore::Swiftiply::Proxy.config(m,new_config)
|
243
|
+
end
|
244
|
+
|
245
|
+
updater = nil
|
246
|
+
if config[Cupdates]
|
247
|
+
uconf = config[Cupdates]
|
248
|
+
require Updaters[uconf[Cupdater]].first
|
249
|
+
updater = Updaters[uconf[Cupdater]].last
|
250
|
+
end
|
251
|
+
|
252
|
+
updater_class = Swiftcore::Swiftiply::class_by_name(updater) if updater
|
253
|
+
self.class.const_set(:Updater, updater_class.new(uconf)) if updater
|
254
|
+
|
255
|
+
#EventMachine.set_effective_user = config[Cuser] if config[Cuser] and RunningConfig[Cuser] != config[Cuser]
|
256
|
+
run_as(config[Cuser],config[Cgroup]) if (config[Cuser] and RunningConfig[Cuser] != config[Cuser]) or (config[Cgroup] and RunningConfig[Cgroup] != config[Cgroup])
|
257
|
+
new_config[Cuser] = config[Cuser]
|
258
|
+
new_config[Cgroup] = config[Cgroup]
|
259
|
+
|
260
|
+
ProxyBag.server_unavailable_timeout ||= config[Ctimeout]
|
261
|
+
|
262
|
+
# By default any file over 16k will be sent via chunked encoding
|
263
|
+
# if the client supports HTTP 1.1. Generally there is no reason
|
264
|
+
# to change this, but it is configurable.
|
265
|
+
|
266
|
+
puts "CHUNKED ENCODING THRESHOLD: #{config[Cchunked_encoding_threshold] || 16384}"
|
267
|
+
ProxyBag.chunked_encoding_threshold = config[Cchunked_encoding_threshold] || 16384
|
268
|
+
|
269
|
+
# The default cache_threshold is set to 100k. Files above this size
|
270
|
+
# will not be cached. Customize this value in your configurations
|
271
|
+
# as necessary for the best performance on your site.
|
272
|
+
|
273
|
+
ProxyBag.cache_threshold = config['cache_threshold'] || 102400
|
274
|
+
|
275
|
+
unless RunningConfig[:initialized]
|
276
|
+
EventMachine.add_periodic_timer(0.1) { ProxyBag.recheck_or_expire_clients }
|
277
|
+
#EventMachine.next_tick { ProxyBag.do_and_requeue_recheck_or_expire_clients }
|
278
|
+
EventMachine.add_periodic_timer(1) { ProxyBag.update_ctime }
|
279
|
+
EventMachine.add_periodic_timer(1) { ProxyBag.request_worker_resources }
|
280
|
+
new_config[:initialized] = true
|
281
|
+
end
|
282
|
+
|
283
|
+
Updater.start if updater
|
284
|
+
RunningConfig.replace new_config
|
285
|
+
end
|
286
|
+
|
287
|
+
def self._config_loggers(config,defaults)
|
288
|
+
if defaults['logger']
|
289
|
+
if config['logger']
|
290
|
+
config['logger'].rmerge!(defaults['logger'])
|
291
|
+
else
|
292
|
+
config['logger'] = {}.rmerge!(defaults['logger'])
|
293
|
+
end
|
294
|
+
else
|
295
|
+
config['logger'] = {'log_level' => 0, 'type' => 'stderror'} unless config['logger']
|
296
|
+
end
|
297
|
+
|
298
|
+
new_log = handle_logger_config(config['logger']) if config['logger']
|
299
|
+
ProxyBag.logger = new_log[:logger] if new_log
|
300
|
+
ProxyBag.log_level = new_log[:log_level] if new_log
|
301
|
+
new_log
|
302
|
+
end
|
303
|
+
|
304
|
+
def self._config_determine_ssl_addresses(config)
|
305
|
+
ssl_addresses = {}
|
306
|
+
# Determine which address/port combos should be running SSL.
|
307
|
+
(config[Cssl] || []).each do |sa|
|
308
|
+
if sa.has_key?(Cat)
|
309
|
+
ssl_addresses[sa[Cat]] = {Ccertfile => sa[Ccertfile], Ckeyfile => sa[Ckeyfile]}
|
310
|
+
end
|
311
|
+
end
|
312
|
+
ssl_addresses
|
313
|
+
end
|
314
|
+
|
315
|
+
# This can be used to change the effective user and group that
|
316
|
+
# Swiftiply is running as.
|
317
|
+
|
318
|
+
def self.run_as(user = "nobody", group = "nobody")
|
319
|
+
Process.initgroups(user,Etc.getgrnam(group).gid) if user and group
|
320
|
+
::Process::GID.change_privilege(Etc.getgrnam(group).gid) if group
|
321
|
+
::Process::UID.change_privilege(Etc.getpwnam(user).uid) if user
|
322
|
+
rescue Errno::EPERM
|
323
|
+
raise "Failed to change the effective user to #{user} and the group to #{group}"
|
324
|
+
end
|
325
|
+
|
326
|
+
def self.exists_and_is_readable(file)
|
327
|
+
FileTest.exist?(file) and FileTest.readable?(file)
|
328
|
+
end
|
329
|
+
|
330
|
+
# There are 4 levels of logging supported.
|
331
|
+
# :disabled or 0 means no logging
|
332
|
+
# :minimal or 1 logs only essential items
|
333
|
+
# :normal or 2 logs everything useful/interesting
|
334
|
+
# :full or 3 logs all major events
|
335
|
+
#
|
336
|
+
def self.determine_log_level(lvl)
|
337
|
+
case lvl.to_s
|
338
|
+
when /^d|0/
|
339
|
+
0
|
340
|
+
when /^m|1/
|
341
|
+
1
|
342
|
+
when /^n|2/
|
343
|
+
2
|
344
|
+
when /^f|3/
|
345
|
+
3
|
346
|
+
else
|
347
|
+
1
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def self.get_const_from_name(name,space)
|
352
|
+
r = nil
|
353
|
+
space.constants.each do |c|
|
354
|
+
if c =~ /#{name}/i
|
355
|
+
r = c
|
356
|
+
break
|
357
|
+
end
|
358
|
+
end
|
359
|
+
"#{space.name}::#{r}".split('::').inject(Object) { |o,n| o.const_get n }
|
360
|
+
end
|
361
|
+
|
362
|
+
def self.class_by_name(name)
|
363
|
+
klass = Object
|
364
|
+
name.sub(/^::/,'').split('::').each {|n| klass = klass.const_get n}
|
365
|
+
klass
|
366
|
+
end
|
367
|
+
|
368
|
+
def self.handle_logger_config(logger_config = nil,handle_default = true)
|
369
|
+
new_logger = {}
|
370
|
+
if logger_config
|
371
|
+
type = logger_config['type'] || 'Analogger'
|
372
|
+
begin
|
373
|
+
load_attempted ||= false
|
374
|
+
require "swiftcore/Swiftiply/loggers/#{type}"
|
375
|
+
rescue LoadError
|
376
|
+
if load_attempted
|
377
|
+
raise SwiftiplyLoggerNotFound.new("The logger that was specified, #{type}, could not be found.")
|
378
|
+
else
|
379
|
+
load_attempted = true
|
380
|
+
require 'rubygems'
|
381
|
+
retry
|
382
|
+
end
|
383
|
+
end
|
384
|
+
new_logger[:log_level] = determine_log_level(logger_config['level'] || logger_config['log_level'])
|
385
|
+
begin
|
386
|
+
log_class = get_const_from_name(type,::Swiftcore::Swiftiply::Loggers)
|
387
|
+
|
388
|
+
new_logger[:logger] = log_class.new(logger_config)
|
389
|
+
new_logger[:logger].log(Cinfo,"Logger type #{type} started; log level is #{new_logger[:log_level]}.") if new_logger[:log_level] > 0
|
390
|
+
rescue NameError
|
391
|
+
raise SwiftiplyLoggerNameError.new("The logger class specified, Swiftcore::Swiftiply::Loggers::#{type} was not defined.")
|
392
|
+
end
|
393
|
+
elsif handle_default
|
394
|
+
# Default to the stderror logger with a log level of 0
|
395
|
+
begin
|
396
|
+
load_attempted ||= false
|
397
|
+
require "swiftcore/Swiftiply/loggers/stderror"
|
398
|
+
rescue LoadError
|
399
|
+
if load_attempted
|
400
|
+
raise SwiftiplyLoggerNotFound.new("The attempt to load the default logger (swiftcore/Swiftiply/loggers/stderror.rb) failed. This should not happen. Please double check your Swiftiply installation.")
|
401
|
+
else
|
402
|
+
load_attempted = true
|
403
|
+
require 'rubygems'
|
404
|
+
retry
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
log_class = get_const_from_name('stderror',::Swiftcore::Swiftiply::Loggers)
|
409
|
+
new_logger[:logger] = log_class.new(logger_config)
|
410
|
+
new_logger[:log_level] = log_level
|
411
|
+
else
|
412
|
+
new_logger = nil
|
413
|
+
end
|
414
|
+
|
415
|
+
new_logger
|
416
|
+
end
|
417
|
+
|
418
|
+
end
|
775
419
|
end
|
776
420
|
|