Rob Kinyon - HTTP.Request-0.03

Documentation | Source

NAME

HTTP.Request

DESCRIPTION

This provides a way to make a HTTP request in a completely browser-independent fashion, if the browser can support this kind of activity.

DEPENDENCIES

None.

CLASSES

HTTP.Request

This is the basic AJAX communicator. It does all of its work within the constructor, using callbacks as provided (if in asynchronous mode).

The constructor takes a hash of options. All option names are case-insensitive. These options are:

  • method (post|get|head)
  • This defaults to 'post'. If any value other than 'post', 'get', or 'head' is passed, this will be set to 'post'.

  • asychronous (true|false)
  • This defaults to true. This determines whether or not the request is blocking.

  • uri
  • this is the URL you wish to call. If this is set, then request() will be called at the end of the constructor. If this is not set, then you can call request() with your URI.

  • parameters
  • This is a string containing the URL parameters you wish to pass. Depending on the method, they will either be appended to the URL or sent as if they were in a form.

  • postbody
  • This is a string containing the body of the HTTP request, if the method is set to 'post'. This will default to the parameters, if not set.

  • transport
  • This is the class that is used for the transport. It defaults to HTTP.Request.Transport and is expected to conform to its API. (q.v. below for more information.) If this is not a function, then it will be set to HTTP.Request.Transport.

  • onUninitialized
  • This is a callback that will be called when the Uninitialized event occurs. It will only be called if the request is asynchronous.

  • onLoading
  • This is a callback that will be called when the Loading event occurs. It will only be called if the request is asynchronous.

  • onLoaded
  • This is a callback that will be called when the Loaded event occurs. It will only be called if the request is asynchronous.

  • onInteractive
  • This is a callback that will be called when the Interactive event occurs. It will only be called if the request is asynchronous.

  • onComplete
  • This is a callback that will be called when the Complete event occurs. It will only be called if the request is asynchronous.

  • on[status] / onSuccess / onFailure
  • These are callbacks that will be called when the Complete event occurs. If [status] is found (numeric), then that will be called. Otherwise, onSuccess will be called if this is a success (as defined by isSuccess()) or onFailure will be called.

The callback functions must be defined as so:

  onComplete: function (trans) {
      ...
  }

where 'trans' is the transport being used. This will be an object of HTTP.Request.Transport, as detailed below.

The following public methods are defined:

  • getOption( name )
  • Given an option name, it will return the value that has been set, if any.

  • setOption( name, value )
  • This will set the option specified by name to value. It will also gatekeeper certain options to make sure that their values are sane.

  • request( [uri] )
  • This will actually make the HTTP request. If you specified a uri in the constructor, this will be called at the end of the constructor. If you do not specify a URI in the function call and the URI option isn't set, then this will return without doing anything.

    Note: Currently, this will append an additional parameter of '_' set to nothing to the parameter list.

  • setRequestHeaders()
  • This will set the request headers.

  • isSuccess()
  • This will return back true or false depending on if the request was successful.

HTTP.Request.Transport

This is the class that actually does the tranport. In Gecko-based browsers, this is equivalent to the XMLHttpRequest class. In IE-based browsers, this is equivalent to the correct ActiveX object. In other browsers, this is an object that abstracts away the layers/iframe method that is used.

TODO

  • Add support for DIV/Layers/IFRAME methods of transport
  • Better error-handling
  • Add user-defined headers
  • Remove need for try/catch (degrade to JS 1.2 or lower, if possible)
  • Tests, tests, and more tests

HTTP.Request.Transport

This is a class that is the default transport mechanism. It is set, at compile-time, to the best XMLHttpRequest option the browser supports.

If the browser does not support XMLHttpRequest or any of the ActiveXObject() variants, then an HTTP.Request.Transport class is created to transparently do IFRAME/DIV support.

If you wish to write your own transport class, you can do so and pass it in as the 'transport' option to new() or set it using setOption( "transport", My.Transport.Class ).

Any class you provide must support the following API:

  • void abort()
  • Stops the request if it is still running.

  • string getAllResponseHeaders()
  • Returns all response headers as one string.

  • string getResponseHeader(string headerName)
  • Returns the value of the specified header.

  • functionRef onerror
  • If set, this function will be called whenever an error occurs during the request.

  • functionRef onload
  • If set, the references function will be called when the request completes successfully and the response has been received. Use when an asynchronous request is used.

  • void open (string HTTP_Method, string URL, [boolean async, [string userName, [string password]]])
  • Initializes the request for the specified URL, using either GET or POST as the HTTP method. To send the request, call the send() method after initialization. If async is false, the request is synchronous, else it defaults to asynchronous. Optionally, you can specify a username and password for the given URL needed.

  • int readyState
  • State of the request. Possible values:

    • UNINITIALIZED - open() has not been called yet.
    • 1
    • LOADING - send() has not been called yet.

    • 2
    • LOADED - send() has been called, headers and status are available.

    • 3
    • INTERACTIVE - Downloading, responseText holds partial data.

    • 4
    • COMPLETED - Finished with all operations.

  • string responseText
  • String containing the response.

  • DOMDocument responseXML
  • DOM Document containing the response.

  • void send(variant body)
  • Initiates the request. If body is defined, it is sent as the body of the POST request. body can be an XML document or a string serialized XML document.

  • void setRequestHeader (string headerName, string headerValue)
  • Sets an HTTP request header for use in the HTTP request. Has to be called after open() is called.

  • string status
  • The status code of the HTTP response.

(This was taken from http://www-128.ibm.com/developerworks/web/library/wa-ie2mozgd/)

SUPPORT

Currently, there is no mailing list or IRC channel. Please send bug reports and patches to the author.

AUTHOR

Rob Kinyon (rob.kinyon@iinteractive.com)

Code taken from several sources, including those written by Sam Stephenson, Adam Kennedy, and Eric Andreychek. Many thanks to their generous help.

My time is generously donated by Infinity Interactive, Inc. http://www.iinteractive.com

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 107:

You forgot a '=back' before '=head2'

/*

*/

if ( typeof( Method ) == "undefined" ) {
    Method = {};
}

if ( typeof( Method["bind"] ) == "undefined" ) {
    Method.bind = function ( method, object ) {
        return function() {
            method.apply(object, arguments);
        }
    };
}

/*

*/

if ( typeof( HTTP ) == "undefined" ) {
    HTTP = {};
}

if ( typeof( HTTP.Request ) == "undefined" ) {
    HTTP.Request = function ( options ) {
        if ( !options ) options = {};

        this.options = {};
        for ( var i in options ) {
            this.setOption( i, options[i] );
        }

        if ( this.getOption( "method" ) == undefined ) {
            this.setOption( "method", "post" );
        }

        if ( this.getOption( "asynchronous" ) == undefined ) {
            this.setOption( "asynchronous", true );
        }

        if ( this.getOption( "parameters" ) == undefined ) {
            this.setOption( "parameters", "" );
        }

        if ( this.getOption( "transport" ) == undefined ) {
            this.setOption( "transport", HTTP.Request.Transport );
        }

        if ( this.getOption( "uri" ) )
            this.request();
    };

    HTTP.Request.EventNames = [
        "uninitialized"
       ,"loading"
       ,"loaded"
       ,"interactive"
       ,"complete"
    ];

/*

*/

    HTTP.Request.prototype.getOption = function( name ) {
        if ( typeof( name ) != "string" ) {
            return;
        }
        return this.options[name.toLowerCase()];
    };

/*

*/

    HTTP.Request.prototype.setOption = function( name, value ) {
        if ( typeof( name ) != "string" ) {
            return;
        }

        name = name.toLowerCase();

        this.options[name] = value;

        if ( name == "method" ) {
            if ( ! ( this.options.method == "get" || this.options.method == "post" || this.options.method == "head" ) ) {
                this.options.method = "post";
            }
        }

        if ( name == "transport" ) {
            if ( typeof( value ) != "function" ) {
                this.options.transport = HTTP.Request.Transport;
            }
        }
    };

/*

*/

    HTTP.Request.prototype.request = function ( uri ) {
        if ( ! uri ) uri = this.getOption( "uri" );
        if ( ! uri ) return;

        var parameters = this.getOption( "parameters" );
        // XXX Why?
        if (parameters.length > 0) parameters += "&_=";

        var method = this.getOption( "method" );
        if ( method == "get" ) {
            uri += "?" + parameters;
        }

        this.transport = new (this.getOption( "transport" ))();

        var async = this.getOption( "asynchronous" );
        this.transport.open( method ,uri ,async );

        if ( async ) {
            this.transport.onreadystatechange = Method.bind(
                this.onStateChange, this
            );

            setTimeout(
                Method.bind(
                    function() { this.respondToReadyState(1) }
                   ,this
                )
               ,10
           );
        }

        this.setRequestHeaders();

        if ( method == "post" ) {
            var body = this.getOption( "postbody" );
            if ( ! body ) body = parameters;

            this.transport.send( body );
        }
        else {
            this.transport.send( null );
        }
    };

/*

*/

    HTTP.Request.prototype.setRequestHeaders = function() {
        this.transport.setRequestHeader( "X-Requested-With", "HTTP.Request" );
        this.transport.setRequestHeader( "X-HTTP-Request-Version", HTTP.Request.VERSION );

        if (this.getOption( "method" ) == "post") {
            this.transport.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" );

            /* Force "Connection: close" for Mozilla browsers to work around
             * a bug where XMLHttpReqeuest sends an incorrect Content-length
             * header. See Mozilla Bugzilla #246651.
             */
            if (this.transport.overrideMimeType) {
                this.transport.setRequestHeader( "Connection", "close" );
            }
        }

/* TODO Add support for this back in later
        if (this.options.requestHeaders)
            requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
*/
    };

/*

*/

    // XXX This confuses me a little ... how are undefined and 0 considered a success?
    HTTP.Request.prototype.isSuccess = function () {
        return this.transport.status == undefined
            || this.transport.status == 0
            || (this.transport.status >= 200 && this.transport.status < 300);
    };

    HTTP.Request.prototype.onStateChange = function() {
        var readyState = this.transport.readyState;
        if (readyState != 1) {
            this.respondToReadyState( this.transport.readyState );
        }
    };

    HTTP.Request.prototype.respondToReadyState = function( readyState ) {
        var event = HTTP.Request.EventNames[readyState];

        if (event == "complete") {
            var func = this.getOption( "on" + this.transport.status );
            if ( ! func ) {
                if ( this.isSuccess() ) {
                    func = this.getOption( "onsuccess" );
                }
                else {
                    func = this.getOption( "onfailure" );
                }
            }

            if ( func ) {
                ( func )( this.transport );
            }
        }

        if ( this.getOption( "on" + event ) )
            ( this.getOption( "on" + event ) )( this.transport );

        /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
        if (event == "complete") {
            this.transport.onreadystatechange = function (){};
        }
    };

    HTTP.Request.VERSION = 0.03;
}

/*

*/

if ( typeof( HTTP.Request.Transport ) == "undefined" ) {
    if ( window.XMLHttpRequest ) {
        HTTP.Request.Transport = window.XMLHttpRequest;
    }
    // This tests for ActiveXObject in IE5+
    else if ( window.ActiveXObject && window.clipboardData ) {
        var msxmls = new Array(
            "Msxml2.XMLHTTP.5.0"
           ,"Msxml2.XMLHTTP.4.0"
           ,"Msxml2.XMLHTTP.3.0"
           ,"Msxml2.XMLHTTP"
           ,"Microsoft.XMLHTTP"
        );
        for ( var i = 0; i < msxmls.length; i++ ) {
            try {
                new ActiveXObject(msxmls[i]);
                HTTP.Request.Transport = function () {
                    return new ActiveXObject(msxmls[i]);
                };
                break;
            }
            catch(e) {
            }
        }
    }

    if ( typeof( HTTP.Request.Transport ) == "undefined" ) {
        // This is where we add DIV/IFRAME support masquerading as an XMLHttpRequest object
    }

    if ( typeof( HTTP.Request.Transport ) == "undefined" ) {
        throw new Error("Unable to locate XMLHttpRequest or other HTTP transport mechanism");
    }
}

/*

*/