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