I have a simple golang rest application that answer to an echo request like this:
// Return echo message
func echoHandler(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
message := params["message"]
fmt.Fprintf(w, "%s", message)
}
func main() {
// Config data
port := ":9596"
router := mux.NewRouter() //.StrictSlash(true)
router.HandleFunc("/echo/{message}", echoHandler).Methods("GET")
srv := &http.Server{
Addr: port,
// Good practice to set timeouts to avoid Slowloris attacks.
// Using just the read parameter due to this article
// https://stackoverflow.com/questions/29334407/creating-an-idle-timeout-in-go
//WriteTimeout: time.Second * 60,
ReadTimeout: time.Second * 15,
////: time.Second * 120,
//Handler: router,
Handler: router,
}
// Run our server in a goroutine so that it doesn't block.
go func() {
log.Println("Running server....")
//log.Fatal(http.ListenAndServe(port, router))
//log.Println(s.ListenAndServe())
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
}()
c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
// Block until we receive our signal.
<-c
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
fmt.Println("
")
log.Println("shutting down")
log.Println("Goddbye!....")
os.Exit(0)
}
This program is called by another service through a GET request:
// Return default message for root routing
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}
// Handle iterative path and calls iterative calculation service
func echoHandler(calledServiceURL string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
url := calledServiceURL + "/echo/" + params["message"]
tr := &http.Transport{
//MaxIdleConns: 500,
//MaxIdleConnsPerHost: 500,
}
netClient := &http.Client{Transport: tr}
req, reqErr := http.NewRequest("GET", url, nil)
if reqErr != nil {
log.Fatal("Error en response: ", reqErr)
fmt.Fprintf(w, "Error en response: %s", reqErr)
return
}
//req.Header.Set("Connection", "close")
//req.Header.Set("Connection", "Keep-Alive")
//resp, err := netClient.Get(url)
resp, err := netClient.Do(req)
if err != nil {
log.Fatal("Error en response: ", err)
fmt.Fprintf(w, "Error en response: %s", err)
return
}
respData, errResp := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if errResp != nil {
log.Fatal("Error en RespData", errResp)
fmt.Fprintf(w, "Error en respData: %s", err)
return
}
fmt.Fprintf(w, "%s", respData)
}
}
// Main function
func main() {
// Set default values
port := ":9296"
calledServiceURL := "http://localhost:9596"
router := mux.NewRouter() //.StrictSlash(true)
router.HandleFunc("/", Index).Methods("GET")
router.HandleFunc("/echo/{message}", echoHandler(calledServiceURL)).Methods("GET")
srv := &http.Server{
Addr: port,
// Good practice to set timeouts to avoid Slowloris attacks.
// Using just the read parameter due to this article
// https://stackoverflow.com/questions/29334407/creating-an-idle-timeout-in-go
//WriteTimeout: time.Second * 60,
ReadTimeout: time.Second * 15,
//IdleTimeout: time.Second * 120,
//Handler: router, // Pass our instance of gorilla/mux in.
Handler: router,
}
// Run our server in a goroutine so that it doesn't block.
go func() {
log.Println("Running server....")
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
}()
c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
// Block until we receive our signal.
<-c
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
fmt.Println("
")
log.Println("shutting down")
log.Println("Goddbye!....")
os.Exit(0)
}
I have been having problems of connections closed on load when tested with ab tool for 500 concurrent connections with 50000 requests on my mac that I solved by sending a header close connection in the GET request and now the test is successful on my mac. However, when I tried to run the test in a windows 10 box I am again receiving messages like this:
2019/03/08 20:47:47 Error en response: Get http://localhost:9596/echo/javier: dial tcp [::1]:9596: bind: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
After googling for a while looking for a windows alternative to ulimit in order to change resource limits I have found messages like this:
I don't believe that current Windows O/S have a limit on the total number of file descriptors, but the MS runtime library (msvcrt.dll) has a per process limit of 2048, albeit as far as I know that's not enforced by the O/S.
It can allegedly be increased only by building your own version of the MS runtime library from source.
Then I am curious because I have the same kind of service made with Java and NodeJS and both can run the test successfuly in the same machines where my go app fails because the errors mentioned above.
So now my questions/doubts are:
what are Java and NodeJS doing different from go http server to be able to pass my test without reach OS limits?
What can I do to get my go services to be successful in any system as Java and NodeJS did?
Finally, is there any way to let my go services know the OS limits to prevent it to reach them and fail the tests?
This is issue is driving me crazy and even when it allowed me to learn a lot about http protocol is getting more dark everyday as I could not find a solution and I will love to have my app developed in go rather than in Java because it seems to be far better when runs well.
Thanks in advance J