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

Commit 4ec1d8e

Browse files
kseamonIgorMinar
authored andcommitted
feat($xhr,$resource): expose response headers in callbacks
all $xhr*, $resource and related mocks now have access to headers from their callbacks
1 parent c37bfde commit 4ec1d8e

12 files changed

+215
-72
lines changed

src/Browser.js

+33-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ function Browser(window, document, body, XHR, $log) {
8484
* @param {string} method Requested method (get|post|put|delete|head|json)
8585
* @param {string} url Requested url
8686
* @param {?string} post Post data to send (null if nothing to post)
87-
* @param {function(number, string)} callback Function that will be called on response
87+
* @param {function(number, string, function([string]))} callback Function that will be called on
88+
* response. The third argument is a function that can be called to return a specified response
89+
* header or an Object containing all headers (when called with no arguments).
8890
* @param {object=} header additional HTTP headers to send with XHR.
8991
* Standard headers are:
9092
* <ul>
@@ -97,6 +99,8 @@ function Browser(window, document, body, XHR, $log) {
9799
* Send ajax request
98100
*/
99101
self.xhr = function(method, url, post, callback, headers) {
102+
var parsedHeaders;
103+
100104
outstandingRequestCount ++;
101105
if (lowercase(method) == 'json') {
102106
var callbackId = ("angular_" + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
@@ -123,7 +127,34 @@ function Browser(window, document, body, XHR, $log) {
123127
if (xhr.readyState == 4) {
124128
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
125129
var status = xhr.status == 1223 ? 204 : xhr.status || 200;
126-
completeOutstandingRequest(callback, status, xhr.responseText);
130+
completeOutstandingRequest(callback, status, xhr.responseText, function(header) {
131+
header = lowercase(header);
132+
133+
if (header) {
134+
return parsedHeaders
135+
? parsedHeaders[header] || null
136+
: xhr.getResponseHeader(header);
137+
} else {
138+
// Return an object containing each response header
139+
parsedHeaders = {};
140+
141+
forEach(xhr.getAllResponseHeaders().split('\n'), function(line) {
142+
var i = line.indexOf(':'),
143+
key = lowercase(trim(line.substr(0, i))),
144+
value = trim(line.substr(i + 1));
145+
146+
if (parsedHeaders[key]) {
147+
// Combine repeated headers
148+
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
149+
parsedHeaders[key] += ', ' + value;
150+
} else {
151+
parsedHeaders[key] = value;
152+
}
153+
});
154+
155+
return parsedHeaders;
156+
}
157+
});
127158
}
128159
};
129160
xhr.send(post || '');

src/Resource.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ ResourceFactory.prototype = {
111111
action.method,
112112
route.url(extend({}, action.params || {}, extractParams(data), params)),
113113
data,
114-
function(status, response) {
114+
function(status, response, responseHeaders) {
115115
if (response) {
116116
if (action.isArray) {
117117
value.length = 0;
@@ -122,7 +122,7 @@ ResourceFactory.prototype = {
122122
copy(response, value);
123123
}
124124
}
125-
(success||noop)(value);
125+
(success||noop)(value, responseHeaders);
126126
},
127127
error || action.verifyCache,
128128
action.verifyCache);

src/angular-mocks.js

+26-9
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,14 @@ function MockBrowser() {
152152
throw new Error("Missing HTTP request header: " + key + ": " + value);
153153
}
154154
});
155-
callback(expectation.code, expectation.response);
155+
callback(expectation.code, expectation.response, function(header) {
156+
if (header) {
157+
header = header.toLowerCase();
158+
return expectation.responseHeaders && expectation.responseHeaders[header] || null;
159+
} else {
160+
return expectation.responseHeaders || {};
161+
}
162+
});
156163
});
157164
};
158165
self.xhr.expectations = expectations;
@@ -162,12 +169,22 @@ function MockBrowser() {
162169
if (data && angular.isString(data)) url += "|" + data;
163170
var expect = expectations[method] || (expectations[method] = {});
164171
return {
165-
respond: function(code, response) {
172+
respond: function(code, response, responseHeaders) {
166173
if (!angular.isNumber(code)) {
174+
responseHeaders = response;
167175
response = code;
168176
code = 200;
169177
}
170-
expect[url] = {code:code, response:response, headers: headers || {}};
178+
angular.forEach(responseHeaders, function(value, key) {
179+
delete responseHeaders[key];
180+
responseHeaders[key.toLowerCase()] = value;
181+
});
182+
expect[url] = {
183+
code: code,
184+
response: response,
185+
headers: headers || {},
186+
responseHeaders: responseHeaders || {}
187+
};
171188
}
172189
};
173190
};
@@ -268,7 +285,7 @@ function MockBrowser() {
268285
self.defer = function(fn, delay) {
269286
delay = delay || 0;
270287
self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
271-
self.deferredFns.sort(function(a,b){ return a.time - b.time;});
288+
self.deferredFns.sort(function(a,b){return a.time - b.time;});
272289
return self.deferredNextId++;
273290
};
274291

@@ -374,7 +391,7 @@ angular.service('$browser', function(){
374391
* See {@link angular.mock} for more info on angular mocks.
375392
*/
376393
angular.service('$exceptionHandler', function() {
377-
return function(e) { throw e;};
394+
return function(e) {throw e;};
378395
});
379396

380397

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

395412
function MockLogFactory() {
396413
var $log = {
397-
log: function(){ $log.log.logs.push(arguments); },
398-
warn: function(){ $log.warn.logs.push(arguments); },
399-
info: function(){ $log.info.logs.push(arguments); },
400-
error: function(){ $log.error.logs.push(arguments); }
414+
log: function(){$log.log.logs.push(arguments);},
415+
warn: function(){$log.warn.logs.push(arguments);},
416+
info: function(){$log.info.logs.push(arguments);},
417+
error: function(){$log.error.logs.push(arguments);}
401418
};
402419

403420
$log.log.logs = [];

src/service/resource.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,15 @@
147147
*
148148
<pre>
149149
var User = $resource('/user/:userId', {userId:'@id'});
150-
User.get({userId:123}, function(u){
150+
User.get({userId:123}, function(u, responseHeaders){
151151
u.abc = true;
152-
u.$save();
152+
u.$save(function(u, responseHeaders) {
153+
// Get an Object containing all response headers
154+
var allHeaders = responseHeaders();
155+
156+
// Get a specific response header
157+
u.newId = responseHeaders('Location');
158+
});
153159
});
154160
</pre>
155161

src/service/xhr.bulk.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,14 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
4848
queue.requests = [];
4949
queue.callbacks = [];
5050
$xhr('POST', url, {requests: currentRequests},
51-
function(code, response) {
51+
function(code, response, responseHeaders) {
5252
forEach(response, function(response, i) {
5353
try {
5454
if (response.status == 200) {
55-
(currentRequests[i].success || noop)(response.status, response.response);
55+
(currentRequests[i].success || noop)
56+
(response.status, response.response, responseHeaders);
5657
} else if (isFunction(currentRequests[i].error)) {
57-
currentRequests[i].error(response.status, response.response);
58+
currentRequests[i].error(response.status, response.response, responseHeaders);
5859
} else {
5960
$error(currentRequests[i], response);
6061
}
@@ -64,11 +65,11 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
6465
});
6566
(success || noop)();
6667
},
67-
function(code, response) {
68+
function(code, response, responseHeaders) {
6869
forEach(currentRequests, function(request, i) {
6970
try {
7071
if (isFunction(request.error)) {
71-
request.error(code, response);
72+
request.error(code, response, responseHeaders);
7273
} else {
7374
$error(request, response);
7475
}

src/service/xhr.cache.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
* @param {string} method HTTP method.
2323
* @param {string} url Destination URL.
2424
* @param {(string|Object)=} post Request body.
25-
* @param {function(number, (string|Object))} success Response success callback.
26-
* @param {function(number, (string|Object))=} error Response error callback.
25+
* @param {function(number, (string|Object), Function)} success Response success callback.
26+
* @param {function(number, (string|Object), Function)} error Response error callback.
2727
* @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache
2828
* (if present) while a request is sent to the server for a fresh response that will update the
2929
* cached entry. The `success` function will be called when the response is received.
@@ -55,9 +55,9 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
5555
if (dataCached = cache.data[url]) {
5656

5757
if (sync) {
58-
success(200, copy(dataCached.value));
58+
success(200, copy(dataCached.value), copy(dataCached.headers));
5959
} else {
60-
$defer(function() { success(200, copy(dataCached.value)); });
60+
$defer(function() { success(200, copy(dataCached.value), copy(dataCached.headers)); });
6161
}
6262

6363
if (!verifyCache)
@@ -70,28 +70,28 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
7070
} else {
7171
inflight[url] = {successes: [success], errors: [error]};
7272
cache.delegate(method, url, post,
73-
function(status, response) {
73+
function(status, response, responseHeaders) {
7474
if (status == 200)
75-
cache.data[url] = {value: response};
75+
cache.data[url] = {value: response, headers: responseHeaders};
7676
var successes = inflight[url].successes;
7777
delete inflight[url];
7878
forEach(successes, function(success) {
7979
try {
80-
(success||noop)(status, copy(response));
80+
(success||noop)(status, copy(response), responseHeaders);
8181
} catch(e) {
8282
$log.error(e);
8383
}
8484
});
8585
},
86-
function(status, response) {
86+
function(status, response, responseHeaders) {
8787
var errors = inflight[url].errors,
8888
successes = inflight[url].successes;
8989
delete inflight[url];
9090

9191
forEach(errors, function(error, i) {
9292
try {
9393
if (isFunction(error)) {
94-
error(status, copy(response));
94+
error(status, copy(response), copy(responseHeaders));
9595
} else {
9696
$error(
9797
{method: method, url: url, data: post, success: successes[i]},

src/service/xhr.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,17 @@
9696
* angular generated callback function.
9797
* @param {(string|Object)=} post Request content as either a string or an object to be stringified
9898
* as JSON before sent to the server.
99-
* @param {function(number, (string|Object))} success A function to be called when the response is
99+
* @param {function(number, (string|Object), Function)} success A function to be called when the response is
100100
* received. The success function will be called with:
101101
*
102102
* - {number} code [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of
103103
* the response. This will currently always be 200, since all non-200 responses are routed to
104104
* {@link angular.service.$xhr.error} service (or custom error callback).
105105
* - {string|Object} response Response object as string or an Object if the response was in JSON
106106
* format.
107+
* - {function(string=)} responseHeaders A function that when called with a {string} header name,
108+
* returns the value of that header or null if it does not exist; when called without
109+
* arguments, returns an object containing every response header
107110
* @param {function(number, (string|Object))} error A function to be called if the response code is
108111
* not 2xx.. Accepts the same arguments as success, above.
109112
*
@@ -198,7 +201,7 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
198201
post = toJson(post);
199202
}
200203

201-
$browser.xhr(method, url, post, function(code, response){
204+
$browser.xhr(method, url, post, function(code, response, responseHeaders){
202205
try {
203206
if (isString(response)) {
204207
if (response.match(/^\)\]\}',\n/)) response=response.substr(6);
@@ -207,9 +210,9 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
207210
}
208211
}
209212
if (200 <= code && code < 300) {
210-
success(code, response);
213+
success(code, response, responseHeaders);
211214
} else if (isFunction(error)) {
212-
error(code, response);
215+
error(code, response, responseHeaders);
213216
} else {
214217
$error(
215218
{method: method, url: url, data: post, success: success},

test/BrowserSpecs.js

+42
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ describe('browser', function(){
4949
this.send = function(post){
5050
xhr.post = post;
5151
};
52+
this.getResponseHeader = function(header) {
53+
return header;
54+
};
55+
this.getAllResponseHeaders = function() {
56+
return 'Content-Type: application/json\n\rContent-Encoding: gzip\n\rContent-Type: text/json';
57+
}
58+
5259
};
5360

5461
logs = {log:[], warn:[], info:[], error:[]};
@@ -198,6 +205,41 @@ describe('browser', function(){
198205
expect(code).toEqual(202);
199206
expect(response).toEqual('RESPONSE');
200207
});
208+
209+
describe('response headers', function() {
210+
it('should return a single response header', function() {
211+
var headerA;
212+
213+
browser.xhr('GET', 'URL', null, function(code, resp, headers) {
214+
headerA = headers('A-Header');
215+
});
216+
217+
xhr.status = 200;
218+
xhr.responseText = 'RESPONSE';
219+
xhr.readyState = 4;
220+
xhr.onreadystatechange();
221+
222+
expect(headerA).toEqual('a-header');
223+
});
224+
225+
it('should return an object containing all response headers', function() {
226+
var allHeaders;
227+
228+
browser.xhr('GET', 'URL', null, function(code, resp, headers) {
229+
allHeaders = headers();
230+
});
231+
232+
xhr.status = 200;
233+
xhr.responseText = 'RESPONSE';
234+
xhr.readyState = 4;
235+
xhr.onreadystatechange();
236+
237+
expect(allHeaders).toEqual({
238+
'content-type': 'application/json, text/json',
239+
'content-encoding': 'gzip'
240+
});
241+
});
242+
});
201243
});
202244

203245
describe('defer', function() {

0 commit comments

Comments
 (0)