摘要:同源策略指的是當(dāng)前頁(yè)面和目標(biāo)協(xié)議域名和端口均相同。發(fā)出請(qǐng)求的頁(yè)面所在域。響應(yīng)的頭部信息在后端處理,不在此處講解。該事件會(huì)在數(shù)據(jù)接收期間不斷觸發(fā),但間隔不確定。服務(wù)器確認(rèn)允許之后,才發(fā)起實(shí)際的請(qǐng)求。
ajax作為前端開發(fā)必需的基礎(chǔ)能力之一,你可能會(huì)使用它,但并不一定懂得其原理,以及更深入的服務(wù)器通信相關(guān)的知識(shí)。在最近兩天的整理過程中,看了大量的文章,發(fā)現(xiàn)自己的后端能力已經(jīng)限制自己在網(wǎng)絡(luò)通信相關(guān)的知識(shí)領(lǐng)域的探索,還是應(yīng)該盡快補(bǔ)齊短板。
下面我們來聊一聊ajax相關(guān)的東西,包括xhr/xdr/ajax/cors/http的一部分內(nèi)容,其中會(huì)拋棄一些被棄用的歷史包袱,如IE6/7等。
Ajax的出現(xiàn)2005年,Jesse James Garrett提出了Ajax的技術(shù),其全稱為Asynchronous Javascript and XML,Ajax的核心是XMLHttpRequest對(duì)象,簡(jiǎn)稱XHR,它用于使瀏覽器向服務(wù)器請(qǐng)求額外的數(shù)據(jù)而不卸載頁(yè)面,極大的提高了用戶體驗(yàn)。在此之前,其實(shí)這種技術(shù)已經(jīng)存在并被一些人實(shí)現(xiàn),但并沒有流行也沒有被瀏覽器支持。不過在此之后,IE5第一次引入XHR對(duì)象,并支持ajax技術(shù),后續(xù)被所有瀏覽器支持。
XMLHttpRequest對(duì)象和請(qǐng)求XHR是一個(gè)API,為客戶端提供服務(wù)端和客戶端之間通信的功能,并且不會(huì)刷新頁(yè)面。它并不僅僅能取回XML類型的數(shù)據(jù),而能取回所有類型的數(shù)據(jù),除了http協(xié)議,還支持file和ftp協(xié)議。我們可以通過其構(gòu)造函數(shù)來創(chuàng)建一個(gè)新的XHR對(duì)象,這個(gè)操作需要在其它所有操作之前完成:
var xhr = new XMLHttpRequest();
通過控制臺(tái)我們可以很方便看到XHR的原型鏈:Object -> EventTarget -> XMLHttpRequestEventTarget -> XMLHttpRequest。它擁有原型鏈上和本身的方法和屬性,現(xiàn)在看下我們常用的方法:
我們解釋下它的幾個(gè)主要方法,我們?cè)趧?chuàng)建了新的xhr對(duì)象之后,首先要調(diào)用它的open()方法:
// 第一個(gè)參數(shù)可以為get/post等,表示該請(qǐng)求的類型 // 第二個(gè)參數(shù)是請(qǐng)求的url,可以為相對(duì)路徑或絕對(duì)路徑 // 第三個(gè)參數(shù)代表是否異步,為true時(shí)異步,為false時(shí)同步 // 第四五個(gè)參數(shù)為可選的授權(quán)使用的參數(shù),因?yàn)榘踩圆煌扑]明文使用 xhr.open("get", "example.php", true, username, password);
在這里受同源策略的影響,當(dāng)?shù)诙€(gè)參數(shù)url跨域的時(shí)候會(huì)被瀏覽器報(bào)安全錯(cuò)誤。同源策略指的是當(dāng)前頁(yè)面和目標(biāo)url協(xié)議、域名和端口均相同。后面也會(huì)講到,除IE之外的瀏覽器通過XHR對(duì)象實(shí)現(xiàn)跨域請(qǐng)求,只需將url設(shè)置為絕對(duì)url即可。
當(dāng)初始化請(qǐng)求完成后,我們調(diào)用send()方法發(fā)送請(qǐng)求:
var data = new FormData(); data.append("name", "Nicholas"); // 接受一個(gè)請(qǐng)求主體發(fā)送的數(shù)據(jù),如果不需要,傳入null xhr.send(data);
當(dāng)請(qǐng)求的類型為get/head時(shí),send()的參數(shù)會(huì)被忽略并置為null,send()傳遞的參數(shù)會(huì)影響到我們請(qǐng)求的頭部content-type的默認(rèn)值,該字段代表返回的資源內(nèi)容的類型,用于瀏覽器處理,如果沒有設(shè)置或在一些場(chǎng)景下,瀏覽器會(huì)進(jìn)行MIME嗅探來確定怎么處理返回的資源。
在XHR2級(jí)中定義了FormData數(shù)據(jù),用于常見的類表單數(shù)據(jù)序列化:
// 直接傳入表單id var data = new FormData(document.getElementById("user-form")); // 創(chuàng)建類表單數(shù)據(jù) var data = new FormData(); data.append("name", "Nicholas"); // `FormData`可以直接被send()調(diào)用,會(huì)自動(dòng)修改xhr的content-type頭部 xhr.send(data); // 請(qǐng)求頭部的content-type: multipart/form-data; boundary=----WebKitFormBoundaryjn3q2KKRYrEH55Vz // 請(qǐng)求的上傳數(shù)據(jù) Request Payload: ------WebKitFormBoundaryjn3q2KKRYrEH55Vz Content-Disposition: form-data; name="name" Nicholas ------WebKitFormBoundaryjn3q2KKRYrEH55Vz--
FormData常用的方法有append/delete/entries/forEach/get/getAll/has/keys/set/values,都是常用的跟數(shù)組類似的方法,不再解釋。
請(qǐng)求方法GET是最常見的請(qǐng)求類型,可以將查詢字符串參數(shù)添加到URL尾部,對(duì)XHR而言,該查詢字符串必須經(jīng)過正確編碼,每個(gè)鍵值對(duì)必須使用encodeURIComponent()進(jìn)行編碼,鍵值對(duì)之間由&分割:
// 封裝序列化鍵值對(duì) function addURLParam(url, name, value) { url += (url.indexOf("?") === -1 ? "?" : "&"; url += encodeURIComponent(name) + "=" + encodeURIComponent(value); return url; }
POST請(qǐng)求使用頻率僅次于GET請(qǐng)求,通常發(fā)送較多數(shù)據(jù),且格式不限,數(shù)據(jù)傳遞給send()作為參數(shù)。
HTTP一共規(guī)定了九種請(qǐng)求方法,每一個(gè)動(dòng)詞代表不同的語(yǔ)義,但是常用的只有上面兩種:
- OPTIONS:返回服務(wù)器針對(duì)特定資源所支持的HTTP請(qǐng)求方法。也可以利用向Web服務(wù)器發(fā)送"*"的請(qǐng)求來測(cè)試服務(wù)器的功能性。 - HEAD:向服務(wù)器索要與GET請(qǐng)求相一致的響應(yīng),只不過響應(yīng)體將不會(huì)被返回。這一方法可以在不必傳輸整個(gè)響應(yīng)內(nèi)容的情況下,就可以獲取包含在響應(yīng)消息頭中的元信息。 - GET:向特定的資源發(fā)出請(qǐng)求。 - POST:向指定資源提交數(shù)據(jù)進(jìn)行處理請(qǐng)求(例如提交表單或者上傳文件)。數(shù)據(jù)被包含在請(qǐng)求體中。POST請(qǐng)求可能會(huì)導(dǎo)致新的資源的創(chuàng)建和/或已有資源的修改。 - PUT:向指定資源位置上傳其最新內(nèi)容。 - DELETE:請(qǐng)求服務(wù)器刪除Request-URI所標(biāo)識(shí)的資源。 - TRACE:回顯服務(wù)器收到的請(qǐng)求,主要用于測(cè)試或診斷。 - CONNECT:HTTP/1.1協(xié)議中預(yù)留給能夠?qū)⑦B接改為管道方式的代理服務(wù)器。 - PATCH: 用于對(duì)資源進(jìn)行部分修改HTTP頭部信息
每個(gè)HTTP請(qǐng)求和響應(yīng)都帶有頭部信息,xhr對(duì)象允許我們操作部分頭部信息。我們可以通過xhr.setRequestHeader()方法來設(shè)置自定義的頭部信息或者修改瀏覽器默認(rèn)的正常頭部信息。常用的請(qǐng)求頭部:
// 下面的實(shí)例是從我本地的一次請(qǐng)求取出的 Accept: 瀏覽器能夠處理的內(nèi)容類型。// */* Accept-Charset: 瀏覽器能夠顯示的字符集。// 未取到 Accept-Encoding: 瀏覽器能夠處理的壓縮編碼。// gzip,deflate Accept-Language: 瀏覽器當(dāng)前設(shè)置的語(yǔ)言。// zh-CN,zh;q=0.8,en;q=0.6 Connection: 瀏覽器與服務(wù)器之間連接的類型。// keep-alive Cookie: 當(dāng)前頁(yè)面設(shè)置的任意Cookie。// JlogDataSource=jomodb Host: 發(fā)出請(qǐng)求的頁(yè)面所在域。// gzhxy-cdn-oss-06.gzhxy.baidu.com:8090 Referer: 發(fā)出請(qǐng)求的頁(yè)面URI。// http://gzhxy-cdn-oss-06.gzhxy.baidu.com:8090/jomocha/index.php?r=tools/offline/index User-Agent: 瀏覽器的用戶代理字符串。// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
我們一般不修改瀏覽器正常的頭部信息,可能會(huì)影響到服務(wù)器響應(yīng)。如果需要可以通過xhr.setRequestHeader()進(jìn)行修改:
// 傳入頭部鍵值對(duì),鍵值不區(qū)分大小寫,如果多次設(shè)置,則追加 // 此時(shí)請(qǐng)求頭部的content-type: application/json, text/html xhr.setRequestHeader("content-type", "application/json"); xhr.setRequestHeader("content-type", "application/json");
設(shè)置頭部信息需要在open()之后,send()之前進(jìn)行調(diào)用。響應(yīng)的頭部信息在后端處理,不在此處講解。有一部分請(qǐng)求頭部信息不允許設(shè)置,如Accept-Encoding, Cookie等。
在請(qǐng)求返回后,我們可以獲取到響應(yīng)頭部:
// 獲取指定項(xiàng)的響應(yīng)頭 xhr.getResponseHeader("content-type"); // application/json;charset=utf-8 // 獲取所有的響應(yīng)頭部信息 xhr.getAllResponseHeaders();
這里簡(jiǎn)單說下content-type值,指的是請(qǐng)求和響應(yīng)的HTTP內(nèi)容類型,影響到服務(wù)器和瀏覽器對(duì)數(shù)據(jù)的處理方式,默認(rèn)為text/html,常用的如:
// 包含資源類型,字符編碼, 邊界字符串三個(gè)參數(shù),可選填 text/html;charset=utf-8 // html標(biāo)簽文本 text/plain // 純文本 text/css // css文件 text/javascript // js文件 // 普通的表單數(shù)據(jù),可以通過表單標(biāo)簽的enctype屬性指定 application/x-www-form-urlencode // 發(fā)送文件的POST包,包過大需要分片時(shí)使用`boundary`屬性分割數(shù)據(jù)作邊界 multipart/form-data; boundary=something // json數(shù)據(jù)格式 application/json // xml類型的標(biāo)記語(yǔ)言 application/xmlXHR對(duì)象的響應(yīng)
我們現(xiàn)在對(duì)請(qǐng)求的發(fā)起很了解了,接著看下如何拿到響應(yīng)數(shù)據(jù)。如果我們給open()傳遞的第三個(gè)參數(shù)是true,則代表為同步請(qǐng)求,那么js會(huì)被阻塞直到拿到響應(yīng),而如果為false則是異步請(qǐng)求,我們只需要綁定xhr.onreadystatechange()事件監(jiān)聽響應(yīng)即可。最上面的圖已經(jīng)說明了readystate的值含義,所以我們可以:
// xhr v1 的寫法,檢測(cè)readystate的值,為4則說明數(shù)據(jù)準(zhǔn)備完畢,需要在open()前定義 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200 || xhr.status === 304) { console.log(xhr.responseText); } else { console.log(xhr.statusText); } } } // xhr v2 的寫法,onload()事件說明數(shù)據(jù)準(zhǔn)備完畢 xhr.onload = function () { if (xhr.status === 200 || xhr.status === 304) { console.log(xhr.responseText); } else { console.log(xhr.statusText); } }
xhr對(duì)象的響應(yīng)數(shù)據(jù)中包含幾個(gè)屬性:
response // 響應(yīng)的數(shù)據(jù) responseURL // 發(fā)起響應(yīng)的URL responseType // 響應(yīng)的類型,用于瀏覽器強(qiáng)行重置響應(yīng)數(shù)據(jù)的類型 responseText // 如果為普通文本,則在這顯示 responseXML // 如果為xml類型文本,在這里顯示
數(shù)據(jù)會(huì)出現(xiàn)在responseText/responseXML中的哪一個(gè),取決于服務(wù)器返回的MIME類型,當(dāng)然我們也有一些方式在瀏覽器端設(shè)置如何處理這些數(shù)據(jù):
// xhr v1 的寫法,設(shè)置響應(yīng)資源的處理類型 xhr.overrideMimeType("text/xml"); // xhr v2 的寫法, 可用值為 arraybuffer/blob/document/json/text xhr.responseType = "document";
響應(yīng)數(shù)據(jù)相關(guān)的屬性默認(rèn)為null / "",只有當(dāng)請(qǐng)求完成并被正確解析的時(shí)候才會(huì)有值,取決于responseType的值,來確定response/responseText/responseXML誰最終具有值。
XHR的高級(jí)功能在xhr v2里提供了超時(shí)和進(jìn)度事件。
超時(shí)xhr.timeout = 1000; // 1分鐘,單位為ms xhr.ontimeout = function () {};
在請(qǐng)求send()之后開始計(jì)時(shí),等待timeout時(shí)長(zhǎng)后,如果沒有收到響應(yīng),則觸發(fā)ontimeout()事件,超時(shí)會(huì)將readystate=4,直接觸發(fā)onreadystatechange()事件。
請(qǐng)求進(jìn)度像上圖所示,xhr v2定義了不同的進(jìn)度事件:loadstart/progress/error/abort/load/loadend,這其中我們已經(jīng)說過了onload()事件為內(nèi)容加載完成可用。現(xiàn)在說一下onprogress()進(jìn)度事件:
xhr.onprogress = function (event) { if (event.lengthComputable) { console.log(event.loaded / event.total); } }
該事件會(huì)接收一個(gè)event對(duì)象,其target屬性為該xhr對(duì)象,lengthComputable屬性為total size是否已知,即是否可用進(jìn)度信息,loaded屬性為已經(jīng)接收的字節(jié)數(shù),total為總字節(jié)數(shù)。該事件會(huì)在數(shù)據(jù)接收期間不斷觸發(fā),但間隔不確定。
跨域CORS提到XHR對(duì)象,我們就會(huì)講到跨域問題,它是為了預(yù)防某些惡意行為的安全策略,但有時(shí)候我們需要跨域來實(shí)現(xiàn)某些功能。需要注意的是跨域并不僅僅是前端單方面的事情,它需要后端代碼進(jìn)行配合,我們只是通過一些方式跳過了瀏覽器的阻攔。
對(duì)那些可能對(duì)服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請(qǐng)求方法(特別是 GET 以外的 HTTP 請(qǐng)求,或者搭配某些 MIME 類型的 POST 請(qǐng)求),瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個(gè)預(yù)檢請(qǐng)求(preflight request),從而獲知服務(wù)端是否允許該跨域請(qǐng)求。服務(wù)器確認(rèn)允許之后,才發(fā)起實(shí)際的 HTTP 請(qǐng)求。
CORS(Cross-Origin Resource Sharing, 跨域資源共享)的思想是瀏覽器和服務(wù)端通過頭部信息來進(jìn)行溝通確認(rèn)是否給予響應(yīng)。如:
Origin: http://www.baidu.com // 瀏覽器的頭部信息 // 如果服務(wù)端認(rèn)可這個(gè)域名的跨域請(qǐng)求,如下設(shè)置就可跨域訪問資源 Access-Control-Allow-Origin: http://www.baidu.com
如上就可以實(shí)現(xiàn)最簡(jiǎn)單的跨域訪問,但是此時(shí)不能攜帶任何的cookie,如果我們需要傳遞cookie進(jìn)行身份認(rèn)證,需要設(shè)置:
xhr.withCredentials = true; // 瀏覽器端 Access-Control-Allow-Credentials: true; // 服務(wù)端
這樣我們就可以傳遞認(rèn)證信息了,但如果允許認(rèn)證,Access-Control-Allow-Origin不能設(shè)置為*,而一定是具體的域名信息。
現(xiàn)在的瀏覽器都對(duì)CORS有了實(shí)現(xiàn),如IE使用XDomainRequest對(duì)象,其它瀏覽器使用XMLHttpRequest對(duì)象。所以在此之前有很多奇技淫巧,如通過jsonp/圖像 Ping方法都不再詳述,而且其都需要服務(wù)端配合并且有很多局限性。
IE實(shí)現(xiàn): XDomainRequestvar xdr = new XDomainRequest(); xdr.open("get", "http://www.site.com/page"); xdr.send(null);
XDR區(qū)別于普通XHR:
不能傳輸cookie
只能設(shè)置請(qǐng)求頭部的content-type
不能訪問響應(yīng)頭部信息
只支持get/post方法
通過這些區(qū)別可以阻止一部分的CSRF(Cross-Site Request Forgery,跨站點(diǎn)請(qǐng)求偽造)和XSS(Cross-Site Scripting,跨站點(diǎn)腳本)。
XDR與XHR的使用上非常相似,區(qū)別有幾點(diǎn):
open()方法只接受兩個(gè)參數(shù),請(qǐng)求類型和URL
只允許異步請(qǐng)求
響應(yīng)完成觸發(fā)onload()事件,但我們只能訪問responseText原始文本,并且無法獲取響應(yīng)的status.
異常事件都會(huì)觸發(fā)error事件,并且無錯(cuò)誤信息可用。
其余瀏覽器實(shí)現(xiàn): XMLHttpRequest其余瀏覽器通過XHR對(duì)象直接實(shí)現(xiàn)了CORS,你只需要做的就是open()方法中傳入一個(gè)絕對(duì)URL。
xhr.open("get", "http://www.site.com/page", true);
相對(duì)于普通的XHR對(duì)象,CORS-XHR依然有部分限制:
不能使用setRequestHeader()定義頭部
不能傳遞cookie
調(diào)用getAllResponseHeaders(),結(jié)果為空
其余跨域方法上面的兩種方法已經(jīng)很成熟了,但是仍然有一部分方法可以跨域,比如圖像Ping:
var img = new Image(); img.onload = img.onerror = function () { console.log("done"); } img.src = "http://www.site.com/test?name=Nicholas";
這種方式常用于服務(wù)端統(tǒng)計(jì)廣告的點(diǎn)擊次數(shù),其缺陷為:
只能是GET請(qǐng)求
單向通信,無法獲取響應(yīng)文本
另外還有JSONP:
function handleResponse(response) { console.log(response.ip, response.city); } var script = document.createElement("script"); script.src = "http://freegeoip.net/json?callback=handleResponse"; document.body.insertBefore(script, document.body.firstChild);
這種方式通過和服務(wù)器配合,跨域請(qǐng)求一個(gè)js文件并被服務(wù)器處理后傳回:
handleResponse({"name": "Nicholas"});
然后直接在瀏覽器調(diào)用了該函數(shù),傳回的數(shù)據(jù)被當(dāng)做response形參進(jìn)行處理。但它也有一些缺陷:
訪問的方式是請(qǐng)求js,所以如果域名不安全,則很容易被惡意代碼直接執(zhí)行并攻擊
無法檢測(cè)是否錯(cuò)誤,因?yàn)閖s不支持這樣的接口事件,只能超時(shí)判斷
上面兩種方式很容易看出,我們?cè)谥С諧ORS之前,使用的方法只不過是采用img/css/js等不受跨域訪問限制的對(duì)象,變相拿到了響應(yīng)數(shù)據(jù),但都有缺陷,所以如果沒有歷史包袱,建議采用XDR或XHR對(duì)象來實(shí)現(xiàn)跨域訪問。
XHR的兼容性我們可以直接到Can I use這個(gè)網(wǎng)站上查詢兼容性問題:
MDN - 禁止修改的消息首部:https://developer.mozilla.org...
理解HTTP之Content-Type:http://homeway.me/2015/07/19/...
MDN - Content-Type:https://developer.mozilla.org...
HTTP請(qǐng)求方法:https://developer.mozilla.org...
Javascript高級(jí)程序設(shè)計(jì) 第21章(Ajax與Comet)
你真的會(huì)使用XHR嗎? https://segmentfault.com/a/11...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/89027.html
摘要:關(guān)于,強(qiáng)烈推薦閱讀跨域資源共享詳解阮一峰另外,這里也整理了一個(gè)實(shí)現(xiàn)原理圖簡(jiǎn)化版如何判斷是否是簡(jiǎn)單請(qǐng)求瀏覽器將請(qǐng)求分成兩類簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求。 前言 從剛接觸前端開發(fā)起,跨域這個(gè)詞就一直以很高的頻率在身邊重復(fù)出現(xiàn),一直到現(xiàn)在,已經(jīng)調(diào)試過N個(gè)跨域相關(guān)的問題了,16年時(shí)也整理過一篇相關(guān)文章,但是感覺還是差了點(diǎn)什么,于是現(xiàn)在重新梳理了一下。 個(gè)人見識(shí)有限,如有差錯(cuò),請(qǐng)多多見諒,歡迎提出iss...
摘要:在接觸前端開發(fā)起,跨域這個(gè)詞就一直以很高的頻率在我們學(xué)習(xí)工作中重復(fù)出現(xiàn),最近在工作中遇到了跨域的相關(guān)問題,這里我把它總結(jié)記錄一下。 在接觸前端開發(fā)起,跨域這個(gè)詞就一直以很高的頻率在我們學(xué)習(xí)工作中重復(fù)出現(xiàn),最近在工作中遇到了跨域的相關(guān)問題,這里我把它總結(jié)記錄一下。關(guān)于跨域,有N種類型,現(xiàn)在我只專注于ajax請(qǐng)求跨域(ajax跨域只是屬于瀏覽器同源策略中的一部分,其它的這里不做介紹),內(nèi)容...
摘要:摘自阮一峰博客延伸閱讀不涉及跨域跨源資源分享為標(biāo)準(zhǔn)兼容性參考優(yōu)點(diǎn)提供安全的跨域數(shù)據(jù)傳輸,且不限于請(qǐng)求。跨域資源共享阿里云技術(shù)文檔跨域資源共享詳解阮一峰 參考:維基百科 - Root domainhttps://en.wikipedia.org/wiki...瀏覽器同源政策及其規(guī)避方法 - 阮一峰http://www.ruanyifeng.com/blo...window.name 跨域...
摘要:所以瀏覽器認(rèn)為這是安全的。如果你細(xì)心的話你會(huì)發(fā)現(xiàn),其實(shí)請(qǐng)求已經(jīng)發(fā)送出去了,你只是拿不到響應(yīng)而已。所以瀏覽器這個(gè)策略的本質(zhì)是,一個(gè)域名的,在未經(jīng)允許的情況下,不得讀取另一個(gè)域名的內(nèi)容。但瀏覽器并不阻止你向另一個(gè)域名發(fā)送請(qǐng)求。 同源策略與CORS跨域 PS:這篇文章是緊接著JSONP原理和Ajax學(xué)習(xí)與理解寫的,有些內(nèi)容是承接了上兩篇文章.這篇文章只算是我的個(gè)人學(xué)習(xí)筆記,內(nèi)容沒有經(jīng)過精心排...
閱讀 1296·2023-04-25 19:33
閱讀 1180·2021-10-21 09:39
閱讀 3652·2021-09-09 09:32
閱讀 2631·2019-08-30 10:58
閱讀 1627·2019-08-29 16:17
閱讀 884·2019-08-29 15:29
閱讀 2895·2019-08-26 11:55
閱讀 2666·2019-08-26 10:33