摘要:瀏覽器的同源策略固然保障了互聯(lián)網(wǎng)世界的數(shù)據(jù)隱私與數(shù)據(jù)安全,但是如果當我們需要使用跨域請求資源時,同源策略又會成為開發(fā)者的阻礙。我們之前提到過,如果想要繞過瀏覽器同源策略,實現(xiàn)使用技術(shù)跨域獲取資源,需要服務(wù)端和客戶端的協(xié)同合作。
瀏覽器的“同源策略”固然保障了互聯(lián)網(wǎng)世界的數(shù)據(jù)隱私與數(shù)據(jù)安全,但是如果當我們需要使用AJAX跨域請求資源時,“同源策略”又會成為開發(fā)者的阻礙。在本文中,我們會簡單介紹需要跨域請求資源的兩種情景,然后,詳細解釋目前主流的四種跨域請求資源方案。
讓我們開始吧!
一、何時需要跨域試想,當我們擁有多個站點,并且這些站點又經(jīng)常共享相同的數(shù)據(jù),那么為每個站點存儲一份數(shù)據(jù)看起來就蠢透了。更好的方案是,我們建設(shè)一臺靜態(tài)資源存儲服務(wù)器,然后讓我們的所有站點都從這一臺服務(wù)器上獲取資源。很理想的方案,但是現(xiàn)實中,我們首要解決的問題便是瀏覽器的“同源策略”,別忘了,不同域之間無法通過AJAX技術(shù)獲取資源。這是需要跨域獲取資源的主要情景。
另外,站在互聯(lián)網(wǎng)“開放,平等,自由”精神的角度上講,如果所有人的數(shù)據(jù)都被設(shè)置為只有同域才能訪問,那么互聯(lián)網(wǎng)世界未免也太無聊了,如果我就是想要與更多的人分享我的數(shù)據(jù),難道不應(yīng)該有辦法讓我做到這一點嗎?
當然有辦法,下面我們就將一一解釋當下主流的跨域請求資源方式。
二、跨域請求資源方案我們將主要介紹以下四種跨域請求資源的方案,并逐一解釋他們的原理,實用方式以及優(yōu)缺點,希望你和我一樣有耐心,耐心總是能帶來回報:
野路子出身卻好用的方式:JSONP;
官方推薦的跨域資源共享方案:CORS;
使用HTML5 API:postMessage;
拋棄HTTP,使用:Web Sockets;
在開始下面的內(nèi)容之前,我們首先需要強調(diào)一點,無論是怎樣的跨域資源獲取方案,本質(zhì)上都需要服務(wù)器端的支持。跨域獲取資源之所以能夠成功,本質(zhì)是服務(wù)器默許了你有權(quán)限獲取相應(yīng)資源。下面我們所運用的種種方式,實際上是客戶端和服務(wù)端互相配合,繞過同源策略進行數(shù)據(jù)交互的工作,千萬不要誤以為掌握了下述技術(shù)后,我們就能成為一個黑客 ???♂?。
(一)野路子出身卻異常好用的方式:JSONP正如標題所描述的那樣,JSONP技術(shù)是早期某個(些?)聰明的程序員發(fā)明的跨域資源獲取方式,由于該技術(shù)的簡單易用,逐漸變得越來越流行,最終成為經(jīng)典的跨域獲取資源方案。
JSONP是“JSON with padding”的簡寫,我將其翻譯為“被包裹的JSON”,當你看完這個章節(jié),你一定會覺得這個名字相當貼切。
讓我們模擬一下當初想到JSONP技術(shù)的高手程序員是如何推理的:
首先,我們應(yīng)該清楚的認識到,瀏覽器的“同源策略”只是阻止了通過AJAX技術(shù)跨域獲取資源,而并沒有禁止跨域獲取資源這件事本身,正因如此,我們可以通過標簽,標簽以及標簽中的href屬性或src屬性獲取異域的CSS,JS資源和圖片(雖然我們其實并不能讀取這些資源的內(nèi)容)。
其次,我們知道(也許你不知道,但是,還記得嗎,我在模擬那個高手程序員?)標簽通過src屬性加載的JS資源,實際上只是將JS文件內(nèi)容原封不動的放置在
也就是說,如果我們的sayHi.js文件只有這樣一段代碼:
// sayHi.js alert("Hi")
當我們在HTML文件中,成功加載sayHi.js文件時,瀏覽器只不過是做了如下操作:
這意味著什么呢?這意味著被加載的文件與HTML文件下的其他JS文件共享一個全局作用域。也就是說,
但是慢著!如果標簽加載到的一些數(shù)據(jù)并不符合JavaScript語法規(guī)定的數(shù)據(jù)類型,JavaScript就無法處理這些錯誤不是嗎?而且就算數(shù)據(jù)類型正常了,我們還應(yīng)該將數(shù)據(jù)存儲于一個變量內(nèi),然后調(diào)用這個變量...
說的沒錯!不過我們其實已經(jīng)離正確答案很近了。
還記的我們這一方案的名稱嗎?JSONP!,也就是說我們已經(jīng)約定好了數(shù)據(jù)的格式為JSON,這是JavaScript可以處理的數(shù)據(jù)類型,并且JSON格式的數(shù)據(jù)可以承載大量信息。那么有關(guān)變量的問題呢?這個回答則更巧妙些,因為我們會通過向服務(wù)器傳入一個函數(shù)的方式,將數(shù)據(jù)變?yōu)楹瘮?shù)的參數(shù),讓我們直接看看JSONP的使用方式:
1. function handleResponse(response) { 2. alert(`You get the data : ${response}`) 3. } 4. const script = document.createElement("script") 5. script.src = "http://somesite.com/json/?callback=handleResponse" 6. document.body.insertBefore(script, document.body.firstChild)
很容易看到,我們在1-3行中創(chuàng)建了一個函數(shù),該函數(shù)用來處理我們將要獲得的數(shù)據(jù),該函數(shù)的參數(shù)response即是服務(wù)器響應(yīng)的數(shù)據(jù)。在4-6行中我們所做的是利用JavaScript動態(tài)生成一個script標簽,并將其插入HTML文檔。但是注意第5行我們制定的src值,在URL末尾,我們有這樣一段查詢參數(shù)callback=handleResponse,callback的值正是我們先前創(chuàng)建的函數(shù)。
事情開始變得有些令人困惑了,究竟發(fā)生了什么呢?我們?nèi)绾瓮ㄟ^上述代碼最終實現(xiàn)跨域獲取資源?
答案就藏在服務(wù)端的代碼中,當服務(wù)端支持JSONP技術(shù)時,會做如下一些設(shè)置:
識別請求的URL,提取callback參數(shù)的值,并動態(tài)生成一個執(zhí)行該參數(shù)值(一個函數(shù))的JavaScript語句;
將需要返回的數(shù)據(jù)放入動態(tài)生成的函數(shù)中,等待其加在到頁面時被執(zhí)行;
此時該文件內(nèi)容看起來就像這樣:
handleResponse(response) // response為被請求的JSON格式的數(shù)據(jù)
因此,當資源加載到位,內(nèi)容顯示在script標簽內(nèi)時,瀏覽器引擎會執(zhí)行這條語句,我們想要的數(shù)據(jù)就可以被我們以任何想要的方式處理了。真不可思議!
你現(xiàn)在知道為什么這項技術(shù)被命名為JSONP了吧?那個“padding”指的就是我們的“callback”函數(shù),真是恰如其名。
最后,我們還要對JSONP技術(shù)再強調(diào)兩點:
JSONP技術(shù)與AJAX技術(shù)無關(guān):雖然同樣牽扯到跨域獲取資源這個主題,但我們應(yīng)該已經(jīng)清楚的看到,JSONP的本質(zhì)是繞過AJAX獲取資源的機制,使用原始的src屬性獲取異域資源;
JSONP技術(shù)存在一下三點缺陷:
無法發(fā)送POST請求,也就是說JSONP技術(shù)只能用于請求異域資源,無法上傳數(shù)據(jù)或修改異域數(shù)據(jù);
無法監(jiān)測JSONP請求是否失敗;
可能存在安全隱患:別忘了,JSONP之所以能成功獲取異域服務(wù)器資源,靠的是服務(wù)器動態(tài)生成了回調(diào)函數(shù),并在頁面中執(zhí)行,那么如果服務(wù)器在原有的回調(diào)函數(shù)下再添加些別的惡意JavaScript代碼會怎樣?當然也會被執(zhí)行!所以在使用JSONP技術(shù)時,一定要確保請求資源的服務(wù)器是值得信賴的;
雖然存在一些缺陷,但JSONP的瀏覽器兼容性卻是非常好的,可以說是一種非常小巧高效的跨域資源獲取技術(shù)。
(二)官方推薦的跨域資源共享方案:CORSCORS是W3C頒布的一個瀏覽器技術(shù)規(guī)范,其全稱為“跨域資源共享”(Cross-origin resource sharing),它的意義在于,它是由W3C官方推廣的允許通過AJAX技術(shù)跨域獲取資源的規(guī)范,因此相較于JSONP而言,功能更加強大,使用起來也沒有了hack的味道。
關(guān)于CORS的具體細節(jié),我建議你可以移步阮一峰的同主題博客閱讀,我認為該文章已經(jīng)將這個主題講解的十分透徹了。
你當然也可以選擇繼續(xù)向下閱讀,看看我是怎樣理解CORS技術(shù)并重新梳理CORS技術(shù)相關(guān)知識的,希望也能給你帶來幫助。
我們之前提到過,如果想要繞過瀏覽器“同源策略”,實現(xiàn)使用AJAX技術(shù)跨域獲取資源,需要服務(wù)端和客戶端的協(xié)同合作。而對于CORS標準而言,實現(xiàn)AJAX跨域獲取資源,重點還在于服務(wù)器端返回的響應(yīng)是否清楚的告知了瀏覽器此次跨域AJAX請求的合法性。
那么?服務(wù)器端該如何向瀏覽器傳達這一信息呢?答案是要看AJAX請求的復(fù)雜程度,也就是說,對于簡單的AJAX請求,服務(wù)器要向瀏覽器做出的“說明”就少,而如果是復(fù)雜的AJAX,服務(wù)器則要向瀏覽器多“解釋”幾句。
那么,如何區(qū)分AJAX請求的復(fù)雜度呢,標準在于簡單的AJAX請求只符合下面兩個條件:
請求方法只屬于HEAD,GET,POST請求的其中一種;
HTTP的頭信息只限于以下字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type(只能為application/x-www-form-urlencoded,multipart/form-data和text/plain其中一種)
而當瀏覽器檢測到一個簡單的跨域AJAX請求,瀏覽器會首先為我們添加一個頭部信息:Origin它的值為請求發(fā)送代碼所在的源(希望你還記得,一個源由“協(xié)議”,“域名和端口”組成)。類似這樣:
GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0 ...
而當這樣的一條HTTP請求發(fā)送到服務(wù)端時,服務(wù)端會檢測該請求報頭中的Origin字段的值是否在許可范圍內(nèi),如果的確是服務(wù)端認可的域,那么服務(wù)端會在響應(yīng)報文中添加如下字段:
Access-Control-Allow-Origin(必須):該字段用來告知瀏覽器服務(wù)端接受的能夠發(fā)送跨域AJAX請求的域,它的值要么是該次AJAX請求報頭中由瀏覽器自動添加的Origin值,要么還可以是一個*號,表示可以接受任意的域名請求;
Access-Control-Allow-Credentials(可選):該字段用來告知瀏覽器是否允許客戶端向服務(wù)端發(fā)送Cookie。默認情況下,CORS規(guī)范會阻止跨域AJAX向服務(wù)端發(fā)送Cookie,因此該字段默認值為false,當你顯式的將該字段值設(shè)置為true時,則表示允許此次跨域AJAX向服務(wù)端發(fā)送Cookie。
Access-Control-Expose-Headers(可選):該字段用來向客戶端暴露可獲取的響應(yīng)頭;
CORS規(guī)范規(guī)定,客戶端XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本的字段:
* `Cache-Control`:表示響應(yīng)遵循的緩存機制; * `Content-Language`:表示響應(yīng)體的語言; * `Content-Type`:表示響應(yīng)體的MIME類型; * `Expires`:表示文檔的過期時間,到期不再緩存; * `Last-Modified`:表示文檔的最后改動時間; * `Pragma`:用來包含特定的指令;
但是當客戶端想要獲取額外的響應(yīng)頭字段時,就需要服務(wù)端通過在該字段后定義相應(yīng)的客戶端可獲取的響應(yīng)頭字段名稱。
以上就是簡單跨域AJAX請求,客戶端與服務(wù)端的交互,在繼續(xù)介紹復(fù)雜的跨域AJAX請求前,讓我們先停一停,回過頭來看看響應(yīng)報頭的Access-Control-Allow-Origin字段,談一談CORS規(guī)范中為什么默認不允許跨域AJAX請求攜帶Cookie,以及如果客戶端需要傳送Cookie時,客戶端與服務(wù)端又該如何交互的問題。
首先,我們要知道,在客戶端與服務(wù)端數(shù)據(jù)傳輸?shù)倪^程中,Cookie一直是以明文的形式伴隨著數(shù)據(jù)的傳輸,只要客戶端發(fā)送了Cookie至服務(wù)端,服務(wù)端就會至少返回該段Cookie。而我們又提到過,大多數(shù)網(wǎng)站都使用Cookie短暫存儲用戶會話中的身份信息,因此將Cookie暴露在外是存在安全隱患的,CSRF攻擊的目的便是獲取用戶的Cookie信息,因此在跨域AJAX請求中,為了減少Cookie泄露的風(fēng)險,CORS規(guī)范默認禁止跨域AJAX請求攜帶Cookie。
那么如果客戶端實在需要攜帶Cookie信息怎么辦呢?正如上文提到過的,需要客戶端與服務(wù)端一起配合,讓我們看看具體細節(jié):
首先是客戶端:
開發(fā)者需要在創(chuàng)建XMLHttpRequest對象實例時,手動配置withCredentials屬性,將其值設(shè)置為true:
var xhr = new XMLHttpRequest() xhr.withCredentials = true
某些瀏覽器會默認允許在跨域AJAX請求中發(fā)送Cookie,此時如果不想要發(fā)送Cookie,你只需要將其值設(shè)置為false。
其次是服務(wù)端:
對于服務(wù)端而言,除了像之前提到的要在響應(yīng)報頭設(shè)置Access-Control-Allow-Credential字段的值為true之外,還需要為Access-Control-Allow-Origin字段設(shè)置一個明確的域,不可以再使用*號。
相信你也能明白,這一切都是為了保護客戶端與服務(wù)端Cookie的隱私和安全。
現(xiàn)在我們可以繼續(xù)我們的主題,一起看一看如果我們的跨域AJAX請求超出了“簡單”的標準,客戶端與服務(wù)端又應(yīng)該如何相互配合,實現(xiàn)跨域的資源共享。
與簡單AJAX跨域請求不同,“復(fù)雜“的AJAX跨域請求一共會發(fā)送兩次HTTP請求,其中第一次為”查詢請求“,第二次才是我們正式的”AJAX跨域請求“。為什么多出了一次”查詢請求“呢?道理其實很簡單,我們想象一下當發(fā)送”復(fù)雜“的AJAX跨域請求時,瀏覽器最先拿到請求開始識別,然后發(fā)現(xiàn)這個請求并不“單純”(不滿足簡單跨域AJAX請求標準),于是感到十分疑惑的瀏覽器會試探的沿著請求的地址向服務(wù)端發(fā)問,詢問服務(wù)端是否允許異域的客戶端向它發(fā)送額外的請求信息,這一次“發(fā)問”,即是第一次HTTP請求,即“查詢請求”。而服務(wù)端當然也會這次“發(fā)問”給出相應(yīng)的回答,然后瀏覽器就會根據(jù)回答的結(jié)果決定是否繼續(xù)發(fā)送該跨域AJAX請求。
讓我們看看具體的實現(xiàn)細節(jié):
首先,讓我們創(chuàng)造出一個“復(fù)雜”的AJAX跨域請求:
var url = "http://another.com/cors" var xhr = new XMLHttpRequest() xhr.open("put", url, true) // 這里我們設(shè)置請求的方式為"put" xhr.setRequestHeader("X-Custom-Header", "Value") // 這里我們自定義了一個請求頭字段 xhr.send()
當瀏覽器識別到該請求“并不簡單”時,就會自動向服務(wù)其發(fā)送一個“查詢請求”,其報頭信息大致如下:
OPTIONS /cors HTTP/1.1 Origin: http://thisOne.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: another.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
注意這次“查詢請求”使用了“OPTIONS”的請求方法,表明了這是一個查詢請求。請求頭部的信息說明了請求來源的域,請求使用的HTTP方法以及請求額外發(fā)送的頭部字段。
讓我們再轉(zhuǎn)換至服務(wù)器視角,當服務(wù)端接收到瀏覽器發(fā)來的這樣一個查詢請求后,就可以判斷出是否應(yīng)該接收該請求。如果想要向瀏覽器表示允許該請求,則會返回這樣的響應(yīng)報文:
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61(Unix) Access-Control-Allow-Origin: http://thisOne.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header // 該字段值為以“,”號分割的字符串 Content-type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
讀到這里我們已經(jīng)大概猜的出服務(wù)端向瀏覽器傳遞的信息了:
首先,Access-Control-Allow-Origin字段向瀏覽器說明了發(fā)起AJAX請求的域是被服務(wù)器認可的(注意這個字段的值也可以為一個“*”號);
其次,Access-Control-Allow-Methods字段向瀏覽器說明了服務(wù)器接收跨域AJAX的請求方式;
最后,Access-Control-Allow-Headers字段向瀏覽器說明了服務(wù)器允許跨域AJAX額外發(fā)送的報頭信息;
當瀏覽器收到服務(wù)端這樣的表示同意請求的響應(yīng)后,就會正常發(fā)送接下來的跨域AJAX請求,而服務(wù)器也會正常的回應(yīng)。值的一提的是,在服務(wù)端與客戶端整個跨域AJAX請求的交互中,Access-Control-Allow-Origin頭信息自始至終都是必須攜帶的。
而當服務(wù)器在檢查“查詢請求”后,如果不同該請求,則會返回一個正常的HTTP響應(yīng),報文中包含任何與CORS規(guī)范有關(guān)的報頭字段,此時,瀏覽器就會心領(lǐng)神會的明白服務(wù)器拒絕接收發(fā)出的跨域AJAX請求,因此會返回一個錯誤狀態(tài)(可以被XML對象實例使用onerror回調(diào)函數(shù)捕獲)并在控制臺打印一條錯誤信息:
XMLHttpRequest cannot load http://another.com Origin http://thisOne.com is not allowed by Access-Control-Allow-Origin
至此,無論是“簡單”的跨域AJAX請求還是“復(fù)雜”的跨域AJAX請求,我們都已經(jīng)清楚的知曉了他們的運作原理,這真是件了不起的事情。但是先別著急慶祝,我們剛才還遺漏了一個話題沒有談到:“節(jié)約復(fù)雜AJAX跨域請求的HTTP請求數(shù)”。
相信你還記的,對于“復(fù)雜”的跨域AJAX請求,瀏覽器會向服務(wù)器發(fā)送兩次HTTP請求,雖然實際上兩次HTTP請求與一次HTTP請求所耗費的時間幾乎難以感知,但是如果我們有辦法一次搞定,又為什么還要重復(fù)做兩次呢?
對于服務(wù)器而言,“一次搞定”的方法就在于,在瀏覽器第一次發(fā)送復(fù)雜的跨域AJAX查詢請求時,在響應(yīng)報頭中添加Access-Control-Max-Age字段,這是一個可選的字段,它用來指定本次查詢請求的有效期,單位為秒。也就是說,通過該字段,服務(wù)器擁有了告知瀏覽器“這個請求我批準了,X秒以內(nèi)不需要再向我確認”的能力。至此,我們成功的將接下來的跨域請求數(shù)由兩次節(jié)約為一次!
三、小結(jié)一口氣看到這里?真不容易! 希望這是值得的,讓我們總結(jié)一下我們在本文中都談到了些什么。首先,我們談到了我們何時需要發(fā)起跨域AJAX請求的問題,做到了“知其然”。其次,我們深入探討了使用JSONP技術(shù)和CORS規(guī)范實現(xiàn)發(fā)送跨域AJAX請求的細節(jié),成功達到了我們“知其所以然”的目標。相信現(xiàn)在的你已經(jīng)對向他人談?wù)摗翱缬颉边@個主題充滿自信。真的很棒對吧?
如果你依然覺得意猶未盡,不妨接著和我繼續(xù)深入這個主題,看看實現(xiàn)跨域共享資源的另外兩種“時髦”的方式:使用 postMessage 和 webSocket。
感興趣嗎?休息一下,然后再回來,目前為止你表現(xiàn)的都非常出色!? 。
? Hey!喜歡這篇文章嗎?別忘了在下方? 點贊讓我知道。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/90213.html
摘要:一基于維基百科的定義,是一種在單個連接上進行全雙工通訊的協(xié)議。讓我們看看這個模型的具體實現(xiàn)下面是客戶端告知服務(wù)端要升級為協(xié)議的報頭下面是服務(wù)端向客戶端返回的響應(yīng)報頭想知道這些報頭中的字段中代表什么可以參考維基百科下的說明。 讓我們先簡單回顧一下之前談到的內(nèi)容,AJAX是一種無頁面刷新的獲取服務(wù)器資源的混合技術(shù)。而基于瀏覽器的同源策略,不同域之間不可以發(fā)送AJAX請求。但是在某些情境下,...
摘要:同源策略做了很嚴格的限制,但是在實際的場景中,又確實有很多地方需要突破同源策略的限制,也就是我們常說的跨域。使用跨域由于同源策略,一般來說位于的網(wǎng)頁無法與不是的服務(wù)器溝通,而的元素是一個例外。 本菜雞最近在寫某個頁面請求數(shù)據(jù)時,報了如下的錯誤。 Failed to load https://...:No Access-Control-Allow-Origin header is pre...
摘要:同源策略做了很嚴格的限制,但是在實際的場景中,又確實有很多地方需要突破同源策略的限制,也就是我們常說的跨域。使用跨域由于同源策略,一般來說位于的網(wǎng)頁無法與不是的服務(wù)器溝通,而的元素是一個例外。 本菜雞最近在寫某個頁面請求數(shù)據(jù)時,報了如下的錯誤。 Failed to load https://...:No Access-Control-Allow-Origin header is pre...
摘要:同源策略做了很嚴格的限制,但是在實際的場景中,又確實有很多地方需要突破同源策略的限制,也就是我們常說的跨域。使用跨域由于同源策略,一般來說位于的網(wǎng)頁無法與不是的服務(wù)器溝通,而的元素是一個例外。 本菜雞最近在寫某個頁面請求數(shù)據(jù)時,報了如下的錯誤。 Failed to load https://...:No Access-Control-Allow-Origin header is pre...
摘要:需要注意的是,并不是的替代品,兩者各自有其適應(yīng)的場景。但為了方便交流,我們通常將獲取資源的一方稱為客戶端主要的工具是瀏覽器,而將派發(fā)資源的一方稱為服務(wù)端又稱為服務(wù)器。它可以幫助我們?yōu)橹蟾拍罴毠?jié)的學(xué)習(xí)打下良好基礎(chǔ)。 再也不學(xué)AJAX了是一個與AJAX主題相關(guān)的文章系列,包含以下三個部分的內(nèi)容: AJAX概述:主要回答AJAX是什么這個問題; 使用AJAX:介紹如何通過JavaSc...
閱讀 1804·2023-04-26 02:32
閱讀 567·2021-11-18 13:12
閱讀 2446·2021-10-20 13:48
閱讀 2515·2021-10-14 09:43
閱讀 3825·2021-10-11 10:58
閱讀 3483·2021-09-30 10:00
閱讀 2932·2019-08-30 15:53
閱讀 3487·2019-08-30 15:53