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

实现聊天机器人时遇POST /predict 404错误的排查与解决

React + Flask 聊天机器人前后端交互问题排查

问题现象

点击提交用户输入时,浏览器控制台出现两个错误:

POST http://127.0.0.1:3000/predict 404 (Not Found)
Error: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON"

代码详情

前端(app.js)

import React, { useState, useEffect } from "react";
import "./style.css";

const Chatbox = () => {
  const [state, setState] = useState(false);
  const [messages, setMessages] = useState([]);

  const toggleState = (chatbox) => {
    setState(!state);

    // show or hide the box
    if (!state) {
      chatbox.classList.add("chatbox--active");
    } else {
      chatbox.classList.remove("chatbox--active");
    }
  };

  const onSendButton = (chatbox) => {
    var textField = chatbox.querySelector("input");
    let text1 = textField.value;
    if (text1 === "") {
      return;
    }

    let msg1 = { name: "User", message: text1 };
    setMessages((prevMessages) => [...prevMessages, msg1]);

    fetch("http://127.0.0.1:3000/predict", {
      method: "POST",
      body: JSON.stringify({ message: text1 }),
      mode: "cors",
      headers: {
        "Content-Type": "application/json",
      },
    })
      .then((r) => r.json())
      .then((r) => {
        let msg2 = { name: "Sam", message: r.answer };
        setMessages((prevMessages) => [...prevMessages, msg2]);
        updateChatText(chatbox);
        textField.value = "";
      })
      .catch((error) => {
        console.error("Error:", error);
        updateChatText(chatbox);
        textField.value = "";
      });
  };

  const updateChatText = (chatbox) => {
    var html = "";
    messages
      .slice()
      .reverse()
      .forEach(function (item, index) {
        if (item.name === "Sam") {
          html += `<div class="messages__item messages__item--visitor">${item.message}</div>`;
        } else {
          html += `<div class="messages__item messages__item--operator">${item.message}</div>`;
        }
      });

    const chatmessage = chatbox.querySelector(".chatbox__messages");
    chatmessage.innerHTML = html;
  };

  useEffect(() => {
    const openButton = document.querySelector(".chatbox__button");
    const chatBox = document.querySelector(".chatbox__support");
    const sendButton = document.querySelector(".send__button");
    const node = chatBox.querySelector("input");

    openButton.addEventListener("click", () => toggleState(chatBox));

    sendButton.addEventListener("click", () => onSendButton(chatBox));

    node.addEventListener("keyup", ({ key }) => {
      if (key === "Enter") {
        onSendButton(chatBox);
      }
    });
  }, []);

  return (
    <div className="chatbox">
      {/* Your chatbox HTML structure goes here */}
      <button className="chatbox__button">
        <img src="./images/chatbox-icon.svg" alt="Chat" />
      </button>
      <div className={`chatbox__support ${state ? "chatbox--active" : ""}`}>
        {/* Your chatbox support HTML structure goes here */}
        <div className="chatbox__header">
          {/* Your chatbox header HTML structure goes here */}
          <div className="chatbox__image--header">
            <img
              src="https://img.icons8.com/color/48/000000/circled-user-female-skin-type-5--v1.png"
              alt="User"
            />
          </div>
          <div className="chatbox__content--header">
            <h4 className="chatbox__heading--header">Chat support</h4>
            <p className="chatbox__description--header">
              Hi. My name is Sam. How can I help you?
            </p>
          </div>
        </div>
        <div className="chatbox__messages">
          {/* Your chatbox messages HTML structure goes here */}
          {messages.map((item, index) => (
            <div
              key={index}
              className={`messages__item messages__item--${
                item.name === "Sam" ? "visitor" : "operator"
              }`}
            >
              {item.message}
            </div>
          ))}
        </div>
        <div className="chatbox__footer">
          <input type="text" placeholder="Write a message..." />
          <button className="send__button">Send</button>
        </div>
      </div>
    </div>
  );
};

export default Chatbox;

后端(app.py)

import sys
print("Python Interpreter Path:", sys.executable)

from flask import Flask, render_template, request, jsonify
from chat import get_response

app = Flask(__name__)

@app.route("/", methods=['GET'])
def index_get():
    return render_template("base.html")

@app.route("/predict", methods=['POST'])
def predict():
    text = request.get_json().get("message")
    # TO-DO: check if text is valid
    response = get_response(text)
    message = {"answer": response}
    return jsonify(message)

if __name__ == "__main__":
    app.run(debug=True, port=3000)

用户已做的尝试与观察

  • 前后端服务器均正常运行,前端地址http://localhost:3000,后端地址http://127.0.0.1:3000
  • 错误触发点:前端向后端发送POST请求到http://127.0.0.1:3000/predict时
  • 伴随JSON解析错误:提示返回的是HTML内容(<!DOCTYPE开头)而非JSON

问题解答

1. 404 (Not Found) 错误的可能原因

  • 端口冲突:同一个端口不能被两个服务同时占用,你说前后端都用3000端口,这是核心矛盾,必然导致其中一个服务无法正常监听,请求找不到对应路由。
  • 服务未正确启动:检查Flask控制台日志,确认是否输出Running on http://127.0.0.1:3000,如果没有则说明后端服务启动失败。
  • 路由路径不匹配:后端@app.route("/predict")的路径是否和前端请求路径完全一致,注意大小写、多余斜杠等细节。
  • 跨域拦截:Flask默认不处理跨域请求,即使前端设置mode: "cors",也可能导致请求被拦截,表现为404。

2. SyntaxError: Unexpected token '<' 与后端响应的关系

完全相关。当请求返回404时,Flask会默认返回HTML格式的404错误页面(开头为<!DOCTYPE html>),而前端代码尝试将该HTML解析为JSON,就会触发这个语法错误。解决404问题后,该错误会自动消失。

3. React + Flask 通信的常见陷阱与最佳实践

常见陷阱

  • 端口冲突:前后端必须使用不同端口运行。
  • 跨域配置缺失:Flask默认不支持跨域,需手动配置。
  • 请求路径错误:前端请求路径需与后端路由完全匹配。
  • Content-Type不匹配:前端发送JSON时必须设置Content-Type: application/json,否则后端无法解析请求体。
  • 未处理异常:后端抛出异常时会返回HTML错误页面,导致前端JSON解析失败。

最佳实践

  • 使用代理配置:在React的package.json中添加"proxy": "http://127.0.0.1:5000"(假设后端用5000端口),前端可直接用fetch("/predict")发起请求,无需写完整域名,同时规避跨域问题。
  • 配置CORS:安装flask-cors(pip install flask-cors),在Flask代码中添加:
    from flask_cors import CORS
    CORS(app)
    
  • 统一错误处理:后端捕获异常并返回JSON格式错误信息,示例:
    @app.route("/predict", methods=['POST'])
    def predict():
        try:
            text = request.get_json().get("message")
            if not text:
                return jsonify({"error": "消息不能为空"}), 400
            response = get_response(text)
            return jsonify({"answer": response})
        except Exception as e:
            return jsonify({"error": str(e)}), 500
    
  • 避免直接操作DOM:React依赖状态更新驱动UI渲染,删除updateChatText这类手动操作DOM的代码,由messages状态变化自动渲染聊天内容。

4. 新手调试步骤

  1. 解决端口冲突:修改后端端口为5000(app.run(debug=True, port=5000)),确保前后端端口不同。
  2. 验证后端路由:用Postman或curl发送POST请求到http://127.0.0.1:5000/predict,请求体为{"message": "测试消息"},确认能正常返回JSON响应。
  3. 配置React代理:在React项目package.json中添加"proxy": "http://127.0.0.1:5000",将前端fetch地址改为"/predict"。
  4. 添加CORS支持:安装flask-cors并在后端代码中配置。
  5. 简化React状态管理:删除updateChatText函数,依赖messages状态自动渲染聊天列表。

内容的提问来源于stack exchange,提问作者Paris Vanuya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 01:34:49