Very odd error, go api works with curl and bruno but not javascript fetch

I’m new to go. I’m liking go but I’m loosing my mind over this part.

Here is the relevant go code. All it does is send a tcp string command.

type CasparRequest struct {
		Command string
	}
	mux.HandleFunc("/casparconnecting", func(w http.ResponseWriter, req *http.Request) {
		enableCors(&w)

		connection, err := amcp.Dial("127.0.0.1:5250")
		if err != nil {

			// log.Fatal(err)

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

		}

		if connection == nil {

			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Connection has not been established"))
			return
		}
		defer connection.Close()
		var command CasparRequest

		err1 := json.NewDecoder(req.Body).Decode(&command)

		if err1 != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		fmt.Println("the command sent", command.Command)

		code, replyData, err := connection.Do(command.Command)

		if err != nil {
			log.Fatal(err)

			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusBadRequest)
		}
		fmt.Println("replyCode", code)
		fmt.Println("replyCode", replyData)

		replyString := fmt.Sprintf("%v", replyData)

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK) // Set your code here, after setting your headers
		w.Write([]byte(replyString))
		// defer c.Close()
	})

The following curl request works:
curl --header "Content-Type: application/json" --request POST --data '{"command":"PLAY 1-1 myfile000.mp4"}' http://localhost:8080/casparconnecting

However as soon as I do a request from the browser the server panicks


            const fetched = await fetch("http://localhost:8080/casparconnecting", {
                        headers: {
                            "Content-Type": "application/json",
                        },
                        method: "POST",
                        body: JSON.stringify({ command: "PLAY 1-1 myfile000.mp4" }),
                    });

This is the details of javascript POST request

{
  "args": {}, 
  "data": "{\"command\":\"PLAY 1-1 myfile000.mp4\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate, br, zstd", 
    "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 
    "Connection": "keep-alive", 
    "Content-Length": "36", 
    "Content-Type": "application/json", 
    "Host": "localhost:9090", 
    "Origin": "http://localhost:5173", 
    "Referer": "http://localhost:5173/", 
    "Sec-Ch-Ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"128\"", 
    "Sec-Ch-Ua-Mobile": "?0", 
    "Sec-Ch-Ua-Platform": "\"Linux\"", 
    "Sec-Fetch-Dest": "empty", 
    "Sec-Fetch-Mode": "cors", 
    "Sec-Fetch-Site": "same-site", 
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
  }, 
  "json": {
    "command": "PLAY 1-1 myfile000.mp4"
  }, 
  "method": "POST", 
  "origin": "172.17.0.1", 
  "url": "http://localhost:9090/anything"
}

This is the details of the curl request:

{
  "args": {}, 
  "data": "{\"command\":\"play 1-1 myfile000.mp4\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "36", 
    "Content-Type": "application/json", 
    "Host": "localhost:9090", 
    "User-Agent": "curl/7.81.0"
  }, 
  "json": {
    "command": "play 1-1 myfile000.mp4"
  }, 
  "method": "POST", 
  "origin": "172.17.0.1", 
  "url": "http://localhost:9090/anything"
}

why is javascript request causing an issue here. Even if I copy the request as curl from the browser it works. But then I don’t understand how is this causing an error on the backend.
This is how my go code fails

2024/09/04 12:58:22 http: panic serving [::1]:34408: runtime error: invalid memory address or nil pointer dereference
goroutine 6 [running]:
net/http.(*conn).serve.func1()
/usr/local/go/src/net/http/server.go:1898 +0xbe
panic({0x6b2360?, 0x93e4f0?})
/usr/local/go/src/runtime/panic.go:770 +0x132
main.main.func3({0x785120, 0xc0000e6620}, 0xc0000ca360)
/home/john_doe/repos/basketball-backend-go/main.go:139 +0x7f5
net/http.HandlerFunc.ServeHTTP(0xc0000bab60?, {0x785120?, 0xc0000e6620?}, 0x651dda?)
/usr/local/go/src/net/http/server.go:2166 +0x29
net/http.(*ServeMux).ServeHTTP(0x467db9?, {0x785120, 0xc0000e6620}, 0xc0000ca360)
/usr/local/go/src/net/http/server.go:2683 +0x1ad
net/http.serverHandler.ServeHTTP({0xc0000b8cf0?}, {0x785120?, 0xc0000e6620?}, 0x6?)
/usr/local/go/src/net/http/server.go:3137 +0x8e
net/http.(*conn).serve(0xc0000fe000, {0x7856b0, 0xc0000b8c00})
/usr/local/go/src/net/http/server.go:2039 +0x5e8
created by net/http.(*Server).Serve in goroutine 1
/usr/local/go/src/net/http/server.go:3285 +0x4b4

Please help. I don’t understand where it could be going wrong.

ANSWER:

It was a cors error from the browser sending preflight request with a method of options. The browser does not show that in the network tab.
Using debugger in vscode helped

I was able to fix it with following snippet:

		if req.Method == "OPTIONS" {
			w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Origin, Accept, token")
			w.WriteHeader(http.StatusOK)
			return
		} 

You might want to use a middleware library for handling CORS; it’s more complicated than meets the eye.

For instance, req.Method == "OPTIONS" is too restrictive, because it “traps” all OPTIONS requests, and not all OPTIONS requests are CORS-preflight requests; more on this topic elsewhere.

Shameless plug: GitHub - jub0bs/cors: perhaps the best CORS middleware library for Go

Wow thank you so much that is such a useful blog. I never knew! Quick look at MDN and stack overflow and I thought the options method catch was enough.

I was just trying to use only standard libraries to learn more since that’s what everyone recommends.
Thanks!

1 Like