如何解决C++ Crow框架后端POST请求的CORS跨域问题?
问题描述
用C++ Crow框架搭建HTTP后端实现CRUD功能,GET请求正常,但通过React/Axios发起POST请求时触发CORS错误:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8000/ethernetdata. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
XHRPOST http://localhost:8000/ethernetdata
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8000/ethernetdata. (Reason: CORS request did not succeed). Status code: (null).
但使用Postman发起POST/GET请求均正常,原始后端代码见下文。
问题根源
- 未处理OPTIONS预检请求:浏览器发起跨域POST前会自动发送OPTIONS预检请求,你的后端路由只支持GET和POST,未处理OPTIONS,导致预检失败。
- CORS头配置不一致:GET请求返回的响应仅设置了
Access-Control-Allow-Origin: *,缺少其他必要CORS头;POST请求虽配置了完整头,但预检请求未得到正确响应,被浏览器拦截。 *与凭证头冲突:当设置Access-Control-Allow-Credentials: true时,Access-Control-Allow-Origin不能设为通配符*,必须指定前端具体域名(如http://localhost:3000)。
修复方案
1. 添加全局CORS中间件(推荐)
通过全局中间件统一处理所有请求的CORS头,包括OPTIONS预检请求,避免每个路由重复配置。
2. 修正Origin配置
将Access-Control-Allow-Origin设为前端实际域名,而非通配符,确保与Access-Control-Allow-Credentials兼容。
3. 捕获JSON解析异常
POST请求中添加JSON解析异常捕获,避免因前端传入无效JSON导致后端崩溃。
修改后的完整代码
#include <crow.h> #include <iostream> #include <sqlite3.h> #include <nlohmann/json.hpp> using json = nlohmann::json; static int callback(void *data, int argc, char **argv, char **azColName){ json *result = reinterpret_cast<json*>(data); json row; for(int i=0; i<argc; i++){ row[azColName[i]] = argv[i] ? argv[i] : ""; } result->push_back(row); return 0; } int main() { crow::SimpleApp app; // 全局CORS中间件,统一处理跨域请求 app.use([](crow::request& req, crow::response& res, crow::context& ctx) { // 动态获取请求Origin,或直接指定前端域名(如http://localhost:3000) std::string origin = req.get_header_value("Origin"); res.set_header("Access-Control-Allow-Origin", origin.empty() ? "http://localhost:3000" : origin); res.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); res.set_header("Access-Control-Allow-Credentials", "true"); // 处理OPTIONS预检请求,直接返回200 if (req.method == "OPTIONS"_method) { res.code = 200; res.end(); return crow::middleware::handler_return::HANDLED; } return crow::middleware::handler_return::CONTINUE; }); sqlite3 *db; int rc = sqlite3_open_v2("/home/khairy/work/eventsenderwebserver/Backend/db/data.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); if (rc != SQLITE_OK) { std::cerr << "无法打开数据库: " << sqlite3_errmsg(db) << std::endl; sqlite3_close(db); return 1; } const char* create_table_sql = "CREATE TABLE IF NOT EXISTS ethernetdata (" "id INTEGER PRIMARY KEY," "ip TEXT NOT NULL," "netmask TEXT NOT NULL," "router TEXT NOT NULL," "dns1 TEXT NOT NULL," "dns2 TEXT NOT NULL," "enabled TEXT NOT NULL" ");"; char* err_msg = nullptr; rc = sqlite3_exec(db, create_table_sql, nullptr, nullptr, &err_msg); if (rc != SQLITE_OK) { std::cerr << "SQL错误: " << err_msg << std::endl; sqlite3_free(err_msg); sqlite3_close(db); return 1; } CROW_ROUTE(app, "/") .methods("GET"_method,"POST"_method) ([]() { return crow::response{"Hello,world !"}; }); CROW_ROUTE(app, "/ethernetdata") .methods("GET"_method, "POST"_method) ([&db, &rc](const crow::request& req){ json result; if (req.method == "GET"_method) { const char* sql = "SELECT * FROM ethernetdata;"; char* err_msg = nullptr; rc = sqlite3_exec(db, sql, callback, &result, &err_msg); if (rc != SQLITE_OK) { std::cerr << "SQL错误: " << err_msg << std::endl; sqlite3_free(err_msg); } return crow::response{result.dump()}; } else if (req.method == "POST"_method) { try { // 解析POST请求的JSON数据,捕获解析异常 json post_data = json::parse(req.body); // 更新数据库 const char* update_sql = "UPDATE ethernetdata SET ip = ?, netmask = ?, router = ?, dns1 = ?, dns2 = ?, enabled = ? WHERE id = 1;"; sqlite3_stmt* stmt; rc = sqlite3_prepare_v2(db, update_sql, -1, &stmt, nullptr); if (rc != SQLITE_OK) { std::cerr << "无法准备SQL语句: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); return crow::response{500}; } sqlite3_bind_text(stmt, 1, post_data["ip"].get<std::string>().c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, post_data["netmask"].get<std::string>().c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 3, post_data["router"].get<std::string>().c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 4, post_data["dns1"].get<std::string>().c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 5, post_data["dns2"].get<std::string>().c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 6, post_data["enabled"].get<std::string>().c_str(), -1, SQLITE_TRANSIENT); rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { std::cerr << "无法执行SQL语句: " << sqlite3_errmsg(db) << std::endl; sqlite3_finalize(stmt); return crow::response{500}; } sqlite3_finalize(stmt); // 查询更新后的数据 const char* select_sql = "SELECT * FROM ethernetdata WHERE id = 1;"; char* err_msg = nullptr; rc = sqlite3_exec(db, select_sql, callback, &result, &err_msg); if (rc != SQLITE_OK) { std::cerr << "SQL错误: " << err_msg << std::endl; sqlite3_free(err_msg); } return crow::response{result.dump()}; } catch (const json::parse_error& e) { std::cerr << "JSON解析错误: " << e.what() << std::endl; return crow::response{400, "无效的JSON数据"}; } } return crow::response{405}; // 返回方法不允许状态码 }); app.port(8000).multithreaded().run(); sqlite3_close(db); return 0; }
前端Axios配置注意
如果需要携带Cookie等凭证,Axios请求需开启withCredentials:
axios.post('http://localhost:8000/ethernetdata', yourData, { withCredentials: true })
内容的提问来源于stack exchange,提问作者Khaireddine Baâtour

