Wednesday, February 9, 2011

Sinatra Proxy

Anyone that has developed for the web has come accross the Same Origin Policy problem.  Basically it says that for an AJAX call to be valid it must come from the same host as the page, using the same port, subdomain, and protocol.  I find there are many cases where I need to gather information from remote hosts, which leaves me two choices: proxy the request through my server, or use an iframe.

In general since it is JSON/XML data I am interested in then an iframe will not work so I have to create a proxy.  Below is what I do using Sinatra.

Required Gems
  1. Sinatra
  2. Net::HTTP
  3. CGI
  4. URI
Directory Structure
This is just the default that I use for all sinatra apps because it makes things easier.  Sinatra will automatically serve files in the public directory if a file by that name exists
  • index.html - static file that includes all other files
  • public - place where other static files will go
    • javascript
    • stylesheets
    • images
  • server.rb - the sinatra app
Server.rb


/remote/*
The * tells sinatra to blindly take everything up to the "?" and put it in params[:splat].  I then split out the first item for the server name and port.  You could also split out username/password if you want, but I rarely use HTTP Basic Auth so I never do.

I delete the key 'splat' and anything I do not want to forward so that they are not URL encoded to the remote.

urlencode
A simple function that re-encodes params that were placed in the params hash.  Sinatra does a good job of taking params that look like color[]=red&color[]=blue and turning them into {"color" => ["red","blue"]}.  Therefore I check the value's class for Array and if so I add the "[]" to the param name.

Proxy
The proxy code is a call to Net::HTTP returning the response body.  I could also have returned the headers, but in this case I do not care.



In Action
/remote/medic/this->/medic:80/this
/remote/medic:4567/this->/medic:4567/this
/remote/medic:4567/this?_dc=1->/medic:4567/this
/remote/medic/this?foo=bar->/medic:80/this?foo=bar
/remote/medic/this?foo[]=bar&foo[]=baz->/medic:80/this?foo[]=bar&foo[]=baz

2 comments:

  1. You can use Sinatra's send_file method instead of File.read; it adds the mime type and content length header and all that stuff.

    ReplyDelete
  2. This doesn't take care of forwarding the cookies from the original requestor so it would be useless for passing oauth tokens, etc. Is there a better solution?

    ReplyDelete