返回

解决JS Fetch API向FastAPI发送POST请求的CORS和400/422错误

python

使用 JavaScript Fetch API 向 FastAPI 应用发送 POST 请求

在开发过程中,常常需要在前端使用 JavaScript 代码与后端 FastAPI 应用进行交互。一个常见的情况是,你需要发送 POST 请求传递数据给 FastAPI 接口。这期间可能会遇到一些问题,特别是跨域资源共享 (CORS) 和请求数据格式错误等问题。本文将深入探讨这些问题的常见原因和相应的解决方案。

问题分析

从来看,报错信息显示了两个问题:

  • 400 Bad Request: 表示服务器理解请求,但是由于客户端的原因,服务器不会继续处理它
  • 422 Unprocessable Entity: 表示服务器能够理解客户端的请求,但是请求参数类型有误,导致无法处理

问题根源在于以下几点:

  1. CORS 问题 : 浏览器的安全机制会限制跨域请求,如果你的 HTML 页面和 FastAPI 应用不在同一个域,就需要配置 CORS,否则请求可能会被浏览器拦截。你的 FastAPI 代码里通过设置origins=["*"]的方式来允许所有的域,看起来像是没有问题的。但它有可能仍然没有正常生效。
  2. 请求格式错误 :FastAPI 需要请求体以 JSON 格式发送,并且数据结构需要和后端声明的pydantic模型匹配,前端代码有可能请求体不是JSON或者数据格式不正确。
  3. Preflight Request (OPTIONS): CORS 请求首先会发起一个 OPTIONS 请求,询问服务端是否允许接下来的实际请求, 如果服务端没有处理,则会报400 错误。

解决方案

接下来,我们将分步介绍如何解决这些问题。

方案一:配置正确的 CORS 中间件

尽管你的代码设置 origins=["*"]允许所有域,但这还不足以确保所有的跨域场景都能工作。确保中间件被正确地添加。 某些情况下,浏览器仍会发送OPTIONS预检请求, 如果FastAPI没有正确的处理, 会报400错误。建议直接把FastAPI应用服务器设成可被所有的来源访问的服务器,代码如下。同时建议指定方法(allow_methods)以及headers(allow_headers),可以有效防止后续出现的一些安全问题:

FastAPI 代码:

from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 允许所有来源
    allow_credentials=True,
    allow_methods=["*"], # 允许所有请求方法
    allow_headers=["*"], # 允许所有请求头
)

# 其他代码保持不变

修改之后, 需要重启FastAPI 服务器使改动生效。这个改动会放宽服务器对跨域的限制。

方案二:发送正确的 POST 请求

请使用 Fetch API 或者 axios 来发送 POST 请求。并且确保请求体的格式是 JSON ,并设置 Content-Typeapplication/json。使用jquery的话注意观察版本号,老版本可能对ajax post的处理有所区别。

HTML 代码 (使用 fetch):

<!DOCTYPE html>
<html>
<body>
<br><br><br><span style="text-align: center;"><button id="send">Send Request</button></span>
</body>

<script>
    const sendButton = document.getElementById("send");

    sendButton.addEventListener('click', () => {
          const url = "http://localhost:8000/add_points/";
        fetch(url, {
          method: 'POST',
          headers: {
              'Accept': 'application/json',
              'Content-Type': 'application/json'
              },
           body: JSON.stringify({"name":"John", "points":50.0})
        }).then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
               return response.json(); // 成功请求
          })
          .then(data => {
                alert("Data: " + data);
                console.log('Data:', data);
             })
          .catch(error => {
             console.error('There was a problem with the fetch operation:', error.message);
             });
      });

</script>

</html>

使用 fetch 的步骤:

  1. 创建一个 fetch 调用,传入 URL 以及配置对象。
  2. 在配置对象中,设置请求方法为 'POST',添加 headers,确保 Content-Typeapplication/json
  3. 使用 JSON.stringify 把请求体转换成 JSON 字符串。
  4. 处理响应, 使用response.ok确保返回的请求成功,使用 response.json() 来获取后端返回的 JSON 结果,错误捕获应该放在.catch 代码块中。

方案三:检查 FastAPI 请求参数结构

确认前端发送的数据格式是否和 FastAPI 中 AddTransaction 模型匹配。 AddTransaction模型有name和points这两个参数, 并且需要是json格式.确保你在 JavaScript 代码中发送的请求体,和你的Pydantic的模型完全一致,以下列出常见的请求body和预期模型的示例。

  • 错误格式
  {"name": "string", "point":5.0} 

上面JSON和 AddTransaction模型的属性不一致,应该叫 points , 且需要保持名称一致性。

  • 正确格式
{"name": "John", "points": 5.0} 
{
 "name": "string",
 "points": 5.0
}
  • 如果服务端 Pydantic 模型接受的类型是 float 而不是字符串,那么确保 points 的值是数字类型,而不是字符串形式的数值。
  • UserItem 以及 UserPoints 遵循类似的逻辑

其他要点:

  1. 错误处理: JavaScript 代码中应包含错误处理机制(例如fetch.catch代码块),以便及时发现问题。
  2. 安全: 允许 origins=["*"] 方便本地调试,在生产环境中需精确设置允许的域名。
  3. 日志: FastAPI 日志可以帮助调试,注意查看报错信息,以此排查后端逻辑问题

总结

要解决使用 JavaScript Fetch API 发送 POST 请求给 FastAPI 应用的问题,需要正确配置 CORS,确保发送的请求体格式正确,并检查前后端数据结构的匹配。在调试过程中,仔细查看错误信息并运用浏览器开发者工具是关键。遵循这些步骤,你应该能够顺利实现前后端数据的交互。