Proxy handling

Simple proxy

HTTP 1.1 laid out how HTTP should work through a proxy. A “GET” request should be made to a proxy. However, the URL requested should be the full URL of the destination. In addition the HTTP header should contain a “Host” field, set to the proxy. As long as the proxy is configured to pass such requests through, then that is all that needs to be done.

Go considers this to be part of the HTTP transport layer. To manage this it has a class Transport. This contains a field which can be set to a function that returns a URL for a proxy. If we have a URL as a string for the proxy, the appropriate transport object is created and then given to a client object by

  1. proxyURL, err := url.Parse(proxyString)
  2. transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
  3. client := &http.Client{Transport: transport}

The client can then continue as before.

The following program illustrates this:

  1. /* ProxyGet
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "net/http/httputil"
  9. "net/url"
  10. "os"
  11. )
  12. func main() {
  13. if len(os.Args) != 3 {
  14. fmt.Println("Usage: ", os.Args[0], "http://proxy-host:port http://host:port/page")
  15. os.Exit(1)
  16. }
  17. proxyString := os.Args[1]
  18. proxyURL, err := url.Parse(proxyString)
  19. checkError(err)
  20. rawURL := os.Args[2]
  21. url, err := url.Parse(rawURL)
  22. checkError(err)
  23. transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
  24. client := &http.Client{Transport: transport}
  25. request, err := http.NewRequest("GET", url.String(), nil)
  26. dump, _ := httputil.DumpRequest(request, false)
  27. fmt.Println(string(dump))
  28. response, err := client.Do(request)
  29. checkError(err)
  30. fmt.Println("Read ok")
  31. if response.Status != "200 OK" {
  32. fmt.Println(response.Status)
  33. os.Exit(2)
  34. }
  35. fmt.Println("Reponse ok")
  36. var buf [512]byte
  37. reader := response.Body
  38. for {
  39. n, err := reader.Read(buf[0:])
  40. if err != nil {
  41. os.Exit(0)
  42. }
  43. fmt.Print(string(buf[0:n]))
  44. }
  45. os.Exit(0)
  46. }
  47. func checkError(err error) {
  48. if err != nil {
  49. if err == io.EOF {
  50. return
  51. }
  52. fmt.Println("Fatal error ", err.Error())
  53. os.Exit(1)
  54. }
  55. }

If you have a proxy at, say, XYZ.com on port 8080, test this by

  1. go run ProxyGet.go http://XYZ.com:8080/ http://www.google.com

If you don’t have a suitable proxy to test this, then download and install the Squid proxy to your own computer.

The above program used a known proxy passed as an argument to the program. There are many ways in which proxies can be made known to applications. Most browsers have a configuration menu in which you can enter proxy information: such information is not available to a Go application. Some applications may get proxy information from an autoproxy.pac file somewhere in your network: Go does not (yet) know how to parse these JavaScript files and so cannot use them. Linux systems using Gnome have a configuration system called gconf in which proxy information can be stored: Go cannot access this. But it can find proxy information if it is set in operating system environment variables such as HTTP_PROXY or http_proxy using the function

  1. func ProxyFromEnvironment(req *Request) (*url.URL, error)

If your programs are running in such an environment you can use this function instead of having to explicitly know the proxy parameters.

Authenticating proxy

Some proxies will require authentication, by a user name and password in order to pass requests. A common scheme is “basic authentication” in which the user name and password are concatenated into a string “user:password” and then BASE64 encoded. This is then given to the proxy by the HTTP request header “Proxy-Authorisation” with the flag that it is the basic authentication

The following program illustrates this, adding the Proxy-Authentication header to the previous proxy program:

  1. /* ProxyAuthGet
  2. */
  3. package main
  4. import (
  5. "encoding/base64"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/http/httputil"
  10. "net/url"
  11. "os"
  12. )
  13. const auth = "jannewmarch:mypassword"
  14. func main() {
  15. if len(os.Args) != 3 {
  16. fmt.Println("Usage: ", os.Args[0], "http://proxy-host:port http://host:port/page")
  17. os.Exit(1)
  18. }
  19. proxy := os.Args[1]
  20. proxyURL, err := url.Parse(proxy)
  21. checkError(err)
  22. rawURL := os.Args[2]
  23. url, err := url.Parse(rawURL)
  24. checkError(err)
  25. // encode the auth
  26. basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
  27. transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
  28. client := &http.Client{Transport: transport}
  29. request, err := http.NewRequest("GET", url.String(), nil)
  30. request.Header.Add("Proxy-Authorization", basic)
  31. dump, _ := httputil.DumpRequest(request, false)
  32. fmt.Println(string(dump))
  33. // send the request
  34. response, err := client.Do(request)
  35. checkError(err)
  36. fmt.Println("Read ok")
  37. if response.Status != "200 OK" {
  38. fmt.Println(response.Status)
  39. os.Exit(2)
  40. }
  41. fmt.Println("Reponse ok")
  42. var buf [512]byte
  43. reader := response.Body
  44. for {
  45. n, err := reader.Read(buf[0:])
  46. if err != nil {
  47. os.Exit(0)
  48. }
  49. fmt.Print(string(buf[0:n]))
  50. }
  51. os.Exit(0)
  52. }
  53. func checkError(err error) {
  54. if err != nil {
  55. if err == io.EOF {
  56. return
  57. }
  58. fmt.Println("Fatal error ", err.Error())
  59. os.Exit(1)
  60. }
  61. }