As the README states, “WEBrick is an HTTP server toolkit that can be configured as an HTTPS server, a proxy server, and a virtual-host server”. I recently needed to create a small proxy, with minimal effort, in order to hide some functionality of a proof-of-concept API. WEBrick is quite powerful, and a small proxy does not require more than 20-25 lines or code.
The AbstractServletclass allows us to respond to GET
, HEAD
and OPTIONS
requests. We can use it to encapsulate our logic of receiving a request, forwarding it to the server (possibly changing it) and responding back.
Let’s write a simple proxy, that receives a request, appends a custom query param and forwards it to the API server https://api-server.com
. We will append the realm
query parameter with a hardcoded value of qa-realm
.
First, let’s create our proxy as a subclass of AbstractServlet
. We will implement the do_GET method as we care only for GET
requests. The incoming request object is stored in the request
parameter, we will manipulate this later in order to add our query parameter. In order to respond, we need to set the content_type
and body values on the response
object.
require "webrick"
class MyProxy < WEBrick::HTTPServlet::AbstractServlet
def do_GET(request, response)
response.content_type = "text/plain"
response.body = "It works!"
end
end
Next, we will add the realm parameter. We need to parse the request URL in order to make sure that we handle correctly all use cases involving any existing parameters. We will use URI for this.
require "webrick"
require "uri"
class MyProxy < WEBrick::HTTPServlet::AbstractServlet
REALM = "qa-realm"
def do_GET(request, response)
uri = forwarded_uri(request.unparsed_uri)
response.content_type = "text/plain"
response.body = "It works! New URI is #{uri}"
end
private
def forwarded_uri(unparsed_uri)
uri = URI(unparsed_uri)
params = URI.decode_www_form(uri.query || "") << ["realm", REALM]
uri.query = URI.encode_www_form(params)
uri.to_s
end
end
Right now, we manipulate the request URI, but we don’t forward anything to the intended endpoint. We will use Net::HTTP to forward the request and pass the response back. We will also use the body, and the content type we retrieve from the proxied server when we pass the response back.
require "webrick"
require "net/http"
require "uri"
class MyProxy < WEBrick::HTTPServlet::AbstractServlet
HOST = "api-server.com"
REALM = "qa-realm"
def do_GET(request, response)
uri = forwarded_uri(request.unparsed_uri)
http = Net::HTTP.new(HOST, 443)
http.use_ssl = true
resp = http.request(Net::HTTP::Get.new(uri))
body = resp.body
response.content_type = resp["content-type"]
response.body = body
end
private
def forwarded_uri(unparsed_uri)
uri = URI(unparsed_uri)
params = URI.decode_www_form(uri.query || "") << ["realm", REALM]
uri.query = URI.encode_www_form(params)
uri.to_s
end
end
In order to run our proxy, we need a few more missing pieces. First we need to create a newWEBrick::HTTPServer
server = WEBrick::HTTPServer.new(:Port => ENV["PORT"] || 8080)
Then we need to mount our proxy under an endpoint, we can use the root endpoint or any other we want.
server.mount "/", MyProxy
Finally, let’s allow stopping the server using Ctrl+C and then start the server.
trap("INT"){ server.shutdown }
server.start
If we run our proxy with ruby myproxy.rb
it will start serving using port 8080 under /.
[2021-01-24 21:36:00] INFO WEBrick 1.6.0
[2021-01-24 21:36:00] INFO ruby 2.7.1 (2020-03-31) [x86_64-darwin18]
[2021-01-24 21:36:00] INFO WEBrick::HTTPServer#start: pid=47104 port=8080
The final code is the following:
#!/usr/bin/env ruby
# frozen_string_literal: true
require "webrick"
require "net/http"
require "uri"
class MyProxy < WEBrick::HTTPServlet::AbstractServlet
HOST = "api-server.com"
REALM = "qa-realm"
def do_GET(request, response)
uri = forwarded_uri(request.unparsed_uri)
http = Net::HTTP.new(HOST, 443)
http.use_ssl = true
resp = http.request(Net::HTTP::Get.new(uri))
body = resp.body
response.content_type = resp["content-type"]
response.body = body
end
private
def forwarded_uri(unparsed_uri)
uri = URI(unparsed_uri)
params = URI.decode_www_form(uri.query || "") << ["realm", REALM]
uri.query = URI.encode_www_form(params)
uri.to_s
end
end
server = WEBrick::HTTPServer.new(:Port => ENV["PORT"] || 8080)
server.mount "/", MyProxy
trap("INT"){ server.shutdown }
server.start
Top comments (1)
how do you handle CONNECT Requests?