Setting HTTP request headers

翻译正在进行中。

HTTP 是网络背后的核心技术之一。除了实质内容之外,一些重要的信息通过HTTP 头传递给HTTP 请求和响应。

你可以添加你自己的HTTP头到应用程序所做的任何请求中,不论你的代码是否启动了显式打开了基于 XMLHttpRequest 活动、<img>元素或者甚至是从css中的 HTTP Channel的请求。

HTTP Channels

当你处理HTTP请求和响应时,通常情况下你是通过 nsIHttpChannel来做的。 nsIHttpChannel接口有一些属性和方法,但是这下方法中我们只对setRequestHeader感兴趣。这个方法允许我们设置一个 HTTP request header.

下面是设置一个HTTP header 的简单代码

// adds "X-Hello: World" header to the request
httpChannel.setRequestHeader("X-Hello", "World", false);

在这个示例代码中我们有一个变量名为httpChannel,它是一个nsIHttpChannel的实例。

setRequestHeader有三个参数,第一个参数是HTTP请求头的名字,第二个参数是HTTP请求头的值,第三个参数我们暂时忽略它,总设置其为false。

在我们的示例代码中,我们为HTTP reques header中增加了被我们命名为X-Hello的HTTP请求头,其值为World

NOTE: If you are making up your own HTTP header, you MUST put a X- in front of the name. (In our example, our made up HTTP header is X-Hello and NOT Hello because we correctly added the X- in front of our name.)


No longer the case: http://tools.ietf.org/html/draft-ietf-appsawg-xdash-02

Notifications

The question that may be coming to your mind right now is, how do you get the nsIHttpChannel when an HTTP request is made.

In the case your code initiates the request, you probably already have one. Trapping other requests is done with notifications, which are a lot like events or signals found in other languages and frameworks.

In particular, to get the nsIHttpChannel just before the HTTP request is made we need to observe the "http-on-modify-request" topic. (And yes, "http-on-modify-request" is a string.)

NOTE: There are many topics, besides just "http-on-modify-request", that you can get notifications about, for example "http-on-examine-response" and "xpcom-shutdown". You can also make up your own topics and send out your own notifications.

For more information about notifications framework and a list of common notification topics, see Observer Notifications.

Observers

To get notified about some topic (like "http-on-modify-request") we need to create an observer. An observer is a component implementing nsIObserver interface. And once the observer is registered for a topic, it will get notified about the topic by having its observe method called.

Below is an example observer that adds a custom header "X-Hello" to the channel passed in for http-on-modify-request notification:

var httpRequestObserver =
{
  observe: function(subject, topic, data) 
  {
    if (topic == "http-on-modify-request") {
      var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
      httpChannel.setRequestHeader("X-Hello", "World", false);
    }
  }
};

div class="note"> Doesn't seem very suitable for this article; readers should are typically assumed to be familiar with JS. Nickolay '''NOTE''': Often people expect JavaScript to be just like Java. And while superficially, they look very similar, there are some important differences between the two. For example, while Java is an <em>object-oriented programming language</em>, JavaScript is not. JavaScript is <em>prototype-based programming language</em> and as such while it has <em>objects</em> it does not have <em>classes</em>. (Which is why, if you are not well versed with JavaScript, the object creation in the sample code above may look strange.) </div

Note that the number of parameter that the observe method takes is important. It takes 3 parameters (as we've shown in the example code above). For the "http-on-modify-request" topic, the first parameter (named subject in the code above) will be the nsIHttpChannel. However, it is passed to us as an nsISupports. So we need to change the nsISupports into a nsIHttpChannel which is what the QueryInterface call does. And yes, converting objects from one kind to another is very ugly, and lacks (what is usually called) syntactic sugar.

The second line of code in the if block should already be familiar to you. It is the same code we used before, earlier in this article, to add the HTTP request header.

The name of this object -- httpRequestObserver -- isn't important. We could have named it anything we liked.

Registering

After we've created the observer, we need to register it. In our case, we want to register it for the "http-on-modify-request" topic. We can do this with the code below.

var observerService = Components.classes["@mozilla.org/observer-service;1"]
                                .getService(Components.interfaces.nsIObserverService);
observerService.addObserver(httpRequestObserver, "http-on-modify-request", false);

The first statement gets the object that will let us register with topics that we want to get notified about.

The second statement does the actual registering. We are saying we want httpRequestObserver to be notified (by calling its observe method) when a "http-on-modify-request" topic takes place (which we know happens just before each HTTP request).

Unregistering

You should unregister the observer on shutdown. Failing to do that may cause memory leaks. To unregister the observer use nsIObserverService.removeObserver as follows:

observerService.removeObserver(httpRequestObserver, "http-on-modify-request");

All-in-one example

Here is a slightly different version of our httpRequestObserver object. While the previous version we showed before was good for learning, in an actual real-world application, you'd probably want to code it more like the following.

var httpRequestObserver =
{
  observe: function(subject, topic, data)
  {
    if (topic == "http-on-modify-request") {
      var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
      httpChannel.setRequestHeader("X-Hello", "World", false);
    }
  },

  get observerService() {
    return Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
  },

  register: function()
  {
    this.observerService.addObserver(this, "http-on-modify-request", false);
  },

  unregister: function()
  {
    this.observerService.removeObserver(this, "http-on-modify-request");
  }
};

This object has a convenience register() and unregister() methods, so in order to activate it you just need to call:

httpRequestObserver.register();

You should also remember to unregister the observer at shutdown:

httpRequestObserver.unregister();

And that's it.

XPCOM components

You need to register a single http-on-modify-request observer per application (and not one per window). This means that you should put the observer's implementation in an XPCOM component instead of an overlay. If you want to support Gecko2 (Firefox4) you need to register your javascript component as described here: https://developer.mozilla.org/zh-cn/XPCOM/XPCOM_changes_in_Gecko_2.0#JavaScript_components.

var headerName  = "X-hello";
var headerValue = "world";

function LOG(text)
{
    //    var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
    //    consoleService.logStringMessage(text);
}

function myHTTPListener() { }

myHTTPListener.prototype = {
    
  observe: function(subject, topic, data)
  {
      if (topic == "http-on-modify-request") {

          LOG("----------------------------> (" + subject + ") mod request");

          var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
          httpChannel.setRequestHeader(headerName, headerValue, false);
          return;
      }


      if (topic == "profile-after-change") {

          LOG("----------------------------> profile-after-change");

          var os = Components.classes["@mozilla.org/observer-service;1"]
                             .getService(Components.interfaces.nsIObserverService);

          os.addObserver(this, "http-on-modify-request", false);
          return;
      }
  },
 
  QueryInterface: function (iid) {
        if (iid.equals(Components.interfaces.nsIObserver) ||
            iid.equals(Components.interfaces.nsISupports))
            return this;
        
        Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
        return null;
    },
};

var myModule = {
    registerSelf: function (compMgr, fileSpec, location, type) {

        var compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
        compMgr.registerFactoryLocation(this.myCID,
                                        this.myName,
                                        this.myProgID,
                                        fileSpec,
                                        location,
                                        type);


          LOG("----------------------------> registerSelf");

        var catMgr = Components.classes["@mozilla.org/categorymanager;1"].getService(Components.interfaces.nsICategoryManager);
        catMgr.addCategoryEntry("app-startup", this.myName, this.myProgID, true, true);
    },


    getClassObject: function (compMgr, cid, iid) {

          LOG("----------------------------> getClassObject");

        return this.myFactory;
    },

    myCID: Components.ID("{9cf5f3df-2505-42dd-9094-c1631bd1be1c}"),

    myProgID: "@dougt/myHTTPListener;1",

    myName:   "Simple HTTP Listener",

    myFactory: {
        QueryInterface: function (aIID) {
            if (!aIID.equals(Components.interfaces.nsISupports) &&
                !aIID.equals(Components.interfaces.nsIFactory))
                throw Components.results.NS_ERROR_NO_INTERFACE;
            return this;
        },

        createInstance: function (outer, iid) {

          LOG("----------------------------> createInstance");

          return new myHTTPListener();
        }
    },

    canUnload: function(compMgr) {
        return true;
    }
};

function NSGetModule(compMgr, fileSpec) {
    return myModule;
}

Privacy and security good practice

A use case for setting specific a HTTP request header is to have a specific web site be able to check if a specific plugin / addon / extension is installed.

The good practice is not to have this specific HTTP header (for example "X-site.net-extension") sent all the time but only when doing requests with this specific web sites. By not advertising to all sites what extensions are installed this improves both privacy (this makes it harder to track a user known by his set of plugins, addons and extensions) and security (some plugins, addons and extensions may be known to have flaws by attackers).

With this privacy and security addition the code to use becomes:

 observe: function(subject, topic, data)
  {
    if (topic == "http-on-modify-request") {
      var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
      if (/site.net/.test(httpChannel.originalURI.host)) {
        httpChannel.setRequestHeader("X-Hello", "World", false);
      }
    }
  },

文档标签和贡献者

 此页面的贡献者: Leonard-sc, ziyunfei
 最后编辑者: Leonard-sc,