Skip to content

Instantly share code, notes, and snippets.

@skateman
Created December 15, 2018 19:28
Show Gist options
  • Save skateman/a4b8fa9192e96b432f7a4c476f617ffe to your computer and use it in GitHub Desktop.
Save skateman/a4b8fa9192e96b432f7a4c476f617ffe to your computer and use it in GitHub Desktop.
Cockpit reverse-proxy written in Ruby
require 'surro-gate'
require 'websocket/driver'
require 'rack/reverse_proxy'
require 'permessage_deflate'
class WSRack
attr_reader :env, :url
def initialize(env, sock)
@env = env
@sock = sock
@url = "ws://#{env['HTTP_HOST']}#{env['REQUEST_URI']}"
@driver = WebSocket::Driver.rack(self, :protocols => ['cockpit1'])
@driver.add_extension(PermessageDeflate)
@driver.start
end
def fetch(length)
@driver.on(:message) { |msg| yield(msg.data) } if @driver.listeners(:message).length.zero?
data = @sock.read_nonblock(length)
@driver.parse(data)
end
def issue(data)
@driver.text(data)
end
def write(data)
@sock.write_nonblock(data)
end
end
class WSClient
attr_reader :url
def initialize(env, sock)
@sock = sock
@url = "ws://localhost:9090#{env['REQUEST_URI']}"
@driver = WebSocket::Driver.client(self, :protocols => ['cockpit1'])
@driver.add_extension(PermessageDeflate)
@driver.set_header('Origin', 'http://localhost:9090')
@driver.set_header('Cookie', env['HTTP_COOKIE'])
@driver.start
end
def fetch(length)
@driver.on(:message) { |msg| yield(msg.data) } if @driver.listeners(:message).length.zero?
data = @sock.read_nonblock(length)
@driver.parse(data)
end
def issue(data)
@driver.text(data)
end
def write(data)
@sock.write_nonblock(data)
end
end
class Server
def initialize
@rp = Rack::ReverseProxy.new do
reverse_proxy '/', 'http://localhost:9090'
end
@sg = SurroGate.new
@adapters = {}
Thread.new do
loop do
@sg.select(0.1)
@sg.each_ready do |left, right|
@adapters[left].fetch(4096) { |data| @adapters[right].issue(data) }
end
end
end
end
def call(env)
if WebSocket::Driver.websocket?(env)
rsock = env['rack.hijack'].call
csock = TCPSocket.new('localhost', 9090)
@adapters[rsock] = WSRack.new(env, rsock)
@adapters[csock] = WSClient.new(env, csock)
@sg.push(rsock, csock)
[-1, {}, []]
else
@rp.call(env)
end
end
end
run Server.new
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment