experella-proxy 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +15 -0
- data/Gemfile +3 -0
- data/README.md +219 -0
- data/Rakefile +25 -0
- data/TODO.txt +20 -0
- data/bin/experella-proxy +54 -0
- data/config/default/404.html +16 -0
- data/config/default/503.html +18 -0
- data/config/default/config.rb +64 -0
- data/config/default/ssl/certs/experella-proxy.pem +18 -0
- data/config/default/ssl/private/experella-proxy.key +28 -0
- data/dev/experella-proxy +62 -0
- data/experella-proxy.gemspec +39 -0
- data/lib/experella-proxy/backend.rb +58 -0
- data/lib/experella-proxy/backend_server.rb +100 -0
- data/lib/experella-proxy/configuration.rb +154 -0
- data/lib/experella-proxy/connection.rb +557 -0
- data/lib/experella-proxy/connection_manager.rb +167 -0
- data/lib/experella-proxy/globals.rb +37 -0
- data/lib/experella-proxy/http_status_codes.rb +45 -0
- data/lib/experella-proxy/proxy.rb +61 -0
- data/lib/experella-proxy/request.rb +106 -0
- data/lib/experella-proxy/response.rb +204 -0
- data/lib/experella-proxy/server.rb +68 -0
- data/lib/experella-proxy/version.rb +15 -0
- data/lib/experella-proxy.rb +93 -0
- data/spec/echo-server/echo_server.rb +49 -0
- data/spec/experella-proxy/backend_server_spec.rb +101 -0
- data/spec/experella-proxy/configuration_spec.rb +27 -0
- data/spec/experella-proxy/connection_manager_spec.rb +159 -0
- data/spec/experella-proxy/experella-proxy_spec.rb +471 -0
- data/spec/experella-proxy/request_spec.rb +88 -0
- data/spec/experella-proxy/response_spec.rb +44 -0
- data/spec/fixtures/404.html +16 -0
- data/spec/fixtures/503.html +18 -0
- data/spec/fixtures/spec.log +331 -0
- data/spec/fixtures/test_config.rb +34 -0
- data/spec/spec.log +235 -0
- data/spec/spec_helper.rb +35 -0
- data/test/sinatra/hello_world_server.rb +17 -0
- data/test/sinatra/server_one.rb +89 -0
- data/test/sinatra/server_two.rb +89 -0
- metadata +296 -0
@@ -0,0 +1,557 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module ExperellaProxy
|
4
|
+
|
5
|
+
# The proxies TCP Connection to the client
|
6
|
+
#
|
7
|
+
# Responsible for parsing and buffering the clients http requests,
|
8
|
+
# connecting to the backend server, sending data to the backend server and returning responses to the client.
|
9
|
+
#
|
10
|
+
# See EventMachine::Connection documentation for more information
|
11
|
+
#
|
12
|
+
# @see http://eventmachine.rubyforge.org/EventMachine/Connection.html EventMachine::Connection
|
13
|
+
class Connection < EventMachine::Connection
|
14
|
+
include ExperellaProxy::Globals
|
15
|
+
|
16
|
+
# Used to pass an optional block to the connection which will be executed when the {#connected} event occurs
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# # called on successful backend connection
|
20
|
+
# # backend is the name of the connected server
|
21
|
+
# conn.on_connect do |backend|
|
22
|
+
#
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @param blk [Block] a block to be executed
|
26
|
+
def on_connect(&blk)
|
27
|
+
@on_connect = blk
|
28
|
+
end
|
29
|
+
|
30
|
+
# Used to pass an optional block to the connection which will be executed when the {#receive_data} event occurs
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# # modify / process request stream
|
34
|
+
# # and return modified data
|
35
|
+
# conn.on_data do |data|
|
36
|
+
# data
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @param blk [Block] a block to be executed
|
40
|
+
# @return [String] the modified data
|
41
|
+
def on_data(&blk)
|
42
|
+
@on_data = blk
|
43
|
+
end
|
44
|
+
|
45
|
+
# Used to pass an optional block to the connection which will be executed when the {#relay_from_backend} event occurs
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# # modify / process response stream
|
49
|
+
# # and return modified response
|
50
|
+
# # backend is the name of the connected server
|
51
|
+
# conn.on_response do |backend, resp|
|
52
|
+
# resp
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @param blk [Block] a block to be executed
|
56
|
+
# @return [String] the modified response
|
57
|
+
def on_response(&blk)
|
58
|
+
@on_response = blk
|
59
|
+
end
|
60
|
+
|
61
|
+
# Used to pass an optional block to the connection which will be executed when the {#unbind_backend} event occurs
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# # termination logic
|
65
|
+
# # backend is the name of the connected server
|
66
|
+
# conn.on_finish do |backend|
|
67
|
+
#
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# @param blk [Block] a block to be executed
|
71
|
+
def on_finish(&blk)
|
72
|
+
@on_finish = blk
|
73
|
+
end
|
74
|
+
|
75
|
+
# Used to pass an optional block to the connection which will be executed when the {#unbind} event occurs
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# # called if client terminates connection
|
79
|
+
# # or timeout occurs
|
80
|
+
# conn.on_unbind do
|
81
|
+
#
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# @param blk [Block] a block to be executed
|
85
|
+
def on_unbind(&blk)
|
86
|
+
@on_unbind = blk
|
87
|
+
end
|
88
|
+
|
89
|
+
# calls EventMachine close_connection_after_writing method with 1 tick delay
|
90
|
+
# waits 1 tick to make sure reactor i/o does not have unnecessary loop delay
|
91
|
+
def close
|
92
|
+
@unbound = true
|
93
|
+
EM.next_tick(method(:close_connection_after_writing))
|
94
|
+
log.debug [msec, :unbind_client_after_writing, @signature.to_s]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Connects self to a BackendServer object
|
98
|
+
#
|
99
|
+
# Any request mangling configured in {BackendServer#mangle} will be done here
|
100
|
+
#
|
101
|
+
# Method provides additional support for BackendServer's named "web".
|
102
|
+
# Host and Port will be determined through the Request instead of BackendServer settings.
|
103
|
+
#
|
104
|
+
# @param backend [BackendServer] the BackendServer object
|
105
|
+
def connect_backendserver(backend)
|
106
|
+
@backend = backend
|
107
|
+
connection_manager.free_connection(self)
|
108
|
+
#mangle http header if backend wants to
|
109
|
+
unless @backend.mangle.nil?
|
110
|
+
@backend.mangle.each do |k, v|
|
111
|
+
if v.respond_to?(:call)
|
112
|
+
get_request.update_header({k => v.call(get_request.header[k])})
|
113
|
+
else
|
114
|
+
get_request.update_header({k => v})
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# reconstruct the request header
|
120
|
+
get_request.reconstruct_header
|
121
|
+
|
122
|
+
#special web support for unknown hosts
|
123
|
+
if @backend.name.eql?("web")
|
124
|
+
xport = get_request.header[:Host].match(/:[0-9]+/)
|
125
|
+
if xport.nil? || xport.to_s.empty?
|
126
|
+
xport = "80"
|
127
|
+
else
|
128
|
+
xport = xport.to_s.gsub(/:/, "")
|
129
|
+
end
|
130
|
+
xhost = get_request.header[:Host].gsub(":#{xport}", "")
|
131
|
+
server(@backend.name, :host => xhost, :port => xport)
|
132
|
+
else
|
133
|
+
server(@backend.name, :host => @backend.host, :port => @backend.port)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Called by backend connections when the remote TCP connection attempt completes successfully.
|
138
|
+
#
|
139
|
+
# {#on_connect} block will be executed here
|
140
|
+
#
|
141
|
+
# This method triggers the {#relay_to_server} method
|
142
|
+
#
|
143
|
+
# @param name [String] name of the Server used for logging
|
144
|
+
def connected(name)
|
145
|
+
log.debug [msec, :connected]
|
146
|
+
@on_connect.call(name) if @on_connect
|
147
|
+
log.info msec + 'on_connect'.ljust(12) + " @" + @signature.to_s + ' ' + name
|
148
|
+
relay_to_server
|
149
|
+
end
|
150
|
+
|
151
|
+
# Used for accessing the connections first request
|
152
|
+
#
|
153
|
+
# Buffered requests must not be handled before first in done
|
154
|
+
#
|
155
|
+
# @return [Request] the Request to be handled
|
156
|
+
def get_request
|
157
|
+
@requests.first
|
158
|
+
end
|
159
|
+
|
160
|
+
# Called by the EventMachine loop whenever data has been received by the network connection.
|
161
|
+
# It is never called by user code. {#receive_data} is called with a single parameter,
|
162
|
+
# a String containing the network protocol data, which may of course be binary.
|
163
|
+
#
|
164
|
+
# Data gets passed to the specified {#on_data} block first
|
165
|
+
# Then data gets passed to the parser and the {#relay_to_server} method gets fired
|
166
|
+
#
|
167
|
+
# On Http::Parser::Error a 400 Bad Request error send to the client and the Connection will be closed
|
168
|
+
#
|
169
|
+
# @param data [String] Opaque incoming data
|
170
|
+
def receive_data(data)
|
171
|
+
log.debug [msec, :connection, data]
|
172
|
+
data = @on_data.call(data) if @on_data
|
173
|
+
begin
|
174
|
+
@request_parser << data
|
175
|
+
rescue Http::Parser::Error
|
176
|
+
log.warn [msec, "Parser error caused by invalid request data", "@#{@signature}"]
|
177
|
+
# on error unbind request_parser object, so additional data doesn't get parsed anymore
|
178
|
+
#
|
179
|
+
# assigning a string to the parser variable, will cause incoming data to get buffered
|
180
|
+
# imho this is a better solution than adding a condition for this rare error case
|
181
|
+
@request_parser = ""
|
182
|
+
send_data "HTTP/1.1 400 Bad Request\r\nVia: 1.1 experella\r\nConnection: close\r\n\r\n"
|
183
|
+
close
|
184
|
+
end
|
185
|
+
|
186
|
+
log.info msec + 'on_data'.ljust(12) + " @" + @signature.to_s
|
187
|
+
log.debug data
|
188
|
+
relay_to_server
|
189
|
+
end
|
190
|
+
|
191
|
+
# Called by {Backend} connections.
|
192
|
+
# Relays data from backend server to the client
|
193
|
+
#
|
194
|
+
# {#on_response} block will be executed here
|
195
|
+
#
|
196
|
+
# @param name [String] name of the Server used for logging
|
197
|
+
# @param data [String] opaque response data
|
198
|
+
def relay_from_backend(name, data)
|
199
|
+
log.info msec + 'on_response'.ljust(12) + " @" + @signature.to_s + " from #{name}"
|
200
|
+
log.debug msec + "#{name.inspect} " + data
|
201
|
+
@got_response = true
|
202
|
+
data = @on_response.call(name, data) if @on_response
|
203
|
+
get_request.response << data
|
204
|
+
end
|
205
|
+
|
206
|
+
# Initialize a {Backend} connection
|
207
|
+
#
|
208
|
+
# Can connect to host:port server address
|
209
|
+
#
|
210
|
+
# @param name [String] name of the Server used for logging
|
211
|
+
# @param opts [Hash] Hash containing connection parameters
|
212
|
+
def server(name, opts)
|
213
|
+
srv = EventMachine::bind_connect(opts[:bind_host], opts[:bind_port], opts[:host], opts[:port], Backend) do |c|
|
214
|
+
c.name = name
|
215
|
+
c.plexer = self
|
216
|
+
end
|
217
|
+
|
218
|
+
@server = srv
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# ip, port of the connected client
|
223
|
+
#
|
224
|
+
def peer
|
225
|
+
@peer ||= begin
|
226
|
+
peername = get_peername
|
227
|
+
peername ? Socket.unpack_sockaddr_in(peername).reverse : nil
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Called by the event loop immediately after the network connection has been established,
|
232
|
+
# and before resumption of the network loop.
|
233
|
+
# This method is generally not called by user code, but is called automatically
|
234
|
+
# by the event loop. The base-class implementation is a no-op.
|
235
|
+
# This is a very good place to initialize instance variables that will
|
236
|
+
# be used throughout the lifetime of the network connection.
|
237
|
+
#
|
238
|
+
# This is currently used to initiate start_tls on @options[:tls] enabled
|
239
|
+
#
|
240
|
+
#
|
241
|
+
# @see #connection_completed
|
242
|
+
# @see #unbind
|
243
|
+
# @see #send_data
|
244
|
+
# @see #receive_data
|
245
|
+
def post_init
|
246
|
+
if @options[:tls]
|
247
|
+
log.info [msec, "starting tls handshake @#{@signature}"]
|
248
|
+
start_tls(:private_key_file => @options[:private_key_file], :cert_chain_file => @options[:cert_chain_file], :verify_peer => false)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# ip, port of the local server connect
|
254
|
+
#
|
255
|
+
def sock
|
256
|
+
@sock ||= begin
|
257
|
+
sockname = get_sockname
|
258
|
+
sockname ? Socket.unpack_sockaddr_in(sockname).reverse : nil
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
# Called by EventMachine when the SSL/TLS handshake has been completed, as a result of calling start_tls to
|
264
|
+
# initiate SSL/TLS on the connection.
|
265
|
+
#
|
266
|
+
# This callback exists because {#post_init} and connection_completed are not reliable for indicating when an
|
267
|
+
# SSL/TLS connection is ready to have its certificate queried for.
|
268
|
+
def ssl_handshake_completed
|
269
|
+
log.info [msec, "ssl_handshake_completed successful"]
|
270
|
+
end
|
271
|
+
|
272
|
+
# Called by backend connections whenever their connection is closed.
|
273
|
+
# The close can occur because the code intentionally closes it
|
274
|
+
# (using #close_connection and #close_connection_after_writing), because
|
275
|
+
# the remote peer closed the connection, or because of a network error.
|
276
|
+
#
|
277
|
+
# Therefor connection errors, reconnections and queues need to be handled here
|
278
|
+
#
|
279
|
+
# {#on_finish} block will be executed here
|
280
|
+
#
|
281
|
+
# @param name [String] name of the Server used for logging
|
282
|
+
def unbind_backend(name)
|
283
|
+
|
284
|
+
log.info msec + 'on_finish'.ljust(12) + " @" + @signature.to_s + " for #{name}" + " responded? " + @got_response.to_s
|
285
|
+
if @on_finish
|
286
|
+
@on_finish.call(name)
|
287
|
+
end
|
288
|
+
|
289
|
+
@server = nil
|
290
|
+
|
291
|
+
#if backend responded or client unbound connection (timeout probably triggers this too)
|
292
|
+
if @got_response || @unbound
|
293
|
+
log.info msec + "Request done! @" + @signature.to_s
|
294
|
+
log.debug [msec, :backend_unbound, @requests.size, "keep alive? " + get_request.keep_alive.to_s]
|
295
|
+
unless get_request.keep_alive
|
296
|
+
close
|
297
|
+
log.debug [msec, :connection_closed, "close non persistent connection after writing"]
|
298
|
+
end
|
299
|
+
@requests.shift #pop first element,request is done
|
300
|
+
@got_response = false #reset response flag
|
301
|
+
|
302
|
+
#free backend server and connect to next conn if matching conn exists
|
303
|
+
unless @backend.nil?
|
304
|
+
connect_next
|
305
|
+
end
|
306
|
+
|
307
|
+
#check if queued requests find a matching backend
|
308
|
+
unless @requests.empty? || @unbound
|
309
|
+
#try to dispatch first request to backend
|
310
|
+
dispatch_request
|
311
|
+
end
|
312
|
+
else
|
313
|
+
#handle no backend response here
|
314
|
+
log.error msec + "Error, backend didnt respond"
|
315
|
+
error_page = "HTTP/1.1 503 Service unavailable\r\nContent-Length: #{config.error_pages[503].length}\r\nContent-Type: text/html;charset=utf-8\r\nConnection: close\r\n\r\n"
|
316
|
+
unless get_request.header[:http_method].eql? "HEAD"
|
317
|
+
error_page << config.error_pages[503]
|
318
|
+
end
|
319
|
+
log.error [msec, :error_to_client, error_page]
|
320
|
+
send_data error_page
|
321
|
+
|
322
|
+
close
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Called by the EventMachine loop whenever the client connection is closed.
|
327
|
+
# The close can occur because the code intentionally closes it
|
328
|
+
# (using #close_connection and #close_connection_after_writing), because
|
329
|
+
# the remote peer closed the connection, or because of a network error.
|
330
|
+
#
|
331
|
+
# This is used to clean up associations made to the connection object while it was open.
|
332
|
+
#
|
333
|
+
# {#on_unbind} block will be executed here
|
334
|
+
#
|
335
|
+
def unbind
|
336
|
+
@unbound = true
|
337
|
+
@on_unbind.call if @on_unbind
|
338
|
+
|
339
|
+
log.info [msec, "Client connection unbound! @" + @signature.to_s]
|
340
|
+
#lazy evaluated. if first is true, second would cause a nil-pointer!
|
341
|
+
unless @requests.empty? || get_request.flushed?
|
342
|
+
log.debug [msec, @requests.inspect]
|
343
|
+
end
|
344
|
+
#delete conn from queue if still queued
|
345
|
+
connection_manager.free_connection(self)
|
346
|
+
|
347
|
+
# reconnect backend to new connection if this has not happened already
|
348
|
+
unless @backend.nil?
|
349
|
+
connect_next
|
350
|
+
end
|
351
|
+
# terminate any unfinished backend connections
|
352
|
+
unless @server.nil?
|
353
|
+
@server.close_connection_after_writing
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
|
358
|
+
private
|
359
|
+
|
360
|
+
# @private constructor, gets called by EventMachine::Connection's overwritten new method
|
361
|
+
# Initializes http parser and timeout_timer
|
362
|
+
#
|
363
|
+
# @param options [Hash] options Hash passed to the connection
|
364
|
+
def initialize(options)
|
365
|
+
@options = options
|
366
|
+
@backend = nil
|
367
|
+
@server = nil
|
368
|
+
@requests = [] #contains request objects
|
369
|
+
@unbound = false
|
370
|
+
@got_response = false
|
371
|
+
@request_parser = Http::Parser.new
|
372
|
+
init_http_parser
|
373
|
+
timeout_timer
|
374
|
+
@start = Time.now
|
375
|
+
end
|
376
|
+
|
377
|
+
# checks if the free backend matches any queued connection
|
378
|
+
# if there is a match, fire connection event to that connection
|
379
|
+
#
|
380
|
+
# @note DeHerr: imho ugly, initiating anothers connection backend from a connection feels just wrong
|
381
|
+
# did this for testability. 27.11.2013
|
382
|
+
#
|
383
|
+
def connect_next
|
384
|
+
#free backend server and connect to next conn if matching conn exists
|
385
|
+
next_conn = connection_manager.free_backend(@backend)
|
386
|
+
unless next_conn.nil?
|
387
|
+
next_conn.connect_backendserver(@backend)
|
388
|
+
end
|
389
|
+
@backend = nil
|
390
|
+
end
|
391
|
+
|
392
|
+
# Tries to dispatch the connections first request to a BackendServer object. Usually this should not be called
|
393
|
+
# more than once per request.
|
394
|
+
#
|
395
|
+
# connects to the backend if a BackendServer object is available
|
396
|
+
#
|
397
|
+
# logs if the connection got queued
|
398
|
+
#
|
399
|
+
# sends a 404 Error to client if no registered BackendServer matches the request
|
400
|
+
def dispatch_request
|
401
|
+
backend = connection_manager.backend_available?(get_request)
|
402
|
+
|
403
|
+
if backend.is_a?(BackendServer)
|
404
|
+
log.debug [msec + "Backend found"]
|
405
|
+
connect_backendserver(backend)
|
406
|
+
elsif backend == :queued
|
407
|
+
log.debug [msec + "pushed on queue: no backend available"]
|
408
|
+
else
|
409
|
+
log.error msec + "Error, send client error message and unbind! No backend will match"
|
410
|
+
error_page = "HTTP/1.1 404 Not Found\r\nContent-Length: #{config.error_pages[404].length}\r\nContent-Type: text/html;charset=utf-8\r\nConnection: close\r\n\r\n"
|
411
|
+
unless get_request.header[:http_method].eql? "HEAD"
|
412
|
+
error_page << config.error_pages[404]
|
413
|
+
end
|
414
|
+
log.error [msec, :error_to_client, error_page]
|
415
|
+
send_data error_page
|
416
|
+
close
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# initializes http parser callbacks and blocks
|
421
|
+
def init_http_parser
|
422
|
+
|
423
|
+
@request_parser.on_message_begin = proc do
|
424
|
+
@requests.push(Request.new(self))
|
425
|
+
# this log also triggers if client sends new keep-alive request before backend was unbound
|
426
|
+
log.info [msec + "pipelined request"] if @requests.length > 1
|
427
|
+
log.debug [msec, :message_begin]
|
428
|
+
end
|
429
|
+
|
430
|
+
#called when request headers are completely parsed (first \r\n\r\n triggers this)
|
431
|
+
@request_parser.on_headers_complete = proc do |h|
|
432
|
+
log.info [msec, "new Request @#{@signature} Host: " + h["Host"] + " Request Path: " + @request_parser.request_url]
|
433
|
+
request = @requests.last
|
434
|
+
|
435
|
+
# cache if client wants persistent connection
|
436
|
+
if @request_parser.http_version[0] == 1 && @request_parser.http_version[1] == 0
|
437
|
+
request.keep_alive = false unless h["Connection"].to_s.downcase.eql? "keep-alive"
|
438
|
+
else
|
439
|
+
request.keep_alive = false if h["Connection"].to_s.downcase.include? "close"
|
440
|
+
end
|
441
|
+
request.update_header({:Connection => "close"}) #update Connection header to close for backends
|
442
|
+
|
443
|
+
# if there is a transfer-encoding, stream the message as Transfer-Encoding: chunked to backends
|
444
|
+
unless h["Transfer-Encoding"].nil?
|
445
|
+
h.delete("Content-Length")
|
446
|
+
request.chunked = true
|
447
|
+
request.update_header({:"Transfer-Encoding" => "chunked"})
|
448
|
+
end
|
449
|
+
|
450
|
+
# remove all hop-by-hop header fields
|
451
|
+
unless h["Connection"].nil?
|
452
|
+
h["Connection"].each do |s|
|
453
|
+
h.delete(s)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
HOP_HEADERS.each do |s|
|
457
|
+
h.delete(s)
|
458
|
+
end
|
459
|
+
|
460
|
+
|
461
|
+
via = h.delete("Via")
|
462
|
+
if via.nil?
|
463
|
+
via = "1.1 experella"
|
464
|
+
else
|
465
|
+
via << "1.1 experella"
|
466
|
+
end
|
467
|
+
request.update_header({:Via => via})
|
468
|
+
|
469
|
+
request.update_header(h)
|
470
|
+
request.update_header({:http_version => @request_parser.http_version})
|
471
|
+
request.update_header({:http_method => @request_parser.http_method}) # for requests
|
472
|
+
request.update_header({:request_url => @request_parser.request_url})
|
473
|
+
if @request_parser.request_url.include? "http://"
|
474
|
+
u = URI.parse(@request_parser.request_url)
|
475
|
+
request.update_header(:Host => u.host)
|
476
|
+
log.debug [msec, "Host set to absolut host from request_url", u.host]
|
477
|
+
else
|
478
|
+
u = URI.parse("http://" + h["Host"] + @request_parser.request_url)
|
479
|
+
end
|
480
|
+
|
481
|
+
request.add_uri(:port => u.port, :path => u.path, :query => u.query)
|
482
|
+
|
483
|
+
|
484
|
+
# try to connect request to backend
|
485
|
+
# but only try to connect if this (.last) equals (.first), true at length == 1
|
486
|
+
# according to http-protocol requests must always be handled in order.
|
487
|
+
if @requests.length == 1
|
488
|
+
dispatch_request
|
489
|
+
end
|
490
|
+
|
491
|
+
log.debug [msec, :on_header_complete, @requests.size]
|
492
|
+
end
|
493
|
+
|
494
|
+
@request_parser.on_body = proc do |chunk|
|
495
|
+
request = @requests.last
|
496
|
+
if request.chunked
|
497
|
+
# add hexadecimal chunk size
|
498
|
+
request << chunk.size.to_s(16)
|
499
|
+
request << "\r\n"
|
500
|
+
request << chunk
|
501
|
+
request << "\r\n"
|
502
|
+
else
|
503
|
+
request << chunk
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
@request_parser.on_message_complete = proc do
|
508
|
+
request = @requests.last
|
509
|
+
if request.chunked
|
510
|
+
# add closing chunk
|
511
|
+
request << "0\r\n\r\n"
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
end
|
516
|
+
|
517
|
+
# This method sends the first requests send_buffer to the backend server, if
|
518
|
+
# any backend is set and there is request data to dispatch
|
519
|
+
#
|
520
|
+
# Request header will be reconstructed here before dispatch
|
521
|
+
#
|
522
|
+
# If the backend server is not yet connected, data is already buffered to be sent when the connection gets established
|
523
|
+
#
|
524
|
+
def relay_to_server
|
525
|
+
log.debug [:relay_to_server, "@" + @signature.to_s, @backend, @requests.empty?, get_request.flushed?, @server.nil?]
|
526
|
+
if @backend && !@requests.empty? && !get_request.flushed? && !@server.nil?
|
527
|
+
# save some memory here if logger isn't set on debug
|
528
|
+
if log.debug?
|
529
|
+
data = get_request.flush
|
530
|
+
@server.send_data data
|
531
|
+
log.debug [msec, :data_to_server_send, "@" + @signature.to_s, data]
|
532
|
+
else
|
533
|
+
@server.send_data get_request.flush
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
#returns milliseconds since connection startup as string
|
539
|
+
def msec
|
540
|
+
(((Time.now.tv_sec - @start.tv_sec) * 1000) + ((Time.now.tv_usec - @start.tv_usec) / 1000.0)).to_s + "ms: "
|
541
|
+
end
|
542
|
+
|
543
|
+
# starts the timeout timer and closes connection if timeout was exceeded
|
544
|
+
def timeout_timer
|
545
|
+
timer = EventMachine::PeriodicTimer.new(1) do
|
546
|
+
if get_idle_time.nil?
|
547
|
+
timer.cancel
|
548
|
+
elsif get_idle_time > config.timeout
|
549
|
+
log.info [msec, :unbind_client, :timeout, "@" + @signature.to_s]
|
550
|
+
timer.cancel
|
551
|
+
close
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
end
|
557
|
+
end
|