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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTORS +2 -0
  3. data/README.md +62 -0
  4. data/bin/{mongrel_rails → evented_mongrel_rails} +6 -14
  5. data/bin/swiftiplied_mongrel_rails +246 -0
  6. data/bin/swiftiply +136 -116
  7. data/bin/swiftiply_mongrel_rails +2 -2
  8. data/bin/swiftiplyctl +283 -0
  9. data/cleanup.sh +5 -0
  10. data/ext/deque/extconf.rb +162 -0
  11. data/ext/deque/swiftcore/rubymain.cpp +435 -0
  12. data/ext/fastfilereader/extconf.rb +2 -2
  13. data/ext/fastfilereader/mapper.cpp +2 -0
  14. data/ext/map/extconf.rb +161 -0
  15. data/ext/map/rubymain.cpp +500 -0
  16. data/ext/splaytree/extconf.rb +161 -0
  17. data/ext/splaytree/swiftcore/rubymain.cpp +580 -0
  18. data/ext/splaytree/swiftcore/splay_map.h +635 -0
  19. data/ext/splaytree/swiftcore/splay_set.h +575 -0
  20. data/ext/splaytree/swiftcore/splay_tree.h +1127 -0
  21. data/external/httpclient.rb +231 -0
  22. data/external/package.rb +13 -13
  23. data/setup.rb +18 -2
  24. data/src/swiftcore/Swiftiply.rb +417 -773
  25. data/src/swiftcore/Swiftiply/backend_protocol.rb +213 -0
  26. data/src/swiftcore/Swiftiply/cache_base.rb +49 -0
  27. data/src/swiftcore/Swiftiply/cache_base_mixin.rb +52 -0
  28. data/src/swiftcore/Swiftiply/cluster_managers/rest_based_cluster_manager.rb +9 -0
  29. data/src/swiftcore/Swiftiply/cluster_protocol.rb +70 -0
  30. data/src/swiftcore/Swiftiply/config.rb +370 -0
  31. data/src/swiftcore/Swiftiply/config/rest_updater.rb +26 -0
  32. data/src/swiftcore/Swiftiply/constants.rb +101 -0
  33. data/src/swiftcore/Swiftiply/content_cache_entry.rb +44 -0
  34. data/src/swiftcore/Swiftiply/content_response.rb +45 -0
  35. data/src/swiftcore/Swiftiply/control_protocol.rb +49 -0
  36. data/src/swiftcore/Swiftiply/dynamic_request_cache.rb +41 -0
  37. data/src/swiftcore/Swiftiply/etag_cache.rb +64 -0
  38. data/src/swiftcore/Swiftiply/file_cache.rb +46 -0
  39. data/src/swiftcore/Swiftiply/hash_cache_base.rb +22 -0
  40. data/src/swiftcore/Swiftiply/http_recognizer.rb +267 -0
  41. data/src/swiftcore/Swiftiply/loggers/Analogger.rb +21 -0
  42. data/src/swiftcore/Swiftiply/loggers/stderror.rb +13 -0
  43. data/src/swiftcore/Swiftiply/mocklog.rb +10 -0
  44. data/src/swiftcore/Swiftiply/proxy.rb +15 -0
  45. data/src/swiftcore/Swiftiply/proxy_backends/keepalive.rb +286 -0
  46. data/src/swiftcore/Swiftiply/proxy_backends/traditional.rb +286 -0
  47. data/src/swiftcore/Swiftiply/proxy_backends/traditional/redis_directory.rb +87 -0
  48. data/src/swiftcore/Swiftiply/proxy_backends/traditional/static_directory.rb +69 -0
  49. data/src/swiftcore/Swiftiply/proxy_bag.rb +716 -0
  50. data/src/swiftcore/Swiftiply/rest_based_cluster_manager.rb +15 -0
  51. data/src/swiftcore/Swiftiply/splay_cache_base.rb +21 -0
  52. data/src/swiftcore/Swiftiply/support_pagecache.rb +6 -3
  53. data/src/swiftcore/Swiftiply/swiftiply_2_http_proxy.rb +7 -0
  54. data/src/swiftcore/Swiftiply/swiftiply_client.rb +20 -5
  55. data/src/swiftcore/Swiftiply/version.rb +5 -0
  56. data/src/swiftcore/evented_mongrel.rb +26 -8
  57. data/src/swiftcore/hash.rb +43 -0
  58. data/src/swiftcore/method_builder.rb +28 -0
  59. data/src/swiftcore/streamer.rb +46 -0
  60. data/src/swiftcore/swiftiplied_mongrel.rb +91 -23
  61. data/src/swiftcore/types.rb +20 -3
  62. data/swiftiply.gemspec +14 -8
  63. data/test/TC_Deque.rb +152 -0
  64. data/test/TC_ProxyBag.rb +147 -166
  65. data/test/TC_Swiftiply.rb +576 -169
  66. data/test/TC_Swiftiply/mongrel/evented_hello.rb +1 -1
  67. data/test/TC_Swiftiply/mongrel/swiftiplied_hello.rb +1 -1
  68. data/test/TC_Swiftiply/test_serve_static_file_xsendfile/sendfile_client.rb +27 -0
  69. data/test/TC_Swiftiply/test_ssl/bin/validate_ssl_capability.rb +21 -0
  70. data/test/TC_Swiftiply/test_ssl/test.cert +16 -0
  71. data/test/TC_Swiftiply/test_ssl/test.key +15 -0
  72. data/{bin → test/bin}/echo_client +0 -0
  73. metadata +136 -94
  74. data/README +0 -126
  75. 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,13 @@
1
+ module Swiftcore
2
+ module Swiftiply
3
+ module Loggers
4
+ class Stderror
5
+ def initialize(*args);end
6
+
7
+ def log(severity, msg)
8
+ $stderr.puts "#{Time.now.asctime}:#{severity}:#{msg}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # This is just a mock log to provide a bit bucket logging location, if needed.
2
+
3
+ module Swiftcore
4
+ module Swiftiply
5
+ class MockLog
6
+ def log(*args)
7
+ end
8
+ end
9
+ end
10
+ 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