99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Javascript Fetch:跨源請(qǐng)求

2023-02-17 10:57 更新

如果我們向另一個(gè)網(wǎng)站發(fā)送 ?fetch? 請(qǐng)求,則該請(qǐng)求可能會(huì)失敗。

例如,讓我們嘗試向 http://example.com 發(fā)送 fetch 請(qǐng)求:

try {
  await fetch('http://example.com');
} catch(err) {
  alert(err); // fetch 失敗
}

正如所料,獲取失敗。

這里的核心概念是 源(origin)—— 域(domain)/端口(port)/協(xié)議(protocol)的組合。

跨源請(qǐng)求 —— 那些發(fā)送到其他域(即使是子域)、協(xié)議或端口的請(qǐng)求 —— 需要來(lái)自遠(yuǎn)程端的特殊 header。

這個(gè)策略被稱(chēng)為 “CORS”:跨源資源共享(Cross-Origin Resource Sharing)。

為什么需要 CORS?跨源請(qǐng)求簡(jiǎn)史

CORS 的存在是為了保護(hù)互聯(lián)網(wǎng)免受黑客攻擊。

說(shuō)真的,在這說(shuō)點(diǎn)兒題外話,講講它的歷史。

多年來(lái),來(lái)自一個(gè)網(wǎng)站的腳本無(wú)法訪問(wèn)另一個(gè)網(wǎng)站的內(nèi)容。

這個(gè)簡(jiǎn)單有力的規(guī)則是互聯(lián)網(wǎng)安全的基礎(chǔ)。例如,來(lái)自 hacker.com 的腳本無(wú)法訪問(wèn) gmail.com 上的用戶(hù)郵箱。基于這樣的規(guī)則,人們感到很安全。

在那時(shí)候,JavaScript 并沒(méi)有任何特殊的執(zhí)行網(wǎng)絡(luò)請(qǐng)求的方法。它只是一種用來(lái)裝飾網(wǎng)頁(yè)的玩具語(yǔ)言而已。

但是 Web 開(kāi)發(fā)人員需要更多功能。人們發(fā)明了各種各樣的技巧去突破該限制,并向其他網(wǎng)站發(fā)出請(qǐng)求。

使用表單

其中一種和其他服務(wù)器通信的方法是在那里提交一個(gè) <form>。人們將它提交到 <iframe>,只是為了停留在當(dāng)前頁(yè)面,像這樣:

<!-- 表單目標(biāo) -->
<iframe name="iframe"></iframe>

<!-- 表單可以由 JavaScript 動(dòng)態(tài)生成并提交 -->
<form target="iframe" method="POST" action="http://another.com/…">
  ...
</form>

因此,即使沒(méi)有網(wǎng)絡(luò)方法,也可以向其他網(wǎng)站發(fā)出 GET/POST 請(qǐng)求,因?yàn)楸韱慰梢詫?shù)據(jù)發(fā)送到任何地方。但是由于禁止從其他網(wǎng)站訪問(wèn) <iframe> 中的內(nèi)容,因此就無(wú)法讀取響應(yīng)。

確切地說(shuō),實(shí)際上有一些技巧能夠解決這個(gè)問(wèn)題,這在 iframe 和頁(yè)面中都需要添加特殊腳本。因此,與 iframe 的通信在技術(shù)上是可能的?,F(xiàn)在我們沒(méi)必要講其細(xì)節(jié)內(nèi)容,我們還是讓這些古董代碼不要再出現(xiàn)了吧。

使用 script

另一個(gè)技巧是使用 script 標(biāo)簽。script 可以具有任何域的 src,例如 <script src="http://another.com/…" rel="external nofollow" >。也可以執(zhí)行來(lái)自任何網(wǎng)站的 script。

如果一個(gè)網(wǎng)站,例如 another.com 試圖公開(kāi)這種訪問(wèn)方式的數(shù)據(jù),則會(huì)使用所謂的 “JSONP (JSON with padding)” 協(xié)議。

這是它的工作方式。

假設(shè)在我們的網(wǎng)站,需要以這種方式從 http://another.com 網(wǎng)站獲取數(shù)據(jù),例如天氣:

  1. 首先,我們先聲明一個(gè)全局函數(shù)來(lái)接收數(shù)據(jù),例如 ?gotWeather?。
  2. // 1. 聲明處理天氣數(shù)據(jù)的函數(shù)
    function gotWeather({ temperature, humidity }) {
      alert(`temperature: ${temperature}, humidity: ${humidity}`);
    }
  3. 然后我們創(chuàng)建一個(gè)特性(attribute)為 src="http://another.com/weather.json?callback=gotWeather" rel="external nofollow"  的 <script> 標(biāo)簽,使用我們的函數(shù)名作為它的 callback URL-參數(shù)。
  4. let script = document.createElement('script');
    script.src = `http://another.com/weather.json?callback=gotWeather`;
    document.body.append(script);
  5. 遠(yuǎn)程服務(wù)器 another.com 動(dòng)態(tài)生成一個(gè)腳本,該腳本調(diào)用 gotWeather(...),發(fā)送它想讓我們接收的數(shù)據(jù)。
  6. // 我們期望來(lái)自服務(wù)器的回答看起來(lái)像這樣:
    gotWeather({
      temperature: 25,
      humidity: 78
    });
  7. 當(dāng)遠(yuǎn)程腳本加載并執(zhí)行時(shí),?gotWeather? 函數(shù)將運(yùn)行,并且因?yàn)樗俏覀兊暮瘮?shù),我們就有了需要的數(shù)據(jù)。

這是可行的,并且不違反安全規(guī)定,因?yàn)殡p方都同意以這種方式傳遞數(shù)據(jù)。而且,既然雙方都同意這種行為,那這肯定不是黑客攻擊了。現(xiàn)在仍然有提供這種訪問(wèn)的服務(wù),因?yàn)榧词故欠浅Ef的瀏覽器它依然適用。

不久之后,網(wǎng)絡(luò)方法出現(xiàn)在了瀏覽器 JavaScript 中。

起初,跨源請(qǐng)求是被禁止的。但是,經(jīng)過(guò)長(zhǎng)時(shí)間的討論,跨源請(qǐng)求被允許了,但是任何新功能都需要服務(wù)器明確允許,以特殊的 header 表述。

安全請(qǐng)求

有兩種類(lèi)型的跨源請(qǐng)求:

  1. 安全請(qǐng)求。
  2. 所有其他請(qǐng)求。

安全請(qǐng)求很簡(jiǎn)單,所以我們先從它開(kāi)始。

如果一個(gè)請(qǐng)求滿(mǎn)足下面這兩個(gè)條件,則該請(qǐng)求是安全的:

  1. 安全的方法:GET,POST 或 HEAD
  2. 安全的 header —— 僅允許自定義下列 header:
    • ?Accept?,
    • ?Accept-Language?,
    • ?Content-Language?,
    • ?Content-Type? 的值為 ?application/x-www-form-urlencoded?,?multipart/form-data? 或 ?text/plain?。

任何其他請(qǐng)求都被認(rèn)為是“非安全”請(qǐng)求。例如,具有 PUT 方法或 API-Key HTTP-header 的請(qǐng)求就不是安全請(qǐng)求。

本質(zhì)區(qū)別在于,可以使用 <form> 或 <script> 進(jìn)行安全請(qǐng)求,而無(wú)需任何其他特殊方法。

因此,即使是非常舊的服務(wù)器也能很好地接收安全請(qǐng)求。

與此相反,帶有非標(biāo)準(zhǔn) header 或者例如 DELETE 方法的請(qǐng)求,無(wú)法通過(guò)這種方式創(chuàng)建。在很長(zhǎng)一段時(shí)間里,JavaScript 都不能進(jìn)行這樣的請(qǐng)求。所以,舊的服務(wù)器可能會(huì)認(rèn)為此類(lèi)請(qǐng)求來(lái)自具有特權(quán)的來(lái)源(privileged source),“因?yàn)榫W(wǎng)頁(yè)無(wú)法發(fā)送它們”。

當(dāng)我們嘗試發(fā)送一個(gè)非安全請(qǐng)求時(shí),瀏覽器會(huì)發(fā)送一個(gè)特殊的“預(yù)檢(preflight)”請(qǐng)求到服務(wù)器 —— 詢(xún)問(wèn)服務(wù)器,你接受此類(lèi)跨源請(qǐng)求嗎?

并且,除非服務(wù)器明確通過(guò) header 進(jìn)行確認(rèn),否則非安全請(qǐng)求不會(huì)被發(fā)送。

現(xiàn)在,我們來(lái)詳細(xì)介紹它們。

用于安全請(qǐng)求的 CORS

如果一個(gè)請(qǐng)求是跨源的,瀏覽器始終會(huì)向其添加 Origin header。

例如,如果我們從 https://javascript.info/page 請(qǐng)求 https://anywhere.com/request,請(qǐng)求的 header 將如下所示:

GET /request
Host: anywhere.com
Origin: https://javascript.info
...

正如你所看到的,Origin 包含了確切的源(domain/protocol/port),沒(méi)有路徑(path)。

服務(wù)器可以檢查 Origin,如果同意接受這樣的請(qǐng)求,就會(huì)在響應(yīng)中添加一個(gè)特殊的 header Access-Control-Allow-Origin。該 header 包含了允許的源(在我們的示例中是 https://javascript.info),或者一個(gè)星號(hào) *。然后響應(yīng)成功,否則報(bào)錯(cuò)。

瀏覽器在這里扮演受被信任的中間人的角色:

  1. 它確保發(fā)送的跨源請(qǐng)求帶有正確的 ?Origin?。
  2. 它檢查響應(yīng)中的許可 ?Access-Control-Allow-Origin?,如果存在,則允許 JavaScript 訪問(wèn)響應(yīng),否則將失敗并報(bào)錯(cuò)。


這是一個(gè)帶有服務(wù)器許可的響應(yīng)示例:

200 OK
Content-Type:text/html; charset=UTF-8
Access-Control-Allow-Origin: https://javascript.info

Response header

對(duì)于跨源請(qǐng)求,默認(rèn)情況下,JavaScript 只能訪問(wèn)“安全的” response header:

  • ?Cache-Control?
  • ?Content-Language?
  • ?Content-Type?
  • ?Expires?
  • ?Last-Modified?
  • ?Pragma?

訪問(wèn)任何其他 response header 都將導(dǎo)致 error。

請(qǐng)注意:

請(qǐng)注意:列表中沒(méi)有 Content-Length header!

該 header 包含完整的響應(yīng)長(zhǎng)度。因此,如果我們正在下載某些內(nèi)容,并希望跟蹤進(jìn)度百分比,則需要額外的權(quán)限才能訪問(wèn)該 header(請(qǐng)見(jiàn)下文)。

要授予 JavaScript 對(duì)任何其他 response header 的訪問(wèn)權(quán)限,服務(wù)器必須發(fā)送 Access-Control-Expose-Headers header。它包含一個(gè)以逗號(hào)分隔的應(yīng)該被設(shè)置為可訪問(wèn)的非安全 header 名稱(chēng)列表。

例如:

200 OK
Content-Type:text/html; charset=UTF-8
Content-Length: 12345
API-Key: 2c9de507f2c54aa1
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Expose-Headers: Content-Length,API-Key

有了這種 Access-Control-Expose-Headers header,此腳本就被允許讀取響應(yīng)的 Content-Length 和 API-Key header。

“非安全”請(qǐng)求

我們可以使用任何 HTTP 方法:不僅僅是 GET/POST,也可以是 PATCH,DELETE 及其他。

之前,沒(méi)有人能夠設(shè)想網(wǎng)頁(yè)能發(fā)出這樣的請(qǐng)求。因此,可能仍然存在有些 Web 服務(wù)將非標(biāo)準(zhǔn)方法視為一個(gè)信號(hào):“這不是瀏覽器”。它們可以在檢查訪問(wèn)權(quán)限時(shí)將其考慮在內(nèi)。

因此,為了避免誤解,任何“非安全”請(qǐng)求 —— 在過(guò)去無(wú)法完成的,瀏覽器不會(huì)立即發(fā)出此類(lèi)請(qǐng)求。首先,它會(huì)先發(fā)送一個(gè)初步的、所謂的“預(yù)檢(preflight)”請(qǐng)求,來(lái)請(qǐng)求許可。

預(yù)檢請(qǐng)求使用 OPTIONS 方法,它沒(méi)有 body,但是有三個(gè) header:

  • ?Access-Control-Request-Method header? 帶有非安全請(qǐng)求的方法。
  • ?Access-Control-Request-Headers header? 提供一個(gè)以逗號(hào)分隔的非安全 HTTP-header 列表。

如果服務(wù)器同意處理請(qǐng)求,那么它會(huì)進(jìn)行響應(yīng),此響應(yīng)的狀態(tài)碼應(yīng)該為 200,沒(méi)有 body,具有 header:

  • ?Access-Control-Allow-Origin? 必須為 ?*? 或進(jìn)行請(qǐng)求的源(例如 ?https://javascript.info?)才能允許此請(qǐng)求。
  • ?Access-Control-Allow-Methods? 必須具有允許的方法。
  • ?Access-Control-Allow-Headers? 必須具有一個(gè)允許的 header 列表。
  • 另外,header ?Access-Control-Max-Age? 可以指定緩存此權(quán)限的秒數(shù)。因此,瀏覽器不是必須為滿(mǎn)足給定權(quán)限的后續(xù)請(qǐng)求發(fā)送預(yù)檢。


讓我們?cè)谝粋€(gè)跨源 PATCH 請(qǐng)求的例子中一步一步地看它是如何工作的(此方法經(jīng)常被用于更新數(shù)據(jù)):

let response = await fetch('https://site.com/service.json', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'API-Key': 'secret'
  }
});

這里有三個(gè)理由解釋為什么它不是一個(gè)安全請(qǐng)求(其實(shí)一個(gè)就夠了):

  • 方法 ?PATCH?
  • ?Content-Type? 不是這三個(gè)中之一:?application/x-www-form-urlencoded?,?multipart/form-data?,?text/plain?。
  • “非安全” ?API-Key? header。

Step 1 預(yù)檢請(qǐng)求(preflight request)

在發(fā)送我們的請(qǐng)求前,瀏覽器會(huì)自己發(fā)送如下所示的預(yù)檢請(qǐng)求:

OPTIONS /service.json
Host: site.com
Origin: https://javascript.info
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type,API-Key
  • 方法:?OPTIONS?。
  • 路徑 —— 與主請(qǐng)求完全相同:?/service.json?。
  • 特殊跨源頭:
    • ?Origin? —— 來(lái)源。
    • ?Access-Control-Request-Method? —— 請(qǐng)求方法。
    • ?Access-Control-Request-Headers? —— 以逗號(hào)分隔的“非安全” header 列表。

Step 2 預(yù)檢響應(yīng)(preflight response)

服務(wù)應(yīng)響應(yīng)狀態(tài) 200 和 header:

  • ?Access-Control-Allow-Origin: https://javascript.info?
  • ?Access-Control-Allow-Methods: PATCH?
  • ?Access-Control-Allow-Headers: Content-Type,API-Key?。

這將允許后續(xù)通信,否則會(huì)觸發(fā)錯(cuò)誤。

如果服務(wù)器將來(lái)需要其他方法和 header,則可以通過(guò)將這些方法和 header 添加到列表中來(lái)預(yù)先允許它們。

例如,此響應(yīng)還允許 ?PUT?、?DELETE? 以及其他 header:

200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Methods: PUT,PATCH,DELETE
Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
Access-Control-Max-Age: 86400

現(xiàn)在,瀏覽器可以看到 PATCH 在 Access-Control-Allow-Methods 中,Content-Type,API-Key 在列表 Access-Control-Allow-Headers 中,因此它將發(fā)送主請(qǐng)求。

如果 Access-Control-Max-Age 帶有一個(gè)表示秒的數(shù)字,則在給定的時(shí)間內(nèi),預(yù)檢權(quán)限會(huì)被緩存。上面的響應(yīng)將被緩存 86400 秒,也就是一天。在此時(shí)間范圍內(nèi),后續(xù)請(qǐng)求將不會(huì)觸發(fā)預(yù)檢。假設(shè)它們符合緩存的配額,則將直接發(fā)送它們。

Step 3 實(shí)際請(qǐng)求(actual request)

預(yù)檢成功后,瀏覽器現(xiàn)在發(fā)出主請(qǐng)求。這里的過(guò)程與安全請(qǐng)求的過(guò)程相同。

主請(qǐng)求具有 Origin header(因?yàn)樗强缭吹模?

PATCH /service.json
Host: site.com
Content-Type: application/json
API-Key: secret
Origin: https://javascript.info

Step 4 實(shí)際響應(yīng)(actual response)

服務(wù)器不應(yīng)該忘記在主響應(yīng)中添加 Access-Control-Allow-Origin。成功的預(yù)檢并不能免除此要求:

Access-Control-Allow-Origin: https://javascript.info

然后,JavaScript 可以讀取主服務(wù)器響應(yīng)了。

請(qǐng)注意:

預(yù)檢請(qǐng)求發(fā)生在“幕后”,它對(duì) JavaScript 不可見(jiàn)。

JavaScript 僅獲取對(duì)主請(qǐng)求的響應(yīng),如果沒(méi)有服務(wù)器許可,則獲得一個(gè) error。

憑據(jù)(Credentials)

默認(rèn)情況下,由 JavaScript 代碼發(fā)起的跨源請(qǐng)求不會(huì)帶來(lái)任何憑據(jù)(cookies 或者 HTTP 認(rèn)證(HTTP authentication))。

這對(duì)于 HTTP 請(qǐng)求來(lái)說(shuō)并不常見(jiàn)。通常,對(duì) http://site.com 的請(qǐng)求附帶有該域的所有 cookie。但是由 JavaScript 方法發(fā)出的跨源請(qǐng)求是個(gè)例外。

例如,fetch('http://another.com') 不會(huì)發(fā)送任何 cookie,即使那些 (!) 屬于 another.com 域的 cookie。

為什么?

這是因?yàn)榫哂袘{據(jù)的請(qǐng)求比沒(méi)有憑據(jù)的請(qǐng)求要強(qiáng)大得多。如果被允許,它會(huì)使用它們的憑據(jù)授予 JavaScript 代表用戶(hù)行為和訪問(wèn)敏感信息的全部權(quán)力。

服務(wù)器真的這么信任這種腳本嗎?是的,它必須顯式地帶有允許請(qǐng)求的憑據(jù)和附加 header。

要在 fetch 中發(fā)送憑據(jù),我們需要添加 credentials: "include" 選項(xiàng),像這樣:

fetch('http://another.com', {
  credentials: "include"
});

現(xiàn)在,fetch 將把源自 another.com 的 cookie 和我們的請(qǐng)求發(fā)送到該網(wǎng)站。

如果服務(wù)器同意接受 帶有憑據(jù) 的請(qǐng)求,則除了 Access-Control-Allow-Origin 外,服務(wù)器還應(yīng)該在響應(yīng)中添加 header Access-Control-Allow-Credentials: true

例如:

200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Credentials: true

請(qǐng)注意:對(duì)于具有憑據(jù)的請(qǐng)求,禁止 Access-Control-Allow-Origin 使用星號(hào) *。如上所示,它必須有一個(gè)確切的源。這是另一項(xiàng)安全措施,以確保服務(wù)器真的知道它信任的發(fā)出此請(qǐng)求的是誰(shuí)。

總結(jié)

從瀏覽器角度來(lái)看,有兩種跨源請(qǐng)求:“安全”請(qǐng)求和其他請(qǐng)求。

“安全”請(qǐng)求必須滿(mǎn)足以下條件:

  • 方法:GET,POST 或 HEAD。
  • header —— 我們僅能設(shè)置:
    • ?Accept?
    • ?Accept-Language?
    • ?Content-Language?
    • ?Content-Type? 的值為 ?application/x-www-form-urlencoded?,?multipart/form-data? 或 ?text/plain?。

安全請(qǐng)求和其他請(qǐng)求的本質(zhì)區(qū)別在于,自古以來(lái)就可以使用 <form> 或 <script> 標(biāo)簽來(lái)實(shí)現(xiàn)安全請(qǐng)求,而對(duì)于瀏覽器來(lái)說(shuō),非安全請(qǐng)求在很長(zhǎng)一段時(shí)間都是不可能的。

所以,實(shí)際區(qū)別在于,安全請(qǐng)求會(huì)立即發(fā)送,并帶有 Origin header,而對(duì)于其他請(qǐng)求,瀏覽器會(huì)發(fā)出初步的“預(yù)檢”請(qǐng)求,以請(qǐng)求許可。

對(duì)于安全請(qǐng)求:

  • → 瀏覽器發(fā)送帶有源的 ?Origin? header。
  • ← 對(duì)于沒(méi)有憑據(jù)的請(qǐng)求(默認(rèn)不發(fā)送),服務(wù)器應(yīng)該設(shè)置:
    • ?Access-Control-Allow-Origin? 為 ?*? 或與 ?Origin? 的值相同
  • ← 對(duì)于具有憑據(jù)的請(qǐng)求,服務(wù)器應(yīng)該設(shè)置:
    • ?Access-Control-Allow-Origin? 值與 ?Origin? 的相同
    • ?Access-Control-Allow-Credentials? 為 ?true?

此外,要授予 JavaScript 訪問(wèn)除 Cache-Control,Content-Language,Content-TypeExpires,Last-Modified 或 Pragma 外的任何 response header 的權(quán)限,服務(wù)器應(yīng)該在 header Access-Control-Expose-Headers 中列出允許的那些 header。

對(duì)于非安全請(qǐng)求,會(huì)在請(qǐng)求之前發(fā)出初步“預(yù)檢”請(qǐng)求:

  • → 瀏覽器將具有以下 header 的 ?OPTIONS? 請(qǐng)求發(fā)送到相同的 URL:
    • ?Access-Control-Request-Method? 有請(qǐng)求方法。
    • ?Access-Control-Request-Headers? 以逗號(hào)分隔的“非安全” header 列表。
  • ← 服務(wù)器應(yīng)該響應(yīng)狀態(tài)碼為 200 和 header:
    • ?Access-Control-Allow-Methods? 帶有允許的方法的列表,
    • ?Access-Control-Allow-Headers? 帶有允許的 header 的列表,
    • ?Access-Control-Max-Age? 帶有指定緩存權(quán)限的秒數(shù)。
  • 然后,發(fā)送實(shí)際的請(qǐng)求,并應(yīng)用之前的“安全”方案。

任務(wù)


我們?yōu)槭裁葱枰矗∣rigin)?

重要程度: 5

你可能知道有一個(gè) HTTP-header Referer,它通常包含發(fā)起網(wǎng)絡(luò)請(qǐng)求的頁(yè)面的 url。

例如,當(dāng)從 http://javascript.info/some/url fetch http://google.com 時(shí),header 看起來(lái)如下:

Accept: */*
Accept-Charset: utf-8
Accept-Encoding: gzip,deflate,sdch
Connection: keep-alive
Host: google.com
Origin: http://javascript.info
Referer: http://javascript.info/some/url

正如你所看到的,存在 Referer 和 Origin。

問(wèn)題是:

  1. 為什么需要 ?Origin?,如果 ?Referer? 甚至具有更多信息?
  2. 如果這里沒(méi)有 ?Referer? 或 ?Origin? 可行嗎,還是說(shuō)會(huì)出問(wèn)題?

解決方案

我們需要 Origin,是因?yàn)橛袝r(shí)會(huì)沒(méi)有 Referer。例如,當(dāng)我們從 HTTPS(從高安全性訪問(wèn)低安全性)fetch HTTP 頁(yè)面時(shí),便沒(méi)有 Referer。

內(nèi)容安全策略 可能會(huì)禁止發(fā)送 Referer

正如我們將看到的,fetch 也具有阻止發(fā)送 Referer 的選項(xiàng),甚至允許修改它(在同一網(wǎng)站內(nèi))。

根據(jù)規(guī)范,Referer 是一個(gè)可選的 HTTP-header。

正是因?yàn)?nbsp;Referer 不可靠,才發(fā)明了 Origin。瀏覽器保證跨源請(qǐng)求的正確 Origin


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)