React向Golang API发送带请求头POST请求时后端无法读取请求体
实现目标
我正在学习Golang并开发Todo待办应用,需要实现React前端向Golang编写的API发送POST请求,由后端读取请求参数完成待办事项创建。
现有代码
- Go后端代码
package todos import ( "database/sql" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" "strings" "time" "todo-app/auth" "github.com/joho/godotenv" ) type Todo struct { // UserID int `json:"userid"` Todo string `json:"todo"` CreatedAt time.Time `json:"createdat"` UpdatedAt time.Time `json:"updatedat"` } type TodoBody struct { Todo string `json:"todo"` } func CreateTodo(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "*") w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000") w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Access-Control-Allow-Headers", "*") e := godotenv.Load() if e != nil { log.Fatal(e) } dbConnectionInfo := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/go_todo", os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD")) db, err := sql.Open("mysql", dbConnectionInfo) if err != nil { log.Fatal(err) } defer db.Close() tokenString := r.Header.Get("Authorization") tokenString = strings.TrimPrefix(tokenString, "Bearer ") log.Printf("request token=%s\n", tokenString) body, err := ioutil.ReadAll(r.Body) if err != nil { log.Fatal(err) } log.Printf("request body=%s\n", body) var data TodoBody if err := json.Unmarshal(body, &data); err != nil { log.Println(err) } // userId := 12 todo := data.Todo todoData := Todo{todo, time.Now(), time.Now()} _, err2 := auth.TokenVerify(tokenString) if err2 != nil { log.Fatal(err) } else { stmt, err := db.Prepare("INSERT INTO todos (Todo,CreatedAt,UpdatedAt) VALUES(?,?,?)") if err != nil { log.Fatal(err) } _, err = stmt.Exec(todoData.Todo, todoData.CreatedAt, todoData.UpdatedAt) if err != nil { log.Fatal(err) } json.NewEncoder(w).Encode(todoData) } }
- React前端组件代码
import { useState } from "react"; import { useCreateTodo } from "../../hooks/useCreateTodo"; import { BaseButton } from "../atoms/baseButton"; import { TextArea } from "../atoms/textArea"; export const AddTodo = () => { const [text, setText] = useState(""); const CreateTodo = useCreateTodo(); const token = "Bearer " + sessionStorage.getItem("token"); const onClickCreate = () => { CreateTodo(token, text); }; return ( <div> <TextArea onChange={(e: any) => setText(e.target.value)} defaultValue="" /> <BaseButton text="タスクを追加" onClick={onClickCreate} /> </div> ); };
- axios POST请求逻辑
import axios from "axios"; export const useCreateTodo = () => { const CreateTodo = (token: string, todo: any) => { const URL = "http://127.0.0.1:8080/createtodo"; const data = { todo: todo }; console.log(data); axios .post(URL, JSON.stringify(data), { headers: { Authorization: token } }) .then((res) => console.log(res)) .catch((err) => console.log(err)); }; return CreateTodo; };
遇到的问题
POST请求不携带请求头时,Go后端通过ioutil.ReadAll(r.Body)可正常读取到正确的请求体内容;但添加Authorization请求头后,后端无法获取请求体值。使用Advanced REST client携带相同请求头调用该API可正常处理,怀疑是前端POST请求写法存在问题,自行排查未解决,求助解决方案。
问题原因
三个核心问题导致带请求头时请求异常:
- 未处理CORS预检请求:跨域场景下,只要请求携带
Authorization这类自定义头,浏览器会先发送OPTIONS方法的预检请求,校验后端是否允许跨域、是否放行对应请求头和方法。当前后端代码没有处理OPTIONS请求,预检不通过时浏览器会拦截正式POST请求,后端收到的往往是没有请求体的OPTIONS请求,自然读不到参数。 - 跨域配置不符合规范:当后端设置了
Access-Control-Allow-Credentials: true时,Access-Control-Allow-Headers不能用*通配符,必须明确列出允许的请求头,否则浏览器会判定跨域不合法。 - 前端请求缺少Content-Type声明:手动调用
JSON.stringify处理请求体时,没有同步设置Content-Type: application/json请求头,后端无法正确识别请求体格式。
修复步骤
1. 调整Go后端CORS逻辑
将CORS配置放在函数最开头,单独处理OPTIONS预检请求,修正跨域头配置,同时修复代码里的错误日志引用问题:
func CreateTodo(w http.ResponseWriter, r *http.Request) { // 统一设置CORS头,所有请求类型都生效 w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000") w.Header().Set("Access-Control-Allow-Credentials", "true") // 携带凭证时明确列出允许的请求头,不能用* w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") w.Header().Set("Content-Type", "application/json") // 预检请求直接返回200,不执行业务逻辑 if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } // 原有业务逻辑保留,修正错误引用 e := godotenv.Load() if e != nil { log.Fatal(e) } dbConnectionInfo := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/go_todo", os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD")) db, err := sql.Open("mysql", dbConnectionInfo) if err != nil { log.Fatal(err) } defer db.Close() tokenString := r.Header.Get("Authorization") tokenString = strings.TrimPrefix(tokenString, "Bearer ") log.Printf("request token=%s\n", tokenString) body, err := ioutil.ReadAll(r.Body) if err != nil { log.Fatal(err) } log.Printf("request body=%s\n", body) var data TodoBody if err := json.Unmarshal(body, &data); err != nil { log.Println(err) } todoData := Todo{data.Todo, time.Now(), time.Now()} _, err2 := auth.TokenVerify(tokenString) if err2 != nil { // 原代码此处错误引用了读body的err,改为token校验的err2 log.Fatal(err2) } stmt, err := db.Prepare("INSERT INTO todos (Todo,CreatedAt,UpdatedAt) VALUES(?,?,?)") if err != nil { log.Fatal(err) } _, err = stmt.Exec(todoData.Todo, todoData.CreatedAt, todoData.UpdatedAt) if err != nil { log.Fatal(err) } json.NewEncoder(w).Encode(todoData) }
2. 修正前端axios请求写法
推荐直接传JS对象,axios会自动处理JSON转换和Content-Type头,不需要手动序列化:
import axios from "axios"; export const useCreateTodo = () => { const CreateTodo = (token: string, todo: any) => { const URL = "http://127.0.0.1:8080/createtodo"; const data = { todo: todo }; axios .post(URL, data, { headers: { Authorization: token } }) .then((res) => console.log(res)) .catch((err) => console.log(err)); }; return CreateTodo; };
如果需要手动序列化请求体,必须手动添加Content-Type头:
axios.post(URL, JSON.stringify(data), { headers: { Authorization: token, "Content-Type": "application/json" } })
内容的提问来源于stack exchange,提问作者hiroto22
相关产品推荐
相关产品推荐

