Skip to content

Instantly share code, notes, and snippets.

@rsutphin
Created November 19, 2009 03:04
Show Gist options
  • Save rsutphin/238518 to your computer and use it in GitHub Desktop.
Save rsutphin/238518 to your computer and use it in GitHub Desktop.
require 'socket'
require 'webrick/httprequest'
require 'stringio'
# Super-primitive transparent, recording, single-threaded HTTP Proxy
class TransparentHttpProxy
attr_reader :port
def initialize(port, recorder)
@port = port
@recorder = recorder
end
def start
proxy_socket = TCPServer.open('0.0.0.0', @port)
$stderr.puts "#{self.class.name} started on #{@port}"
loop {
cycle = ProxyCycle.new(proxy_socket.accept).start
@recorder.call(cycle.request_text, cycle.response_text)
}
end
class ProxyCycle
attr_accessor :request_text, :response_text
def initialize(client)
@client = client
end
def start
read_request
propagate_request
propagate_response
self
end
def request
@request ||= begin
r = WEBrick::HTTPRequest.new({})
r.parse(StringIO.new(request_text))
r
end
end
private
def debug(msg=nil)
if ENV['VERBOSE']
if block_given?
$stderr.puts yield
else
$stderr.puts msg
end
end
end
def log_lines(heading, http_message)
heading + http_message.gsub(/\r\n/, "\r\n#{heading}")
end
def read_request
debug "read_request"
@request_text = ""
until @request_text =~ /\r\n\r\n/ || (line = @client.gets).nil?
@request_text += line
end
read_entity_if_necessary
debug { log_lines("read_request: ", request_text) }
end
def read_entity_if_necessary
if @request_text =~ /Transfer-Encoding:/
msg = "Lucky you: you get to implement Transfer-Encoding in #{__FILE__}!"
$stderr.puts '*' * msg.length
$stderr.puts msg
$stderr.puts "This request will fail."
$stderr.puts '*' * msg.length
# If you are the lucky winner, see section 4.4 of RFC 2616.
elsif @request_text =~ /Content-Length:\s+(\d+)/
content_length = $1.to_i
debug { "Attempting to read #{content_length} bytes" }
@request_text += @client.read(content_length)
end
end
def propagate_request
debug { "propagate_request to #{request.host}:#{request.port}" }
http = TCPSocket.open(request.host, request.port)
debug { log_lines("corrected_request_text: ", corrected_request_text) }
http << corrected_request_text
@response_text = ""
while line = http.gets
debug { "recvd: #{line}" }
@response_text += line
end
http.close
end
def propagate_response
debug "propagate_response"
debug { log_lines("response_text: ", response_text) }
@client << response_text
@client.close
end
# Remove & replace Connection headers, if any
def corrected_request_text
request_text.
gsub(/^Proxy-Connection:.*\r\n/, ''). # don't care about current value -- always close
gsub(/^Connection:.*\r\n/, ''). # don't care about current value -- always close
sub(/\r\n\r\n/, "\r\nConnection: close\r\n\r\n") # add Connection: close as last header
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment