require 'addressable/uri' require 'eventmachine' require 'http/parser' require 'em-http' require 'evma_httpserver' require 'em-synchrony' module Billy class ProxyConnection < EventMachine::Connection include EventMachine::Deferrable attr_accessor :handler attr_accessor :cache def post_init @parser = Http::Parser.new(self) end def receive_data(data) @parser << data end def on_message_begin @headers = nil @body = '' end def on_headers_complete(headers) @headers = headers end def on_body(chunk) @body << chunk end def on_message_complete if @parser.http_method == 'CONNECT' restart_with_ssl(@parser.request_url) else if defined?(@ssl) && @ssl uri = Addressable::URI.parse(@parser.request_url) @url = "https://#{@ssl}#{[uri.path, uri.query].compact.join('?')}" else @url = @parser.request_url end handle_request end end protected def restart_with_ssl(url) @ssl = url @parser = Http::Parser.new(self) send_data("HTTP/1.0 200 Connection established\r\nProxy-agent: Puffing-Billy/0.0.0\r\n\r\n") start_tls(certificate_chain(url)) end def handle_request EM.synchrony do handler.handle_request(@parser.http_method, @url, @headers, @body).tap do |response| if response.key?(:error) close_connection fail "puffing-billy: #{response[:error]}" else send_response(response) end end end end private def prepare_response_headers_for_evma_httpserver(headers) # Remove the headers below because they will be added later by evma_httpserver (EventMachine::DelegatedHttpResponse). # See https://github.com/eventmachine/evma_httpserver/blob/master/lib/evma_httpserver/response.rb headers_to_remove = [ 'transfer-encoding', 'content-length', 'content-encoding' ] headers.delete_if {|key, value| headers_to_remove.include?(key.downcase) } end def send_response(response) res = EM::DelegatedHttpResponse.new(self) res.status = response[:status] res.headers = prepare_response_headers_for_evma_httpserver(response[:headers]) res.content = response[:content] res.send_response end def certificate_chain(url) domain = url.split(':').first ca = Billy.certificate_authority.cert cert = Billy::Certificate.new(domain) chain = Billy::CertificateChain.new(domain, cert.cert, ca) { private_key_file: cert.key_file, cert_chain_file: chain.file } end end end