Initial XhrHttpImpl implementation
parent
e0f1c600ba
commit
6d0acf5916
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* XMLHttpRequest HTTP protocol implementation
|
||||
*
|
||||
* Copyright (C) 2014 LoVullo Associates, Inc.
|
||||
*
|
||||
* This file is part of the Liza Data Collection Framework
|
||||
*
|
||||
* Liza is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var Class = require( 'easejs' ).Class,
|
||||
HttpImpl = require( './HttpImpl' );
|
||||
|
||||
|
||||
/**
|
||||
* An HTTP implementation using the standardized XMLHttpRequest prototype.
|
||||
*/
|
||||
module.exports = Class( 'XhrHttpImpl' )
|
||||
.implement( HttpImpl )
|
||||
.extend(
|
||||
{
|
||||
/**
|
||||
* XMLHttpRequest constructor
|
||||
* @type {XMLHttpRequest}
|
||||
* @constructor
|
||||
*/
|
||||
'private _Xhr': null,
|
||||
|
||||
|
||||
/**
|
||||
* Initializes with constructor to the object through which XHRs will be
|
||||
* made
|
||||
*
|
||||
* @param {Object} XMLHttpRequest ctor to object to perform requests
|
||||
*/
|
||||
__construct: function( XMLHttpRequest )
|
||||
{
|
||||
this._Xhr = XMLHttpRequest;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Perform HTTP request using the standard XMLHttpRequest
|
||||
*
|
||||
* @param {Object|string} data request params
|
||||
* @param {function(Error, Object)} callback server response callback
|
||||
*
|
||||
* @return {HttpImpl} self
|
||||
*/
|
||||
'public requestData': function( url, method, data, callback )
|
||||
{
|
||||
var req = new this._Xhr();
|
||||
|
||||
try
|
||||
{
|
||||
this.openRequest( req, url, method );
|
||||
this.onLoad( req, function( err, resp )
|
||||
{
|
||||
callback( err, resp );
|
||||
} );
|
||||
}
|
||||
catch ( e )
|
||||
{
|
||||
callback( e, null );
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Prepares a request to the given URL using the given HTTP method
|
||||
*
|
||||
* This method may be overridden by subtypes to set authentication data,
|
||||
* modify headers, hook XHR callbacks, etc.
|
||||
*
|
||||
* Subtypes may throw exceptions; the caller of this method catches and
|
||||
* properly forwards them to the callback.
|
||||
*
|
||||
* This method must be synchronous.
|
||||
*
|
||||
* @param {XMLHttpRequest} req request to prepare
|
||||
* @param {string} url destination URL
|
||||
* @param {string} method HTTP method
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
'virtual protected openRequest': function( req, url, method )
|
||||
{
|
||||
// alway async
|
||||
req.open( method, url, true );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Hooks ready state change to handle data
|
||||
*
|
||||
* Subtypes may override this method to alter the ready state change
|
||||
* actions taken (e.g. to display progress, handle errors, etc.)
|
||||
*
|
||||
* @param {XMLHttpRequest} req request to hook
|
||||
* @param {function(string)} callback continuation to invoke with response
|
||||
*
|
||||
* @return {undefined}
|
||||
*
|
||||
* @throws {Error} if non-200 response received from server
|
||||
*/
|
||||
'virtual protected onLoad': function( req, callback )
|
||||
{
|
||||
req.onreadystatechange = function()
|
||||
{
|
||||
// ready state of 4 (DONE) indicates that the request is complete
|
||||
if ( req.readyState !== 4 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if ( req.status !== 200 )
|
||||
{
|
||||
callback(
|
||||
Error( req.status + " error from server" ),
|
||||
{
|
||||
status: req.status,
|
||||
data: req.responseText
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// successful
|
||||
callback( null, req.responseText );
|
||||
};
|
||||
}
|
||||
} );
|
||||
|
|
@ -27,7 +27,7 @@ var dapi = require( '../../../' ).dapi,
|
|||
dummy_url = 'http://foo',
|
||||
dummy_impl = Class
|
||||
.implement( dapi.http.HttpImpl )
|
||||
.extend( { requestData: function( _, __, ___, ____ ) {} } ),
|
||||
.extend( { requestData: function( _, __, ___, ____ ) {} } )(),
|
||||
|
||||
dummy_sut = Sut( dummy_url, 'GET', dummy_impl );
|
||||
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
/**
|
||||
* Test case for XMLHttpRequest HTTP protocol implementation
|
||||
*
|
||||
* Copyright (C) 2014 LoVullo Associates, Inc.
|
||||
*
|
||||
* This file is part of the Liza Data Collection Framework
|
||||
*
|
||||
* Liza is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var dapi = require( '../../../' ).dapi,
|
||||
expect = require( 'chai' ).expect,
|
||||
Class = require( 'easejs' ).Class,
|
||||
HttpImpl = dapi.http.HttpImpl,
|
||||
Sut = dapi.http.XhrHttpImpl,
|
||||
|
||||
DummyXhr = function()
|
||||
{
|
||||
this.open = function()
|
||||
{
|
||||
DummyXhr.args = arguments;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
describe( 'XhrHttpImpl', function()
|
||||
{
|
||||
/**
|
||||
* Since ECMAScript does not have return typing, we won't know if the ctor
|
||||
* actually returns an XMLHttpRequest until we try.
|
||||
*/
|
||||
it( 'will accept any constructor', function()
|
||||
{
|
||||
expect( function()
|
||||
{
|
||||
Sut( function() {} );
|
||||
} ).to.not.throw( Error );
|
||||
} );
|
||||
|
||||
|
||||
it( 'is an HttpImpl', function()
|
||||
{
|
||||
var sut = Sut( function() {} );
|
||||
expect( Class.isA( HttpImpl, sut ) ).to.be.ok;
|
||||
} );
|
||||
|
||||
|
||||
describe( '.requestData', function()
|
||||
{
|
||||
it( 'requests a connection using the given url and method', function()
|
||||
{
|
||||
var url = 'http://foonugget',
|
||||
method = 'GET',
|
||||
sut = Sut( DummyXhr );
|
||||
|
||||
sut.requestData( url, method, {}, function() {} );
|
||||
|
||||
var args = DummyXhr.args;
|
||||
expect( args[ 0 ] ).to.equal( method );
|
||||
expect( args[ 1 ] ).to.equal( url );
|
||||
expect( args[ 1 ] ).to.be.ok; // async
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* Since the request is asynchronous, we should be polite and not return
|
||||
* errors in two different formats; we will catch it and instead pass it
|
||||
* back via the callback.
|
||||
*/
|
||||
it( 'returns XHR open() errors via callback', function( done )
|
||||
{
|
||||
var e = Error( "Test error" ),
|
||||
Xhr = function()
|
||||
{
|
||||
this.open = function()
|
||||
{
|
||||
throw e;
|
||||
};
|
||||
};
|
||||
|
||||
// should not throw an exception
|
||||
expect( function()
|
||||
{
|
||||
// should instead provide to callback
|
||||
Sut( Xhr )
|
||||
.requestData( 'http://foo', 'GET', {}, function( err, data )
|
||||
{
|
||||
expect( err ).to.equal( e );
|
||||
expect( data ).to.equal( null );
|
||||
done();
|
||||
} );
|
||||
} ).to.not.throw( Error );
|
||||
} );
|
||||
|
||||
|
||||
it( 'returns XHR response via callback when no error', function( done )
|
||||
{
|
||||
var retdata = "foobar",
|
||||
src = "moocow",
|
||||
StubXhr = createStubXhr();
|
||||
|
||||
StubXhr.prototype.responseText = retdata;
|
||||
StubXhr.prototype.readyState = 4; // done
|
||||
StubXhr.prototype.status = 200; // OK
|
||||
|
||||
StubXhr.prototype.send = function( data )
|
||||
{
|
||||
expect( data ).is.equal( src );
|
||||
StubXhr.inst.onreadystatechange();
|
||||
};
|
||||
|
||||
Sut( StubXhr )
|
||||
.requestData( 'http://bar', 'GET', src, function( err, resp )
|
||||
{
|
||||
expect( err ).to.equal( null );
|
||||
expect( resp ).to.equal( retdata );
|
||||
done();
|
||||
} );
|
||||
|
||||
// invoke callback
|
||||
StubXhr.inst.send( src );
|
||||
} );
|
||||
|
||||
|
||||
describe( 'if return status code is not 200', function()
|
||||
{
|
||||
/**
|
||||
* This is the default behavior, but can be changed by overriding
|
||||
* the onLoad method.
|
||||
*/
|
||||
it( 'returns an error to the callback', function( done )
|
||||
{
|
||||
var StubXhr = createStubXhr();
|
||||
StubXhr.prototype.status = 404;
|
||||
|
||||
Sut( StubXhr )
|
||||
.requestData( 'http://foo', 'GET', '', function( err, _ )
|
||||
{
|
||||
expect( err ).to.be.instanceOf( Error );
|
||||
expect( err.message ).to.contain(
|
||||
StubXhr.prototype.status
|
||||
);
|
||||
|
||||
done();
|
||||
} );
|
||||
|
||||
StubXhr.inst.send( '' );
|
||||
} );
|
||||
|
||||
|
||||
it( 'returns response text with error code', function( done )
|
||||
{
|
||||
var StubXhr = createStubXhr(),
|
||||
status = 404,
|
||||
reply = 'foobunny';
|
||||
|
||||
StubXhr.prototype.status = status;
|
||||
StubXhr.prototype.responseText = reply;
|
||||
|
||||
Sut( StubXhr )
|
||||
.requestData( 'http://foo', 'GET', '', function( _, resp )
|
||||
{
|
||||
expect( resp.status ).to.equal( status );
|
||||
expect( resp.data ).to.equal( reply );
|
||||
done();
|
||||
} );
|
||||
|
||||
StubXhr.inst.send( '' );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
it( 'returns self', function()
|
||||
{
|
||||
var sut = Sut( function() {} ),
|
||||
ret = sut.requestData(
|
||||
'http://foo', 'GET', {}, function() {}
|
||||
);
|
||||
|
||||
expect( ret ).to.equal( sut );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
function createStubXhr()
|
||||
{
|
||||
var StubXhr = function()
|
||||
{
|
||||
StubXhr.inst = this;
|
||||
};
|
||||
|
||||
StubXhr.prototype = {
|
||||
onreadystatechange: null,
|
||||
responseText: '',
|
||||
readyState: 4, // don,
|
||||
status: 200, // O,
|
||||
|
||||
open: function() {},
|
||||
send: function( data )
|
||||
{
|
||||
this.onreadystatechange();
|
||||
}
|
||||
};
|
||||
|
||||
return StubXhr;
|
||||
}
|
||||
|
Loading…
Reference in New Issue