Skip to content

Instantly share code, notes, and snippets.

@torsten
Last active March 12, 2023 21:44
Show Gist options
  • Save torsten/74107 to your computer and use it in GitHub Desktop.
Save torsten/74107 to your computer and use it in GitHub Desktop.
A quick HTTP proxy server in Ruby.
#!/usr/bin/env ruby
# A quick and dirty implementation of an HTTP proxy server in Ruby
# because I did not want to install anything.
#
# Copyright (C) 2009-2014 Torsten Becker <torsten.becker@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require 'socket'
require 'uri'
class Proxy
def run port
begin
# Start our server to handle connections (will raise things on errors)
@socket = TCPServer.new port
# Handle every request in another thread
loop do
s = @socket.accept
Thread.new s, &method(:handle_request)
end
# CTRL-C
rescue Interrupt
puts 'Got Interrupt..'
# Ensure that we release the socket on errors
ensure
if @socket
@socket.close
puts 'Socked closed..'
end
puts 'Quitting.'
end
end
def handle_request to_client
request_line = to_client.readline
verb = request_line[/^\w+/]
url = request_line[/^\w+\s+(\S+)/, 1]
version = request_line[/HTTP\/(1\.\d)\s*$/, 1]
uri = URI::parse url
# Show what got requested
puts((" %4s "%verb) + url)
to_server = TCPSocket.new(uri.host, (uri.port.nil? ? 80 : uri.port))
to_server.write("#{verb} #{uri.path}?#{uri.query} HTTP/#{version}\r\n")
content_len = 0
loop do
line = to_client.readline
if line =~ /^Content-Length:\s+(\d+)\s*$/
content_len = $1.to_i
end
# Strip proxy headers
if line =~ /^proxy/i
next
elsif line.strip.empty?
to_server.write("Connection: close\r\n\r\n")
if content_len >= 0
to_server.write(to_client.read(content_len))
end
break
else
to_server.write(line)
end
end
buff = ""
loop do
to_server.read(4048, buff)
to_client.write(buff)
break if buff.size < 4048
end
# Close the sockets
to_client.close
to_server.close
end
end
# Get parameters and start the server
if ARGV.empty?
port = 8008
elsif ARGV.size == 1
port = ARGV[0].to_i
else
puts 'Usage: proxy.rb [port]'
exit 1
end
Proxy.new.run port
@randomcamel
Copy link

I don't see a license: is it okay if I use this code? I want to adapt it into a custom no-dependencies proxy for the Chrome Postman app.

Copy link

ghost commented Mar 27, 2014

Would love to see a license as well. :-)

@torsten
Copy link
Author

torsten commented Jun 5, 2014

@randomcamel @dg-vrnetze I added MIT license, I hope that works for you guys!

@maxmeyer
Copy link

Perfect. Thanks a lot.

@evianzhow
Copy link

Hi, I have a question on Line 82. I tried to comment out this line and found the server stop proxy. Why should we send an Connection: Close line to the server while neglecting the client's origin Connection field? I don't understand it, can you explain?

Newbie in network, not native English speaker :)

@emad-elsaid
Copy link

i tried to fire it up and put it as my nexus7 proxy but every time i try to load a website in browser it shows a white screen, when i logged all data in console it showed the pages are received but apparently it isn't sent to the machine or send in the wrong way.

@MarioRuiz
Copy link

it works really well... just one problem, huge for me, cannot use it for ssl content (https)

@gotoken
Copy link

gotoken commented Mar 5, 2018

A http (and https) proxy server comes bundled with Ruby
ruby -r webrick/httpproxy -e 'WEBrick::HTTPProxyServer.new(Port: 8080).start'

API reference:
http://ruby-doc.org/stdlib-2.5.0/libdoc/webrick/rdoc/WEBrick/HTTPProxyServer.html

Hope this helps,

@speckins
Copy link

The loop at line 95 should check for a nil return value from #read, otherwise it may not work correctly for chunked responses.

while to_server.read(4048, buff)
  to_client.write(buff)
end

"If length is a positive integer, read tries to read length bytes without any conversion (binary mode). It returns nil if an EOF is encountered before anything can be read."

https://ruby-doc.org/core-2.7.1/IO.html#method-i-read

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment