为何jQuery post()方法发送的是GET请求而非POST请求?
我有一个简单的单文件应用,用于演示如何与API服务器进行摘要认证交互。第一步本应是不带凭证发送POST请求以获取nonce,但调用$.post(...)时,网络追踪显示发送的是GET请求,控制台记录“method not allowed”。
控制台显示状态码405,但代码中获取到的request.status是404。我真正关注的是本该发送POST请求并得到401响应。
我查找了许多关于jQuery发送POST而非GET的问题,但大多较旧,解决方案对我无效或不适用。我确认使用的是jQuery 3.5.1,因此关于type与method的旧答案不适用。
在Chrome和Edge中均出现此问题。将下方代码保存为index.html并在Chrome中打开,点击按钮会发送GET请求。请求google.com可能成功,但为何是GET?若换成我的REST API服务器地址,会因该端点不支持GET而返回405。
我怀疑忽略了某些明显问题,但不知具体是什么。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>SSO Test Client</title> <link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="css/site.css" /> <script src="lib/jquery/dist/jquery.min.js"></script> <script src="lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="js/site.js" asp-append-version="true"></script> <style> input[type="text"] { width: 50em; } </style> </head> <body> <script> async function init() { console.log(jQuery().jquery); // Doesn't matter what I put here, it fails $("#server").val("https://google.com"); $("#submit").click(handleSubmit); }; function handleSubmit() { var server = $("#server").val(); // Get nonce from API server by attempting to post without credentials // and handling the challenge response. $.post({ url: server, dataType: "jsonp", // I get back a 0 response without this headers: { 'Content-Type':'application/json' // The server seems to require this }, data: {}, success: function (data, textStatus, request) { // This should never succeed; we expect a 401 alert(textStatus); }, // Adapted from https://stackoverflow.com/a/14563181/7685 error: function (request, textStatus, errorThrown) { var msg = ""; if (request.status == 0) { msg = `Could not connect to "${server}".`; } else if (request.status == 404) { msg = "Page not found (404)"; } else if (request.status == 405) { msg = "Method not allowed (405)"; } else if (request.status == 500) { msg = "Internal server error (500)"; } // This is expected. else if (request.status == 401) { msg = "Got nonce from server."; } else { msg = "Unhandled status. " + textStatus; } alert(msg); } }); }; function docReady(fn) { // see if DOM is already available if (document.readyState === "complete" || document.readyState === "interactive") { // call on next available tick setTimeout(fn, 1); } else { document.addEventListener("DOMContentLoaded", fn); } }; docReady(init); </script> <div class="container"> <main role="main" class="pb-3"> <div class="text-center"> <div id="sso-client"> <input id="server" type="text" title="API server to use for SSO authorization" /> <br /> <input type="button" id="submit" value="Test" title="Try to get a nonce" onclick=""/> </div> </div> </main> </div> </body> </html>
补充说明:
- 我认为是
jsonp数据类型导致jQuery将POST转为GET,但去掉jsonp后,请求无法发出,状态码为0。若移除jsonp,我需要知道如何让请求正常发送。 - 我尝试在
$.post()前添加jQuery.support.cors = true;,但无效果。 - 我认为无需修改服务器(如设置Access-Control-Allow-Origin),因为同一台电脑上用Postman可正常操作,无需特殊源处理。
1. 核心问题:JSONP强制转为GET请求
JSONP的工作原理是通过创建<script>标签加载远程资源,而<script>标签只能发起GET请求,所以只要设置了dataType: "jsonp",不管用$.post还是$.ajax指定method: "POST",最终都会被转为GET请求,这是JSONP的固有特性,和jQuery版本无关。这就是POST变GET的根本原因。
2. 解决跨域问题,移除JSONP
去掉dataType: "jsonp"后出现的状态码0,是浏览器的CORS(跨域资源共享)限制导致的。Postman不受浏览器同源策略限制,所以能正常请求,但浏览器中运行的前端代码必须遵守同源规则。
解决这个问题有两种可行方案:
方案一:配置服务器支持CORS
这是标准跨域解决方案,需要在API服务器上添加CORS响应头,比如:Access-Control-Allow-Origin: * (或指定你的前端页面域名) Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: Content-Type由于请求携带了自定义的
Content-Type: application/json,属于预检请求(OPTIONS)范畴,服务器需要正确响应OPTIONS请求,否则浏览器会拦截后续的POST请求。方案二:使用代理服务器
如果无法修改API服务器,可以在前端部署代理服务器,让前端请求先发送到同源的代理服务器,再由代理服务器转发到目标API服务器。比如本地开发时,可用Nginx或Node.js搭建简单代理。
3. 修正后的请求代码示例
移除dataType: "jsonp",确保服务器已配置CORS后,修改后的handleSubmit函数如下:
function handleSubmit() { var server = $("#server").val(); $.ajax({ url: server, method: "POST", headers: { 'Content-Type':'application/json' }, data: JSON.stringify({}), // 发送JSON格式数据需要序列化 success: function (data, textStatus, request) { alert(textStatus); }, error: function (request, textStatus, errorThrown) { var msg = ""; if (request.status == 0) { msg = `Could not connect to "${server}".`; } else if (request.status == 404) { msg = "Page not found (404)"; } else if (request.status == 405) { msg = "Method not allowed (405)"; } else if (request.status == 500) { msg = "Internal server error (500)"; } else if (request.status == 401) { msg = "Got nonce from server."; } else { msg = "Unhandled status. " + textStatus; } alert(msg); } }); };
注意:改用$.ajax并明确指定method: "POST"(显式指定更清晰),同时将data用JSON.stringify序列化,因为Content-Type设置为application/json时,需要发送JSON字符串而非对象。
内容的提问来源于stack exchange,提问作者Chris Nelson

