Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($xhr,$resource): expose response headers in callbacks
Browse files Browse the repository at this point in the history
all $xhr*, $resource and related mocks now have access to headers from
their callbacks
  • Loading branch information
kseamon authored and IgorMinar committed Aug 19, 2011
1 parent c37bfde commit 4ec1d8e
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 72 deletions.
35 changes: 33 additions & 2 deletions src/Browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ function Browser(window, document, body, XHR, $log) {
* @param {string} method Requested method (get|post|put|delete|head|json)
* @param {string} url Requested url
* @param {?string} post Post data to send (null if nothing to post)
* @param {function(number, string)} callback Function that will be called on response
* @param {function(number, string, function([string]))} callback Function that will be called on
* response. The third argument is a function that can be called to return a specified response
* header or an Object containing all headers (when called with no arguments).
* @param {object=} header additional HTTP headers to send with XHR.
* Standard headers are:
* <ul>
Expand All @@ -97,6 +99,8 @@ function Browser(window, document, body, XHR, $log) {
* Send ajax request
*/
self.xhr = function(method, url, post, callback, headers) {
var parsedHeaders;

outstandingRequestCount ++;
if (lowercase(method) == 'json') {
var callbackId = ("angular_" + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
Expand All @@ -123,7 +127,34 @@ function Browser(window, document, body, XHR, $log) {
if (xhr.readyState == 4) {
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
var status = xhr.status == 1223 ? 204 : xhr.status || 200;
completeOutstandingRequest(callback, status, xhr.responseText);
completeOutstandingRequest(callback, status, xhr.responseText, function(header) {
header = lowercase(header);

if (header) {
return parsedHeaders
? parsedHeaders[header] || null
: xhr.getResponseHeader(header);
} else {
// Return an object containing each response header
parsedHeaders = {};

forEach(xhr.getAllResponseHeaders().split('\n'), function(line) {
var i = line.indexOf(':'),
key = lowercase(trim(line.substr(0, i))),
value = trim(line.substr(i + 1));

if (parsedHeaders[key]) {
// Combine repeated headers
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
parsedHeaders[key] += ', ' + value;
} else {
parsedHeaders[key] = value;
}
});

return parsedHeaders;
}
});
}
};
xhr.send(post || '');
Expand Down
4 changes: 2 additions & 2 deletions src/Resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ ResourceFactory.prototype = {
action.method,
route.url(extend({}, action.params || {}, extractParams(data), params)),
data,
function(status, response) {
function(status, response, responseHeaders) {
if (response) {
if (action.isArray) {
value.length = 0;
Expand All @@ -122,7 +122,7 @@ ResourceFactory.prototype = {
copy(response, value);
}
}
(success||noop)(value);
(success||noop)(value, responseHeaders);
},
error || action.verifyCache,
action.verifyCache);
Expand Down
35 changes: 26 additions & 9 deletions src/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,14 @@ function MockBrowser() {
throw new Error("Missing HTTP request header: " + key + ": " + value);
}
});
callback(expectation.code, expectation.response);
callback(expectation.code, expectation.response, function(header) {
if (header) {
header = header.toLowerCase();
return expectation.responseHeaders && expectation.responseHeaders[header] || null;
} else {
return expectation.responseHeaders || {};
}
});
});
};
self.xhr.expectations = expectations;
Expand All @@ -162,12 +169,22 @@ function MockBrowser() {
if (data && angular.isString(data)) url += "|" + data;
var expect = expectations[method] || (expectations[method] = {});
return {
respond: function(code, response) {
respond: function(code, response, responseHeaders) {
if (!angular.isNumber(code)) {
responseHeaders = response;
response = code;
code = 200;
}
expect[url] = {code:code, response:response, headers: headers || {}};
angular.forEach(responseHeaders, function(value, key) {
delete responseHeaders[key];
responseHeaders[key.toLowerCase()] = value;
});
expect[url] = {
code: code,
response: response,
headers: headers || {},
responseHeaders: responseHeaders || {}
};
}
};
};
Expand Down Expand Up @@ -268,7 +285,7 @@ function MockBrowser() {
self.defer = function(fn, delay) {
delay = delay || 0;
self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
self.deferredFns.sort(function(a,b){ return a.time - b.time;});
self.deferredFns.sort(function(a,b){return a.time - b.time;});
return self.deferredNextId++;
};

Expand Down Expand Up @@ -374,7 +391,7 @@ angular.service('$browser', function(){
* See {@link angular.mock} for more info on angular mocks.
*/
angular.service('$exceptionHandler', function() {
return function(e) { throw e;};
return function(e) {throw e;};
});


Expand All @@ -394,10 +411,10 @@ angular.service('$log', MockLogFactory);

function MockLogFactory() {
var $log = {
log: function(){ $log.log.logs.push(arguments); },
warn: function(){ $log.warn.logs.push(arguments); },
info: function(){ $log.info.logs.push(arguments); },
error: function(){ $log.error.logs.push(arguments); }
log: function(){$log.log.logs.push(arguments);},
warn: function(){$log.warn.logs.push(arguments);},
info: function(){$log.info.logs.push(arguments);},
error: function(){$log.error.logs.push(arguments);}
};

$log.log.logs = [];
Expand Down
10 changes: 8 additions & 2 deletions src/service/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,15 @@
*
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
User.get({userId:123}, function(u){
User.get({userId:123}, function(u, responseHeaders){
u.abc = true;
u.$save();
u.$save(function(u, responseHeaders) {
// Get an Object containing all response headers
var allHeaders = responseHeaders();
// Get a specific response header
u.newId = responseHeaders('Location');
});
});
</pre>
Expand Down
11 changes: 6 additions & 5 deletions src/service/xhr.bulk.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
queue.requests = [];
queue.callbacks = [];
$xhr('POST', url, {requests: currentRequests},
function(code, response) {
function(code, response, responseHeaders) {
forEach(response, function(response, i) {
try {
if (response.status == 200) {
(currentRequests[i].success || noop)(response.status, response.response);
(currentRequests[i].success || noop)
(response.status, response.response, responseHeaders);
} else if (isFunction(currentRequests[i].error)) {
currentRequests[i].error(response.status, response.response);
currentRequests[i].error(response.status, response.response, responseHeaders);
} else {
$error(currentRequests[i], response);
}
Expand All @@ -64,11 +65,11 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
});
(success || noop)();
},
function(code, response) {
function(code, response, responseHeaders) {
forEach(currentRequests, function(request, i) {
try {
if (isFunction(request.error)) {
request.error(code, response);
request.error(code, response, responseHeaders);
} else {
$error(request, response);
}
Expand Down
18 changes: 9 additions & 9 deletions src/service/xhr.cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
* @param {string} method HTTP method.
* @param {string} url Destination URL.
* @param {(string|Object)=} post Request body.
* @param {function(number, (string|Object))} success Response success callback.
* @param {function(number, (string|Object))=} error Response error callback.
* @param {function(number, (string|Object), Function)} success Response success callback.
* @param {function(number, (string|Object), Function)} error Response error callback.
* @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache
* (if present) while a request is sent to the server for a fresh response that will update the
* cached entry. The `success` function will be called when the response is received.
Expand Down Expand Up @@ -55,9 +55,9 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
if (dataCached = cache.data[url]) {

if (sync) {
success(200, copy(dataCached.value));
success(200, copy(dataCached.value), copy(dataCached.headers));
} else {
$defer(function() { success(200, copy(dataCached.value)); });
$defer(function() { success(200, copy(dataCached.value), copy(dataCached.headers)); });
}

if (!verifyCache)
Expand All @@ -70,28 +70,28 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
} else {
inflight[url] = {successes: [success], errors: [error]};
cache.delegate(method, url, post,
function(status, response) {
function(status, response, responseHeaders) {
if (status == 200)
cache.data[url] = {value: response};
cache.data[url] = {value: response, headers: responseHeaders};
var successes = inflight[url].successes;
delete inflight[url];
forEach(successes, function(success) {
try {
(success||noop)(status, copy(response));
(success||noop)(status, copy(response), responseHeaders);
} catch(e) {
$log.error(e);
}
});
},
function(status, response) {
function(status, response, responseHeaders) {
var errors = inflight[url].errors,
successes = inflight[url].successes;
delete inflight[url];

forEach(errors, function(error, i) {
try {
if (isFunction(error)) {
error(status, copy(response));
error(status, copy(response), copy(responseHeaders));
} else {
$error(
{method: method, url: url, data: post, success: successes[i]},
Expand Down
11 changes: 7 additions & 4 deletions src/service/xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,17 @@
* angular generated callback function.
* @param {(string|Object)=} post Request content as either a string or an object to be stringified
* as JSON before sent to the server.
* @param {function(number, (string|Object))} success A function to be called when the response is
* @param {function(number, (string|Object), Function)} success A function to be called when the response is
* received. The success function will be called with:
*
* - {number} code [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of
* the response. This will currently always be 200, since all non-200 responses are routed to
* {@link angular.service.$xhr.error} service (or custom error callback).
* - {string|Object} response Response object as string or an Object if the response was in JSON
* format.
* - {function(string=)} responseHeaders A function that when called with a {string} header name,
* returns the value of that header or null if it does not exist; when called without
* arguments, returns an object containing every response header
* @param {function(number, (string|Object))} error A function to be called if the response code is
* not 2xx.. Accepts the same arguments as success, above.
*
Expand Down Expand Up @@ -198,7 +201,7 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
post = toJson(post);
}

$browser.xhr(method, url, post, function(code, response){
$browser.xhr(method, url, post, function(code, response, responseHeaders){
try {
if (isString(response)) {
if (response.match(/^\)\]\}',\n/)) response=response.substr(6);
Expand All @@ -207,9 +210,9 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
}
}
if (200 <= code && code < 300) {
success(code, response);
success(code, response, responseHeaders);
} else if (isFunction(error)) {
error(code, response);
error(code, response, responseHeaders);
} else {
$error(
{method: method, url: url, data: post, success: success},
Expand Down
42 changes: 42 additions & 0 deletions test/BrowserSpecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ describe('browser', function(){
this.send = function(post){
xhr.post = post;
};
this.getResponseHeader = function(header) {
return header;
};
this.getAllResponseHeaders = function() {
return 'Content-Type: application/json\n\rContent-Encoding: gzip\n\rContent-Type: text/json';
}

};

logs = {log:[], warn:[], info:[], error:[]};
Expand Down Expand Up @@ -198,6 +205,41 @@ describe('browser', function(){
expect(code).toEqual(202);
expect(response).toEqual('RESPONSE');
});

describe('response headers', function() {
it('should return a single response header', function() {
var headerA;

browser.xhr('GET', 'URL', null, function(code, resp, headers) {
headerA = headers('A-Header');
});

xhr.status = 200;
xhr.responseText = 'RESPONSE';
xhr.readyState = 4;
xhr.onreadystatechange();

expect(headerA).toEqual('a-header');
});

it('should return an object containing all response headers', function() {
var allHeaders;

browser.xhr('GET', 'URL', null, function(code, resp, headers) {
allHeaders = headers();
});

xhr.status = 200;
xhr.responseText = 'RESPONSE';
xhr.readyState = 4;
xhr.onreadystatechange();

expect(allHeaders).toEqual({
'content-type': 'application/json, text/json',
'content-encoding': 'gzip'
});
});
});
});

describe('defer', function() {
Expand Down
Loading

0 comments on commit 4ec1d8e

Please sign in to comment.