Oktatóanyag: Egyoldalas webalkalmazás létrehozása

Figyelmeztetés

2020. október 30-án a Bing Search API-k átkerültek az Azure AI-szolgáltatásokból a Bing Search Servicesbe. Ez a dokumentáció csak referenciaként szolgál. A frissített dokumentációt a Bing search API dokumentációjában találja. Az új Azure-erőforrások Bing-kereséshez való létrehozásával kapcsolatos útmutatásért lásd: Bing Search-erőforrás létrehozása a Azure Marketplace keresztül.

A Bing News Search API lehetővé teszi az interneten való keresést és a keresési lekérdezésnek megfelelő hírtípus szerinti eredmények lekérését. Ebben az oktatóanyagban létrehozunk egy egyoldalas webalkalmazást, amely a Bing News Search API-t használja a keresési eredmények megjelenítéséhez az oldalon. Az alkalmazás HTML-, CSS- és JavaScript-összetevőkből áll. A minta forráskódja elérhető a GitHubon.

Megjegyzés

A lap alján található JSON- és HTTP-fejlécek a JSON-válasz és a HTTP-kérés adatait mutatják, ha rájuk kattint. Ezek a részletek hasznosak lehetnek, ha meg szeretne ismerkedni a szolgáltatással.

Az oktatóanyag részeként összeállított alkalmazás a következők bemutatására szolgál:

  • Bing News Search API-hívás indítása a JavaScriptben
  • Keresési beállítások továbbítása a Bing News Search API-nak
  • A keresési eredményül kapott híreket négy kategóriából (bármilyen, üzleti, egészséggel kapcsolatos vagy politikai), az elmúlt 24 órára, az elmúlt hétre vagy elmúlt hónapra korlátozódó, vagy minden elérhető időtartományt magában foglaló időkeretből jelenítheti meg.
  • Keresési eredmények közötti lapozás
  • A Bing-ügyfélazonosító és az API előfizetői azonosító kezelése
  • Az esetlegesen előforduló problémák kezelése

Az oktatóanyag oldala teljesen önálló; nem használ semmilyen külső keretrendszert, stíluslapot vagy képfájlt. Egyedül széles körben támogatott JavaScript nyelvi funkciókat használ, és az összes nagyobb webböngésző aktuális verziójával kompatibilis.

Előfeltételek

Az oktatóanyag követéséhez az Bing Search API-hoz szükséges előfizetési kulcsok szükségesek. Ha nem rendelkezik ezekkel, létre kell hoznia őket:

Alkalmazás-összetevők

Mint minden egyoldalas webalkalmazás, ez az oktatóalkalmazás is három részből áll:

  • HTML – Meghatározza az oldal szerkezetét és tartalmát
  • CSS – Meghatározza az oldal megjelenését
  • JavaScript – Meghatározza az oldal viselkedését

A HTML és a CSS nagy része hagyományos, ezért az oktatóanyag részeként nem vesszük át őket. A HTML tartalmazza a keresési űrlapot, amelyen a felhasználó megad egy lekérdezést, és kiválasztja a keresési beállításokat. Az űrlap a JavaScripthez van csatlakoztatva, amely ténylegesen végrehajtja a keresést a <form> címke onsubmit attribútumával:

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

Az onsubmit kezelő false értéket ad vissza, ami megakadályozza az űrlap elküldését a kiszolgálóra. A JavaScript-kód begyűjti a szükséges adatokat az űrlapból, és végrehajtja a keresést.

A HTML azokat a részlegeket (HTML <div> címkéket) is tartalmazza, amelyekben a keresési eredmények megjelennek.

Előfizetői azonosító kezelése

Annak érdekében, hogy a Bing Search API előfizetői azonosítóját ki lehessen hagyni a kódból, a böngésző állandó tárolójában tároljuk az azonosítót. Mielőtt az azonosítót eltárolnánk, elkérjük a felhasználó azonosítóját. Ha az azonosítót később elutasítja az API, érvénytelenítjük a tárolt azonosítót, ezért a felhasználótól újra el kell kérnünk az övét.

Meghatározzuk a storeValue és retrieveValue függvényeket, amelyek vagy a localStorage objektumot használják (amelyet nem minden böngésző támogat), vagy egy cookie-t. A getSubscriptionKey() függvény a felhasználó azonosítójának tárolására és lekérésére használja ezeket a függvényeket. Használhatja az alábbi globális végpontot, vagy az erőforráshoz tartozó Azure Portal megjelenő egyéni altartományvégpontot.

// Cookie names for data we store
API_KEY_COOKIE   = "bing-search-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";

// Bing Search API endpoint
BING_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/news";

// ... omitted definitions of storeValue() and retrieveValue()
// Browsers differ in their support for persistent storage by 
// local HTML files. See the source code for browser-specific
// options.

// Get stored API subscription key, or 
// prompt if it's not found.
function getSubscriptionKey() {
    var key = retrieveValue(API_KEY_COOKIE);
    while (key.length !== 32) {
        key = prompt("Enter Bing Search API subscription key:", "").trim();
    }
    // always set the cookie in order to update the expiration date
    storeValue(API_KEY_COOKIE, key);
    return key;
}

Egy HTML <form> címke, az onsubmit meghívja a bingWebSearch függvényt, hogy visszaadja a keresési eredményeket. A bingWebSearch minden lekérdezés hitelesítéséhez a getSubscriptionKey() függvényt használja. Ahogy az előző definíció is mutatja, a getSubscriptionKey elkéri a felhasználótól az azonosítót, ha az még nem lett megadva. A rendszer ezután eltárolja az azonosítót az alkalmazás által további használatra.

<form name="bing" onsubmit="this.offset.value = 0; return bingWebSearch(this.query.value, 
    bingSearchOptions(this), getSubscriptionKey())">

Keresési beállítások kiválasztása

A következő ábrán a lekérdezési szövegmező és egy iskolafinanszírozási hírekkel kapcsolatos keresést meghatározó beállítások láthatók.

A Bing News Search beállításai

A HTML-űrlap a következő nevű elemeket tartalmazza:

Elem Leírás
where Legördülő menü a kereséshez használt piac (hely és nyelv) kiválasztásához.
query Szövegmező a keresőkifejezések megadásához.
category Jelölőnégyzetek az eredmények bizonyos típusainak előléptetéséhez. Az Egészség előléptetése esetén például magasabb lesz az egészségügyi hírek besorolása.
when Legördülő menü a keresés opcionális, az elmúlt napra, hétre vagy hónapra történő korlátozásához.
safe Jelölőnégyzet, amellyel megadható, hogy a Bing SafeSearch funkció kiszűrje-e a „felnőtteknek szánt” tartalmakat.
count Rejtett mező. A kérésenként visszaadandó keresési eredmények száma. A módosításával oldalanként kevesebb vagy több keresési eredmény jeleníthető meg.
offset Rejtett mező. Az első keresési eredmény eltolása a kérésben; a lapozást segíti elő. Új kérésnél 0 értékre van visszaállítva.

Megjegyzés

A Bing Web Search más lekérdezési paramétereket is kínál, de mi ezek közül csak néhányat használunk.

// 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.when.value.length) options.push("freshness=" + form.when.value);

    for (var i = 0; i < form.category.length; i++) {
        if (form.category[i].checked) {
            category = form.category[i].value;
            break;
        }
    }
    if (category.valueOf() != "all".valueOf()) { 
        options.push("category=" + category); 
        }
    options.push("count=" + form.count.value);
    options.push("offset=" + form.offset.value);
    return options.join("&");
}

A SafeSearch paraméter értéke például egy tényleges API-hívásban strict, moderate vagy off lehet. Ezek közül a moderate az alapértelmezett. A mi űrlapunk viszont egy jelölőnégyzetet használ, amelynek csak két állapota van. A JavaScript-kód ezt a beállítást strict vagy off értékké alakítja (a moderate nem használatos).

A kérés végrehajtása

A lekérdezés, a beállítási sztring és az API-kulcs ismeretében a BingNewsSearch függvény egy XMLHttpRequest objektumot használ arra, hogy kérést intézzen a Bing News Search-végponthoz.

// perform a search given query, options string, and API key
function bingNewsSearch(query, 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("results", "related", "_json", "_http", "paging1", "paging2", "error");

    var request = new XMLHttpRequest();
     if (category.valueOf() != "all".valueOf()) {
        var queryurl = BING_ENDPOINT + "/search?" + "?q=" + encodeURIComponent(query) + "&" + options;
    }
    else
    {
        if (query){
        var queryurl = BING_ENDPOINT + "?q=" + encodeURIComponent(query) + "&" + options;
        }
        else {
            var queryurl = BING_ENDPOINT + "?" + 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);

    // 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;
}

A HTTP-kérés sikeres befejezése esetén a JavaScript meghívja a load eseménykezelőjét, a handleBingResponse() függvényt, hogy kezeljen egy, az API-hoz intézett sikeres HTTP GET kérést.

// 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 === "News") {
                renderSearchResults(jsobj);
            } else {
                renderErrorMessage("No search results in JSON response");
            }
        } else {
            renderErrorMessage("Empty response (are you sending too many requests too quickly?)");
        }
    }

    // Any other HTTP response is an error
    else {
        // 401 is unauthorized; force re-prompt for API key for next request
        if (this.status === 401) invalidateSubscriptionKey();

        // 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"));
    }
}

Fontos

Egy sikeres HTTP-kérés nem feltétlenül jelenti azt, hogy maga a keresés is sikeres volt. Ha a keresési műveletben hiba merül fel, a Bing News Search API visszaad egy nem 200-as értékű HTTP-állapotkódot, és belefoglalja a hibára vonatkozó információkat a JSON-válaszba. Továbbá, ha a kérés sebessége korlátozva volt, az API egy üres választ ad vissza.

Az előző két függvény kódjainak nagy része a hibakezelésért felel. A következő fázisoknál léphetnek fel hibák:

Fázis Lehetséges hiba vagy hibák Kezelő
A JavaScript-kérésobjektum létrehozása Érvénytelen URL-cím try/catch blokk
Kérés végrehajtása Hálózati hibák, megszakított kapcsolatok error és abort eseménykezelők
Keresés végrehajtása Érvénytelen kérés, érvénytelen JSON, sebességkorlátok tesztek a load eseménykezelőben

A hibák kezelése a renderErrorMessage() meghívásával történik a hibával kapcsolatos ismert részletek megadásával. Ha a válasz hibatesztek teljes skáláját továbbítja, meghívjuk a renderSearchResults() függvényt, hogy megjelenítse a keresési eredményeket az oldalon.

Keresési eredmények megjelenítése

A keresési eredmények megjelenítésére használatos fő függvény a renderSearchResults(). Ez a függvény a Bing News Search szolgáltatás által visszaadott JSON segítségével rendereli a hírtalálatokat és a kapcsolódó kereséseket, ha vannak.

// render the search results given the parsed JSON response
    function renderSearchResults(results) {

    // add Prev / Next links with result count
    var pagingLinks = renderPagingLinks(results);
    showDiv("paging1", pagingLinks);
    showDiv("paging2", pagingLinks);

    showDiv("results", renderResults(results.value));
    if (results.relatedSearches)
        showDiv("sidebar", renderRelatedItems(results.relatedSearches));
}

A JSON-válasz a fő keresési eredményeket a legfelső szintű value objektumként adja vissza. Mi elküldjük őket a renderResults() függvénynek, amely végighalad rajtuk, és meghív egy külön függvényt az egyes elemek HTML-be történő rendereléséhez. A kapott HTML vissza lesz adva a renderSearchResults() függvénynek, amelyben a lap results részlegébe lesz beszúrva.

function renderResults(items) {
    var len = items.length;
    var html = [];
    if (!len) {
        showDiv("noresults", "No results.");
        hideDivs("paging1", "paging2");
        return "";
    }
    for (var i = 0; i < len; i++) {
        html.push(searchItemRenderers.news(items[i], i, len));
    }
    return html.join("\n\n");
}

A Bing News Search API legfeljebb négy különböző típusú kapcsolódó eredményt ad vissza, mindegyiket a saját legfelső szintű objektumában. Ezek a következők:

Kapcsolat típusa Description
pivotSuggestions Lekérdezések, amelyek az eredeti keresés egyik lecserélhető szavát egy másikra cserélik. Ha például a „piros virágok” kifejezésre keres, a „piros” egy lecserélhető szó, a „sárga virágok” pedig egy alternatív javaslat.
queryExpansions Lekérdezések, amelyek további kifejezések hozzáadásával szűkítik az eredeti keresést. Ha például a „Microsoft Surface” kifejezésre keres, a lekérdezés egyik lehetséges kibővítése a „Microsoft Surface Pro”.
relatedSearches Az eredeti keresőkifejezéssel is próbálkozó felhasználók további lekérdezései. Ha például a „Mount Rainier” kifejezésre keres, egy lehetséges kapcsolódó keresés lehet a „Mt. Saint Helens”.
similarTerms Lekérdezések, amelyeknek az eredeti kereséshez hasonló jelentésük van. Ha például az „iskolák” kifejezésre keres, az „oktatás” az egyik ilyen hasonló értelmű keresőszó.

Ahogy a renderSearchResults() függvényben láthatta, csak a relatedItems javaslatokat rendereljük, és a kapott hivatkozásokat a lap oldalsávjában helyezzük el.

Eredményelemek renderelése

A JavaScript-kódban a searchItemRenderers objektum leképezőket is tartalmaz, vagyis olyan függvényeket, amelyek minden típusú keresési eredményhez létrehoznak egy HTML-t.

searchItemRenderers = {
    news: function(item) { ... },
    webPages: function (item) { ... }, 
    images: function(item, index, count) { ... },
    relatedSearches: function(item) { ... }
}

Egy leképező függvény a következő paramétereket fogadja el:

Paraméter Leírás
item A JavaScript-objektum, amely az elem tulajdonságait tartalmazza, például az URL-címét és a leírását.
index Az eredményelem indexe a saját gyűjteményén belül.
count Az eredményelem gyűjteményében található elemek száma.

Az index és count paraméterek használhatók a találatok megszámozására, egy gyűjtemény elején vagy végén egy speciális HTML létrehozására, egy bizonyos számú elem utáni sortörés beszúrására és így tovább. Ha egy leképezőnek nincs szüksége erre a funkcióra, akkor nem kell elfogadnia ezt a két paramétert.

A news renderelő a következő JavaScript-részletben látható:

    // render news story
    news: function (item) {
        var html = [];
        html.push("<p class='news'>");
        if (item.image) {
            width = 60;
            height = Math.round(width * item.image.thumbnail.height / item.image.thumbnail.width);
            html.push("<img src='" + item.image.thumbnail.contentUrl +
                "&h=" + height + "&w=" + width + "' width=" + width + " height=" + height + ">");
        }
        html.push("<a href='" + item.url + "'>" + item.name + "</a>");
        if (item.category) html.push(" - " + item.category);
        if (item.contractualRules) {    // MUST display source attributions
            html.push(" (");
            var rules = [];
            for (var i = 0; i < item.contractualRules.length; i++)
                rules.push(item.contractualRules[i].text);
                html.push(rules.join(", "));
                html.push(")");
            }
        html.push(" (" + getHost(item.url) + ")");
        html.push("<br>" + item.description);
        return html.join("");
    },

A hírleképező függvény:

  • Létrehoz egy bekezdéscímkét, hozzárendeli az news osztályhoz, és leküldéssel továbbítja a HTML-tömbnek.
  • Kiszámítja a képminiatűr méretét (a szélesség 60 képpontban rögzített, a magasságot ezzel arányosan számítja ki).
  • Létrehozza az <img> HTML-címkét a képminiatűr megjelenítéséhez.
  • Létrehozza az <a> HTML-címkéket, amelyek a képre és a képet tartalmazó oldalra hivatkoznak.
  • Létrehozza a leírást, amely információkat jelenít meg a képről és a képet tartalmazó oldalról.

A miniatűr méretét az <img> címke, illetve a miniatűr URL-címének h és w mezője is használja. A Bing miniatűr-szolgáltatása ezután egy pontosan ekkora méretű miniatűrt állít elő.

Ügyfél-azonosító megőrzése

A Bing Search API-k válaszai tartalmazhatnak egy X-MSEdge-ClientID fejlécet, amelyet egymást követő kérésekkel vissza kell küldeni az API-nak. Ha több Bing Search API-t is használ, mindegyikhez ugyanazt az ügyfél-azonosítót használja, ha lehetséges.

Az X-MSEdge-ClientID fejléc megadása lehetővé teszi, hogy a Bing API-k egymáshoz társítsák a felhasználó összes keresését, amelynek két fontos előnye van.

Egyrészt lehetővé teszi, hogy a Bing keresőmotorja korábbi kontextusokat is alkalmazzon a keresésekhez olyan találatok megjelenítése érdekében, amelyek jobban megfelelnek a felhasználó igényeinek. Ha például a felhasználó korábban vitorlázáshoz kapcsolódó kifejezésekre keresett rá, egy későbbi keresés a „csomó” kifejezésre nagy valószínűséggel a vitorlázásban használt csomókkal kapcsolatos információkat fog eredményezni.

Másrészt a Bing véletlenszerűen kiválaszthat felhasználókat, hogy új funkciókat próbálhassanak ki, mielőtt azok széles körben elérhetővé válnának. Ha minden kéréshez ugyanazt az ügyfél-azonosítót adja meg, akkor azok a felhasználók, akiknek megjelenik a funkció, mindig látni fogják. Az ügyfél-azonosító nélkül a felhasználó azt tapasztalhatja, hogy egy funkció látszólag véletlenszerűen hol megjelenik, hol eltűnik a keresési eredményeknél.

A böngészők biztonsági szabályzatai (CORS) megakadályozhatják, hogy a JavaScript hozzáférjen az X-MSEdge-ClientID fejléchez. Ez a korlátozás akkor léphet életbe, ha a keresési válasz eredete különbözik az azt lekérő oldalétól. Éles környezetben egy olyan kiszolgálóoldali szkript futtatásával oldhatja fel a szabályzat okozta korlátozást, amely a weboldaléval megegyező tartományból hívja meg az API-t. Mivel a szkript eredete megegyezik a weboldaléval, az X-MSEdge-ClientID fejléc elérhető lesz a JavaScript számára.

Megjegyzés

Éles webalkalmazásban kiszolgálói oldalról hajtsa végre a kérést. Ellenkező esetben a weboldalnak tartalmaznia kell a Bing Search API-kulcsot, ahol a forrást megtekintők is hozzáférhetnek. Az API előfizetési kulcsával történő összes használatért Ön fizet, még az illetéktelen felek által létrehozott kérésekért is, ezért fontos, hogy a kulcsot ne tegye elérhetővé.

Fejlesztési célokból a Bing Web Search API-kérést egy CORS-proxyn keresztül is végrehajthatja. Az ilyen proxy válasza olyan fejléccel rendelkezik Access-Control-Expose-Headers , amely lehetővé teszi a válaszfejlécek használatát, és elérhetővé teszi őket a JavaScript számára.

CORS-proxyt könnyedén telepíthet annak érdekében, hogy oktatóalkalmazásunk hozzáférhessen az ügyfél-azonosító fejlécéhez. Első lépésként telepítse a Node.js-t, ha még nem tette meg. Ezután hajtsa végre egy parancsablakban a következő parancsot:

npm install -g cors-proxy-server

Következő lépésként írja át a Bing Web Search-végpontot a HTML-fájlban a következőre:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Végül indítsa el a CORS-proxyt a következő paranccsal:

cors-proxy-server

Ne zárja be a parancsablakot, amíg használja az oktatóalkalmazást; az ablak bezárása leállítja a proxyt. A bővíthető HTTP-fejlécek szakaszában, a keresési eredmények alatt, most már az X-MSEdge-ClientID fejléc is megjelenik, és ellenőrizheti, hogy ugyanaz a fejléc szerepel-e minden kérésnél.

Következő lépések