经过这几天学习AJAX,对於接API开始有点认识了,虽然有把一些例子顺利写出来跟大家分享,但是背後也曾经出了不少奇怪问题,例如以下经典问题:
fetch('https://www.facebook.com/')
.then( (response) => console.log(response))
.catch( (error) => console.log(error))
那时候马上google查找答案,发现一堆术语,什麽同源政策、CORS、JSONP之类的,但当时真的没有时间消化好这些知识(现在文章都是当天学当天写当天发QQ),所以即使在铁人赛最後一天,本来用来写心得的,只好乖乖来继续写技术文了,完赛心得就留待明天再发罗~
回到重点,这篇文章会整理以下知识:
这里推荐Huli老师关於AJAX的文章,以及卡斯伯老师使用dev tool检查要求的文章,对新手的我非常有帮忙!
简单讲就是,自己网站的资源不能被别人存取或修改。
如果我从目前浏览器的网页向跟自己「不同源」的网址发出请求和存取资料,就是被视作「跨来源存取」,一般情况下是不允许的,只有「同源」才会被允许。
原因:这是基於网络安全的考量,避免有骇客恶意呼叫其他人的网络服务。若没有这个政策保护,别人就可以任意修改和存取你网页里的资源了。
同源政策有两种:
这里会集中讲第一点,DOM同源政策。
什麽是DOM?在浏览器里载入的所有图片、文字、程序码等等的资源,会变成一个个DOM元素。同源政策会禁止我去存取别人网站里的DOM元素,即是别人网站里的网络资源。
那麽什麽同源(Same-Origin)?
要判断是否同源,就看这两个网址在以下的部分是否相同:
MDN的例子:
所以简单讲,不同domain就是不同源,http
和https
就是不同源,port
不同就是不同源。当我们接别人的API时,多数就是不同源的情况。
要注意一点:我的请求(request)的确是有发出去,我的浏览器之後也收到回应(response)。但多得浏览器的同源政策,它把回应挡下来了,不会把回拿到的回应掉给我的JavaScript去做另一些的处理。
但在某些情况下,即使两个网站是「不同源」,也可以允许存取的。例如以下情况:
跨来源写入:
例如允许:表单送出(form)、连结(link)、重新导向(redirect)
跨来源嵌入:
例如允许:嵌入图片<img>
、影片<video>
、<iframe>
、放在<script>
里的程序码、CSS stylesheet <link rel="stylesheet" href="...">
等等。然而,虽然我的网页可以显示到这些资源,但我的JavaScript并不能读取这些资源的内容。
fetch
和XMLhttprequest
都是会跟从同源政策,我们再次看这张图:
里面有一个关键:No 'Access-Control-Allow-Origin' header is present on the request resource.
。
Access-Control-Allow-Origin
的设定决定了我这边是否能顺利存取资源。如果我想发出跨来源请求的话,对方的服务器必须在回应表头(response header)里加上Access-Control-Allow-Origin
,并在Access-Control-Allow-Origin
的设定里,新增我的Origin(即是我的网址),或者设定为万用字符*
,代表所有Origin都接受,这是在公共API里常见的设定。
例如我的网址是https://amazing.site
Access-Control-Allow-Origin: https://amazing.site
//或者
Access-Control-Allow-Origin: *
只要服务器设定好Access-Control-Allow-Origin
(加入我的网址或*
号),当我发出请求,以及服务器那边回传回应後,浏览器就会检查回应表头,看看里面的Access-Control-Allow-Origin
是否有我的网址或者有*
,如果有的话就会允许通过,成功存取资料。
例如我去接randam user这个公共API,我会成功收到资料。这时候打开dev tool去查,access-control-allow-origin
的确是设定为*
:
如果要测试对方伺器服是否有设定好Access-Control-Allow-Origin
,我们可以用test-cors.org这个平台去查。
除了做以上的设定,我们也可以透过JSONP(JSON with Padding)这个方法来解决。刚才提及过<script>
tag是不受同源政策限制的,我们可以用它来解决问题。
JSONP的做法就是,在一个<script>
tag里的放入服务器端提供的网址,之後在另一个<script>
tag里宣告一个函式,函式名字是由服务器端提供,也可以在服务器端所提供的网址里找到,例如它提供了https://...callback=abc
这个网址,那麽该函式的名字就是abc
。
这里用randomuser的API来做范例:
<script>
function randomuserdata(response){
console.log(response);
}
</script>
<script src="https://randomuser.me/api/?gender=female&nat=us&callback=randomuserdata"></script>
下面那行URL会回传randomuserdata
函式,并在回传randomuserdata
函式时带入那笔我本来想抓的资料,整个过程可以想像成以下这样:
<script>
function randomuserdata(response){
console.log(response);
}
</script>
<script>randomuserdata({那些你想抓的资料})</script>
注意,这两个<script>
有次序之分,要先写那个宣告randomuserdata
函式的<script>
,之後才写负责回传randomuserdata
函式的那个<script>
,不然是报错。
虽然JSONP解决了跨来源问题,但是JSONP只适用於GET请求,无法做到POST,所以首选还是上面提及的CORS的方法。
最後来谈谈Preflight Request。
Preflight request并不是我本身想要发出的请求。Preflight request是我(浏览器端)发出请求前的一个「预检请求」,这个预检请求是负责查问服务器,问它是否批准我们发出请求给它。
Preflight request会带有一些关於我想发的请求的一些资讯,例如我将会使用的HTTP请求方法(GET、POST...)、Authorization
等等。
什麽时候会使浏览器发出Preflight request呢?当我发出的请求不是简单请求时,就会触发Preflight request,当Preflight request被通过,我本身的请求才会被发出。简单请求有一堆定义,例如请求要是GET
、HEAD
或POST
、headers
其中一个,详细请看这个MDN。
例如,如果我提出DELETE请求,那就一定会触发preflight request。这很合理,因为如果没有preflight request,不管对方服务器有没有把我的网址加入'Access-Control-Allow-Origin'
,我仍然可以发出DELETE请求把对方服务器里的资料删除。即使因为同源政策浏览器会挡下response,这也没关系,因为我的DELETE请求一定会被对方服务器接收的,这就是为什麽我们需要preflight request,否则别人真的可以随便修改自己的东西。
重用昨天的六角学院练习用的API为例,我想先找出所有商品:
const uuid = xxxxxx;
const token = xxxxxx;
const url = `https://course-ec-api.hexschool.io/api/${uuid}/admin/ec/products`;
let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": `Bearer ${token}`,
}
fetch(url,{
method: "GET",
headers: headers
})
.then((response) => {
return response.json();
})
.then( (response) => {
console.log(response);
})
.catch( (error) => console.log(error))
这里显示我的後台有2件T-shirt商品,各有不同ID。之後我想删除第一个商品,於是我跟从六角学院删除後台商品的API,发出DELETE请求:
const uuid = xxxxxx;
const token = xxxxxx;
const id = 'RfmTRZT3QpNZrOvZrPFyZyyYooeCHpW67WngnZ3ZPjQF6IhfFYyiJnFBuVo3coaP'
const url = `https://course-ec-api.hexschool.io/api/${uuid}/admin/ec/product/${id}`;
let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": `Bearer ${token}`,
}
fetch(url,{
method: "DELETE",
headers: headers
})
.then(response => response.json())
.then(json => console.log(json))
.catch( (error) => console.log(error))
成功删除:
这时候看看network,会发现有送出OPTION请求,即是Preflight request,之後也有DELETE请求:
如果Preflight request没有通过,那麽我的DELETE请求就不会发出去了。
呼~ 终於打完最後一篇技术文了,自己对於AJAX这个题目真的很不熟,也没有足够接API的经验,所以好多内容都是边学边写的(擦汗),希望透过整理网上找到的内容来消化知识。虽然铁人赛到这里完结了,但明天我还会发一篇完赛心得,毕竟努力了30天,也需要好好反思一下自己除了技术以外,还学到什麽东西~ 感谢你的阅读,明天再见!
CORS, preflighted requests & OPTIONS method
轻松理解 Ajax 与跨来源请求
Same Origin Policy 同源政策 ! 一切安全的基础
>>: Day 28. 测试HTTP Status Code
tags: 2021铁人赛 React 上一篇在串接API的时候有遇到一个前端蛮常见的问题,跨来源资...
天下武功 唯快不破 目录 前言 IntelliJ 特点 Android 与 Python 下载与安装...
什麽是好的网站设计? 使用者使用网站时是否容易操作及有良好的动线,避免过多不必要的元素,让使用者快速...
才刚提到趋势科技去年在 MITRE Engenuity 的 ATT&CK Evaluatio...
SDLC定义了工程系统时的阶段和过程。由於系统的多样性,它通常不提供特定的设计原则。 系统开发生命周...