tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

lifecycle.rst (11476B)


      1 The Lifecycle of a HTTP Request
      2 ===============================
      3 
      4 
      5 HTTP requests in Firefox go through several steps.  Each piece of the request message and response message become available at certain points.  Extracting that information is a challenge, though.
      6 
      7 What is Available When
      8 ----------------------
      9 
     10 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     11 | Data                  | When it's available                               | Sample JS code                        | Interfaces             | Test code                     |
     12 +=======================+===================================================+=======================================+========================+===============================+
     13 | HTTP request method   | *http-on-modify-request* observer notification    | channel.requestMethod                 | nsIHttpChannel_        |                               |
     14 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     15 | HTTP request URI      | *http-on-modify-request* observer notification    | channel.URI                           | nsIChannel_            |                               |
     16 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     17 | HTTP request headers  | *http-on-modify-request* observer notification    | channel.visitRequestHeaders(visitor)  | nsIHttpChannel_        |                               |
     18 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     19 | HTTP request body     | *http-on-modify-request* observer notification    | channel.uploadStream                  | nsIUploadChannel_      |                               |
     20 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     21 || HTTP response status || *http-on-examine-response* observer notification || channel.responseStatus               || nsIHttpChannel_       || test_basic_functionality.js_ |
     22 ||                      ||                                                  || channel.responseStatusText           ||                       ||                              |
     23 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     24 | HTTP response headers | *http-on-examine-response* observer notification  | channel.visitResponseHeaders(visitor) | nsIHttpChannel_        |                               |
     25 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     26 || HTTP response body   || *onStopRequest* via stream listener tee          || See below                            || nsITraceableChannel_  || test_traceable_channel.js_   |
     27 ||                      ||                                                  ||                                      || nsIStreamListenerTee_ ||                              |
     28 ||                      ||                                                  ||                                      || nsIPipe_              ||                              |
     29 +-----------------------+---------------------------------------------------+---------------------------------------+------------------------+-------------------------------+
     30 
     31 The Request: http-on-modify-request
     32 -----------------------------------
     33 
     34 Firefox fires a "http-on-modify-request" observer notification before sending the HTTP request, and this blocks the sending of the request until all observers exit.  This is generally the point at which you can modify the HTTP request headers (hence the name).
     35 
     36 Attaching a listener for a request is pretty simple::
     37 
     38  const obs = {
     39    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
     40 
     41    observe: function(channel, topic, data) {
     42      if (!(channel instanceof Ci.nsIHttpChannel))
     43        return;
     44 
     45      // process the channel's data
     46    }
     47  }
     48 
     49  Services.obs.addObserver(observer, "http-on-modify-request", false);
     50 
     51 See nsIObserverService_ for the details.
     52 
     53 The request method and URI are immediately available at this time.  Request headers are trivially easy to get::
     54 
     55  /**
     56   * HTTP header visitor.
     57   */
     58  class HeaderVisitor {
     59    #targetObject;
     60 
     61    constructor(targetObject) {
     62      this.#targetObject = targetObject;
     63    }
     64 
     65    // nsIHttpHeaderVisitor
     66    visitHeader(header, value) {
     67      this.#targetObject[header] = value;
     68    }
     69 
     70    QueryInterface = ChromeUtils.generateQI(["nsIHttpHeaderVisitor"]);
     71  }
     72 
     73  // ...
     74  const requestHeaders = {};
     75  const visitor = new HeaderVisitor(requestHeaders);
     76  channel.visitRequestHeaders(visitor);
     77 
     78 This is also the time to set request headers, if you need to.  The method for that on the nsIHttpChannel_ interface is `channel.setRequestHeader(header, value);`
     79 
     80 Most HTTP requests don't have a body, as they are GET requests.  POST requests often have them, though.  As the nsIUploadChannel_ documentation indicates, the body of most HTTP requests is available via a seekable stream (nsISeekableStream_).  So you can simply capture the body stream and its current position, to revisit it later.  network-helper.js_ has code to read the request body.
     81 
     82 The Response: http-on-examine-response
     83 --------------------------------------
     84 
     85 Firefox fires a "http-on-examine-response" observer notification after parsing the HTTP response status and headers, but **before** reading the response body.  Attaching a listener for this phase is also very easy::
     86 
     87  Services.obs.addObserver(observer, "http-on-examine-response", false);
     88 
     89 If you use the same observer for "http-on-modify-request" and "http-on-examine-response", make sure you check the topic argument before interacting with the channel.
     90 
     91 The response status is available via the *responseStatus* and *responseStatusText* properties.  The response headers are available via the *visitResponseHeaders* method, and requires the same interface.
     92 
     93 The Response body: onStopRequest, stream listener tee
     94 -----------------------------------------------------
     95 
     96 During the "http-on-examine-response" notification, the response body is *not* available.  You can, however, use a stream listener tee to *copy* the stream so that the original stream data goes on, and you have a separate input stream you can read from with the same data.
     97 
     98 Here's some sample code to illustrate what you need::
     99 
    100  const Pipe = Components.Constructor(
    101    "@mozilla.org/pipe;1",
    102    "nsIPipe",
    103    "init"
    104  );
    105  const StreamListenerTee = Components.Constructor(
    106    "@mozilla.org/network/stream-listener-tee;1",
    107    "nsIStreamListenerTee"
    108  );
    109  const ScriptableStream = Components.Constructor(
    110    "@mozilla.org/scriptableinputstream;1",
    111    "nsIScriptableInputStream",
    112    "init"
    113  );
    114 
    115  const obs = {
    116    QueryInterface: ChromeUtils.generateQI(["nsIObserver", "nsIRequestObserver"]),
    117 
    118    /** @typedef {WeakMap<nsIHttpChannel, nsIPipe>} */
    119    requestToTeePipe: new WeakMap,
    120 
    121    // nsIObserver
    122    observe: function(channel, topic, data) {
    123      if (!(channel instanceof Ci.nsIHttpChannel))
    124        return;
    125 
    126      /* Create input and output streams to take the new data.
    127         The 0xffffffff argument is the segment count.
    128         It has to be this high because you don't know how much data is coming in the response body.
    129 
    130         As for why these are blocking streams:  I believe this is because there's no actual need to make them non-blocking.
    131         The stream processing happens during onStopRequest(), so we have all the data then and the operation can be synchronous.
    132         But I could be very wrong on this.
    133      */
    134      const pipe = new Pipe(false, false, 0, 0xffffffff);
    135 
    136      // Install the stream listener tee to intercept the HTTP body.
    137      const tee = new StreamListenerTee;
    138      const originalListener = channel.setNewListener(tee);
    139      tee.init(originalListener, pipe.outputStream, this);
    140 
    141      this.requestToTeePipe.set(channel, pipe);
    142    }
    143 
    144    // nsIRequestObserver
    145    onStartRequest: function() {
    146      // do nothing
    147    }
    148 
    149    // nsIRequestObserver
    150    onStopRequest: function(channel, statusCode) {
    151      const pipe = this.requestToTeePipe.get(channel);
    152 
    153      // No more data coming in anyway.
    154      pipe.outputStream.close();
    155      this.requestToTeePipe.delete(channel);
    156 
    157      let length = 0;
    158      try {
    159        length = pipe.inputStream.available();
    160      }
    161      catch (e) {
    162        if (e.result === Components.results.NS_BASE_STREAM_CLOSED)
    163          throw e;
    164      }
    165 
    166      let responseBody = "";
    167      if (length) {
    168        // C++ code doesn't need the scriptable input stream.
    169        const sin = new ScriptableStream(pipe.inputStream);
    170        responseBody = sin.read(length);
    171        sin.close();
    172      }
    173 
    174      void(responseBody); // do something with the body
    175    }
    176  }
    177 
    178 test_traceable_channel.js_ does essentially this.
    179 
    180 Character Encodings and Compression
    181 -----------------------------------
    182 
    183 Canceling Requests
    184 ------------------
    185 
    186 HTTP Activity Distributor Notes
    187 -------------------------------
    188 
    189 URIContentLoader Notes
    190 ----------------------
    191 
    192 Order of Operations
    193 -------------------
    194 
    195 1. The HTTP channel is constructed.
    196 2. The "http-on-modify-request" observer service notification fires.
    197 3. If the request has been canceled, exit at this step.
    198 4. The HTTP channel's request is submitted to the server.  Time passes.
    199 5. The HTTP channel's response comes in from the server.
    200 6. The HTTP channel parses the response status and headers.
    201 7. The "http-on-examine-response" observer service notification fires.
    202 
    203 Useful Code Samples and References
    204 ----------------------------------
    205 
    206 - nsIHttpProtocolHandler_ defines a lot of observer topics, and has a lot of details.
    207 
    208 .. _nsIHttpChannel: https://searchfox.org/mozilla-central/source/netwerk/protocol/http/nsIHttpChannel.idl
    209 .. _nsIChannel: https://searchfox.org/mozilla-central/source/netwerk/base/nsIChannel.idl
    210 .. _nsIUploadChannel: https://searchfox.org/mozilla-central/source/netwerk/base/nsIUploadChannel.idl
    211 .. _nsITraceableChannel: https://searchfox.org/mozilla-central/source/netwerk/base/nsITraceableChannel.idl
    212 .. _nsISeekableStream: https://searchfox.org/mozilla-central/source/xpcom/io/nsISeekableStream.idl
    213 .. _nsIObserverService: https://searchfox.org/mozilla-central/source/xpcom/ds/nsIObserverService.idl
    214 .. _nsIHttpProtocolHandler: https://searchfox.org/mozilla-central/source/netwerk/protocol/http/nsIHttpProtocolHandler.idl
    215 .. _nsIStreamListenerTee: https://searchfox.org/mozilla-central/source/netwerk/base/nsIStreamListenerTee.idl
    216 .. _nsIPipe: https://searchfox.org/mozilla-central/source/xpcom/io/nsIPipe.idl
    217 
    218 .. _test_basic_functionality.js: https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/test/test_basic_functionality.js
    219 .. _test_traceable_channel.js: https://searchfox.org/mozilla-central/source/netwerk/test/unit/test_traceable_channel.js
    220 .. _network-helper.js: https://searchfox.org/mozilla-central/source/devtools/shared/webconsole/network-helper.js