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,267 @@
|
|
1
|
+
# Encoding:ascii-8bit
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require "swiftcore/Swiftiply/constants"
|
5
|
+
|
6
|
+
module Swiftcore
|
7
|
+
module Swiftiply
|
8
|
+
class NotImplemented < Exception; end
|
9
|
+
|
10
|
+
# This module implements the HTTP handling code. I call it a recognizer,
|
11
|
+
# and not a parser because it does not parse HTTP. It is much simpler than
|
12
|
+
# that, being designed only to recognize certain useful bits very quickly.
|
13
|
+
|
14
|
+
class HttpRecognizer < EventMachine::Connection
|
15
|
+
|
16
|
+
attr_accessor :create_time, :last_action_time, :uri, :unparsed_uri, :associate, :name, :redeployable, :data_pos, :data_len, :peer_ip, :connection_header, :keepalive, :header_data
|
17
|
+
|
18
|
+
Crn = "\r\n".freeze
|
19
|
+
Crnrn = "\r\n\r\n".freeze
|
20
|
+
C_blank = ''.freeze
|
21
|
+
C_percent = '%'.freeze
|
22
|
+
Cunknown_host = 'unknown host'.freeze
|
23
|
+
C404Header = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
|
24
|
+
C400Header = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
|
25
|
+
|
26
|
+
def self.proxy_bag_class_is klass
|
27
|
+
const_set(:ProxyBag, klass)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.init_class_variables
|
31
|
+
@count_404 = 0
|
32
|
+
@count_400 = 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.increment_404_count
|
36
|
+
@count_404 += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.increment_400_count
|
40
|
+
@count_400 += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
# Initialize the @data array, which is the temporary storage for blocks
|
44
|
+
# of data received from the web browser client, then invoke the superclass
|
45
|
+
# initialization.
|
46
|
+
|
47
|
+
def initialize *args
|
48
|
+
@data = Deque.new
|
49
|
+
@data_pos = 0
|
50
|
+
@connection_header = C_empty
|
51
|
+
@header_missing_pieces = @name = @uri = @unparsed_uri = @http_version = @request_method = @none_match = @done_parsing = @header_data = nil
|
52
|
+
@keepalive = true
|
53
|
+
@klass = self.class
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
def reset_state
|
58
|
+
@data.clear
|
59
|
+
@data_pos = 0
|
60
|
+
@connection_header = C_empty
|
61
|
+
@header_missing_pieces = @name = @uri = @unparsed_uri = @http_version = @request_method = @none_match = @done_parsing = @header_data = nil
|
62
|
+
@keepalive = true
|
63
|
+
end
|
64
|
+
|
65
|
+
# States:
|
66
|
+
# uri
|
67
|
+
# name
|
68
|
+
# \r\n\r\n
|
69
|
+
# If-None-Match
|
70
|
+
# Done Parsing
|
71
|
+
def receive_data data
|
72
|
+
if @done_parsing
|
73
|
+
@data.unshift data
|
74
|
+
push
|
75
|
+
else
|
76
|
+
unless @uri
|
77
|
+
# It's amazing how, when writing the code, the brain can be in a zone
|
78
|
+
# where line noise like this regexp makes perfect sense, and is clear
|
79
|
+
# as day; one looks at it and it reads like a sentence. Then, one
|
80
|
+
# comes back to it later, and looks at it when the brain is in a
|
81
|
+
# different zone, and 'lo! It looks like line noise again.
|
82
|
+
#
|
83
|
+
# data =~ /^(\w+) +(?:\w+:\/\/([^\/]+))?([^ \?]+)\S* +HTTP\/(\d\.\d)/
|
84
|
+
#
|
85
|
+
# In case it looks like line noise to you, dear reader, too:
|
86
|
+
#
|
87
|
+
# 1) Match and save the first set of word characters.
|
88
|
+
#
|
89
|
+
# Followed by one or more spaces.
|
90
|
+
#
|
91
|
+
# Match but do not save the word characters followed by ://
|
92
|
+
#
|
93
|
+
# 2) Match and save one or more characters that are not a slash
|
94
|
+
#
|
95
|
+
# And allow this whole thing to match 1 or 0 times.
|
96
|
+
#
|
97
|
+
# 3) Match and save one or more characters that are not a question
|
98
|
+
# mark or a space.
|
99
|
+
#
|
100
|
+
# Match zero or more non-whitespace characters, followed by one
|
101
|
+
# or more spaces, followed by "HTTP/".
|
102
|
+
#
|
103
|
+
# 4) Match and save a digit dot digit.
|
104
|
+
#
|
105
|
+
# Thus, this pattern will match both the standard:
|
106
|
+
# GET /bar HTTP/1.1
|
107
|
+
# style request, as well as the valid (for a proxy) but less common:
|
108
|
+
# GET http://foo/bar HTTP/1.0
|
109
|
+
#
|
110
|
+
# If the match fails, then this is a bad request, and an appropriate
|
111
|
+
# response will be returned.
|
112
|
+
#
|
113
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec5.1.2
|
114
|
+
#
|
115
|
+
if data =~ /^(\w+) +(?:\w+:\/\/([^ \/]+))?(([^ \?\#]*)\S*) +HTTP\/(\d\.\d)/
|
116
|
+
@request_method = $1
|
117
|
+
@unparsed_uri = $3
|
118
|
+
@uri = $4
|
119
|
+
@http_version = $5
|
120
|
+
if $2
|
121
|
+
@name = $2.intern
|
122
|
+
@uri = C_slash if @uri.empty?
|
123
|
+
# Rewrite the request to get rid of the http://foo portion.
|
124
|
+
|
125
|
+
data.sub!(/^\w+ +\w+:\/\/[^ \/]+([^ \?]*)/,"#{@request_method} #{@uri}")
|
126
|
+
end
|
127
|
+
@uri = @uri.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) {[$1.delete(C_percent)].pack('H*')} if @uri.include?(C_percent)
|
128
|
+
else
|
129
|
+
send_400_response
|
130
|
+
return
|
131
|
+
end
|
132
|
+
end
|
133
|
+
unless @name
|
134
|
+
if data =~ /^Host: *([^\r\0:]+)/
|
135
|
+
@name = $1.intern
|
136
|
+
end
|
137
|
+
end
|
138
|
+
if @header_missing_pieces
|
139
|
+
# Hopefully this doesn't happen often.
|
140
|
+
d = @data.to_s << data
|
141
|
+
else
|
142
|
+
d = data
|
143
|
+
end
|
144
|
+
if d.include?(Crnrn)
|
145
|
+
@name = ProxyBag.default_name unless ProxyBag.incoming_mapping(@name)
|
146
|
+
if d =~ /If-None-Match: *([^\r]+)/
|
147
|
+
@none_match = $1
|
148
|
+
end
|
149
|
+
@header_data = d.scan(/Cookie:.*/).collect {|c| c =~ /: ([^\r]*)/; $1}
|
150
|
+
@done_parsing = true
|
151
|
+
|
152
|
+
# Keep-Alive works differently on HTTP 1.0 versus HTTP 1.1
|
153
|
+
# HTTP 1.0 was not written to support Keep-Alive initially; it was
|
154
|
+
# bolted on. Thus, for an HTTP 1.0 client to indicate that it
|
155
|
+
# wants to initiate a Keep-Alive session, it must send a header:
|
156
|
+
#
|
157
|
+
# Connection: Keep-Alive
|
158
|
+
#
|
159
|
+
# Then, when the server sends the response, it must likewise add:
|
160
|
+
#
|
161
|
+
# Connection: Keep-Alive
|
162
|
+
#
|
163
|
+
# to the response.
|
164
|
+
#
|
165
|
+
# For HTTP 1.1, Keep-Alive is assumed. If a client does not want
|
166
|
+
# Keep-Alive, then it must send the following header:
|
167
|
+
#
|
168
|
+
# Connection: close
|
169
|
+
#
|
170
|
+
# Likewise, if the server does not want to keep the connection
|
171
|
+
# alive, it must send the same header:
|
172
|
+
#
|
173
|
+
# Connection: close
|
174
|
+
#
|
175
|
+
# to the client.
|
176
|
+
|
177
|
+
if @name
|
178
|
+
unless ProxyBag.keepalive(@name) == false
|
179
|
+
if @http_version == C1_0
|
180
|
+
if data =~ /Connection: Keep-Alive/i
|
181
|
+
# Nonstandard HTTP 1.0 situation; apply keepalive header.
|
182
|
+
@connection_header = CConnection_KeepAlive
|
183
|
+
else
|
184
|
+
# Standard HTTP 1.0 situation; connection will be closed.
|
185
|
+
@keepalive = false
|
186
|
+
@connection_header = CConnection_close
|
187
|
+
end
|
188
|
+
else # The connection is an HTTP 1.1 connection.
|
189
|
+
if data =~ /Connection: [Cc]lose/
|
190
|
+
# Nonstandard HTTP 1.1 situation; connection will be closed.
|
191
|
+
@keepalive = false
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# THIS IS BROKEN; the interaction of @data, data, and add_frontend_client needs to be revised
|
197
|
+
ProxyBag.add_frontend_client(self,@data,data)
|
198
|
+
else
|
199
|
+
send_404_response
|
200
|
+
end
|
201
|
+
else
|
202
|
+
@data.unshift data
|
203
|
+
@header_missing_pieces = true
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Hardcoded 400 response that is sent if the request is malformed.
|
209
|
+
|
210
|
+
def send_400_response
|
211
|
+
ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host
|
212
|
+
error = "The request received on #{ProxyBag.now.asctime} from #{ip} was malformed and could not be serviced."
|
213
|
+
send_data "#{C400Header}Bad Request\n\n#{error}"
|
214
|
+
ProxyBag.logger.log(Cinfo,"Bad Request -- #{error}")
|
215
|
+
close_connection_after_writing
|
216
|
+
increment_400_count
|
217
|
+
end
|
218
|
+
|
219
|
+
# Hardcoded 404 response. This is sent if a request can't be matched to
|
220
|
+
# any defined incoming section.
|
221
|
+
|
222
|
+
def send_404_response
|
223
|
+
ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host
|
224
|
+
error = CGI.escapeHTML "The request (#{ @uri } --> #{@name}), received on #{ProxyBag.now.asctime} from #{ip} did not match any resource know to this server."
|
225
|
+
send_data "#{C404Header}Resource not found.\n\n#{error}"
|
226
|
+
ProxyBag.logger.log(Cinfo,"Resource not found -- #{error}")
|
227
|
+
close_connection_after_writing
|
228
|
+
increment_404_count
|
229
|
+
end
|
230
|
+
|
231
|
+
# The push method pushes data from the HttpRecognizer to whatever
|
232
|
+
# entity is responsible for handling it. You MUST override this with
|
233
|
+
# something useful.
|
234
|
+
|
235
|
+
def push
|
236
|
+
raise NotImplemented
|
237
|
+
end
|
238
|
+
|
239
|
+
# The connection with the web browser client has been closed, so the
|
240
|
+
# object must be removed from the ProxyBag's queue if it is has not
|
241
|
+
# been associated with a backend. If it has already been associated
|
242
|
+
# with a backend, then it will not be in the queue and need not be
|
243
|
+
# removed.
|
244
|
+
|
245
|
+
def unbind
|
246
|
+
ProxyBag.remove_client(self) unless @associate
|
247
|
+
end
|
248
|
+
|
249
|
+
def request_method; @request_method; end
|
250
|
+
def http_version; @http_version; end
|
251
|
+
def none_match; @none_match; end
|
252
|
+
|
253
|
+
def setup_for_redeployment
|
254
|
+
@data_pos = 0
|
255
|
+
end
|
256
|
+
|
257
|
+
def increment_404_count
|
258
|
+
@klass.increment_404_count
|
259
|
+
end
|
260
|
+
|
261
|
+
def increment_400_count
|
262
|
+
@klass.increment_400_count
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# The Swiftiply logging support is really written with Analogger in mind, since
|
2
|
+
# the ideal logging solution should provide a minimal performance impact.
|
3
|
+
|
4
|
+
require 'swiftcore/Analogger/Client'
|
5
|
+
|
6
|
+
module Swiftcore
|
7
|
+
module Swiftiply
|
8
|
+
module Loggers
|
9
|
+
class Analogger
|
10
|
+
def self.new(params)
|
11
|
+
lp = []
|
12
|
+
lp << params['service'] || 'swiftiply'
|
13
|
+
lp << params['host'] || '127.0.0.1'
|
14
|
+
lp << (params['port'] && params['port'].to_i) || 6766
|
15
|
+
lp << params['key']
|
16
|
+
::Swiftcore::Analogger::Client.new(*lp)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Super class for all proxy implementations.
|
2
|
+
module Swiftcore
|
3
|
+
module Swiftiply
|
4
|
+
class Proxy
|
5
|
+
def self.config(conf, new_conf)
|
6
|
+
# Process Proxy config; determine which specific proxy implementation to load and pass config control into.
|
7
|
+
conf[Cproxy] ||= Ckeepalive if conf[Ckeepalive]
|
8
|
+
require "swiftcore/Swiftiply/proxy_backends/#{conf[Cproxy]}"
|
9
|
+
klass = Swiftcore::Swiftiply::get_const_from_name(conf[Cproxy].upcase, ::Swiftcore::Swiftiply::Proxies)
|
10
|
+
# Need error handling here!
|
11
|
+
klass.config(conf, new_conf)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
# Encoding:ascii-8bit
|
2
|
+
|
3
|
+
require 'swiftcore/Swiftiply/config'
|
4
|
+
# Standard style proxy.
|
5
|
+
module Swiftcore
|
6
|
+
module Swiftiply
|
7
|
+
module Proxies
|
8
|
+
class Keepalive < EventMachine::Connection
|
9
|
+
|
10
|
+
def self.is_a_server?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
# While the configuration parsing now lives with the protocol, a lot of the code for it is generic. Those generic pieces need to live back up in Swiftcore::Swiftiply or maybe Swiftcore::Swiftiply::Config.
|
15
|
+
def self.config(m,new_config)
|
16
|
+
#require 'swiftcore/Swiftiply/backend_protocol'
|
17
|
+
# keepalive requests are standard Swiftiply requests.
|
18
|
+
|
19
|
+
# The hash of the "outgoing" config section. It is used to
|
20
|
+
# uniquely identify a section.
|
21
|
+
owners = m[Cincoming].sort.join('|')
|
22
|
+
hash = Digest::SHA256.hexdigest(owners).intern
|
23
|
+
config_data = {:hash => hash, :owners => owners}
|
24
|
+
|
25
|
+
Config.configure_logging(m, config_data)
|
26
|
+
file_cache = Config.configure_file_cache(m, config_data)
|
27
|
+
dynamic_request_cache = Config.configure_dynamic_request_cache(m, config_data)
|
28
|
+
etag_cache = Config.configure_etag_cache(m, config_data)
|
29
|
+
|
30
|
+
# For each incoming entry, do setup.
|
31
|
+
new_config[Cincoming] = {}
|
32
|
+
m[Cincoming].each do |p_|
|
33
|
+
configure_one({ :p_ => p_,
|
34
|
+
:m => m,
|
35
|
+
:new_config => new_config,
|
36
|
+
:config_data => config_data,
|
37
|
+
:file_cache => file_cache,
|
38
|
+
:dynamic_request_cache => dynamic_request_cache,
|
39
|
+
:etag_cache => etag_cache })
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.configure_one(args = {})
|
44
|
+
new_config = args[:new_config]
|
45
|
+
config_data = args[:config_data]
|
46
|
+
p_ = args[:p_]
|
47
|
+
file_cache = args[:file_cache]
|
48
|
+
dynamic_request_cache = args[:dynamic_request_cache]
|
49
|
+
etag_cache = args[:etag_cache]
|
50
|
+
m = args[:m]
|
51
|
+
|
52
|
+
ProxyBag.logger.log(Cinfo,"Configuring incoming #{p_}") if Swiftcore::Swiftiply::log_level > 1
|
53
|
+
p = p_.intern
|
54
|
+
|
55
|
+
Config.setup_caches(new_config, config_data.merge({:p => p, :file_cache => file_cache, :dynamic_request_cache => dynamic_request_cache, :etag_cache => etag_cache}))
|
56
|
+
|
57
|
+
ProxyBag.add_backup_mapping(m[Cbackup].intern,p) if m.has_key?(Cbackup)
|
58
|
+
Config.configure_docroot(m, p)
|
59
|
+
config_data[:permit_xsendfile] = Config.configure_sendfileroot(m, p)
|
60
|
+
Config.configure_xforwardedfor(m, p)
|
61
|
+
Config.configure_redeployable(m, p)
|
62
|
+
Config.configure_key(m, p, config_data)
|
63
|
+
Config.configure_staticmask(m, p)
|
64
|
+
Config.configure_cache_extensions(m,p)
|
65
|
+
Config.configure_cluster_manager(m,p)
|
66
|
+
Config.configure_backends(Coutgoing, { :config => m,
|
67
|
+
:p => p,
|
68
|
+
:config_data => config_data,
|
69
|
+
:new_config => new_config,
|
70
|
+
:self => self,
|
71
|
+
:directory_class => ::Swiftcore::Deque,
|
72
|
+
:directory_args => []})
|
73
|
+
Config.stop_unused_servers(new_config)
|
74
|
+
Config.set_server_queue(config_data, ::Swiftcore::Deque, [])
|
75
|
+
end
|
76
|
+
|
77
|
+
# Swiftcore::Swiftiply::Proxies::Keepalive is the EventMachine::Connection
|
78
|
+
# subclass that handles the communications between Swiftiply and a
|
79
|
+
# persistently connected Swiftiply client process.
|
80
|
+
|
81
|
+
attr_accessor :associate, :id
|
82
|
+
|
83
|
+
C0rnrn = "0\r\n\r\n".freeze
|
84
|
+
Crnrn = "\r\n\r\n".freeze
|
85
|
+
|
86
|
+
def initialize *args
|
87
|
+
@name = self.class.bname
|
88
|
+
@permit_xsendfile = self.class.xsendfile
|
89
|
+
@enable_sendfile_404 = self.class.enable_sendfile_404
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
93
|
+
def name
|
94
|
+
@name
|
95
|
+
end
|
96
|
+
|
97
|
+
# Call setup() and add the backend to the ProxyBag queue.
|
98
|
+
|
99
|
+
def post_init
|
100
|
+
setup
|
101
|
+
@initialized = nil
|
102
|
+
ProxyBag.add_server self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Setup the initial variables for receiving headers and content.
|
106
|
+
|
107
|
+
def setup
|
108
|
+
@headers = ''
|
109
|
+
@headers_completed = @dont_send_data = false
|
110
|
+
#@content_length = nil
|
111
|
+
@content_sent = 0
|
112
|
+
@filter = self.class.filter
|
113
|
+
end
|
114
|
+
|
115
|
+
# Receive data from the backend process. Headers are parsed from
|
116
|
+
# the rest of the content. If a Content-Length header is present,
|
117
|
+
# that is used to determine how much data to expect. Otherwise,
|
118
|
+
# if 'Transfer-encoding: chunked' is present, assume chunked
|
119
|
+
# encoding. Otherwise be paranoid; something isn't the way we like
|
120
|
+
# it to be.
|
121
|
+
|
122
|
+
def receive_data data
|
123
|
+
unless @initialized
|
124
|
+
# preamble = data.slice!(0..24)
|
125
|
+
preamble = data[0..24]
|
126
|
+
data = data[25..-1] || C_empty
|
127
|
+
keylen = preamble[23..24].to_i(16)
|
128
|
+
keylen = 0 if keylen < 0
|
129
|
+
key = keylen > 0 ? data.slice!(0..(keylen - 1)) : C_empty
|
130
|
+
#if preamble[0..10] == Cswiftclient and key == ProxyBag.get_key(@name)
|
131
|
+
if preamble.index(Cswiftclient) == 0 and key == ProxyBag.get_key(@name)
|
132
|
+
@id = preamble[11..22]
|
133
|
+
ProxyBag.add_id(self,@id)
|
134
|
+
@initialized = true
|
135
|
+
else
|
136
|
+
# The worker that connected did not present the proper authentication,
|
137
|
+
# so something is fishy; time to cut bait.
|
138
|
+
close_connection
|
139
|
+
return
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
unless @headers_completed
|
144
|
+
if data.include?(Crnrn)
|
145
|
+
@headers_completed = true
|
146
|
+
h,data = data.split(/\r\n\r\n/,2)
|
147
|
+
#@headers << h << Crnrn
|
148
|
+
if @headers.length > 0
|
149
|
+
@headers << h
|
150
|
+
else
|
151
|
+
@headers = h
|
152
|
+
end
|
153
|
+
|
154
|
+
if @headers =~ /Content-[Ll]ength: *([^\r]+)/
|
155
|
+
@content_length = $1.to_i
|
156
|
+
elsif @headers =~ /Transfer-encoding:\s*chunked/
|
157
|
+
@content_length = nil
|
158
|
+
else
|
159
|
+
@content_length = 0
|
160
|
+
end
|
161
|
+
|
162
|
+
if @permit_xsendfile && @headers =~ /X-[Ss]endfile: *([^\r]+)/
|
163
|
+
@associate.uri = $1
|
164
|
+
if ProxyBag.serve_static_file(@associate,ProxyBag.get_sendfileroot(@associate.name))
|
165
|
+
@dont_send_data = true
|
166
|
+
else
|
167
|
+
if @enable_sendfile_404
|
168
|
+
msg = "#{@associate.uri} could not be found."
|
169
|
+
@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}"
|
170
|
+
@associate.close_connection_after_writing
|
171
|
+
@dont_send_data = true
|
172
|
+
else
|
173
|
+
@associate.send_data (@headers + Crnrn)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
else
|
177
|
+
@associate.send_data (@headers + Crnrn)
|
178
|
+
end
|
179
|
+
|
180
|
+
# If keepalive is turned on, the assumption is that it will stay
|
181
|
+
# on, unless the headers being returned indicate that the connection
|
182
|
+
# should be closed.
|
183
|
+
# So, check for a 'Connection: Closed' header.
|
184
|
+
if keepalive = @associate.keepalive
|
185
|
+
keepalive = false if @headers =~ /Connection: [Cc]lose/
|
186
|
+
if @associate_http_version == C1_0
|
187
|
+
keepalive = false unless @headers == /Connection: Keep-Alive/i
|
188
|
+
end
|
189
|
+
end
|
190
|
+
else
|
191
|
+
@headers << data
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
if @headers_completed
|
196
|
+
@associate.send_data data unless @dont_send_data
|
197
|
+
@content_sent += data.length
|
198
|
+
if ( @content_length and @content_sent >= @content_length ) or data[-6..-1] == C0rnrn or @associate.request_method == CHEAD
|
199
|
+
# If @dont_send_data is set, then the connection is going to be closed elsewhere.
|
200
|
+
unless @dont_send_data
|
201
|
+
# Check to see if keepalive is enabled.
|
202
|
+
if keepalive
|
203
|
+
@associate.reset_state
|
204
|
+
ProxyBag.remove_client(self) unless @associate
|
205
|
+
else
|
206
|
+
@associate.close_connection_after_writing
|
207
|
+
end
|
208
|
+
end
|
209
|
+
@associate = @headers_completed = @dont_send_data = nil
|
210
|
+
@headers = ''
|
211
|
+
#@headers_completed = false
|
212
|
+
#@content_length = nil
|
213
|
+
@content_sent = 0
|
214
|
+
#setup
|
215
|
+
ProxyBag.add_server self
|
216
|
+
end
|
217
|
+
end
|
218
|
+
# TODO: Log these errors!
|
219
|
+
rescue Exception => e
|
220
|
+
puts "Kaboom: #{e} -- #{e.backtrace.inspect}"
|
221
|
+
@associate.close_connection_after_writing if @associate
|
222
|
+
@associate = nil
|
223
|
+
setup
|
224
|
+
ProxyBag.add_server self
|
225
|
+
end
|
226
|
+
|
227
|
+
# This is called when the backend disconnects from the proxy.
|
228
|
+
# If the backend is currently associated with a web browser client,
|
229
|
+
# that connection will be closed. Otherwise, the backend will be
|
230
|
+
# removed from the ProxyBag's backend queue.
|
231
|
+
|
232
|
+
def unbind
|
233
|
+
if @associate
|
234
|
+
if !@associate.redeployable or @content_length
|
235
|
+
@associate.close_connection_after_writing
|
236
|
+
else
|
237
|
+
@associate.associate = nil
|
238
|
+
@associate.setup_for_redeployment
|
239
|
+
ProxyBag.rebind_frontend_client(@associate)
|
240
|
+
end
|
241
|
+
else
|
242
|
+
ProxyBag.remove_server(self)
|
243
|
+
end
|
244
|
+
ProxyBag.remove_id(self)
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.bname=(val)
|
248
|
+
@bname = val
|
249
|
+
end
|
250
|
+
|
251
|
+
def self.bname
|
252
|
+
@bname
|
253
|
+
end
|
254
|
+
|
255
|
+
def self.xsendfile=(val)
|
256
|
+
@xsendfile = val
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.xsendfile
|
260
|
+
@xsendfile
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.enable_sendfile_404=(val)
|
264
|
+
@enable_sendfile_404 = val
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.enable_sendfile_404
|
268
|
+
@enable_sendfile_404
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.filter=(val)
|
272
|
+
@filter = val
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.filter
|
276
|
+
@filter
|
277
|
+
end
|
278
|
+
|
279
|
+
def filter
|
280
|
+
@filter
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|