You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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请求写法存在问题,自行排查未解决,求助解决方案。


问题原因

三个核心问题导致带请求头时请求异常:

  1. 未处理CORS预检请求:跨域场景下,只要请求携带Authorization这类自定义头,浏览器会先发送OPTIONS方法的预检请求,校验后端是否允许跨域、是否放行对应请求头和方法。当前后端代码没有处理OPTIONS请求,预检不通过时浏览器会拦截正式POST请求,后端收到的往往是没有请求体的OPTIONS请求,自然读不到参数。
  2. 跨域配置不符合规范:当后端设置了Access-Control-Allow-Credentials: true时,Access-Control-Allow-Headers不能用*通配符,必须明确列出允许的请求头,否则浏览器会判定跨域不合法。
  3. 前端请求缺少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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 21:27:22