前端修改API接口返回header解决跨域等问题的方法

849 阅读6分钟

一、背景和意义

《前端修改请求资源内容的方法》 这篇文章简要介绍过修改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浏览器控制台上报如下错误:

image.png

接下来本文将介绍这个demo中出现的问题的几种可行方法。

三、使用Chrome插件修改header

3.1 添加插件

Chrome浏览器插件Requestly可以实现修改header的功能,在Chrome应用商店中搜索插件Requestly: image.png

然后添加该扩展程序: image.png

3.2 验证插件是否已生效

启用插件之后,打开地址 http://localhost:8081/demo.html ,如果在chrome控制台上看到有加载JS文件customElements.js,则说明Requestly已经生效,如下图所示:

image.png

有时候如果在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文件:

image.png

3.3 使用插件

如果你按照前面的示例去掉了HTML代码中的头部的<!DOCTYPE html>,现在记得加回去,否则将导致Requestly插件不起作用影响后面的效果验证。打开Requestly插件,在“HTTP Rules”中点击“Create your first Rule”:

image.png

然后选择“Modify Headers”创建一个配置。由于前面的请求接口url为user_info,所以这里的URL选择“Contains” "usef_info”,在“Response Headers”中增加一个“Access-Control-Allow-Origin: *”,如下图所示,后面记得按Ctrl + S保存,且右上角的Enabled按钮处于开启状态:

image.png

接下来访问demo.html,可以看到第一个请求已经成功,但第二个请求还存在问题:

image.png

需要回到Requestly的配置页面,再增加一个header配置“Access-Control-Allow-Methods: GET, POST, DELETE, PUT”:

image.png

再回来访问demo.html,两次接口请求都成功了:

image.png

《前端修改请求资源内容的方法》 这篇文章有提到,如果用Requestly配置请求返回的body,那么即使修改生效了在chrome的控制台上看到的返回body也没有任何变化。但是如果是修改header,在控制台上是可以看到变化的,如下图所示:

image.png

四、使用fiddler修改header

前面的Requestly插件仅适用于chrome浏览器,相比之下使用fiddler修改header是更为通用的做法,可适用于所有浏览器。这里不再介绍fiddler的下载和安装,如果对这部分不清楚可以搜索相关文章。 在打开fiddler之后,勾选“Filters”选项卡,勾选“Use Filters”,Hosts中设置“localhost;”,以避免拦截的请求太多不好找:

image.png

在“Filters”选项卡的底部有一个“Set response header”的选项,如果只需要在API接口返回中添加一条Header配置

image.png

但是在本例的demo中,如果只设置一个“Access-Control-Allow-Origin: *”,则只有一个接口请求可以成功,另一个delete方法的接口请求还是失败。如果要设置多个Header,需要先将“Set response header”选项取消勾选,然后打开菜单“Rules” -> “Customize Rules...”:

image.png

接下来再选择菜单“Go” -> “to OnBeforeResponse”: image.png

页面会定位到“static function OnBeforeResponse”方法,在该方法中增加如下两行代码: oSession.oResponse.headers.Add("Access-Control-Allow-Origin", "*"); oSession.oResponse.headers.Add("Access-Control-Allow-Methods", "DELETE, POST, GET, PUT");

最终结果如下图所示:

image.png

接下来再访问demo.html,将看到两次接口都请求成功了:

image.png

五、针对跨域问题不修改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"}}

image.png

点开“Edit File”中的内容如下: image.png

然后访问demo.html,请求成功,相当于请求/user_info接口时被重定义到某一特定字符串,于是就解决了跨域的问题:

image.png

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代码中的接口调用,如下图所示:

image.png

image.png

接下来访问demo.html,可以看到页面执行结果正确,但从chrome控制台上看并未实际调用/user_info接口:

image.png