教學課程:單頁 Web 應用程式
警告
在 2020 年 10 月 30 日,Bing 搜尋 API 從 Azure AI 服務移至 Bing 搜尋服務。 本檔僅供參考。 如需更新的檔案,請參閱 Bing 搜尋 API 檔案。 如需建立 Bing 搜尋新 Azure 資源的指示,請參閱透過 Azure Marketplace 建立 Bing 搜尋資源。
Bing 實體搜尋 API 可讓您搜尋 Web 以取得 實體 和 地點的相關信息。 您可以在指定的查詢中要求某種結果或兩者。 以下提供地點和實體的定義。
結果 | 說明 |
---|---|
實體 | 您依名稱找到的知名人士、地點和事物 |
地方 | 依名稱 或 類型找到的餐館、酒店和其他當地企業(義大利餐廳) |
在本教學課程中,我們會建置單頁 Web 應用程式,以使用 Bing 實體搜尋 API 在頁面中顯示搜尋結果。 應用程式包含 HTML、CSS 和 JavaScript 元件。
API 可讓您依位置排列結果的優先順序。 在行動裝置應用程式中,您可以要求裝置自己的位置。 在 Web 應用程式中,您可以使用 函 getPosition()
式。 但此呼叫僅適用於安全的內容,而且可能無法提供精確的位置。 此外,使用者可能想要搜尋位於自己以外的位置附近的實體。
因此,我們的應用程式會呼叫 Bing 地圖服務,以從使用者輸入的位置取得緯度和經度。 然後,使用者可以輸入地標名稱(“Space Needle”)或完整或部分位址(“紐約市”),而 Bing 地圖服務 API 會提供座標。
備註
按兩下頁面底部的 JSON 和 HTTP 標題會顯示 JSON 回應和 HTTP 要求資訊。 這些詳細數據在探索服務時很有用。
本教學課程應用程式說明如何:
- 在 JavaScript 中執行 Bing 實體搜尋 API 呼叫
- 在 JavaScript 中執行 Bing 地圖服務
locationQuery
API 呼叫 - 將搜尋選項傳遞至 API 呼叫
- 顯示搜尋結果
- 處理 Bing 用戶端識別碼和 API 訂用帳戶金鑰
- 處理可能發生的任何錯誤
教學課程頁面是完全獨立的;它不會使用任何外部架構、樣式表單,或甚至圖像檔。 它只使用廣受支援的 JavaScript 語言功能,並搭配所有主要網頁瀏覽器的目前版本使用。
在本教學課程中,我們只討論原始程式碼的選取部分。 完整的原始碼可在 不同的頁面上取得。 將此程式代碼複製並貼到文字編輯器中,並將它儲存為 bing.html
。
備註
本教學課程與 單頁 Bing Web 搜尋應用程式教學課程大致類似,但只處理實體搜尋結果。
先決條件
若要遵循本教學課程,您需要 Bing 搜尋 API 和 Bing 地圖服務 API 的訂用帳戶密鑰。
- Azure 訂用帳戶 - 建立免費帳戶
- 擁有 Azure 訂用帳戶之後:
- 在 Azure 入口網站中建立 Bing 搜尋資源 ,以取得您的金鑰和端點。 部署之後,按一下 [移至資源]。
- 在 Azure 入口網站中建立 Bing 地圖服務資源 ,以取得您的金鑰和端點。 部署之後,按一下 [移至資源]。
應用程式元件
如同任何單頁 Web 應用程式,教學課程應用程式包含三個部分:
- HTML - 定義頁面的結構和內容
- CSS - 定義頁面的外觀
- JavaScript - 定義頁面的行為
本教學課程不會詳細說明大部分的 HTML 或 CSS,因為它們很簡單。
HTML 包含使用者輸入查詢並選擇搜尋選項的搜尋表單。 表單透過 <form>
標籤的 onsubmit
屬性連接到實際執行搜尋的 JavaScript:
<form name="bing" onsubmit="return newBingEntitySearch(this)">
onsubmit
處理程式會傳回 false
,防止表單提交至伺服器。 JavaScript 程式代碼實際上會執行從表單收集必要資訊並執行搜尋的工作。
搜尋會分兩個階段完成。 首先,如果使用者已輸入位置限制,Bing 地圖服務查詢就會將它轉換成座標。 然後,此查詢的回呼會啟動 Bing 實體搜尋查詢。
HTML 也包含搜尋結果呈現的區塊(HTML <div>
標籤)。
管理訂用帳戶金鑰
備註
此應用程式需要 Bing 搜尋 API 和 Bing 地圖服務 API 的訂用帳戶密鑰。
為了避免在程式代碼中包含 Bing 搜尋和 Bing 地圖服務 API 訂用帳戶金鑰,我們會使用瀏覽器的永續性記憶體來儲存它們。 如果其中一個金鑰尚未儲存,我們會提示它,並儲存它以供稍後使用。 如果 API 稍後拒絕金鑰,我們會使儲存的金鑰失效,以便在使用者下次搜尋時要求該密鑰。
我們會定義 storeValue
和 retrieveValue
函式,以使用 localStorage
物件(如果瀏覽器支援它)或 Cookie。 我們的 getSubscriptionKey()
函式會使用這些函式來儲存和擷取使用者的密鑰。 您可以使用下方的全域端點,或資源 Azure 入口網站中顯示的 自定義子域 端點。
// cookie names for data we store
SEARCH_API_KEY_COOKIE = "bing-search-api-key";
MAPS_API_KEY_COOKIE = "bing-maps-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";
// API endpoints
SEARCH_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/entities";
MAPS_ENDPOINT = "https://dev.virtualearth.net/REST/v1/Locations";
// ... omitted definitions of storeValue() and retrieveValue()
// get stored API subscription key, or prompt if it's not found
function getSubscriptionKey(cookie_name, key_length, api_name) {
var key = retrieveValue(cookie_name);
while (key.length !== key_length) {
key = prompt("Enter " + api_name + " API subscription key:", "").trim();
}
// always set the cookie in order to update the expiration date
storeValue(cookie_name, key);
return key;
}
function getMapsSubscriptionKey() {
return getSubscriptionKey(MAPS_API_KEY_COOKIE, 64, "Bing Maps");
}
function getSearchSubscriptionKey() {
return getSubscriptionKey(SEARCH_API_KEY_COOKIE, 32, "Bing Search");
}
HTML <body>
標籤包含一個 onload
屬性,當頁面完成載入時會呼叫 getSearchSubscriptionKey()
和 getMapsSubscriptionKey()
。 如果尚未輸入金鑰,這些呼叫會立即提示使用者輸入金鑰。
<body onload="document.forms.bing.query.focus(); getSearchSubscriptionKey(); getMapsSubscriptionKey();">
選取搜尋選項
HTML 表單包含下列控制項:
控制 | 說明 |
---|---|
where |
用於選取用於搜尋之市場(位置和語言)的下拉功能表。 |
query |
要在其中輸入搜尋字詞的文字欄位。 |
safe |
複選框,指出是否開啟SafeSearch(限制「成人」結果) |
what |
選擇搜尋實體、地點或兩者的功能表。 |
mapquery |
用戶可以在其中輸入完整或部分位址、地標等文字字段,以協助 Bing 實體搜尋傳回更相關的結果。 |
備註
地點結果目前僅適用於美國。
where
和 what
功能表有程式代碼可強制執行此限制。 如果您在 what
功能表中選擇 [地點],並選擇非美國市場,則 what
會改為 [任何專案]。 如果您在功能表中選取非美國市場時選擇 where
[地點],則 where
變更為 [美國]。
我們的 JavaScript 函式 bingSearchOptions()
會將這些欄位轉換成 Bing 搜尋 API 的部分查詢字串。
// build query options from the HTML form
function bingSearchOptions(form) {
var options = [];
options.push("mkt=" + form.where.value);
options.push("SafeSearch=" + (form.safe.checked ? "strict" : "off"));
if (form.what.selectedIndex) options.push("responseFilter=" + form.what.value);
return options.join("&");
}
例如,SafeSearch 功能可以是 strict
、 moderate
或 off
,其 moderate
為預設值。 但是我們的表單使用複選框,其只有兩個狀態。 JavaScript 程式代碼會將此設定 strict
轉換成 或 off
(我們不會使用 moderate
)。
欄位 mapquery
不會在 中 bingSearchOptions()
處理,因為它用於 Bing 地圖服務位置查詢,而不是 Bing 實體搜尋。
取得位置
Bing 地圖服務 API 提供一種方法locationQuery
,我們用來尋找使用者輸入之位置的緯度和經度。 然後,這些座標會隨著使用者的要求傳遞至 Bing 實體搜尋 API。 搜尋結果會設定接近指定位置的實體和位置的優先順序。
我們無法在 Web 應用程式中使用一 XMLHttpRequest
般查詢來存取 Bing 地圖服務 API,因為服務不支援跨原始來源查詢。 幸運的是,它支援 JSONP(「P」代表「padded」)。 JSONP 回應是包裝在函數調用中的一般 JSON 回應。 要求是透過在文件中插入 <script>
標記來發出。 (載入文稿不受瀏覽器安全策略約束。
函式 bingMapsLocate()
會建立並插入查詢的 <script>
標籤。
jsonp=bingMapsCallback
查詢字串的區段會指定要以回應呼叫的函式名稱。
function bingMapsLocate(where) {
where = where.trim();
var url = MAPS_ENDPOINT + "?q=" + encodeURIComponent(where) +
"&jsonp=bingMapsCallback&maxResults=1&key=" + getMapsSubscriptionKey();
var script = document.getElementById("bingMapsResult")
if (script) script.parentElement.removeChild(script);
// global variable holds reference to timer that will complete the search if the maps query fails
timer = setTimeout(function() {
timer = null;
var form = document.forms.bing;
bingEntitySearch(form.query.value, "", bingSearchOptions(form), getSearchSubscriptionKey());
}, 5000);
script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("id", "bingMapsResult");
script.setAttribute("src", url);
script.setAttribute("onerror", "BingMapsCallback(null)");
document.body.appendChild(script);
return false;
}
備註
如果 Bing 地圖服務 API 沒有回應,則永遠不會呼叫 函 bingMapsCallBack()
式。 一般而言,這表示 bingEntitySearch()
不會呼叫,且實體搜尋結果不會出現。 若要避免這種情況, bingMapsLocate()
也設定定時器在五秒後呼叫 bingEntitySearch()
。 回呼函式中有邏輯可避免執行實體搜尋兩次。
當查詢完成時, bingMapsCallback()
會依要求呼叫 函式。
function bingMapsCallback(response) {
if (timer) { // we beat the timer; stop it from firing
clearTimeout(timer);
timer = null;
} else { // the timer beat us; don't do anything
return;
}
var location = "";
var name = "";
var radius = 1000;
if (response) {
try {
if (response.statusCode === 401) {
invalidateMapsKey();
} else if (response.statusCode === 200) {
var resource = response.resourceSets[0].resources[0];
var coords = resource.point.coordinates;
name = resource.name;
// the radius is the largest of the distances between the location and the corners
// of its bounding box (in case it's not in the center) with a minimum of 1 km
try {
var bbox = resource.bbox;
radius = Math.max(haversineDistance(bbox[0], bbox[1], coords[0], coords[1]),
haversineDistance(coords[0], coords[1], bbox[2], bbox[1]),
haversineDistance(bbox[0], bbox[3], coords[0], coords[1]),
haversineDistance(coords[0], coords[1], bbox[2], bbox[3]), 1000);
} catch(e) { }
var location = "lat:" + coords[0] + ";long:" + coords[1] + ";re:" + Math.round(radius);
}
}
catch (e) { } // response is unexpected. this isn't fatal, so just don't provide location
}
var form = document.forms.bing;
if (name) form.mapquery.value = name;
bingEntitySearch(form.query.value, location, bingSearchOptions(form), getSearchSubscriptionKey());
}
除了緯度和經度,Bing 實體搜尋查詢還需要一個半徑,以指示位置資訊的精確程度。 我們會使用 Bing 地圖服務回應中提供的 周框方塊 來計算半徑。 周框方塊是圍繞整個位置的矩形。 例如,如果使用者輸入 NYC
,則結果會包含紐約市的大致中央座標,以及包含城市的周框方塊。
我們會先使用 haversineDistance()
函式來計算從主要座標到周框方塊四個角落之間的距離(未顯示)。 我們使用這四個距離中最大的一個做為半徑。 最小半徑為一公里。 如果在回應中未提供周框方塊,這個值也會作為預設值。
取得座標和半徑之後,我們會呼叫 bingEntitySearch()
來執行實際搜尋。
執行搜尋
假設查詢、位置、選項字串和 API 金鑰,函 BingEntitySearch()
式會提出 Bing 實體搜尋要求。
// perform a search given query, location, options string, and API keys
function bingEntitySearch(query, latlong, options, key) {
// scroll to top of window
window.scrollTo(0, 0);
if (!query.trim().length) return false; // empty query, do nothing
showDiv("noresults", "Working. Please wait.");
hideDivs("pole", "mainline", "sidebar", "_json", "_http", "error");
var request = new XMLHttpRequest();
var queryurl = SEARCH_ENDPOINT + "?q=" + encodeURIComponent(query) + "&" + options;
// open the request
try {
request.open("GET", queryurl);
}
catch (e) {
renderErrorMessage("Bad request (invalid URL)\n" + queryurl);
return false;
}
// add request headers
request.setRequestHeader("Ocp-Apim-Subscription-Key", key);
request.setRequestHeader("Accept", "application/json");
var clientid = retrieveValue(CLIENT_ID_COOKIE);
if (clientid) request.setRequestHeader("X-MSEdge-ClientID", clientid);
if (latlong) request.setRequestHeader("X-Search-Location", latlong);
// event handler for successful response
request.addEventListener("load", handleBingResponse);
// event handler for erorrs
request.addEventListener("error", function() {
renderErrorMessage("Error completing request");
});
// event handler for aborted request
request.addEventListener("abort", function() {
renderErrorMessage("Request aborted");
});
// send the request
request.send();
return false;
}
HTTP 要求成功完成時,JavaScript 會呼叫我們的 load
事件處理程式 函 handleBingResponse()
式,以處理 API 的成功 HTTP GET 要求。
// handle Bing search request results
function handleBingResponse() {
hideDivs("noresults");
var json = this.responseText.trim();
var jsobj = {};
// try to parse JSON results
try {
if (json.length) jsobj = JSON.parse(json);
} catch(e) {
renderErrorMessage("Invalid JSON response");
}
// show raw JSON and HTTP request
showDiv("json", preFormat(JSON.stringify(jsobj, null, 2)));
showDiv("http", preFormat("GET " + this.responseURL + "\n\nStatus: " + this.status + " " +
this.statusText + "\n" + this.getAllResponseHeaders()));
// if HTTP response is 200 OK, try to render search results
if (this.status === 200) {
var clientid = this.getResponseHeader("X-MSEdge-ClientID");
if (clientid) retrieveValue(CLIENT_ID_COOKIE, clientid);
if (json.length) {
if (jsobj._type === "SearchResponse") {
renderSearchResults(jsobj);
} else {
renderErrorMessage("No search results in JSON response");
}
} else {
renderErrorMessage("Empty response (are you sending too many requests too quickly?)");
}
if (divHidden("pole") && divHidden("mainline") && divHidden("sidebar"))
showDiv("noresults", "No results.<p><small>Looking for restaurants or other local businesses? Those currently areen't supported outside the US.</small>");
}
// Any other HTTP status is an error
else {
// 401 is unauthorized; force re-prompt for API key for next request
if (this.status === 401) invalidateSearchKey();
// some error responses don't have a top-level errors object, so gin one up
var errors = jsobj.errors || [jsobj];
var errmsg = [];
// display HTTP status code
errmsg.push("HTTP Status " + this.status + " " + this.statusText + "\n");
// add all fields from all error responses
for (var i = 0; i < errors.length; i++) {
if (i) errmsg.push("\n");
for (var k in errors[i]) errmsg.push(k + ": " + errors[i][k]);
}
// also display Bing Trace ID if it isn't blocked by CORS
var traceid = this.getResponseHeader("BingAPIs-TraceId");
if (traceid) errmsg.push("\nTrace ID " + traceid);
// and display the error message
renderErrorMessage(errmsg.join("\n"));
}
}
這很重要
成功的 HTTP 要求 不一定 表示搜尋本身成功。 如果在搜尋作業中發生錯誤,Bing 實體搜尋 API 會傳回非 200 HTTP 狀態代碼,並在 JSON 回應中包含錯誤資訊。 此外,如果要求受到速率限制,API 會傳回空的回應。
上述兩個函式中的大部分程式代碼都專用於錯誤處理。 錯誤可能會在下列階段發生:
舞臺 | 潛在錯誤 | 處理者 |
---|---|---|
建置 JavaScript 要求物件 | URL 無效 |
try
/
catch 區塊 |
提出要求 | 網路錯誤,中止的連線 |
error 和 abort 事件處理程式 |
執行搜尋 | 無效的要求、無效的 JSON、速率限制 |
load 事件處理程式中的測試 |
透過呼叫 renderErrorMessage()
來處理錯誤,並提供任何已知的錯誤詳細資訊。 如果回應通過重重關卡的錯誤測試,我們會調用 renderSearchResults()
在頁面中顯示搜尋結果。
顯示搜尋結果
Bing 實體搜尋 API 會要求您以指定的順序顯示結果。 由於 API 可能會傳回兩種不同類型的回應,因此不足以逐一查看 JSON 回應中的最上層 Entities
或 Places
集合,並顯示這些結果。 (如果您只想要一種類型的結果,請使用 responseFilter
查詢參數。
相反地,我們會在 rankingResponse
搜尋結果中使用 集合來排序要顯示的結果。 這個物件是指 Entitiess
和/或 Places
集合中的項目。
rankingResponse
最多可以包含三個搜尋結果集合,指定 pole
、 mainline
和 sidebar
。
pole
如果存在,則為最相關的搜尋結果,且應該以醒目方式顯示。
mainline
是指大部分的搜尋結果。 主線結果應緊接在 pole
之後顯示(如果 pole
不存在,則為第一個)。
最後。
sidebar
是指輔助搜尋結果。 它們可能會顯示在實際的側邊欄中,或只是在主線結果之後顯示。 我們已為教學課程應用程式選擇後者。
集合中的每個 rankingResponse
項目都會以兩種不同但等效的方式來參考實際的搜尋結果項目。
項目 | 說明 |
---|---|
id |
id 看起來像 URL,但不應該用於連結。
id 排名結果的類型符合id 答案集合中搜尋結果專案的類型,或整個答案集合(例如Entities )。 |
answerType resultIndex |
answerType 是指包含結果的最上層回應集合(例如 , Entities 。
resultIndex 指的是該集合中結果的索引。 如果 resultIndex 省略,排名結果會參考整個集合。 |
備註
如需此部分搜尋回應的詳細資訊,請參閱 排名結果。
您可以使用最方便您的應用程式的方式定位所參考的搜尋結果專案。 在我們的教學課程程序代碼中,我們會使用 answerType
和 resultIndex
來找出每個搜尋結果。
最後,是時候查看函式 renderSearchResults()
了。 此函式會遍歷代表搜尋結果三個區段的三個rankingResponse
集合。 針對每個區段,我們會呼叫 renderResultsItems()
來轉譯該區段的結果。
// render the search results given the parsed JSON response
function renderSearchResults(results) {
// if spelling was corrected, update search field
if (results.queryContext.alteredQuery)
document.forms.bing.query.value = results.queryContext.alteredQuery;
// for each possible section, render the results from that section
for (section in {pole: 0, mainline: 0, sidebar: 0}) {
if (results.rankingResponse[section])
showDiv(section, renderResultsItems(section, results));
}
}
渲染結果項目
在我們的 JavaScript 程式代碼中是物件, searchItemRenderers
其中包含 轉譯器: 針對每種搜尋結果產生 HTML 的函式。
searchItemRenderers = {
entities: function(item) { ... },
places: function(item) { ... }
}
轉譯器函式可能接受下列參數:
參數 | 說明 |
---|---|
item |
包含項目屬性的 JavaScript 物件,例如其 URL 和其描述。 |
index |
其集合中結果項目的索引。 |
count |
搜尋結果專案集合中的項目數。 |
index
和 count
參數可用來編號結果、為集合的開頭或結尾產生特殊的 HTML、在特定數目的項目之後插入換行符等等。 如果轉譯器不需要此功能,則不需要接受這兩個參數。 事實上,我們不會在教學課程應用程式的轉譯器中使用它們。
讓我們進一步了解 entities
轉譯器:
entities: function(item) {
var html = [];
html.push("<p class='entity'>");
if (item.image) {
var img = item.image;
if (img.hostPageUrl) html.push("<a href='" + img.hostPageUrl + "'>");
html.push("<img src='" + img.thumbnailUrl + "' title='" + img.name + "' height=" + img.height + " width= " + img.width + ">");
if (img.hostPageUrl) html.push("</a>");
if (img.provider) {
var provider = img.provider[0];
html.push("<small>Image from ");
if (provider.url) html.push("<a href='" + provider.url + "'>");
html.push(provider.name ? provider.name : getHost(provider.url));
if (provider.url) html.push("</a>");
html.push("</small>");
}
}
html.push("<p>");
if (item.entityPresentationInfo) {
var pi = item.entityPresentationInfo;
if (pi.entityTypeHints || pi.entityTypeDisplayHint) {
html.push("<i>");
if (pi.entityTypeDisplayHint) html.push(pi.entityTypeDisplayHint);
else if (pi.entityTypeHints) html.push(pi.entityTypeHints.join("/"));
html.push("</i> - ");
}
}
html.push(item.description);
if (item.webSearchUrl) html.push(" <a href='" + item.webSearchUrl + "'>More</a>")
if (item.contractualRules) {
html.push("<p><small>");
var rules = [];
for (var i = 0; i < item.contractualRules.length; i++) {
var rule = item.contractualRules[i];
var link = [];
if (rule.license) rule = rule.license;
if (rule.url) link.push("<a href='" + rule.url + "'>");
link.push(rule.name || rule.text || rule.targetPropertyName + " source");
if (rule.url) link.push("</a>");
rules.push(link.join(""));
}
html.push("License: " + rules.join(" - "));
html.push("</small>");
}
return html.join("");
}, // places renderer omitted
我們的實體轉譯器函式:
- 建置 HTML
<img>
標記以顯示影像縮圖,如果有的話。 -
<a>
建置 HTML 標籤,連結至包含影像的頁面。 - 建立描述,以顯示圖片及其所在網站的相關信息。
- 若有提示,則使用顯示提示將實體的分類納入。
- 包含 Bing 搜尋的連結,以取得實體的詳細資訊。
- 顯示數據源所需的任何授權或屬性資訊。
持續保存用戶端識別碼
來自 Bing 搜尋 API 的回應可能包含 X-MSEdge-ClientID
標頭,應該在後續請求中傳回給 API。 如果使用多個 Bing 搜尋 API,則應該盡可能使用相同的用戶端識別碼來搭配所有 API。
X-MSEdge-ClientID
提供標頭可讓 Bing API 讓所有使用者的搜尋產生關聯,這有兩個重要優點。
首先,它可讓 Bing 搜尋引擎套用過去的內容來搜尋,以尋找更符合用戶的結果。 例如,如果使用者先前已搜尋與帆船相關的字詞,則稍後搜尋「碼頭」可能會優先傳回有關停泊帆船地點的資訊。
其次,Bing 可能會隨機選取用戶來體驗新功能,然後再廣泛提供這些功能。 為每個要求提供相同的用戶端標識碼,可確保已選擇查看功能的使用者一律會看到此功能。 若沒有用戶端標識碼,使用者可能會在其搜尋結果中看到功能出現並消失,看似隨機。
瀏覽器安全策略 (CORS) 可能會防止 X-MSEdge-ClientID
標頭可供 JavaScript 使用。 當搜尋回應與要求該回應的頁面不同來源時,就會發生此限制。 在生產環境中,您應該裝載伺服器端腳本,在與網頁相同的網域上執行 API 呼叫,藉此解決此原則。 由於腳本的原點與網頁相同,因此 X-MSEdge-ClientID
標頭接著可供 JavaScript 使用。
備註
在生產環境的 Web 應用程式中,您應該在伺服器端執行此請求。 否則,您的 Bing 搜尋 API 金鑰必須包含在網頁中,供任何檢視來源的人員使用。 您的 API 訂閱金鑰下的所有使用量,甚至是未經授權使用者提出的要求,因此請務必保護您的密鑰不被公開。
基於開發目的,您可以透過 CORS Proxy 提出 Bing Web 搜尋 API 要求。 來自這類 Proxy 的回應具有 Access-Control-Expose-Headers
標頭,可允許列出響應標頭,並將其提供給 JavaScript 使用。
很容易安裝 CORS 代理伺服器,以允許我們的教學應用程式存取客戶端 ID 標頭。 首先,如果您還沒有它,安裝 Node.js。 然後在指令視窗中發出下列命令:
npm install -g cors-proxy-server
接下來,將 HTML 檔案中的 Bing Web 搜尋端點變更為:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search
最後,使用下列命令啟動 CORS Proxy:
cors-proxy-server
當您使用教學課程應用程式時,請保持命令視窗開啟。關閉視窗會停止代理伺服器。 在搜尋結果下方的可展開 HTTP 標頭區段中,您現在可以看到 X-MSEdge-ClientID
標頭(等等),並確認每個要求都相同。