Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/devtools/client/netmonitor/src/har/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 19 kB image not shown  

Quelle  har-builder.js   Sprache: JAVA

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


"use strict";

const appInfo = Services.appinfo;
const { LocalizationHelper } = require("resource://devtools/shared/l10n.js");
const { CurlUtils } = require("resource://devtools/client/shared/curl.js");
const {
  getFormDataSections,
  getUrlQuery,
  parseQueryString,
} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
const {
  buildHarLog,
} = require("resource://devtools/client/netmonitor/src/har/har-builder-utils.js");
const L10N = new LocalizationHelper("devtools/client/locales/har.properties");
const {
  TIMING_KEYS,
} = require("resource://devtools/client/netmonitor/src/constants.js");

/**
 * This object is responsible for building HAR file. See HAR spec:
 * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html
 * http://www.softwareishard.com/blog/har-12-spec/
 *
 * @param {Object} options
 *        configuration object
 * @param {Boolean} options.connector
 *        Set to true to include HTTP response bodies in the result data
 *        structure.
 * @param {String} options.id
 *        ID of the exported page.
 * @param {Boolean} options.includeResponseBodies
 *        Set to true to include HTTP response bodies in the result data
 *        structure.
 * @param {Array} options.items
 *        List of network events to be exported.
 * @param {Boolean} options.supportsMultiplePages
 *        Set to true to create distinct page entries for each navigation.
 */

var HarBuilder = function (options) {
  this._connector = options.connector;
  this._id = options.id;
  this._includeResponseBodies = options.includeResponseBodies;
  this._items = options.items;
  // Page id counter, only used when options.supportsMultiplePages is true.
  this._pageId = options.supportsMultiplePages ? 0 : options.id;
  this._pageMap = [];
  this._supportsMultiplePages = options.supportsMultiplePages;
  this._url = this._connector.currentTarget.url;
};

HarBuilder.prototype = {
  // Public API

  /**
   * This is the main method used to build the entire result HAR data.
   * The process is asynchronous since it can involve additional RDP
   * communication (e.g. resolving long strings).
   *
   * @returns {Promise} A promise that resolves to the HAR object when
   * the entire build process is done.
   */

  async build() {
    this.promises = [];

    // Build basic structure for data.
    const harLog = buildHarLog(appInfo);

    // Build pages.
    this.buildPages(harLog.log);

    // Build entries.
    for (const request of this._items) {
      const entry = await this.buildEntry(harLog.log, request);
      if (entry) {
        harLog.log.entries.push(entry);
      }
    }

    // Some data needs to be fetched from the backend during the
    // build process, so wait till all is done.
    await Promise.all(this.promises);

    return harLog;
  },

  // Helpers

  buildPages(log) {
    if (this._supportsMultiplePages) {
      this.buildPagesFromTargetTitles(log);
    } else if (this._items.length) {
      const firstRequest = this._items[0];
      const page = this.buildPage(this._url, firstRequest);
      log.pages.push(page);
      this._pageMap[this._id] = page;
    }
  },

  buildPagesFromTargetTitles(log) {
    // Retrieve the additional HAR data collected by the connector.
    const { initialURL, navigationRequests } = this._connector.getHarData();
    const firstNavigationRequest = navigationRequests[0];
    const firstRequest = this._items[0];

    if (
      !firstNavigationRequest ||
      firstRequest.resourceId !== firstNavigationRequest.resourceId
    ) {
      // If the first request is not a navigation request, it must be related
      // to the initial page. Create a first page entry for such early requests.
      const initialPage = this.buildPage(initialURL, firstRequest);
      log.pages.push(initialPage);
    }

    for (const request of navigationRequests) {
      const page = this.buildPage(request.url, request);
      log.pages.push(page);
    }
  },

  buildPage(url, networkEvent) {
    const page = {};

    page.id = "page_" + this._pageId;
    page.pageTimings = this.buildPageTimings(page, networkEvent);
    page.startedDateTime = dateToHarString(new Date(networkEvent.startedMs));

    // To align with other existing implementations of HAR exporters, the title
    // should contain the page URL and not the page title.
    page.title = url;

    // Increase the pageId, for upcoming calls to buildPage.
    // If supportsMultiplePages is disabled this method is only called once.
    this._pageId++;

    return page;
  },

  getPage(log, entry) {
    const existingPage = log.pages.findLast(
      ({ startedDateTime }) => startedDateTime <= entry.startedDateTime
    );

    if (!existingPage) {
      throw new Error(
        "Could not find a page for request: " + entry.request.url
      );
    }

    return existingPage;
  },

  async buildEntry(log, networkEvent) {
    const entry = {};
    entry.startedDateTime = dateToHarString(new Date(networkEvent.startedMs));

    let { eventTimings, id } = networkEvent;
    try {
      if (!eventTimings && this._connector.requestData) {
        eventTimings = await this._connector.requestData(id, "eventTimings");
      }

      entry.request = await this.buildRequest(networkEvent);
      entry.response = await this.buildResponse(networkEvent);
      entry.cache = await this.buildCache(networkEvent);
    } catch (e) {
      // Ignore any request for which we can't retrieve lazy data
      // The request has most likely been destroyed on the server side,
      // either because persist is disabled or the request's target/WindowGlobal/process
      // has been destroyed.
      console.warn("HAR builder failed on", networkEvent.url, e, e.stack);
      return null;
    }
    entry.timings = eventTimings ? eventTimings.timings : {};

    // Calculate total time by summing all timings. Note that
    // `networkEvent.totalTime` can't be used since it doesn't have to
    // correspond to plain summary of individual timings.
    // With TCP Fast Open and TLS early data sending data can
    // start at the same time as connect (we can send data on
    // TCP syn packet). Also TLS handshake can carry application
    // data thereby overlapping a sending data period and TLS
    // handshake period.
    entry.time = TIMING_KEYS.reduce((sum, type) => {
      const time = entry.timings[type];
      return typeof time != "undefined" && time != -1 ? sum + time : sum;
    }, 0);

    // Security state isn't part of HAR spec, and so create
    // custom field that needs to use '_' prefix.
    entry._securityState = networkEvent.securityState;

    if (networkEvent.remoteAddress) {
      entry.serverIPAddress = networkEvent.remoteAddress;
    }

    if (networkEvent.remotePort) {
      entry.connection = networkEvent.remotePort + "";
    }

    const page = this.getPage(log, entry);
    entry.pageref = page.id;

    return entry;
  },

  buildPageTimings() {
    // Event timing info isn't available
    const timings = {
      onContentLoad: -1,
      onLoad: -1,
    };

    // TODO: This method currently ignores the networkEvent and always retrieves
    // the same timing markers for all pages. Seee Bug 1833806.
    if (this._connector.getTimingMarker) {
      timings.onContentLoad = this._connector.getTimingMarker(
        "firstDocumentDOMContentLoadedTimestamp"
      );
      timings.onLoad = this._connector.getTimingMarker(
        "firstDocumentLoadTimestamp"
      );
    }

    return timings;
  },

  async buildRequest(networkEvent) {
    // When using HarAutomation, HarCollector will automatically fetch requestHeaders
    // and requestCookies, but when we use it from netmonitor, FirefoxDataProvider
    // should fetch it itself lazily, via requestData.

    let { id, requestHeaders } = networkEvent;
    if (!requestHeaders && this._connector.requestData) {
      requestHeaders = await this._connector.requestData(id, "requestHeaders");
    }

    let { requestCookies } = networkEvent;
    if (!requestCookies && this._connector.requestData) {
      requestCookies = await this._connector.requestData(id, "requestCookies");
    }

    const request = {
      bodySize: 0,
    };
    request.method = networkEvent.method;
    request.url = networkEvent.url;
    request.httpVersion = networkEvent.httpVersion || "";
    request.headers = this.buildHeaders(requestHeaders);
    request.headers = this.appendHeadersPostData(request.headers, networkEvent);
    request.cookies = this.buildCookies(requestCookies);
    request.queryString = parseQueryString(getUrlQuery(networkEvent.url)) || [];
    request.headersSize = requestHeaders.headersSize;
    request.postData = await this.buildPostData(networkEvent);

    if (request.postData?.text) {
      request.bodySize = request.postData.text.length;
    }

    return request;
  },

  /**
   * Fetch all header values from the backend (if necessary) and
   * build the result HAR structure.
   *
   * @param {Object} input Request or response header object.
   */

  buildHeaders(input) {
    if (!input) {
      return [];
    }

    return this.buildNameValuePairs(input.headers);
  },

  appendHeadersPostData(input = [], networkEvent) {
    if (!networkEvent.requestPostData) {
      return input;
    }

    this.fetchData(networkEvent.requestPostData.postData.text).then(value => {
      const multipartHeaders = CurlUtils.getHeadersFromMultipartText(value);
      for (const header of multipartHeaders) {
        input.push(header);
      }
    });

    return input;
  },

  buildCookies(input) {
    if (!input) {
      return [];
    }

    return this.buildNameValuePairs(input.cookies || input);
  },

  buildNameValuePairs(entries) {
    const result = [];

    // HAR requires headers array to be presented, so always
    // return at least an empty array.
    if (!entries) {
      return result;
    }

    // Make sure header values are fully fetched from the server.
    entries.forEach(entry => {
      this.fetchData(entry.value).then(value => {
        result.push({
          name: entry.name,
          value,
        });
      });
    });

    return result;
  },

  async buildPostData(networkEvent) {
    // When using HarAutomation, HarCollector will automatically fetch requestPostData
    // and requestHeaders, but when we use it from netmonitor, FirefoxDataProvider
    // should fetch it itself lazily, via requestData.
    let { id, requestHeaders, requestPostData } = networkEvent;
    let requestHeadersFromUploadStream;

    if (!requestPostData && this._connector.requestData) {
      requestPostData = await this._connector.requestData(
        id,
        "requestPostData"
      );
      requestHeadersFromUploadStream = requestPostData.uploadHeaders;
    }

    if (!requestPostData.postData.text) {
      return undefined;
    }

    if (!requestHeaders && this._connector.requestData) {
      requestHeaders = await this._connector.requestData(id, "requestHeaders");
    }

    const postData = {
      mimeType: findValue(requestHeaders.headers, "content-type"),
      params: [],
      text: requestPostData.postData.text,
    };

    if (requestPostData.postDataDiscarded) {
      postData.comment = L10N.getStr("har.requestBodyNotIncluded");
      return postData;
    }

    // If we are dealing with URL encoded body, parse parameters.
    if (
      CurlUtils.isUrlEncodedRequest({
        headers: requestHeaders.headers,
        postDataText: postData.text,
      })
    ) {
      postData.mimeType = "application/x-www-form-urlencoded";
      // Extract form parameters and produce nice HAR array.
      const formDataSections = await getFormDataSections(
        requestHeaders,
        requestHeadersFromUploadStream,
        requestPostData,
        this._connector.getLongString
      );

      formDataSections.forEach(section => {
        const paramsArray = parseQueryString(section);
        if (paramsArray) {
          postData.params = [...postData.params, ...paramsArray];
        }
      });
    }

    return postData;
  },

  async buildResponse(networkEvent) {
    // When using HarAutomation, HarCollector will automatically fetch responseHeaders
    // and responseCookies, but when we use it from netmonitor, FirefoxDataProvider
    // should fetch it itself lazily, via requestData.

    let { id, responseCookies, responseHeaders } = networkEvent;
    if (!responseHeaders && this._connector.requestData) {
      responseHeaders = await this._connector.requestData(
        id,
        "responseHeaders"
      );
    }

    if (!responseCookies && this._connector.requestData) {
      responseCookies = await this._connector.requestData(
        id,
        "responseCookies"
      );
    }

    const response = {
      status: 0,
    };

    // Arbitrary value if it's aborted to make sure status has a number
    if (networkEvent.status) {
      response.status = parseInt(networkEvent.status, 10);
    }
    response.statusText = networkEvent.statusText || "";
    response.httpVersion = networkEvent.httpVersion || "";

    response.headers = this.buildHeaders(responseHeaders);
    response.cookies = this.buildCookies(responseCookies);
    response.content = await this.buildContent(networkEvent);

    const headers = responseHeaders ? responseHeaders.headers : null;
    const headersSize = responseHeaders ? responseHeaders.headersSize : -1;

    response.redirectURL = findValue(headers, "Location");
    response.headersSize = headersSize;

    // 'bodySize' is size of the received response body in bytes.
    // Set to zero in case of responses coming from the cache (304).
    // Set to -1 if the info is not available.
    if (typeof networkEvent.transferredSize != "number") {
      response.bodySize = response.status == 304 ? 0 : -1;
    } else {
      response.bodySize = networkEvent.transferredSize;
    }

    return response;
  },

  async buildContent(networkEvent) {
    const content = {
      mimeType: networkEvent.mimeType,
      size: -1,
    };

    // When using HarAutomation, HarCollector will automatically fetch responseContent,
    // but when we use it from netmonitor, FirefoxDataProvider should fetch it itself
    // lazily, via requestData.
    let { responseContent } = networkEvent;
    if (!responseContent && this._connector.requestData) {
      responseContent = await this._connector.requestData(
        networkEvent.id,
        "responseContent"
      );
    }
    if (responseContent?.content) {
      content.size = responseContent.content.size;
      content.encoding = responseContent.content.encoding;
    }

    const includeBodies = this._includeResponseBodies;
    const contentDiscarded = responseContent
      ? responseContent.contentDiscarded
      : false;

    // The comment is appended only if the response content
    // is explicitly discarded.
    if (!includeBodies || contentDiscarded) {
      content.comment = L10N.getStr("har.responseBodyNotIncluded");
      return content;
    }

    if (responseContent) {
      const { text } = responseContent.content;
      this.fetchData(text).then(value => {
        content.text = value;
      });
    }

    return content;
  },

  async buildCache(networkEvent) {
    const cache = {};

    // if resource has changed, return early
    if (networkEvent.status != "304") {
      return cache;
    }

    if (networkEvent.responseCacheAvailable && this._connector.requestData) {
      const responseCache = await this._connector.requestData(
        networkEvent.id,
        "responseCache"
      );
      if (responseCache.cache) {
        cache.afterRequest = this.buildCacheEntry(responseCache.cache);
      }
    } else if (networkEvent.responseCache?.cache) {
      cache.afterRequest = this.buildCacheEntry(
        networkEvent.responseCache.cache
      );
    } else {
      cache.afterRequest = null;
    }

    return cache;
  },

  buildCacheEntry(cacheEntry) {
    const cache = {};

    if (typeof cacheEntry !== "undefined") {
      cache.expires = findKeys(cacheEntry, ["expirationTime""expires"]);
      cache.lastFetched = findKeys(cacheEntry, ["lastFetched"]);

      // TODO: eTag support
      // Har format expects cache entries to provide information about eTag,
      // however this is not currently exposed on nsICacheEntry.
      // This should be stored under cache.eTag. See Bug 1799844.

      cache.fetchCount = findKeys(cacheEntry, ["fetchCount"]);

      // har-importer.js, along with other files, use buildCacheEntry
      // initial value comes from properties without underscores.
      // this checks for both in appropriate order.
      cache._dataSize = findKeys(cacheEntry, ["storageDataSize""_dataSize"]);
      cache._lastModified = findKeys(cacheEntry, [
        "lastModified",
        "_lastModified",
      ]);
      cache._device = findKeys(cacheEntry, ["deviceID""_device"]);
    }

    return cache;
  },

  // RDP Helpers

  fetchData(string) {
    const promise = this._connector.getLongString(string).then(value => {
      return value;
    });

    // Building HAR is asynchronous and not done till all
    // collected promises are resolved.
    this.promises.push(promise);

    return promise;
  },
};

// Helpers

/**
 * Find specified keys within an object.
 * Searches object for keys passed in, returns first value returned,
 * or an empty string.
 *
 * @param obj (object)
 * @param keys (array)
 * @returns {string}
 */

function findKeys(obj, keys) {
  if (!keys) {
    return "";
  }

  const keyFound = keys.filter(key => obj[key]);
  if (!keys.length) {
    return "";
  }

  const value = obj[keyFound[0]];
  if (typeof value === "undefined" || typeof value === "object") {
    return "";
  }

  return String(value);
}

/**
 * Find specified value within an array of name-value pairs
 * (used for headers, cookies and cache entries)
 */

function findValue(arr, name) {
  if (!arr) {
    return "";
  }

  name = name.toLowerCase();
  const result = arr.find(entry => entry.name.toLowerCase() == name);
  return result ? result.value : "";
}

/**
 * Generate HAR representation of a date.
 * (YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.45+01:00)
 * See also HAR Schema: http://janodvarko.cz/har/viewer/
 *
 * Note: it would be great if we could utilize Date.toJSON(), but
 * it doesn't return proper time zone offset.
 *
 * An example:
 * This helper returns:    2015-05-29T16:10:30.424+02:00
 * Date.toJSON() returns:  2015-05-29T14:10:30.424Z
 *
 * @param date {Date} The date object we want to convert.
 */

function dateToHarString(date) {
  function f(n, c) {
    if (!c) {
      c = 2;
    }
    let s = String(n);
    while (s.length < c) {
      s = "0" + s;
    }
    return s;
  }

  const result =
    date.getFullYear() +
    "-" +
    f(date.getMonth() + 1) +
    "-" +
    f(date.getDate()) +
    "T" +
    f(date.getHours()) +
    ":" +
    f(date.getMinutes()) +
    ":" +
    f(date.getSeconds()) +
    "." +
    f(date.getMilliseconds(), 3);

  let offset = date.getTimezoneOffset();
  const positive = offset > 0;

  // Convert to positive number before using Math.floor (see issue 5512)
  offset = Math.abs(offset);
  const offsetHours = Math.floor(offset / 60);
  const offsetMinutes = Math.floor(offset % 60);
  const prettyOffset =
    (positive > 0 ? "-" : "+") + f(offsetHours) + ":" + f(offsetMinutes);

  return result + prettyOffset;
}

// Exports from this module
exports.HarBuilder = HarBuilder;

Messung V0.5
C=88 H=97 G=92

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.