您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

教程:创建单页 Web 应用Tutorial: Create a single-page web app

通过必应新闻搜索 API 可以搜索 Web 并获取与搜索查询相关的新闻类型结果。The Bing News Search API lets you search the Web and obtain results of the news type relevant to a search query. 本教程将生成一个单页 Web 应用程序,该应用程序使用必应新闻搜索 API 在页面中显示搜索结果。In this tutorial, we build a single-page Web application that uses the Bing News Search API to display search results on the page. 该应用程序包含 HTML、CSS 和 JavaScript 组件。The application includes HTML, CSS, and JavaScript components. 该示例的源代码可在 GitHub 上获得。The source code for this sample is available on GitHub.


单击时页面底部出现的 JSON 和 HTTP 标题显示 JSON 响应和 HTTP 请求信息。The JSON and HTTP headings at the bottom of the page when clicked show the JSON response and HTTP request information. 这些详细信息在浏览该服务时非常有用。These details can be useful when exploring the service.

教程应用演示了如何:The tutorial app illustrates how to:

  • 在 JavaScript 中执行必应新闻搜索 API 调用Perform a Bing News Search API call in JavaScript
  • 将搜索选项传递到必应新闻搜索 APIPass search options to the Bing News Search API
  • 显示四种类别中的新闻搜索结果:任何类型、业务、运行状况或政治,时间段分别为 24 小时、过去一周、过去一个月或所有可用时间Display news search results from four categories: any-type, business, health, or politics, from time-frames of 24 hours, the past week, month, or all available time
  • 翻页浏览搜索结果Page through search results
  • 处理必应客户端 ID 和 API 订阅密钥Handle the Bing client ID and API subscription key
  • 处理可能出现的错误Handle errors that might occur

教程页是完全独立的;它不使用任何外部框架、样式表或图像文件,The tutorial page is entirely self-contained; it does not use any external frameworks, style sheets, or image files. 而仅使用广泛支持的 JavaScript 语言功能,并且适用于所有主要 Web 浏览器的当前版本。It uses only widely supported JavaScript language features and works with current versions of all major Web browsers.


若要继续学习本教程,需要必应搜索 API 的订阅密钥。To follow along with the tutorial, you need subscription keys for the Bing Search API. 如果没有这些内容,则需要创建:If you don't have these, you'll need to create them:

应用组件App components

与任何单页 Web 应用一样,本教程应用程序包含以下三个部分:Like any single-page Web app, this tutorial application includes three parts:

  • HTML - 定义页面的结构和内容HTML - Defines the structure and content of the page
  • CSS - 定义页面的外观CSS - Defines the appearance of the page
  • JavaScript - 定义页面的行为JavaScript - Defines the behavior of the page

大部分 HTML 和 CSS 是常规的,因此本教程不做讨论。Most of the HTML and CSS is conventional, so the tutorial doesn't discuss it. HTML 包含搜索表单,用户可以在其中输入查询并选择搜索选项。The HTML contains the search form in which the user enters a query and chooses search options. 该表单会连接到实际使用 <form> 标记的 onsubmit 属性执行搜索的 JavaScript:The form is connected to JavaScript that actually performs the search using the onsubmit attribute of the <form> tag:

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

onsubmit 处理程序返回 false,这会阻止表单提交到服务器。The onsubmit handler returns false, which keeps the form from being submitted to a server. JavaScript 代码将执行从表单中收集所需的信息并执行搜索的工作。The JavaScript code does the work of collecting the necessary information from the form and performing the search.

HTML 还包含部门(HTML <div> 标记),其中显示搜索结果。The HTML also contains the divisions (HTML <div> tags) where the search results appear.

管理订阅密钥Managing subscription key

为了避免将必应搜索 API 订阅密钥包含在代码中,我们使用浏览器的持久性存储来存储密钥。To avoid having to include the Bing Search API subscription key in the code, we use the browser's persistent storage to store the key. 在存储密钥之前,系统会提示我们输入用户的密钥。Before the key is stored, we prompt for the user's key. 如果该密钥稍后被 API 拒绝,我们将使已存储的密钥失效,因此系统会再次提示用户输入。If the key is later rejected by the API, we invalidate the stored key so the user will be prompted again.

我们定义使用 localStorage 对象(并非所有浏览器都支持它)或 cookie 的 storeValueretrieveValue 函数。We define storeValue and retrieveValue functions that use either the localStorage object (not all browsers support it) or a cookie. getSubscriptionKey() 函数使用这些函数来存储和检索用户的密钥。The getSubscriptionKey() function uses these functions to store and retrieve the user's key. 可以使用下面的全局终结点,也可以使用资源的 Azure 门户中显示的自定义子域终结点。You can use the global endpoint below, or the custom subdomain endpoint displayed in the Azure portal for your resource.

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

HTML <form> 标记 onsubmit 可调用 bingWebSearch 函数以返回搜索结果。The HTML <form> tag onsubmit calls the bingWebSearch function to return search results. bingWebSearch 使用 getSubscriptionKey() 对每个查询进行身份验证。bingWebSearch uses getSubscriptionKey() to authenticate each query. 如先前定义中所示,如果尚未输入密钥,getSubscriptionKey 会提示用户输入密钥。As shown in the previous definition, getSubscriptionKey prompts the user for the key if the key hasn't been entered. 然后会存储该密钥以供应用程序继续使用。The key is then stored for continuing use by the application.

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

选择搜索选项Selecting search options

下图显示了查询文本框以及用于定义对有关学校资金的新闻的搜索的选项。The following figure shows the query text box and options that define a search for news about school funding.


HTML 表单包括具有以下名称的元素:The HTML form includes elements with the following names:

元素Element 说明Description
where 用于选择市场(位置和语言)进行搜索的下拉菜单。A drop-down menu for selecting the market (location and language) used for the search.
query 用于输入搜索条件的文本字段。The text field to enter the search terms.
category 用于提升特定结果类型的复选框。Checkboxes for promoting particular kinds of results. 提升运行状况,例如,提升运行状况新闻的排名。Promoting Health, for example, increases the ranking of health news.
when 用于视需要将搜索限制为最近一天、最近一周或最近一个月的下拉菜单。Drop-down menu for optionally limiting the search to the most recent day, week, or month.
safe 指示是否使用必应安全搜索功能筛选出“成人”结果的复选框。A checkbox indicating whether to use the Bing SafeSearch feature to filter out "adult" results.
count 隐藏的字段。Hidden field. 将在每个请求后返回的搜索结果数。The number of search results to return on each request. 更改为每页显示更少或更多结果。Change to display fewer or more results per page.
offset 隐藏的字段。Hidden field. 请求中第一个搜索结果的偏移;用于分页。The offset of the first search result in the request; used for paging. 在新请求中将重置为 0It's reset to 0 on a new request.


必应 Web 搜索提供了其他查询参数。Bing Web Search offers other query parameters. 我们仅使用其中一些参数。We're using only a few of them.

// 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;
    if (category.valueOf() != "all".valueOf()) { 
        options.push("category=" + category); 
    options.push("count=" + form.count.value);
    options.push("offset=" + form.offset.value);
    return options.join("&");

例如,实际 API 调用中的 SafeSearch 参数可以为 strictmoderateoff,并将 moderate 作为默认值。For example, the SafeSearch parameter in an actual API call can be strict, moderate, or off, with moderate being the default. 但是,我们的表单会使用仅具有两种状态的复选框。Our form, however, uses a checkbox, which has only two states. JavaScript 代码会将此设置转换为 strictoff(不会使用 moderate)。The JavaScript code converts this setting to either strict or off (moderate is not used).

执行请求Performing the request

鉴于查询、选项字符串和 API 密钥,BingNewsSearch 函数会使用 XMLHttpRequest 对象向必应新闻搜索终结点发出请求。Given the query, the options string, and the API key, the BingNewsSearch function uses an XMLHttpRequest object to make the request to the Bing News Search endpoint.

// 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;
        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
    return false;

HTTP 请求成功完成后,JavaScript 会调用 load 事件处理程序和 handleBingResponse() 函数来处理对 API 的成功 HTTP GET 请求。Upon successful completion of the HTTP request, JavaScript calls the load event handler, the handleBingResponse() function, to handle a successful HTTP GET request to the API.

// handle Bing search request results
function handleBingResponse() {

    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") {
            } 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


成功的 HTTP 请求不一定意味着搜索本身成功。A successful HTTP request does not necessarily mean that the search itself succeeded. 如果搜索操作中出现错误,必应新闻搜索 API 将返回非 200 HTTP 状态代码并将错误信息包含在 JSON 响应中。If an error occurs in the search operation, the Bing News Search API returns a non-200 HTTP status code and includes error information in the JSON response. 此外,如果请求速率受限制,该 API 还会返回空响应。Additionally, if the request was rate-limited, the API returns an empty response.

上面两个函数中的很多代码专用于错误处理。Much of the code in both of the preceding functions is dedicated to error handling. 以下阶段可能会出现错误:Errors may occur at the following stages:

阶段Stage 可能的错误Potential error(s) 处理方式Handled by
生成 JavaScript 请求对象Building the JavaScript request object 无效的 URLInvalid URL try/catchtry/catch block
发出请求Making the request 网络错误,已中止连接Network errors, aborted connections errorabort 事件处理程序error and abort event handlers
执行搜索Performing the search 无效的请求、无效的 JSON、速率限制Invalid request, invalid JSON, rate limits load 事件处理程序中的测试tests in load event handler

处理错误时,会调用 renderErrorMessage(),获取有关该错误的任何已知详细信息。Errors are handled by calling renderErrorMessage() with any details known about the error. 如果响应通过全部错误测试,则会调用 renderSearchResults(),在页面中显示搜索结果。If the response passes the full gauntlet of error tests, we call renderSearchResults() to display the search results in the page.

显示搜索结果Displaying search results

用于显示搜索结果的主函数是 renderSearchResults()The main function for displaying the search results is renderSearchResults(). 此函数将获取必应新闻搜索服务返回的 JSON 并显示新闻结果和相关搜索(如果有)。This function takes the JSON returned by the Bing News Search service and renders the news results and the related searches, if any.

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

主要搜索结果在 JSON 响应中返回为顶级 value 对象。The main search results are returned as the top-level value object in the JSON response. 我们将这些结果传递给函数 renderResults(),该函数会对其进行遍历并调用一个单独的函数以将每一项呈现到 HTML 中。We pass them to our function renderResults(), which iterates through them and calls a separate function to render each item into HTML. 生成的 HTML 将返回到 renderSearchResults(),该 HTML 在其中插入到页面中的 results 划分。The resulting HTML is returned to renderSearchResults(), where it is inserted into the results division in the page.

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

必应新闻搜索 API 最多返回四种不同类型的相关结果,每个都有其自己的顶级对象。The Bing News Search API returns up to four different kinds of related results, each in its own top-level object. 它们分别是:They are:

关系Relation 说明Description
pivotSuggestions 将原始搜索中的透视字替换为其他字的查询。Queries that replace a pivot word in original search with a different one. 例如,如果搜索“red flowers”,透视字可能为“red”,并且透视建议可能为“yellow flowers”。For example, if you search for "red flowers," a pivot word might be "red," and a pivot suggestion might be "yellow flowers."
queryExpansions 通过添加更多词缩小原始搜索范围的查询。Queries that narrow the original search by adding more terms. 例如,如果搜索“Microsoft Surface”,查询扩展可能为“Microsoft Surface Pro”。For example, if you search for "Microsoft Surface," a query expansion might be "Microsoft Surface Pro."
relatedSearches 由输入了原始搜索的用户输入的查询。Queries that have also been entered by other users who entered the original search. 例如,如果搜索“Mount Rainier”,相关搜索可能为“Mt.For example, if you search for "Mount Rainier," a related search might be "Mt. Saint Helens”。Saint Helens."
similarTerms 与原始搜索含义类似的查询。Queries that are similar in meaning to the original search. 例如,如果搜索“schools”,类似字词可能为“education”。For example, if you search for "schools," a similar term might be "education."

如之前在 renderSearchResults() 中所示,我们仅呈现 relatedItems 建议并将生成的链接放在页面的侧栏中。As previously seen in renderSearchResults(), we render only the relatedItems suggestions and place the resulting links in the page's sidebar.

呈现结果项Rendering result items

在 JavaScript 代码对象中,searchItemRenderers 包含 呈现器:为每种搜索结果生成 HTML 的 函数。In the JavaScript code the object, searchItemRenderers, contains renderers: functions that generate HTML for each kind of search result.

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

呈现器函数可以接受以下参数:A renderer function can accept the following parameters:

参数Parameter 说明Description
item 包含项目属性(如其 URL 及其说明)的 JavaScript 对象。The JavaScript object containing the item's properties, such as its URL and its description.
index 结果项集合中的结果项的索引。The index of the result item within its collection.
count 搜索结果项集合中的项数。The number of items in the search result item's collection.

indexcount 参数可用于计算结果数、为集合的开头或末尾生成特殊的 HTML、在一定数量的项后插入换行符,等等。The index and count parameters can be used to number results, to generate special HTML for the beginning or end of a collection, to insert line breaks after a certain number of items, and so on. 呈现器在不需要此功能的情况下,不需接受这两个参数。If a renderer does not need this functionality, it does not need to accept these two parameters.

news 呈现器显示在以下 JavaScript 摘录中:The news renderer is shown in the following JavaScript excerpt:

    // 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++)
                html.push(rules.join(", "));
        html.push(" (" + getHost(item.url) + ")");
        html.push("<br>" + item.description);
        return html.join("");

新闻呈现器函数:The news renderer function:

  • 可创建段落标记,将其分配给 news 类,并将其推到 html 数组。Creates a paragraph tag, assigns it to the news class, and pushes it to the html array.
  • 可计算图像缩略图大小(宽度固定为 60 像素,高度按比例计算)。Calculates image thumbnail size (width is fixed at 60 pixels, height calculated proportionately).
  • 可生成 HTML <img> 标记以显示图像缩略图。Builds the HTML <img> tag to display the image thumbnail.
  • 生成链接到图像及所在页面的 HTML <a> 标记。Builds the HTML <a> tags that link to the image and the page that contains it.
  • 可生成显示有关图像及所在站点的信息的说明。Builds the description that displays information about the image and the site it's on.

<img> 标记以及缩略图 URL 的 hw 字段中均使用了缩略图大小。The thumbnail size is used in both the <img> tag and the h and w fields in the thumbnail's URL. 然后,必应缩略图服务会提供正好为该大小的缩略图。The Bing thumbnail service then delivers a thumbnail of exactly that size.

保留客户端 IDPersisting client ID

来自必应搜索 API 的响应可能包含应通过后续请求发送回 API 的 X-MSEdge-ClientID 标头。Responses from the Bing search APIs may include an X-MSEdge-ClientID header that should be sent back to the API with successive requests. 如果正在使用多个必应搜索 API,应将相同客户端 ID 用于所有这些必应搜索 API(如有可能)。If multiple Bing Search APIs are being used, the same client ID should be used with all of them, if possible.

提供 X-MSEdge-ClientID 标头可以让必应 API 关联用户的所有搜索,这有两个主要好处。Providing the X-MSEdge-ClientID header allows the Bing APIs to associate all of a user's searches, which has two important benefits.

首先,它允许必应搜索引擎将过去的上下文应用于搜索来查找更好地满足用户的结果。First, it allows the Bing search engine to apply past context to searches to find results that better satisfy the user. 例如,如果用户以前搜索过与航海相关的词汇,则稍后搜索“节”时,系统可能会优先返回在航海中使用的节的信息。If a user has previously searched for terms related to sailing, for example, a later search for "knots" might preferentially return information about knots used in sailing.

其次,在新功能广泛应用之前,必应可能会随机选择用户体验该功能。Second, Bing may randomly select users to experience new features before they are made widely available. 为每个请求提供相同客户端 ID 可确保看到该功能的用户可以始终看到。Providing the same client ID with each request ensures that users who see the feature always see it. 如果没有客户端 ID,用户可能会看到功能在其搜索结果中随机出现和消失。Without the client ID, the user might see a feature appear and disappear, seemingly at random, in their search results.

浏览器安全策略 (CORS) 可能会阻止将 X-MSEdge-ClientID 标头提供给 JavaScript。Browser security policies (CORS) may prevent the X-MSEdge-ClientID header from being available to JavaScript. 当搜索响应的域不同于请求搜索的页面时,会出现此限制。This limitation occurs when the search response has a different origin from the page that requested it. 在生产环境中,应该托管一个服务器端脚本,以便在网页所在的域进行 API 调用,这样就可以解决此策略的问题。In a production environment, you should address this policy by hosting a server-side script that does the API call on the same domain as the Web page. 由于脚本具有与网页相同的来源,因此会将 X-MSEdge-ClientID 标头提供给 JavaScript。Since the script has the same origin as the Web page, the X-MSEdge-ClientID header is then available to JavaScript.


在生产 Web 应用程序中,应执行请求服务器端。In a production Web application, you should perform the request server-side. 否则,你的必应搜索 API 密钥必须包含在网页中,该网页可供查看来源的任何人使用。Otherwise, your Bing Search API key must be included in the Web page, where it is available to anyone who views source. 收费取决于 API 订阅密钥下的所有使用量(即使请求是由未经授权的用户发出的,也是如此),因此请确保不要公开你的密钥。You are billed for all usage under your API subscription key, even requests made by unauthorized parties, so it is important not to expose your key.

进行开发时,可以通过 CORS 代理发出必应 Web 搜索 API 请求。For development purposes, you can make the Bing Web Search API request through a CORS proxy. 此类代理的响应包含 Access-Control-Expose-Headers 标头,该标头允许响应标头并使其可供 JavaScript 访问。The response from such a proxy has an Access-Control-Expose-Headers header that allows response headers and makes them available to JavaScript.

安装 CORS 代理很容易,教程应用可以用它来访问客户端 ID 标头。It's easy to install a CORS proxy to allow our tutorial app to access the client ID header. 首先,如果尚未安装 Node.js,请先安装First, if you don't already have it, install Node.js. 然后,在命令窗口中发出以下命令:Then issue the following command in a command window:

npm install -g cors-proxy-server

接下来,在 HTML 文件中将必应 Web 搜索终结点更改为:Next, change the Bing Web Search endpoint in the HTML file to:

最后,运行下面的命令,启动 CORS 代理:Finally, start the CORS proxy with the following command:


使用教程应用期间,不要关闭命令窗口;关闭窗口会导致代理停止运行。Leave the command window open while you use the tutorial app; closing the window stops the proxy. 在搜索结果下的可展开 HTTP 响应头部分中,现在可以看到 X-MSEdge-ClientID 响应头(以及其他响应头),并验证此响应头是否对所有请求都相同。In the expandable HTTP Headers section below the search results, you can now see the X-MSEdge-ClientID header (among others) and verify that it is the same for each request.

后续步骤Next steps