I`m trying to create a Golang net/http based web service, which takes a JSON input from user, and then it queries a third party web API itselves, to get data and then returns answer to client. I use http.Client{} client.Do() method to query the third party web API. When I perform a performance test (JMeter), very often I get the error: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted. I suspected the race state, but race detection shows no state of race. To my mind, posting HTTP requests too often, may cause port exhaustion over time. So, I tried to balance queries between two or three instances (hosts). However, I still get the error rather often. Is there a way to avoid this error? Is there any architecture trick, to make my service high-load ready? You can see the simplified code example below:
var upstream = []string{"1.1.1.1", "2.2.2.2", "3.3.3.3"}
var scheme = "http"
var timeout = 5
func main() {
fmt.Println("Listening on *: 8080")
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, http.StatusText(405), 405)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println("Error:", err)
http.Error(w, http.StatusText(400), 400)
return
}
var jsonEntity = make(map[string]string)
json.Unmarshal(body, &jsonEntity)
payload := bytes.NewReader(body)
url := r.URL
url.Host = upstream[0]
if len(upstream) > 1 {
url.Host = upstream[random(0, len(upstream))]
}
url.Scheme = scheme
proxyReq, err := http.NewRequest(http.MethodPost, url.String(), payload)
if err != nil {
fmt.Println("Error:", err)
http.Error(w, http.StatusText(400), 400)
return
}
client := http.Client{Timeout: time.Duration(timeout) * time.Second}
response, err := client.Do(proxyReq) // <- IT FAILS HERE!!!
if err != nil {
fmt.Println("Error:", err)
http.Error(w, http.StatusText(500), 500)
sendWithCheck(w, "%s", []byte(`{"answerParam":"error"}`))
return
}
defer func() {
if err := response.Body.Close(); err != nil {
log.Println("Error:", err)
}
}()
res, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Println("Error:", err)
http.Error(w, http.StatusText(500), 500)
sendWithCheck(w, "%s", []byte(`{"answerParam":"error"}`))
return
}
if response.StatusCode == 200 {
sendWithCheck(w, "%s", res)
return
}
w.WriteHeader(response.StatusCode)
return
}