一、背景和意义
《前端修改请求资源内容的方法》 这篇文章简要介绍过修改API接口返回体的方法。而有些场景下需要修改API接口返回的header部分而不是body部分。比较常见的场景是,前端请求后端的某个API接口,由于不是处在同一个域名下,会提示跨域访问的错误:
Access to XMLHttpRequest at 'xxxxx' from origin 'xxxx' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果后端同事在接口返回header中增加Access-Control-Allow-Origin: *即可解决问题,但后端同事可能正在忙其他事情,甚至已经下班或者休假了。这种情况下如果前端不想等,可以直接修改接口返回的header。
或许后端没有犯下忘记添加Access-Control-Allow-Origin这么低级的错误,但可能会忘记添加另一个不那么常见的header:Access-Control-Allow-Methods。这种情况下,虽然有Access-Control-Allow-Origin,但跨域访问的请求如果是DELETE、PUT方法,依然会出错:
Access to XMLHttpRequest at 'xxxxxx' from origin 'xxxx' has been blocked by CORS policy: Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response.
这种情况下,前端也可以通过修改接口返回的header临时解决。
二、示例Demo
这里,我们简单构建一个需要修改header的demo。在nginx中添加如下配置:
server {
listen 8081;
server_name localhost;
location / {
root html;
}
}
server {
listen 8082;
server_name localhost;
location /user_info {
default_type 'application/json; utf-8';
return 200 '{"msg":"success","code":200,"data":{"username": "test1"}}';
}
}
在上述配置中,8081提供静态网页,8082提供接口。接下来在8081对应的html目录下创建一个demo.html文件,内容为:
<!DOCTYPE html>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.get('http://localhost:8082/user_info').then(res => document.writeln("<p>get method response: " + JSON.stringify(res.data) + "</p>"));
axios.delete('http://localhost:8082/user_info', {}).then(res => document.writeln("<p>delete method response: " + JSON.stringify(res.data) + "</p>"));
</script>
接下来访问 http://localhost:8081/demo.html ,会看到chrome浏览器控制台上报如下错误:
接下来本文将介绍这个demo中出现的问题的几种可行方法。
三、使用Chrome插件修改header
3.1 添加插件
Chrome浏览器插件Requestly可以实现修改header的功能,在Chrome应用商店中搜索插件Requestly:
然后添加该扩展程序:
3.2 验证插件是否已生效
启用插件之后,打开地址 http://localhost:8081/demo.html ,如果在chrome控制台上看到有加载JS文件customElements.js,则说明Requestly已经生效,如下图所示:
有时候如果在HTML代码中忘记增加头部的<!DOCTYPE html>,那么Requestly可能不认为该页面是一个网页,相关功能就不会生效,chrome控制台上也将看不到加载customElements.js文件。例如将demo.html的HTML代码改成:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.get('http://localhost:8082/user_info').then(res => document.writeln("<p>get method response: " + JSON.stringify(res.data) + "</p>"));
axios.delete('http://localhost:8082/user_info', {}).then(res => document.writeln("<p>delete method response: " + JSON.stringify(res.data) + "</p>"));
</script>
那么访问demo.html时,控制台显示没有加载customElements.js文件:
3.3 使用插件
如果你按照前面的示例去掉了HTML代码中的头部的<!DOCTYPE html>,现在记得加回去,否则将导致Requestly插件不起作用影响后面的效果验证。打开Requestly插件,在“HTTP Rules”中点击“Create your first Rule”:
然后选择“Modify Headers”创建一个配置。由于前面的请求接口url为user_info,所以这里的URL选择“Contains” "usef_info”,在“Response Headers”中增加一个“Access-Control-Allow-Origin: *”,如下图所示,后面记得按Ctrl + S保存,且右上角的Enabled按钮处于开启状态:
接下来访问demo.html,可以看到第一个请求已经成功,但第二个请求还存在问题:
需要回到Requestly的配置页面,再增加一个header配置“Access-Control-Allow-Methods: GET, POST, DELETE, PUT”:
再回来访问demo.html,两次接口请求都成功了:
《前端修改请求资源内容的方法》 这篇文章有提到,如果用Requestly配置请求返回的body,那么即使修改生效了在chrome的控制台上看到的返回body也没有任何变化。但是如果是修改header,在控制台上是可以看到变化的,如下图所示:
四、使用fiddler修改header
前面的Requestly插件仅适用于chrome浏览器,相比之下使用fiddler修改header是更为通用的做法,可适用于所有浏览器。这里不再介绍fiddler的下载和安装,如果对这部分不清楚可以搜索相关文章。 在打开fiddler之后,勾选“Filters”选项卡,勾选“Use Filters”,Hosts中设置“localhost;”,以避免拦截的请求太多不好找:
在“Filters”选项卡的底部有一个“Set response header”的选项,如果只需要在API接口返回中添加一条Header配置
但是在本例的demo中,如果只设置一个“Access-Control-Allow-Origin: *”,则只有一个接口请求可以成功,另一个delete方法的接口请求还是失败。如果要设置多个Header,需要先将“Set response header”选项取消勾选,然后打开菜单“Rules” -> “Customize Rules...”:
接下来再选择菜单“Go” -> “to OnBeforeResponse”:
页面会定位到“static function OnBeforeResponse”方法,在该方法中增加如下两行代码:
oSession.oResponse.headers.Add("Access-Control-Allow-Origin", "*"); oSession.oResponse.headers.Add("Access-Control-Allow-Methods", "DELETE, POST, GET, PUT");
最终结果如下图所示:
接下来再访问demo.html,将看到两次接口都请求成功了:
五、针对跨域问题不修改header的方法
如果存在跨域问题的请求仅仅是查询数据,不涉及任何数据修改,并且返回数据总是固定的,那么在这种特定场景下不修改header也能解决跨域问题。
5.1 使用Resource Override插件
对于前面提到的特殊场景可以用 《前端修改请求资源内容的方法》 这篇文章中提到的chrome浏览器Resource Override插件来解决。例如在本例中,如果只有get请求没有第二个delete请求,即代码变成:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.get('http://localhost:8082/user_info').then(res => document.writeln("<p>get method response: " + JSON.stringify(res.data) + "</p>"));
</script>
那么可以打开插件Resource Override插件,设置http://localhost:8082/user_info接口固定返回{"msg":"success","code":200,"data":{"username": "test1"}}:
点开“Edit File”中的内容如下:
然后访问demo.html,请求成功,相当于请求/user_info接口时被重定义到某一特定字符串,于是就解决了跨域的问题:
5.2 使用Requestly插件
Requestly插件可以拦截js代码中的接口调用,不实际发出请求,直接将设定的结果返回,这也同样解决了跨域问题。在Requestly中创建一个“Modify API response”的配置,URL配置跟前面的Requestly的例子一样,设置Response Body为{"msg":"success","code":200,"data":{"username": "test1"}},并勾选“Serve this response body without making a call to the server.”表示不发生实际的HTTP请求而是直接将预设的结果返回给js代码中的接口调用,如下图所示:
接下来访问demo.html,可以看到页面执行结果正确,但从chrome控制台上看并未实际调用/user_info接口: