Sunday, July 14, 2013

Go Proxy

For a personal project I'm currently doing I needed to write a proxy server for some audio I had hosted on my server.

Authentication is needed to access the audio on the server. But for a variety of reasons I wanted to grant access to some of the audio without exposing the credentials I use to authenticate to the server. So I figured if I wrote a little Internet facing server to process the requests for audio I could then relay them to my audio server with the appropriate credentials added.

As I am exploring Go (golang.org), I decided I would try my hand at writing what I needed using that. It took some trial, error and exploring the documentation. (If I had done it the other way around, there would have been way less error in the trials, but oh well...)

I wanted to make the audio available for streaming. So I was already dreading having to implement some sort of buffering and what not. But I was pleasantly surprised. Turns out that the part that does the heavy lifting is really quite straight forward thanks to the Go's excellent standard library of packages. All it takes is the code below:
func streamUrl(w http.ResponseWriter, url string) {
 response, _ := http.Get(url)
 defer response.Body.Close()
 reader := io.TeeReader(response.Body, w)
 _, err := ioutil.ReadAll(reader)
 if err != nil {
  log.Println("Error: ", err.Error())
 }
}
This function is passed an http.ResponseWriter stuct and a url. The http.Get() function is invoked which returns a pointer to an http.Response struct the Body of which implements the io.Reader interface. The magic happens thanks to the io.TeeReader function, which takes an io.Reader and an io.Writer, and returns an io.Reader. The documentation states:
TeeReader returns a Reader that writes to w what it reads from r. All reads from r performed through it are matched with corresponding writes to w. There is no internal buffering - the write must complete before the read completes. Any error encountered while writing is reported as a read error. (http://golang.org/pkg/io/#TeeReader)
Which is exactly what I was looking for!

Edit: Thanks to +Michael Gebetsroither for pointing out that io.Copy does the job just as well, while providing cleaner code too!
func streamUrl(w http.ResponseWriter, url string) {
 response, _ := http.Get(url)
 defer response.Body.Close()
 _, err := io.Copy(w, response.Body)
 if err != nil {
  log.Println("Error: ", err.Error())
 }
} 

No comments:

Post a Comment