Руководство: Одностраничное веб-приложение

Предупреждение

30 октября 2020 г. API поиска Bing перемещены из служб ИИ Azure в службы поиска Bing. Эта документация предоставляется только для справки. Обновленную информацию см. в документации по API Поиска Bing. Инструкции по созданию ресурсов Azure для Поиска Bing приведены в статье Создание ресурса для Поиска Bing с помощью Azure Marketplace.

API поиска сущностей Bing позволяет искать в Интернете сведения о сущностях и местах. Вы можете запросить любой результат или оба в заданном запросе. Ниже приведены определения мест и сущностей.

Результат Описание
Субъекты Известные люди, места и вещи, которые вы узнаете по имени
Места Рестораны, отели и другие местные предприятия, которые вы найдете по имени или по типу (итальянские рестораны)

В этом руководстве мы создадим одностраничное веб-приложение, использующее API поиска сущностей Bing для отображения результатов поиска прямо на странице. Приложение включает компоненты HTML, CSS и JavaScript.

API позволяет определить приоритет результатов по расположению. В мобильном приложении вы можете запросить у устройства его местоположение. В веб-приложении можно использовать функцию getPosition() . Но эта функция работает только в безопасных контекстах, и она может не предоставлять точное местоположение. Кроме того, пользователь может захотеть искать сущности рядом с местом, отличным от его собственного.

Поэтому наше приложение призывает службу карт Bing получить широту и долготу из введенного пользователем расположения. Затем пользователь может ввести имя ориентира (Space Needle) или полный или частичный адрес ("Нью-йорк"), а API карт Bing предоставляет координаты.

Замечание

Заголовки JSON и HTTP в нижней части страницы отображают ответ JSON и сведения о HTTP-запросе при щелчке. Эти сведения полезны при изучении службы.

На примере учебного приложения показано, как выполнить такие задачи:

  • Выполнение вызова API поиска сущностей Bing в JavaScript
  • Выполнение вызова API карт locationQuery Bing в JavaScript
  • Передача параметров поиска в вызовы API
  • Отображение результатов поиска
  • Обработка идентификатора клиента Bing и ключей подписки API
  • Решение любых ошибок, которые могут возникнуть

Страница учебника полностью самодостаточна; она не использует внешние фреймворки, таблицы стилей или даже файлы изображений. Он использует только широко поддерживаемые функции языка JavaScript и работает с текущими версиями всех основных веб-браузеров.

В этом руководстве мы обсудим только выбранные части исходного кода. Полный исходный код доступен на отдельной странице. Скопируйте и вставьте этот код в текстовый редактор и сохраните его как bing.html.

Замечание

Это руководство значительно похоже на учебник по приложению одностраничного веб-поиска Bing, но только касается результатов поиска сущностей.

Предпосылки

Чтобы следовать вместе с руководством, вам потребуются ключи подписки для API поиска Bing и API карт Bing.

  • подписка Azure — создайте бесплатную учетную запись.
  • После получения подписки Azure:
    • на портале Azure, чтобы получить ключ и конечную точку. После развертывания щелкните Перейти к ресурсу.
    • на портале Azure, чтобы получить ключ и конечную точку. После развертывания щелкните Перейти к ресурсу.

Компоненты приложения

Как и любое одностраничное веб-приложение, приложение учебника включает в себя три части:

  • HTML — определяет структуру и содержимое страницы
  • CSS — определяет внешний вид страницы
  • JavaScript — определяет поведение страницы

В этом руководстве подробно не рассматриваются HTML или CSS, так как они просты.

HTML-код содержит форму поиска, в которой пользователь вводит запрос и выбирает параметры поиска. Форма подключена к JavaScript, которая фактически выполняет поиск по <form> атрибуту тега onsubmit :

<form name="bing" onsubmit="return newBingEntitySearch(this)">

Обработчик onsubmit возвращает false, который предотвращает отправку формы на сервер. Код JavaScript фактически выполняет работу по сбору необходимых сведений из формы и выполнению поиска.

Поиск выполняется на двух этапах. Во-первых, если пользователь ввел ограничение расположения, запрос карты Bing выполняется для преобразования его в координаты. Обратный вызов по этому запросу затем запускает поиск сущностей Bing.

HTML также содержит деления (теги HTML-<div>), в которых отображаются результаты поиска.

Управление ключами подписки

Замечание

Для этого приложения требуются ключи подписки для API поиска Bing и API карт Bing.

Чтобы избежать необходимости включать ключи подписки API Bing Search и Bing Maps в код, мы используем постоянное хранилище браузера для их хранения. Если любой ключ не сохранен, мы запрашиваем его и сохраняем его для последующего использования. Если ключ позже отклоняется 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> содержит атрибут, вызывающий getSearchSubscriptionKey()onload и getMapsSubscriptionKey() когда страница завершит загрузку. Эти вызовы служат для немедленного запроса пользователя на ввод ключей, если они еще не ввели их.

<body onload="document.forms.bing.query.focus(); getSearchSubscriptionKey(); getMapsSubscriptionKey();">

Выбор параметров поиска

[Форма поиска сущностей Bing]

Html-форма включает следующие элементы управления:

Контроль Описание
where Раскрывающееся меню для выбора рынка (расположения и языка), используемого для поиска.
query Текстовое поле, в котором нужно ввести условия поиска.
safe Флажок, указывающий, включена ли функция SafeSearch (ограничивает результаты "взрослый")
what Меню для выбора поиска сущностей, мест или обоих.
mapquery Текстовое поле, в котором пользователь может ввести полный или частичный адрес, ориентир и т. д., чтобы помочь Bing в поиске сущностей возвращать более релевантные результаты.

Замечание

В настоящее время результаты доступны только в Соединенных Штатах. В меню where и what есть код для применения этого ограничения. Если выбрать рынок, отличный от США, в разделе "Выбор места" в what меню, what изменяется на "Что угодно". Если вы выберете "Места", а рынок, отличный от США, выбран в where меню, where изменится на США.

Наша функция bingSearchOptions() JavaScript преобразует эти поля в частичную строку запроса для API поиска Bing.

// 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.

Получение местоположения

API Карт Bing предлагает locationQuery метод, который мы используем для поиска широты и долготы расположения, которое пользователь вводит. Затем эти координаты передаются в API поиска сущностей Bing с запросом пользователя. Результаты поиска определяют приоритеты сущностей и мест, близких к указанному расположению.

Мы не можем получить доступ к API карт Bing с помощью обычного XMLHttpRequest запроса в веб-приложении, так как служба не поддерживает запросы между источниками. К счастью, она поддерживает 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;
}

Замечание

Если API карт Bing не отвечает, 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 Maps. Ограничивающий прямоугольник — это прямоугольная область, которая окружает всё. Например, если пользователь вводит 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() функцию для обработки успешного HTTP-запроса GET к API.

// 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-запрос не обязательно означает, что сам поиск произошёл успешно. Если ошибка возникает в операции поиска, API поиска сущностей Bing возвращает код состояния HTTP, отличный от 200, и содержит сведения об ошибке в ответе JSON. Кроме того, если запрос был ограничен скоростью, API возвращает пустой ответ.

Большая часть кода в обеих предыдущих функциях посвящена обработке ошибок. Ошибки могут возникать на следующих этапах:

Этап Потенциальные ошибки Обрабатывается
Создание объекта запроса JavaScript Недопустимый URL-адрес. блок try/catch
Выполнение запроса Сетевые ошибки, прерванные подключения обработчики событий error и abort
Выполнение поиска Недопустимый запрос, недопустимый JSON, ограничения скорости тесты в обработчике событий load

Ошибки обрабатываются путем вызова renderErrorMessage() со сведениями об ошибке. Если ответ проходит полный набор тестов на ошибки, мы используем renderSearchResults(), чтобы отобразить результаты поиска на странице.

Отображение результатов поиска

API поиска сущностей Bing требует отображения результатов в указанном порядке. Так как API может возвращать два разных типа ответов, недостаточно выполнять итерацию по верхнему уровню Entities или Places коллекции в ответе JSON и отображать эти результаты. (Если требуется только один тип результата, используйте 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("&nbsp;<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> для отображения эскиза изображения( если таковой есть).
  • Создает HTML-тег <a> , который ссылается на страницу, содержащую изображение.
  • Создает описание, отображающее сведения о изображении и сайте, на котором он находится.
  • Включает классификацию сущности с помощью подсказок для отображения, если таковые имеются.
  • Содержит ссылку на поиск Bing, чтобы получить дополнительные сведения об сущности.
  • Отображает все сведения о лицензировании или присвоении, необходимые источникам данных.

Сохранение идентификатора клиента

Ответы от API поиска Bing могут содержать X-MSEdge-ClientID заголовок, который должен быть отправлен обратно в API с последовательными запросами. Если используются несколько API-интерфейсов поиска Bing, при возможности следует использовать один и тот же идентификатор клиента.

X-MSEdge-ClientID Предоставление заголовка позволяет API Bing связать все поисковые запросы пользователя, которые имеют два важных преимущества.

Во-первых, он позволяет поисковой системе Bing применять прошлый контекст к поиску результатов, которые лучше удовлетворяют пользователю. Если пользователь ранее искал термины, связанные с парусным спортом, например, более поздний поиск "доков" может предпочтительно вернуть информацию о местах для прикрепления парусной лодки.

Во-вторых, Bing может случайным образом выбрать пользователей для взаимодействия с новыми функциями, прежде чем они становятся широко доступными. Предоставление одного и того же идентификатора клиента с каждым запросом гарантирует, что пользователи, выбранные для просмотра функции, всегда видят ее. Без идентификатора клиента пользователь может увидеть, что функция появится и исчезнет, казалось бы, случайным образом в результатах поиска.

Политики безопасности браузера (CORS) могут предотвратить доступность заголовка X-MSEdge-ClientID в JavaScript. Это ограничение возникает, когда ответ поиска имеет другой источник от страницы, запрашивающей ее. В рабочей среде следует решить эту политику, размещая серверный скрипт, который вызывает API в том же домене, что и веб-страница. Так как скрипт имеет тот же источник, что и веб-страница, X-MSEdge-ClientID заголовок будет доступен для JavaScript.

Замечание

В рабочем веб-приложении запрос следует выполнять на стороне сервера в любом случае. В противном случае ключ API поиска Bing должен быть включен на веб-страницу, где он доступен любому, кто просматривает источник. Вам выставляется счет за все использование, связанное с ключом подписки API, включая запросы, сделанные несанкционированными сторонами, поэтому важно не разглашать ключ.

В целях разработки можно сделать запрос API Bing для поиска в Интернете с помощью прокси-сервера CORS. Ответ от такого прокси-сервера содержит Access-Control-Expose-Headers заголовок, который позволяет перечислить заголовки ответов и обеспечивает их доступность для JavaScript.

Легко установить прокси-сервер CORS, чтобы разрешить нашему учебному приложению обращаться к заголовку идентификатора клиента. Во-первых, если у вас его еще нет, установите Node.js. Затем выполните следующую команду в командном окне:

npm install -g cors-proxy-server

Затем измените конечную точку для веб-поиска Bing в файле HTML на следующую:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Наконец, запустите прокси-сервер CORS с помощью следующей команды:

cors-proxy-server

Оставьте окно командной строки открытым при использовании приложения учебника; закрытие окна останавливает прокси-сервер. В разделе расширенных заголовков HTTP под результатами поиска теперь можно увидеть заголовок X-MSEdge-ClientID (среди прочего) и убедиться, что он одинаковый для каждого запроса.

Дальнейшие действия