一个HTTP代理

I'm writing a simple proxy in go that relays an HTTP request to one or more backend servers. Right now I'm still using only one backend server, but performance is not good and I'm sure I am doing something wrong. Probably related to how I send the HTTP request to another server: if I comment the call to send() then the server goes blazing fast, yielding more than 14 krps. While with the call to send() performance drops to less than 1 krps and drops even lower with time. This on a MacBook Pro.

The code is based on trivial code samples; I have created the client and reused it following the recommendation in the docs. Tests are done with Apache ab:

$ ab -n 10000 -c 10 -k "http://127.0.0.1:8080/test"

The backend server running on port 55455 does not change between experiments; Apache or nginx can be used. My custom web server yields more than 7 krps when measured directly, without proxy:

$ ab -n 10000 -c 10 -k "http://127.0.0.1:55455/test"

I would expect the proxied version to behave just as well as the non-proxied version, and sustain the performance over time.

The complete sample code follows.

package main

import (
        "fmt"
        "log"
        "net/http"
        )

func main() {

        tr := &http.Transport{
                DisableCompression: true,
                DisableKeepAlives: false,
        }
        client := &http.Client{Transport: tr}

        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

                send(r, client)
                fmt.Fprintf(w, "OK")
        })
        log.Fatal(http.ListenAndServe(":8080", nil))
}

func send(r *http.Request, client *http.Client) int {

        req, err := http.NewRequest("GET", "http://localhost:55455" + r.URL.Path, nil)
        if err != nil {
                log.Fatal(err)
                return 0
        }       
        resp, err := client.Do(req)
        if err != nil {
                log.Fatal(err)
                return 0
        }       
        if resp == nil {
                return 0
        }       
        return 1
}       

Eventually the code should send the request to multiple servers and process their answers, returning an int with the result. But I'm stuck at this step of just making the call.

What am I doing horribly wrong?

As the comment suggests, you should be returning (and dealing with) type error instead of ints, and to reiterate, don't use AB. The biggest thing that stands out to me is

  1. You should set the MaxIdleConnsPerHost in your Transport. This represents how many connections will persist (keep-alive) even if they have nothing to do at the moment.
  2. You have to read and close the body of the response in order for it to be returned to the pool.

...
resp, err := client.Do(req)
if err != nil {
    return err
}
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
    return err
}
defer resp.Body.Close()
...