experella-proxy 0.0.6
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.
- 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
|