Öğretici: Bing Web Araması API’sini kullanarak tek sayfalı uygulama oluşturma
Uyarı
30 Ekim 2020'de Bing Arama API'leri Azure yapay zeka hizmetlerinden Bing Arama Hizmetlerine taşındı. Bu belgeler yalnızca başvuru için sağlanır. Güncelleştirilmiş belgeler için Bing arama API'sinin belgelerine bakın. Bing araması için yeni Azure kaynakları oluşturma yönergeleri için bkz. Azure Market aracılığıyla Bing Arama kaynağı oluşturma.
Bu tek sayfalı uygulama Bing Web Araması API'sinden arama sonuçlarını almayı, ayrıştırmayı ve görüntülemeyi gösterir. Öğretici standart HTML ile CSS kullanır ve JavaScript koduna odaklanır. HTML, CSS ve JS dosyaları, hızlı başlangıç yönergeleriyle birlikte GitHub'da sağlanır.
Bu örnek uygulama şunları yapabilir:
- Arama seçenekleriyle Bing Web Araması API'sini çağırma
- Web, resim, haber ve video sonuçlarını görüntüleme
- Sonuçları sayfalandırma
- Abonelik anahtarlarını yönetme
- Hataları işleme
Bu uygulamayı kullanmak için Bing Arama API'leri olan bir Azure AI hizmetleri hesabı gerekir.
Önkoşullar
Uygulamayı çalıştırmak için ihtiyacınız olacak birkaç şey:
Azure aboneliği - Ücretsiz bir abonelik oluşturun
Azure aboneliğinizi aldıktan sonra anahtarınızı ve uç noktanızı almak için Azure portal bir Bing Arama kaynağı oluşturun. Dağıtıldıktan sonra Kaynağa git'e tıklayın.
Node.js 8 veya üstü
İlk adım, örnek uygulamanın kaynak koduyla depoyu kopyalamaktır.
git clone https://github.com/Azure-Samples/cognitive-services-REST-api-samples.git
Ardından npm install
komutunu çalıştırın. Bu öğreticide tek bağımlılık Express.js'dir.
cd <path-to-repo>/cognitive-services-REST-api-samples/Tutorials/bing-web-search
npm install
Uygulama bileşenleri
Derlediğimiz örnek uygulama dört bölümden oluşur:
-
bing-web-search.js
- Express.js uygulamamız. İstek/yanıt mantığını ve yönlendirmeyi işler. -
public/index.html
- Uygulamamızın çatısı; verilerin kullanıcıya nasıl gösterileceğini tanımlar. -
public/css/styles.css
- Yazı tipi, renk, metin boyutu gibi sayfa stillerini tanımlar. -
public/js/scripts.js
- Bing Web Araması API'sine istek göndermek, abonelik anahtarlarını yönetmek, yanıtları işlemek, ayrıştırmak ve sonuçları görüntülemek için gereken mantığı içerir.
Bu öğretici scripts.js
dosyasına ve Bing Web Araması API'sini çağırıp yanıtı işlemek için gereken mantığa odaklanır.
HTML formu
index.html
, kullanıcıların arama yapmasına ve arama seçeneklerini belirtmesine olanak sağlayan bir form içerir. Form gönderilip scripts.js
dosyasında tanımlanan bingWebSearch()
yöntemi çağrıldığında, onsubmit
özniteliği çağrılır. Üç bağımsız değişken alır:
- Arama sorgusu
- Belirtilen seçenekler
- Abonelik anahtarı
<form name="bing" onsubmit="return bingWebSearch(this.query.value,
bingSearchOptions(this), getSubscriptionKey())">
Sorgu seçenekleri
HTML formu, Bing Web Araması API'si v7'deki sorgu parametrelerine eşlenen seçenekler içerir. Bu tabloda, örnek uygulamayı kullanarak kullanıcıların arama sonuçlarını nasıl filtreleyebileceğini gösteren bir döküm sağlanır:
Parametre | Açıklama |
---|---|
query |
Sorgu dizesinin girileceği metin alanı. |
where |
Pazarın (konum ve dil) seçileceği açılan menü. |
what |
Belirli sonuç türlerini yükseltmek için onay kutuları. Örneğin görüntülerin yükseltilmesi, arama sonuçlarında görüntülerin derecelendirmesini yükseltir. |
when |
Kullanıcının arama sonuçlarını bugün, bu hafta veya bu ay ile sınırlandırmasına olanak tanıyan bir açılan menü. |
safe |
Yetişkinlere yönelik içeriği filtreleyip dışarıda bırakan Bing Güvenli Araması'nı etkinleştirmek için bir onay kutusu. |
count |
Gizli alan. Her istekte döndürülecek arama sonuçlarının sayısı. Sayfada daha az veya daha fazla sonuç görüntülemek için bu değeri değiştirin. |
offset |
Gizli alan. İstekteki ilk arama sonucunun göreli konumu; sayfalama için kullanılır. Her yeni istekle birlikte 0 değerine sıfırlanır. |
Not
Bing Web Araması API'si arama sonuçlarını daraltmak için ek sorgu parametreleri sağlar. Bu örnekte yalnızca birkaç parametre kullanılır. Sağlanan parametrelerin tam listesi için bkz. Bing Web Araması API'si v7 başvurusu.
bingSearchOptions()
işlevi, Bing Arama API'sinin gerektirdiği biçime uyacak şekilde bu seçenekleri dönüştürür.
// Build query options from selections in the HTML form.
function bingSearchOptions(form) {
var options = [];
// Where option.
options.push("mkt=" + form.where.value);
// SafeSearch option.
options.push("SafeSearch=" + (form.safe.checked ? "strict" : "moderate"));
// Freshness option.
if (form.when.value.length) options.push("freshness=" + form.when.value);
var what = [];
for (var i = 0; i < form.what.length; i++)
if (form.what[i].checked) what.push(form.what[i].value);
// Promote option.
if (what.length) {
options.push("promote=" + what.join(","));
options.push("answerCount=9");
}
// Count option.
options.push("count=" + form.count.value);
// Offset option.
options.push("offset=" + form.offset.value);
// Hardcoded text decoration option.
options.push("textDecorations=true");
// Hardcoded text format option.
options.push("textFormat=HTML");
return options.join("&");
}
SafeSearch
strict
, moderate
veya off
olarak ayarlanabilir; Bing Web Araması'nın varsayılan ayarı moderate
ayarıdır. Bu form, iki durumu olan bir onay kutusu kullanır: strict
veya moderate
.
Yükselt onay kutularından herhangi bir seçildiyse, sorguya answerCount
parametresi eklenir.
promote
parametresi kullanıldığında answerCount
gereklidir. Bu kod parçacığında, tüm kullanılabilir sonuç türlerinin döndürülmesi için değer 9
olarak ayarlanmıştır.
Not
Sonuç türünün yükseltilmesi, bunun arama sonuçlarına eklenmesini garanti etmez. Bunun yerine, yükseltme işlemi bu tür sonuçların derecelendirmesini kendi normal derecelerine göre yükseltir. Aramaları belirli sonuç türleriyle sınırlandırmak için, responseFilter
sorgu parametresini kullanın ya da Bing Resim Arama veya Bing Haber Arama gibi daha belirgin bir uç noktayı çağırın.
textDecoration
ve textFormat
sorgu parametreleri betiğe sabit kodlanmıştır ve arama teriminin arama sonuçlarında kalın gösterilmesine neden olur. Bu parametreler gerekli değildir.
Abonelik anahtarlarını yönetme
Bing Arama API'si abonelik anahtarının basit kodlanmasını önlemek için, bu örnek uygulama tarayıcının kalıcı depolamasını kullanarak abonelik anahtarını depolar. Hiçbir abonelik anahtarı depolanmazsa, kullanıcıdan bir anahtar girmesi istenir. Abonelik anahtarı API tarafından reddedilirse, kullanıcının abonelik anahtarını yeniden girmesi istenir.
getSubscriptionKey()
işlevi, storeValue
ve retrieveValue
işlevlerini kullanarak kullanıcının abonelik anahtarını depolar ve alır. Bu işlevler, destekleniyorsa localStorage
nesnesini veya tanımlama bilgilerini kullanır.
// Cookie names for stored data.
API_KEY_COOKIE = "bing-search-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";
BING_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/search";
// See source code for storeValue and retrieveValue definitions.
// Get stored subscription key, or prompt if it isn't 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;
}
Daha önce gördüğümüz gibi, form gönderildiğinde onsubmit
çağrılarak bingWebSearch
çağrısı yapılır. Bu işlev isteği başlatır ve gönderir. Her gönderimde isteğin kimliğini doğrulamak için getSubscriptionKey
çağrılır.
Bing Web Araması'nı çağırma
Sorgu, seçenekler dizesi ve abonelik anahtarı verili durumdayken, BingWebSearch
işlevi Bing Web Araması uç noktasını çağırmak için bir XMLHttpRequest
nesnesi oluşturur.
// Perform a search constructed from the query, options, and subscription key.
function bingWebSearch(query, options, key) {
window.scrollTo(0, 0);
if (!query.trim().length) return false;
showDiv("noresults", "Working. Please wait.");
hideDivs("pole", "mainline", "sidebar", "_json", "_http", "paging1", "paging2", "error");
var request = new XMLHttpRequest();
var queryurl = BING_ENDPOINT + "?q=" + encodeURIComponent(query) + "&" + options;
// Initialize 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 errors.
request.addEventListener("error", function() {
renderErrorMessage("Error completing request");
});
// Event handler for an aborted request.
request.addEventListener("abort", function() {
renderErrorMessage("Request aborted");
});
// Send the request.
request.send();
return false;
}
Başarılı bir isteğin ardından, load
olay işleyicisi çağrılır ve handleBingResponse
işlevini çağırır.
handleBingResponse
sonuç nesnesini ayrıştırıp sonuçları görüntüler ve başarısız istekler için hata mantığını içerir.
function handleBingResponse() {
hideDivs("noresults");
var json = this.responseText.trim();
var jsobj = {};
// Try to parse results object.
try {
if (json.length) jsobj = JSON.parse(json);
} catch(e) {
renderErrorMessage("Invalid JSON response");
return;
}
// Show raw JSON and the 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 the HTTP response is 200 OK, try to render the 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" && "rankingResponse" in jsobj) {
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 considered an error.
else {
// 401 is unauthorized; force a re-prompt for the user's subscription
// key on the next request.
if (this.status === 401) invalidateSubscriptionKey();
// Some error responses don't have a top-level errors object, if absent
// create one.
var errors = jsobj.errors || [jsobj];
var errmsg = [];
// Display the 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]);
}
// Display Bing Trace ID if it isn't blocked by CORS.
var traceid = this.getResponseHeader("BingAPIs-TraceId");
if (traceid) errmsg.push("\nTrace ID " + traceid);
// Display the error message.
renderErrorMessage(errmsg.join("\n"));
}
}
Önemli
Başarılı bir HTTP isteği, aramanın kendisinin başarılı olduğu anlamına gelmez. Arama işleminde hata oluşursa, Bing Web Araması API'si 200 olmayan bir HTTP durum kodu döndürür ve JSON yanıtına hata bilgilerini ekler. İstekte hız sınırlaması varsa API boş yanıt döndürür.
Önceki işlevlerin ikisinde de kodun büyük bölümü hata işlemeye ayrılmıştır. Şu aşamalarda hata oluşabilir:
Aşama | Olası hatalar | İşleyen |
---|---|---|
İstek nesnesini oluşturma | Geçersiz URL |
try
/
catch bloğu |
İstekte bulunma | Ağ hataları, durdurulan bağlantılar |
error ve abort olay işleyicileri |
Aramayı gerçekleştirme | Geçersiz istek, geçersiz JSON, hız sınırları |
load olay işleyicisindeki testler |
Hatalar renderErrorMessage()
çağrısı yapılarak işlenir. Yanıt tüm hata testlerinden geçerse, arama sonuçlarını görüntülemek için renderSearchResults()
çağrılır.
Arama sonuçlarını görüntüleme
Bing Web Araması API'si tarafından döndürülen sonuçlarla ilişkili kullanım ve görüntüleme gereksinimleri vardır. Yanıtta çeşitli yanıt türleri bulunabileceğinden, en üst düzey WebPages
koleksiyonunu yinelemek yeterli olmaz. Bunun yerine, örnek uygulama sonuçları belirtimlere göre sıralamak için RankingResponse
kullanır.
Not
Tek bir sonuç türü istiyorsanız, responseFilter
sorgu parametresini kullanın veya Bing Resim Araması gibi diğer Bing Araması uç noktalarını kullanmayı göz önünde bulundurun.
Her yanıtta bir RankingResponse
nesnesi vardır ve bu nesne en çok üç koleksiyon içerebilir: pole
, mainline
ve sidebar
.
pole
(varsa) en ilgili arama sonucudur ve belirgin bir şekilde görüntülenmelidir.
mainline
arama sonuçlarının büyük bölümünü içerir ve hemen pole
koleksiyonundan sonra görüntülenir.
sidebar
ikincil arama sonuçlarını içerir. Mümkünse, bu sonuçların kenar çubuğunda görüntülenmesi gerekir. Ekran sınırlarından dolayı kenar çubuğunu görüntülemek pratik değilse, bu sonuçlar mainline
sonuçlarından sonra gösterilmelidir.
Her RankingResponse
, sonuçların nasıl sıralanacağını belirten bir RankingItem
dizisi içerir. Örnek uygulamamızda, sonucu tanımlamak için answerType
ve resultIndex
parametreleri kullanılır.
Not
Sonuçları tanımlamanın ve derecelendirmenin başka yolları da vardır. Daha fazla bilgi için bkz. Sonuçları görüntülemek için derecelendirmeyi kullanma.
Şimdi koda bir göz atalım:
// Render the search results from the JSON response.
function renderSearchResults(results) {
// If spelling was corrected, update the search field.
if (results.queryContext.alteredQuery)
document.forms.bing.query.value = results.queryContext.alteredQuery;
// Add Prev / Next links with result count.
var pagingLinks = renderPagingLinks(results);
showDiv("paging1", pagingLinks);
showDiv("paging2", pagingLinks);
// Render the results for each section.
for (section in {pole: 0, mainline: 0, sidebar: 0}) {
if (results.rankingResponse[section])
showDiv(section, renderResultsItems(section, results));
}
}
renderResultsItems()
işlevi her RankingResponse
koleksiyonundaki öğelerde yinelenir, answerType
ve resultIndex
değerlerini kullanarak her derecelendirme sonucunu bir arama sonucuna eşler ve HTML'yi oluşturmak için uygun işleme işlevini çağırır. Öğe için resultIndex
belirtilmediyse, renderResultsItems()
bu türdeki tüm sonuçlarda yinelenir ve her öğe için işleme işlevini çağırır. Sonuçta elde edilen HTML, index.html
dosyasındaki uygun <div>
öğesine eklenir.
// Render search results from the RankingResponse object per rank response and
// use and display requirements.
function renderResultsItems(section, results) {
var items = results.rankingResponse[section].items;
var html = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
// Collection name has lowercase first letter while answerType has uppercase
// e.g. `WebPages` RankingResult type is in the `webPages` top-level collection.
var type = item.answerType[0].toLowerCase() + item.answerType.slice(1);
if (type in results && type in searchItemRenderers) {
var render = searchItemRenderers[type];
// This ranking item refers to ONE result of the specified type.
if ("resultIndex" in item) {
html.push(render(results[type].value[item.resultIndex], section));
// This ranking item refers to ALL results of the specified type.
} else {
var len = results[type].value.length;
for (var j = 0; j < len; j++) {
html.push(render(results[type].value[j], section, j, len));
}
}
}
}
return html.join("\n\n");
}
İşleyici işlevlerini gözden geçirme
Örnek uygulamamızdaki searchItemRenderers
nesnesinde, her tür arama sonucu için HTML oluşturan işlevler vardır.
// Render functions for each result type.
searchItemRenderers = {
webPages: function(item) { ... },
news: function(item) { ... },
images: function(item, section, index, count) { ... },
videos: function(item, section, index, count) { ... },
relatedSearches: function(item, section, index, count) { ... }
}
Önemli
Örnek uygulamanın web sayfaları, haberler, görüntüler, videolar ve ilgili aramalar için işleyicileri vardır. Uygulamanızın alabileceği her tür sonuç için işleyicilere ihtiyacı olacaktır. Bunlar hesaplamaları, yazım önerilerini, varlıkları, saat dilimlerini ve tanımları içerebilir.
İşleme işlevlerinden bazıları yalnızca item
parametresini kabul eder. Diğerleri, bağlama göre öğeleri farklı işlemek için kullanılabilen ek parametreler de kabul eder. Bu bilgileri kullanmayan bir işleyicinin bu parametreleri kabul etmesi gerekmez.
Bağlam bağımsız değişkenleri şunlardır:
Parametre | Açıklama |
---|---|
section |
Öğelerin gösterildiği sonuç bölümü (pole , mainline veya sidebar ). |
index count |
RankingResponse öğesi verili bir koleksiyondaki tüm sonuçların görüntüleneceğini belirtirse sağlanır; aksi takdirde undefined olur. Öğenin koleksiyonu içindeki dizin ve bu koleksiyondaki öğelerin toplam sayısı. Bu bilgileri kullanarak sonuçları numaralandırabilir, ilk veya son sonuç için farklı bir HTML oluşturabilir ve başka işlemler yapabilirsiniz. |
Örnek uygulamada, hem images
hem de relatedSearches
işleyicileri oluşturulan HTML'yi özelleştirmek için bağlam bağımsız değişkenlerini kullanır.
images
işleyicisine daha yakından bakalım:
searchItemRenderers = {
// Render image result with thumbnail.
images: function(item, section, index, count) {
var height = 60;
var width = Math.round(height * item.thumbnail.width / item.thumbnail.height);
var html = [];
if (section === "sidebar") {
if (index) html.push("<br>");
} else {
if (!index) html.push("<p class='images'>");
}
html.push("<a href='" + item.hostPageUrl + "'>");
var title = escape(item.name) + "\n" + getHost(item.hostPageDisplayUrl);
html.push("<img src='"+ item.thumbnailUrl + "&h=" + height + "&w=" + width +
"' height=" + height + " width=" + width + " title='" + title + "' alt='" + title + "'>");
html.push("</a>");
return html.join("");
},
// Other renderers are omitted from this sample...
}
Görüntü işleyicisi:
- Görüntünün küçük resim boyutunu hesaplar (genişlik değişiklik gösterir, yükseklik ise 60 piksele sabitlenmiştir).
- Bağlama göre görüntü sonucunun önüne gelen HTML'yi ekler.
- Resmi içeren sayfaya bağlanan HTML
<a>
etiketini oluşturur. - Resmin küçük resmini görüntülemek için HTML
<img>
etiketini oluşturur.
Görüntü işleyicisi, bulundukları yere bağlı olarak sonuçları farklı görüntülemek için section
ve index
bağımsız değişkenlerini kullanır. Kenar çubuğunda görüntü sonuçlarının arasına satır sonu (<br>
etiketi) eklenerek, kenar çubuğunun bir görüntü sütunu göstermesi sağlanır. Diğer bölümlerde, ilk görüntü sonucunun ((index === 0)
) önüne bir <p>
etiketi gelir.
Küçük resim boyutu hem <img>
etiketinde hem de küçük resmin URL'sindeki h
ve w
alanlarında kullanılır.
title
ve alt
öznitelikleri (görüntünün metin açıklaması), görüntü adından ve URL'deki ana bilgisayar adından oluşturulur.
Burada, örnek uygulamada görüntülerin nasıl gösterildiğine ilişkin bir örnek verilmiştir:
İstemci kimliğinin kalıcı olmasını sağlama
Bing arama API'lerinden gelen yanıtlar, birbirini izleyen her başarılı birlikte API'ye geri gönderilmesi gereken bir X-MSEdge-ClientID
üst bilgisi içerir. Uygulamanız birden çok Bing Arama API'si kullanıyorsa, her istekle birlikte hizmetler arasında aynı istemci kimliğinin gönderildiğinden emin olun.
Böylelikle X-MSEdge-ClientID
üst bilgisi sayesinde Bing API'leri kullanıcının aramalarını ilişkilendirebilir. İlk olarak, Bing arama alt yapısının geçmiş bağlamı aramalara uygulayarak isteğe daha uygun sonuçlar bulabilmesini sağlar. Kullanıcı daha önce yelkencilikle ilgili terim aramaları yaptıysa, daha sonra yapılan "düğümler" araması tercihen yelkencilikte kullanılan düğümler hakkında bilgi döndürebilir. İkincisi, Bing yeni özellikleri geniş ölçekte kullanıma sunmadan önce bunları denemesi için rastgele kullanıcılar seçebilir. Her istekle birlikte aynı istemci kimliğinin sağlanması, özelliği görmesi tercih edilen kullanıcıların bunu sürekli görebilmesini sağlar. İstemci kimliği olmadan, özellik kullanıcıya arama sonuçlarında rastgele gösterilebilir ve gösterilmeyebilir.
Çıkış Noktaları Arası Kaynak Paylaşımı (CORS) gibi tarayıcı güvenlik ilkeleri, örnek uygulamanın X-MSEdge-ClientID
üst bilgisine erişmesini engelleyebilir. Bu sınırlama, arama sonucunun kaynağı istekte bulunan sayfadan farklı olduğunda ortaya çıkar. Üretim ortamında, Web sayfasıyla aynı etki alanında API çağrısı yapan bir sunucu tarafı betiği barındırarak bu ilkeye uymalısınız. Betiğin kaynağı Web sayfasıyla aynı olduğundan, X-MSEdge-ClientID
üst bilgisi JavaScript'in kullanımına sunulur.
Not
Üretim ortamındaki bir Web uygulamasında, isteği sunucu tarafından gerçekleştirmeniz gerekir. Aksi takdirde, Bing Arama API'si abonelik anahtarınız web sayfasına eklenmelidir ve bu durumda kaynağı görüntüleyen herkes tarafından görülebilir. API abonelik anahtarınız altında gerçekleştirilen tüm kullanım, yetkisiz tarafların yaptığı istekler bile size faturalandırılır; dolayısıyla anahtarınızı açıklamamanız önemlidir.
Geliştirme amacıyla, CORS ara sunucusu aracılığıyla bir istek gönderebilirsiniz. Bu ara sunucu türünden gelen yanıt, yanıt üst bilgilerini filtreleyen ve JavaScript için kullanılabilir hale getiren bir Access-Control-Expose-Headers
üst bilgi içerir.
Örnek uygulamamızın istemci kimliği üst bilgisine erişebilmesi için CORS ara sunucusu kolayca yüklenebilir. Şu komutu çalıştırın:
npm install -g cors-proxy-server
Sonra, script.js
dosyasındaki Bing Web Araması uç noktasını şöyle değiştirin:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search
Şu komutla CORS ara sunucusunu başlatın:
cors-proxy-server
Örnek uygulamayı kullanırken komut penceresini açık bırakın; pencere kapatılırsa ara sunucu durdurulur. Arama sonuçlarının alt kısmındaki genişletilebilir HTTP Üst Bilgileri bölümünde X-MSEdge-ClientID
üst bilgisi görünür durumda olmalıdır. Bunun her istekte aynı olduğunu doğrulayın.