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,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
|