Class: ExperellaProxy::Connection
- Inherits:
-
EventMachine::Connection
- Object
- EventMachine::Connection
- ExperellaProxy::Connection
- Includes:
- Globals
- Defined in:
- lib/experella-proxy/connection.rb
Overview
The proxies TCP Connection to the client
Responsible for parsing and buffering the clients http requests, connecting to the backend server, sending data to the backend server and returning responses to the client.
See EventMachine::Connection documentation for more information
Instance Method Summary collapse
-
#close ⇒ Object
calls EventMachine close_connection_after_writing method with 1 tick delay waits 1 tick to make sure reactor i/o does not have unnecessary loop delay.
-
#connect_backendserver(backend) ⇒ Object
Connects self to a BackendServer object.
-
#connected(name) ⇒ Object
Called by backend connections when the remote TCP connection attempt completes successfully.
-
#get_request ⇒ Request
Used for accessing the connections first request.
-
#on_connect(&blk) ⇒ Object
Used to pass an optional block to the connection which will be executed when the #connected event occurs.
-
#on_data(&blk) ⇒ String
Used to pass an optional block to the connection which will be executed when the #receive_data event occurs.
-
#on_finish(&blk) ⇒ Object
Used to pass an optional block to the connection which will be executed when the #unbind_backend event occurs.
-
#on_response(&blk) ⇒ String
Used to pass an optional block to the connection which will be executed when the #relay_from_backend event occurs.
-
#on_unbind(&blk) ⇒ Object
Used to pass an optional block to the connection which will be executed when the #unbind event occurs.
-
#peer ⇒ Object
ip, port of the connected client.
-
#post_init ⇒ Object
Called by the event loop immediately after the network connection has been established, and before resumption of the network loop.
-
#receive_data(data) ⇒ Object
Called by the EventMachine loop whenever data has been received by the network connection.
-
#relay_from_backend(name, data) ⇒ Object
Called by Backend connections.
-
#server(name, opts) ⇒ Object
Initialize a Backend connection.
-
#sock ⇒ Object
ip, port of the local server connect.
-
#ssl_handshake_completed ⇒ Object
Called by EventMachine when the SSL/TLS handshake has been completed, as a result of calling start_tls to initiate SSL/TLS on the connection.
-
#unbind ⇒ Object
Called by the EventMachine loop whenever the client connection is closed.
-
#unbind_backend(name) ⇒ Object
Called by backend connections whenever their connection is closed.
Methods included from Globals
#config, #connection_manager, #event, #logger
Instance Method Details
#close ⇒ Object
calls EventMachine close_connection_after_writing method with 1 tick delay waits 1 tick to make sure reactor i/o does not have unnecessary loop delay
90 91 92 93 94 |
# File 'lib/experella-proxy/connection.rb', line 90 def close @unbound = true EM.next_tick(method(:close_connection_after_writing)) event(:connection_close, :signature => @signature, :msec => msec) end |
#connect_backendserver(backend) ⇒ Object
Connects self to a BackendServer object
Any request mangling configured in BackendServer#mangle will be done here
Method provides additional support for BackendServer’s named “web”. Host and Port will be determined through the Request instead of BackendServer settings.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/experella-proxy/connection.rb', line 104 def connect_backendserver(backend) @backend = backend connection_manager.free_connection(self) # mangle http headers mangle # reconstruct the request header get_request.reconstruct_header # special web support for unknown hosts if @backend.name.eql?("web") xport = get_request.header[:Host].match(/:[0-9]+/) if xport.nil? || xport.to_s.empty? xport = "80" else xport = xport.to_s.gsub(/:/, "") end xhost = get_request.header[:Host].gsub(":#{xport}", "") server(@backend.name, :host => xhost, :port => xport) else server(@backend.name, :host => @backend.host, :port => @backend.port) end end |
#connected(name) ⇒ Object
Called by backend connections when the remote TCP connection attempt completes successfully.
#on_connect block will be executed here
This method triggers the #relay_to_server method
133 134 135 136 137 |
# File 'lib/experella-proxy/connection.rb', line 133 def connected(name) @on_connect.call(name) if @on_connect event(:connection_connected, :msec => msec, :signature => @signature.to_s, :name => name) relay_to_server end |
#get_request ⇒ Request
Used for accessing the connections first request
Buffered requests must not be handled before first in done
144 145 146 |
# File 'lib/experella-proxy/connection.rb', line 144 def get_request @requests.first end |
#on_connect(&blk) ⇒ Object
Used to pass an optional block to the connection which will be executed when the #connected event occurs
25 26 27 |
# File 'lib/experella-proxy/connection.rb', line 25 def on_connect(&blk) @on_connect = blk end |
#on_data(&blk) ⇒ String
Used to pass an optional block to the connection which will be executed when the #receive_data event occurs
40 41 42 |
# File 'lib/experella-proxy/connection.rb', line 40 def on_data(&blk) @on_data = blk end |
#on_finish(&blk) ⇒ Object
Used to pass an optional block to the connection which will be executed when the #unbind_backend event occurs
70 71 72 |
# File 'lib/experella-proxy/connection.rb', line 70 def on_finish(&blk) @on_finish = blk end |
#on_response(&blk) ⇒ String
Used to pass an optional block to the connection which will be executed when the #relay_from_backend event occurs
56 57 58 |
# File 'lib/experella-proxy/connection.rb', line 56 def on_response(&blk) @on_response = blk end |
#on_unbind(&blk) ⇒ Object
Used to pass an optional block to the connection which will be executed when the #unbind event occurs
84 85 86 |
# File 'lib/experella-proxy/connection.rb', line 84 def on_unbind(&blk) @on_unbind = blk end |
#peer ⇒ Object
ip, port of the connected client
210 211 212 213 214 215 |
# File 'lib/experella-proxy/connection.rb', line 210 def peer @peer ||= begin peername = get_peername peername ? Socket.unpack_sockaddr_in(peername).reverse : nil end end |
#post_init ⇒ Object
Called by the event loop immediately after the network connection has been established, and before resumption of the network loop. This method is generally not called by user code, but is called automatically by the event loop. The base-class implementation is a no-op. This is a very good place to initialize instance variables that will be used throughout the lifetime of the network connection.
This is currently used to initiate start_tls on @options enabled
231 232 233 234 235 236 |
# File 'lib/experella-proxy/connection.rb', line 231 def post_init if [:tls] event(:connection_tls_handshake_start, :msec => msec, :signature => @signature) start_tls(:private_key_file => [:private_key_file], :cert_chain_file => [:cert_chain_file], :verify_peer => false) end end |
#receive_data(data) ⇒ Object
Called by the EventMachine loop whenever data has been received by the network connection. It is never called by user code. #receive_data is called with a single parameter, a String containing the network protocol data, which may of course be binary.
Data gets passed to the specified #on_data block first Then data gets passed to the parser and the #relay_to_server method gets fired
On Http::Parser::Error a 400 Bad Request error send to the client and the Connection will be closed
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/experella-proxy/connection.rb', line 158 def receive_data(data) event(:connection_receive_data_start, :msec => msec, :data => data) data = @on_data.call(data) if @on_data begin @request_parser << data rescue Http::Parser::Error event(:connection_receive_data_parser_error, :msec => msec, :signature => @signature, :error => true) # on error unbind request_parser object, so additional data doesn't get parsed anymore # # assigning a string to the parser variable, will cause incoming data to get buffered # imho this is a better solution than adding a condition for this rare error case @request_parser = "" send_data "HTTP/1.1 400 Bad Request\r\nVia: 1.1 experella\r\nConnection: close\r\n\r\n" close end event(:connection_receive_data_stop, :msec => msec, :data => data) relay_to_server end |
#relay_from_backend(name, data) ⇒ Object
Called by Backend connections. Relays data from backend server to the client
#on_response block will be executed here
185 186 187 188 189 190 |
# File 'lib/experella-proxy/connection.rb', line 185 def relay_from_backend(name, data) event(:connection_relay_from_backend, :msec => msec, :data => data, :name => name) @got_response = true data = @on_response.call(name, data) if @on_response get_request.response << data end |
#server(name, opts) ⇒ Object
Initialize a Backend connection
Can connect to host:port server address
198 199 200 201 202 203 204 205 |
# File 'lib/experella-proxy/connection.rb', line 198 def server(name, opts) srv = EventMachine.bind_connect(opts[:bind_host], opts[:bind_port], opts[:host], opts[:port], Backend) do |c| c.name = name c.plexer = self end @server = srv end |
#sock ⇒ Object
ip, port of the local server connect
241 242 243 244 245 246 |
# File 'lib/experella-proxy/connection.rb', line 241 def sock @sock ||= begin sockname = get_sockname sockname ? Socket.unpack_sockaddr_in(sockname).reverse : nil end end |
#ssl_handshake_completed ⇒ Object
Called by EventMachine when the SSL/TLS handshake has been completed, as a result of calling start_tls to initiate SSL/TLS on the connection.
This callback exists because #post_init and connection_completed are not reliable for indicating when an SSL/TLS connection is ready to have its certificate queried for.
253 254 255 |
# File 'lib/experella-proxy/connection.rb', line 253 def ssl_handshake_completed event(:connection_tls_handshake_stop, :msec => msec, :signature => @signature) end |
#unbind ⇒ Object
Called by the EventMachine loop whenever the client connection is closed. The close can occur because the code intentionally closes it (using #close_connection and #close_connection_after_writing), because the remote peer closed the connection, or because of a network error.
This is used to clean up associations made to the connection object while it was open.
#on_unbind block will be executed here
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/experella-proxy/connection.rb', line 322 def unbind @unbound = true @on_unbind.call if @on_unbind event(:connection_unbind_client, :msec => msec, :signature => @signature) # lazy evaluated. if first is true, second would cause a nil-pointer! unless @requests.empty? || get_request.flushed? # what does this mean? # log.debug [msec, @requests.inspect] end # delete conn from queue if still queued connection_manager.free_connection(self) # reconnect backend to new connection if this has not happened already unless @backend.nil? connect_next end # terminate any unfinished backend connections unless @server.nil? @server.close_connection_after_writing end end |
#unbind_backend(name) ⇒ Object
Called by backend connections whenever their connection is closed. The close can occur because the code intentionally closes it (using #close_connection and #close_connection_after_writing), because the remote peer closed the connection, or because of a network error.
Therefor connection errors, reconnections and queues need to be handled here
#on_finish block will be executed here
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/experella-proxy/connection.rb', line 267 def unbind_backend(name) event(:connection_unbind_backend, :msec => msec, :signature => @signature, :response => @got_response) if @on_finish @on_finish.call(name) end @server = nil # if backend responded or client unbound connection (timeout probably triggers this too) if @got_response || @unbound event(:connection_unbind_backend_request_done, :msec => msec, :signature => @signature, :size => @requests.size, :keep_alive => get_request.keep_alive.to_s) unless get_request.keep_alive close event(:connection_unbind_backend_close, :msec => msec, :signature => @signature) end @requests.shift # pop first element,request is done @got_response = false # reset response flag # free backend server and connect to next conn if matching conn exists unless @backend.nil? connect_next end # check if queued requests find a matching backend unless @requests.empty? || @unbound # try to dispatch first request to backend dispatch_request end else # handle no backend response here event(:connection_unbind_backend_error, :msec => msec, :error => true, :error_code => 503) 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" unless get_request.header[:http_method].eql? "HEAD" error_page << config.error_pages[503] end send_data error_page close end end |