Golang API遇到奇怪错误:curl和Bruno测试正常但JavaScript fetch无法使用

Golang API遇到奇怪错误:curl和Bruno测试正常但JavaScript fetch无法使用 我是Go语言的新手。我很喜欢Go,但这个问题让我快疯了。

以下是相关的Go代码。它的作用仅仅是发送一个TCP字符串命令。

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()
	})

以下的curl请求可以正常工作: curl --header "Content-Type: application/json" --request POST --data '{"command":"PLAY 1-1 myfile000.mp4"}' http://localhost:8080/casparconnecting

然而,一旦我从浏览器发起请求,服务器就会发生恐慌(panic)。

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

这是JavaScript POST请求的详细信息:

{
  "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"
}

这是curl请求的详细信息:

{
  "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"
}

为什么JavaScript请求会在这里导致问题?即使我从浏览器复制请求作为curl命令,它也能工作。但我不明白这怎么会导致后端出错。 以下是我的Go代码失败的方式:

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

请帮帮我。我不明白问题可能出在哪里。


更多关于Golang API遇到奇怪错误:curl和Bruno测试正常但JavaScript fetch无法使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

哇,非常感谢,这篇博客真是太有用了。我从来都不知道!快速浏览了一下MDN和Stack Overflow,我还以为options方法的catch就足够了。

我本来只是想只用标准库来学习更多东西,因为大家都这么推荐。 谢谢!

更多关于Golang API遇到奇怪错误:curl和Bruno测试正常但JavaScript fetch无法使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


答案:

这是一个由浏览器发送带有 OPTIONS 方法的预检请求引起的 CORS 错误。浏览器在网络标签页中不会显示此请求。 使用 VSCode 中的调试器解决了问题。

我通过以下代码片段修复了它:

		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
		}

你可能需要使用一个中间件库来处理CORS;这比看起来要复杂得多。

例如,req.Method == "OPTIONS" 的条件过于严格,因为它会“捕获”所有 OPTIONS 请求,但并非所有 OPTIONS 请求都是 CORS 预检请求;关于此主题的更多信息可在此处找到。

顺便自荐一下:GitHub - jub0bs/cors: 可能是 Go 语言中最好的 CORS 中间件库

问题出在你的错误处理逻辑上。当 json.NewDecoder(req.Body).Decode(&command) 失败时,你使用了错误的错误变量。

这是有问题的代码行:

if err1 != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)  // 这里使用了 err 而不是 err1
    return
}

当 JavaScript 的 fetch 请求发送时,可能由于 CORS 预检请求或其他原因,导致 JSON 解码失败,这时 err1 不为 nil,但你却尝试使用 err 变量(这是之前连接错误检查的变量),此时 err 为 nil,所以 err.Error() 会引发空指针解引用恐慌。

修复方案:

type CasparRequest struct {
    Command string
}

mux.HandleFunc("/casparconnecting", func(w http.ResponseWriter, req *http.Request) {
    enableCors(&w)
    
    // 处理 CORS 预检请求
    if req.Method == "OPTIONS" {
        w.WriteHeader(http.StatusOK)
        return
    }
    
    var command CasparRequest
    
    // 正确使用 err1 变量
    err := json.NewDecoder(req.Body).Decode(&command)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)  // 使用正确的错误变量
        return
    }
    
    fmt.Println("the command sent", command.Command)
    
    connection, err := amcp.Dial("127.0.0.1:5250")
    if err != nil {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte(fmt.Sprintf(`{"error":"%v"}`, err)))
        return
    }
    defer connection.Close()
    
    code, replyData, err := connection.Do(command.Command)
    if err != nil {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte(fmt.Sprintf(`{"error":"%v"}`, err)))
        return
    }
    
    fmt.Println("replyCode", code)
    fmt.Println("replyData", replyData)
    
    replyString := fmt.Sprintf("%v", replyData)
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(replyString))
})

同时确保你的 enableCors 函数正确处理了 CORS 头部:

func enableCors(w *http.ResponseWriter) {
    (*w).Header().Set("Access-Control-Allow-Origin", "*")
    (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    (*w).Header().Set("Access-Control-Allow-Headers", "Content-Type")
}

这样修改后,当 JSON 解码失败时,会正确返回错误信息而不是引发恐慌。

回到顶部