API is returning same result for different endpoints

I have a golang API which calls other APIs to obtain results. When its different endpoints are called by a React UI to fetch and display stuff, the API sends same result to multiple or all calls.

It does not seem like the usual database or other concurrency issue.

It seems like it just fetches result for one call and writes the same to other calls from the same client (like one user loading or refeshing their webpage).

We use net/http and Gorilla mux.

Could not find any article that talked about suck issue. Greatly appreciate any help or insight.

Thank you

Thanks

Could you post a piece of relevant code from your API?

Try your API with Postman or another client API in the different situations you mentioned or need to test…

Hi George, here is the code:

The Mux Router code:

router.HandleFunc(/api/{Entity}, functions.GetPostMortemDiag).Methods(GET, POST, PUT, DELETE)|
router.HandleFunc(/api/{entity}/{entityNumber}, functions.GetPostMortemDiag).Methods(GET, PUT)|]router.HandleFunc(/api/{entity}/{entityId}/{entity}, functions.GetPostMortemDiag).Methods(GET)

Handler Function code:
func GetPostMortemDiag(w http.ResponseWriter, r *http.Request) {

log.Println("INFO - GetPostMortemDiag(): Entering GetPostMortem Diag function")

api_base_url = os.Getenv("MYAPP_POSTMORTEM_API") + r.URL.Path

// validate request auth
retcode := CheckRequestAuth(w, r, "MYAPP")
if retcode == 0 {
	log.Println("INFO - GetPostMortemDiag(): Request auth is successful")
} else {
	log.Println("ERR - GetPostMortemDiag(): Request auth is not successful")
	return
}

// Steps to Call the backend API start now
// build the url further with the parameters from request
// Call the backend API with the built urls
query := r.URL.Query() // get the query from the request
api_url, err := url.Parse(api_base_url)
if err != nil {
	log.Println("ERR - GetPgDiag(): Could not parse the backend API base URL: ", err.Error())
	w.WriteHeader(http.StatusUnprocessableEntity)
	//not sure about the above status code
	w.Header().Set("Content-Type", "application/json")
	resp := make(map[string]string)
	resp["message"] = "Could not parse the backend API base URL, please correct the URL or contact the administrator"
	jsonResp, err := json.Marshal(resp)
	if err != nil {
		log.Println("WARN - GetPgDiag(): Error happened in JSON marshal for failed backend-api-parse response. Err: %s", err)
	}
	w.Write(jsonResp)
	return
}
api_url.RawQuery = query.Encode() // builds further the URL struct adding the query
_, err = url.Parse(api_url.String())
if err != nil {
	log.Println("ERR - GetPgDiag(): Could not parse the query to backend API base URL: ", err.Error())
	w.WriteHeader(http.StatusUnprocessableEntity)
	w.Header().Set("Content-Type", "application/json")
	resp := make(map[string]string)
	resp["message"] = "Could not parse query to the backend API base URL, please correct the query or contact the administrator"
	jsonResp, err := json.Marshal(resp)
	if err != nil {
		log.Println("WARN - GetPgDiag(): Error happened in JSON marshal for failed backend-api-query-parse response. Err: %s", err)
	}
	w.Write(jsonResp)
	return
}
log.Println("INFO - GetPostMortemDiag(): Calling API endpoint", api_url)
timeout, err = strconv.Atoi(os.Getenv("MYAPP_POSTMORTEM_API_TIMEOUT"))
if err != nil {
	log.Println("WARN - GetPostMortemDiag(): MYAPP_POSTMORTEM_API_TIMEOUT environment variable not set or erroneous, Using default value ", err)
	timeout = default_timeout
}
token = SetJwt(false, "sgfr") // false means refresh=false - that is, DO NOT force a refresh
if token == "" {
	log.Println("ERR - GetPostMortemDiag(): ForgeRock Token Validation failed: ")
	w.WriteHeader(http.StatusUnauthorized)
	w.Header().Set("Content-Type", "application/json")
	resp := make(map[string]string)
	resp["message"] = "ForgeRock Token Validation failed , Please contact the administrator"
	jsonResp, err := json.Marshal(resp)
	if err != nil {
		log.Println("WARN - GetPostMortemDiag(): Error happened in JSON marshal for Unauthorized response. Err: %s", err)
	}
	w.Write(jsonResp)
	return
}
if r.Method == "POST" || r.Method == "PUT" || r.Method == "DELETE" {
	if r.URL.Path == "/api/images-upload" {
		UploadImages(w, r)
		return
	} else {
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			err_text := fmt.Sprintf("ERR - CallPostMortemAPI(): Error reading request body: %v", err)
			log.Printf(err_text)
			http.Error(w, err_text, http.StatusBadRequest)
			return
		}
		BodyData := string(body)
		myReader = strings.NewReader(BodyData)
	}
}
log.Println("myReader", reflect.TypeOf(myReader))
if myReader != nil {
	log.Println("DEBUG - CallPostMortemAPI() : Request has a body")
	req, err = http.NewRequest(r.Method, api_url.String(), myReader)
} else {
	log.Println("DEBUG - CallPostMortemAPI() : Request does not have a body")
	req, err = http.NewRequest(r.Method, api_url.String(), nil)
}
if err != nil {
	log.Println("ERR - GetPostMortemDiag(): Error while creating request to the backend API: "+api_url.String(), err.Error())
	w.WriteHeader(http.StatusInternalServerError)
	w.Header().Set("Content-Type", "application/json")
	resp := make(map[string]string)
	resp["message"] = "Error while creating request to the backend API" + api_url.String()
	jsonResp, err := json.Marshal(resp)
	if err != nil {
		log.Println("WARN - GetPostMortemDiag(): Error happened in JSON marshal for Unauthorized response. Err: %s", err)
	}
	w.Write(jsonResp)
	return
}
req.Header.Set("Authorization", "Bearer "+token)
if os.Getenv("ENV") == "LOCAL" {
	req.Header.Set("Consumer-Key", os.Getenv("SGFR-CONSUMER-KEY-POSTMORTEM"))
} else {
	req.Header.Set("Consumer-Key", GetDataFromVault(SGFR_CONSUMER_KEY_VALUE))
}

req.Header.Set("Content-Type", "application/json")

client := &http.Client{
	Timeout: time.Second * time.Duration(timeout),
}
response, err := client.Do(req)
if err != nil {
	log.Println("ERR - CallPostMortemAPI(): API Call failed: "+api_url.String(), err.Error())
	w.WriteHeader(http.StatusInternalServerError)
	w.Header().Set("Content-Type", "application/json")
	resp := make(map[string]string)
	resp["message"] = "API Call failed: " + api_url.String()
	jsonResp, err := json.Marshal(resp)
	if err != nil {
		log.Println("WARN - CallPostMortemAPI(): Error happened in JSON marshal for Unauthorized response. Err: %s", err)
	}
	//tbd - handle error condition
	w.Write(jsonResp)
	return
}
w.WriteHeader(response.StatusCode)
defer response.Body.Close()

w.Header().Set("Content-Type", "application/json")

log.Println("INFO - GetPostMortemDiag(): Response code from URL: ", response.StatusCode)
b, err := io.ReadAll(response.Body)
// fmt.Fprint(w, string(b))
w.Write(b)

}
** React Code **
They are axios calls to different endpoints - to different endpoints served my mux, and called from one React page. The problem is that some/all of those multiple calls are showing the same response - which is the response for one of those calls.

As you see in the mux-router code, {entity} makes it easier to overload one mux router entry to serve more than one endpoint. However, even without such {entity} parameterization, if we made separate mux lines for each endpoint, and separate handler function for each such endpoint, the bahaviour has been the same.

We dont face it in Postman - maybe because the calls are not rapidly called one after the other. In React page, they are all called nearly simultaneously to get different results.

See the code snippets posted for George’s response.

router.HandleFunc(/api/{Entity}, functions.GetPostMortemDiag).Methods(GET, POST, PUT, DELETE)|
router.HandleFunc(/api/{entity}/{entityNumber}, functions.GetPostMortemDiag).Methods(GET, PUT)|]
router.HandleFunc(/api/{entity}/{entityId}/{entity}, functions.GetPostMortemDiag).Methods(GET)

At first sight, the problem comes from improper use of the router. It is not good practice to use the same handler with multiple routes having the same methods, as the results may be unexpected. Doing this a different route could be invoked than you expect. Instead, use one route for that handler and parse all parameters inside.

With this code , we are getting correct responses in local…multiple calls at the same time

However when we try to run the same calls from azure deployed web app , responses are getting mixed up

Any reason why we have this behaviour only from azure deployed web app and not locally ?

I don’t know about the Azure environment, but I think your local environment is not the same as the remote one (aka works on my computer) but even that, there is no reason why a well-written program should not work. However, my first guess is that you have some probems with the overloaded routes.

I seperated the routes also and wrote different functions for different routes , but still I am getting overlapping responses

router.HandleFunc(/api/retrieve-images, functions.GetPostMortemDiagImg).Methods(GET)
router.HandleFunc(/api/getplatformportfoliodata, functions.GetPostMortemDiagPortfolioData).Methods(GET)
router.HandleFunc(/api/fetch-postmortem-data-by-number/{number}, functions.GetPostMortemDiagFetchPostMortemDataByNumber).Methods(GET)
router.HandleFunc(/api/fetch-all-post-mortem-data, functions.GetPostMortemDiagFetchPostMortemData).Methods(GET)
router.HandleFunc(/api/fetch-paginated-post-mortem-data, functions.GetPostMortemDiagFetchPaginatedPostMortemData).Methods(GET)
router.HandleFunc(/api/teams/email/{email}, functions.GetTeamsDataByEmail).Methods(GET)
router.HandleFunc(/api/teams/{pmtID}, functions.GetTeamsDataByPostMortemId).Methods(GET)

Good advice. Adding to this: the code @Free_Bird posted isn’t valid code. And multiple named parameters with the same name in this route:

// "entity" declared twice.
// Route isn't a string (this won't compile).
// GET in Methods(GET) is also not a string and won't compile.
// Do they have a package called "functions"? Probably want to rename that.
router.HandleFunc(/api/{entity}/{entityId}/{entity}, functions.GetPostMortemDiag).Methods(GET)

I suspect another problem is: due to the ambiguous route declarations, multiple routes match and the first one is being selected. From the docs:

Routes are tested in the order they were added to the router. If two routes match, the first one wins:

r := mux.NewRouter()
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)

So, you could try putting the more specific routes first in the order and the least specific route last.

Since you are testing with Postman and everything works there, it sounds like the problem may well be in your React app.

1 Like

Inspect your Golang API code for unintentional data sharing, check proper goroutine usage and review middleware to prevent shared state issues.

Hi @Dean_Davidson

Thank you for checking the code

We found the root cause and solution of the above problem

router.HandleFunc(/api/{entity}, functions.CallPostmortemApiHandler).Methods(GET, POST, PUT, DELETE)
router.HandleFunc(/api/{entity}/{entityNumber}, functions.CallPostmortemApiHandler).Methods(GET, PUT)
router.HandleFunc(/api/{entity}/{entityId}/{entity}, functions.CallPostmortemApiHandler).Methods(GET)

This code works absolutely fine…
We were getting mixed up responses , because of global variables used…
Two or more variables were declared globally at the package level…that’s why we were getting mixed up responses…once we defined the scope of variable locally…we were getting proper responses

The above code pattern was done to avoid DRY

Thank you

2 Likes