Class: Purr::Server
- Inherits:
-
Object
- Object
- Purr::Server
- Defined in:
- lib/purr/server.rb
Overview
This class implements a Rack-based server with socket hijacking and proxying to a remote TCP endpoint.
The remote TCP endpoint selection is implemented by passing a block to the class instantiation. If any kind of error happens during the hijacking process a 404 error is returned to the requester.
Instance Method Summary collapse
-
#call(env) ⇒ Object
Method required by the Rack API.
-
#initialize {|env| ... } ⇒ Server
constructor
A new instance of Server.
Constructor Details
#initialize {|env| ... } ⇒ Server
Returns a new instance of Server.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/purr/server.rb', line 15 def initialize(&block) raise ArgumentError, 'The method requires a block with a single argument' unless block && block.arity == 1 @remote = block @proxy = SurroGate.new @transmitter = Thread.new do loop do @proxy.select(1000) @proxy.each_ready do |left, right| begin right.write_nonblock(left.read_nonblock(4096)) rescue => ex # FIXME: env is not available here, logging is probably bad in this way # logger(env, :info, "Connection #{left} <-> #{right} closed due to #{ex}") cleanup(left, right) end end end end @transmitter.abort_on_exception = true end |
Instance Method Details
#call(env) ⇒ Object
Method required by the Rack API
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/purr/server.rb', line 43 def call(env) upgrade = parse_headers(env) # Return with a 404 error if the upgrade header is not present return not_found unless %i(websocket purr).include?(upgrade) host, port = @remote.call(env) # Return with a 404 error if no host:port pair was determined if host.nil? || port.nil? logger(env, :error, "No matching endpoint found for request incoming from #{env['REMOTE_ADDR']}") return not_found end # Hijack the HTTP socket from the Rack middleware http = env['rack.hijack'].call # Write a proper HTTP response http.write(http_response(upgrade)) # Open the remote TCP socket sock = TCPSocket.new(host, port) # Start proxying @proxy.push(http, sock) logger(env, :info, "Redirecting incoming request from #{env['REMOTE_ADDR']} to [#{host}]:#{port}") # Rack requires this line below return [200, {}, []] rescue => ex logger(env, :error, "#{ex.class} happened for #{env['REMOTE_ADDR']} trying to access #{host}:#{port}") cleanup(http, sock) return not_found # Return with a 404 error end |