Skip to content

Commit a805f9b

Browse files
mvuksanoDiana Salsbury
authored and
Diana Salsbury
committed
feat(Http): Http service can make cross-site requests (get, post, put, etc.) which use credentials (such as cookies or authorization headers).
Closes dart-archive#945 Closes dart-archive#1026
1 parent efed0f7 commit a805f9b

File tree

4 files changed

+93
-43
lines changed

4 files changed

+93
-43
lines changed

lib/core_dom/http.dart

+35-25
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ class Http {
410410
* - headers: Map of strings or functions which return strings representing
411411
* HTTP headers to send to the server. If the return value of a function
412412
* is null, the header will not be sent.
413+
* - withCredentials: True if cross-site requests should use credentials such as cookies or
414+
* authorization headers; false otherwise. If not specified, defaults to false.
413415
* - xsrfHeaderName: TBI
414416
* - xsrfCookieName: TBI
415417
* - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors]
@@ -422,6 +424,7 @@ class Http {
422424
data,
423425
Map<String, dynamic> params,
424426
Map<String, dynamic> headers,
427+
bool withCredentials: false,
425428
xsrfHeaderName,
426429
xsrfCookieName,
427430
interceptors,
@@ -481,7 +484,8 @@ class Http {
481484
var result = _backend.request(url,
482485
method: method,
483486
requestHeaders: config.headers,
484-
sendData: config.data).then((dom.HttpRequest value) {
487+
sendData: config.data,
488+
withCredentials: withCredentials).then((dom.HttpRequest value) {
485489
// TODO: Uncomment after apps migrate off of this class.
486490
// assert(value.status >= 200 && value.status < 300);
487491

@@ -535,15 +539,16 @@ class Http {
535539
String data,
536540
Map<String, dynamic> params,
537541
Map<String, String> headers,
542+
bool withCredentials: false,
538543
xsrfHeaderName,
539544
xsrfCookieName,
540545
interceptors,
541546
cache,
542547
timeout
543-
}) => call(method: 'GET', url: url, data: data, params: params,
544-
headers: headers, xsrfHeaderName: xsrfHeaderName,
545-
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
546-
cache: cache, timeout: timeout);
548+
}) => call(method: 'GET', url: url, data: data, params: params, headers: headers,
549+
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
550+
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
551+
timeout: timeout);
547552

548553
/**
549554
* Shortcut method for DELETE requests. See [call] for a complete description
@@ -553,15 +558,16 @@ class Http {
553558
String data,
554559
Map<String, dynamic> params,
555560
Map<String, String> headers,
561+
bool withCredentials: false,
556562
xsrfHeaderName,
557563
xsrfCookieName,
558564
interceptors,
559565
cache,
560566
timeout
561-
}) => call(method: 'DELETE', url: url, data: data, params: params,
562-
headers: headers, xsrfHeaderName: xsrfHeaderName,
563-
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
564-
cache: cache, timeout: timeout);
567+
}) => call(method: 'DELETE', url: url, data: data, params: params, headers: headers,
568+
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
569+
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
570+
timeout: timeout);
565571

566572
/**
567573
* Shortcut method for HEAD requests. See [call] for a complete description
@@ -571,15 +577,16 @@ class Http {
571577
String data,
572578
Map<String, dynamic> params,
573579
Map<String, String> headers,
580+
bool withCredentials: false,
574581
xsrfHeaderName,
575582
xsrfCookieName,
576583
interceptors,
577584
cache,
578585
timeout
579-
}) => call(method: 'HEAD', url: url, data: data, params: params,
580-
headers: headers, xsrfHeaderName: xsrfHeaderName,
581-
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
582-
cache: cache, timeout: timeout);
586+
}) => call(method: 'HEAD', url: url, data: data, params: params, headers: headers,
587+
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
588+
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
589+
timeout: timeout);
583590

584591
/**
585592
* Shortcut method for PUT requests. See [call] for a complete description
@@ -588,15 +595,16 @@ class Http {
588595
async.Future<HttpResponse> put(String url, String data, {
589596
Map<String, dynamic> params,
590597
Map<String, String> headers,
598+
bool withCredentials: false,
591599
xsrfHeaderName,
592600
xsrfCookieName,
593601
interceptors,
594602
cache,
595603
timeout
596-
}) => call(method: 'PUT', url: url, data: data, params: params,
597-
headers: headers, xsrfHeaderName: xsrfHeaderName,
598-
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
599-
cache: cache, timeout: timeout);
604+
}) => call(method: 'PUT', url: url, data: data, params: params, headers: headers,
605+
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
606+
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
607+
timeout: timeout);
600608

601609
/**
602610
* Shortcut method for POST requests. See [call] for a complete description
@@ -605,15 +613,16 @@ class Http {
605613
async.Future<HttpResponse> post(String url, String data, {
606614
Map<String, dynamic> params,
607615
Map<String, String> headers,
616+
bool withCredentials: false,
608617
xsrfHeaderName,
609618
xsrfCookieName,
610619
interceptors,
611620
cache,
612621
timeout
613-
}) => call(method: 'POST', url: url, data: data, params: params,
614-
headers: headers, xsrfHeaderName: xsrfHeaderName,
615-
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
616-
cache: cache, timeout: timeout);
622+
}) => call(method: 'POST', url: url, data: data, params: params, headers: headers,
623+
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
624+
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
625+
timeout: timeout);
617626

618627
/**
619628
* Shortcut method for JSONP requests. See [call] for a complete description
@@ -623,15 +632,16 @@ class Http {
623632
String data,
624633
Map<String, dynamic> params,
625634
Map<String, String> headers,
635+
bool withCredentials: false,
626636
xsrfHeaderName,
627637
xsrfCookieName,
628638
interceptors,
629639
cache,
630640
timeout
631-
}) => call(method: 'JSONP', url: url, data: data, params: params,
632-
headers: headers, xsrfHeaderName: xsrfHeaderName,
633-
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
634-
cache: cache, timeout: timeout);
641+
}) => call(method: 'JSONP', url: url, data: data, params: params, headers: headers,
642+
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
643+
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
644+
timeout: timeout);
635645

636646
/**
637647
* Parse raw headers into key-value object

lib/mock/http_backend.dart

+34-18
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,23 @@ class _MockXhr {
6464
* An internal class used by [MockHttpBackend].
6565
*/
6666
class MockHttpExpectation {
67-
final method;
68-
final url;
67+
final String method;
68+
final /*String or RegExp*/ url;
6969
final data;
7070
final headers;
71+
final bool withCredentials;
7172

7273
var response;
7374

74-
MockHttpExpectation(this.method, this.url, [this.data, this.headers]);
75+
MockHttpExpectation(this.method, this.url, [this.data, this.headers, withCredentials]) :
76+
this.withCredentials = withCredentials == true;
7577

76-
bool match(method, url, [data, headers]) {
78+
bool match(method, url, [data, headers, withCredentials]) {
7779
if (method != method) return false;
7880
if (!matchUrl(url)) return false;
7981
if (data != null && !matchData(data)) return false;
8082
if (headers != null && !matchHeaders(headers)) return false;
83+
if (withCredentials != null && !matchWithCredentials(withCredentials)) return false;
8184
return true;
8285
}
8386

@@ -102,6 +105,8 @@ class MockHttpExpectation {
102105
return JSON.encode(data) == JSON.encode(d);
103106
}
104107

108+
bool matchWithCredentials(withCredentials) => this.withCredentials == withCredentials;
109+
105110
String toString() => "$method $url";
106111
}
107112

@@ -124,7 +129,7 @@ class MockHttpBackend implements HttpBackend {
124129
* This function is called from [Http] and designed to mimic the Dart APIs.
125130
*/
126131
dart_async.Future request(String url,
127-
{String method, bool withCredentials, String responseType,
132+
{String method, bool withCredentials: false, String responseType,
128133
String mimeType, Map<String, String> requestHeaders, sendData,
129134
void onProgress(ProgressEvent e)}) {
130135
dart_async.Completer c = new dart_async.Completer();
@@ -137,7 +142,7 @@ class MockHttpBackend implements HttpBackend {
137142
}
138143
};
139144
call(method == null ? 'GET' : method, url, callback,
140-
data: sendData, headers: requestHeaders);
145+
data: sendData, headers: requestHeaders, withCredentials: withCredentials);
141146
return c.future;
142147
}
143148

@@ -163,7 +168,7 @@ class MockHttpBackend implements HttpBackend {
163168
* A callback oriented API. This function takes a callback with
164169
* will be called with (status, data, headers)
165170
*/
166-
void call(method, url, callback, {data, headers, timeout}) {
171+
void call(method, url, callback, {data, headers, timeout, withCredentials: false}) {
167172
var xhr = new _MockXhr(),
168173
expectation = expectations.isEmpty ? null : expectations[0],
169174
wasExpected = false;
@@ -206,6 +211,11 @@ class MockHttpBackend implements HttpBackend {
206211
'EXPECTED: ${prettyPrint(expectation.headers)}\n'
207212
'GOT: ${prettyPrint(headers)}'];
208213

214+
if (!expectation.matchWithCredentials(withCredentials))
215+
throw ['Expected $expectation with different withCredentials\n'
216+
'EXPECTED: ${prettyPrint(expectation.withCredentials)}\n'
217+
'GOT: ${prettyPrint(withCredentials)}'];
218+
209219
expectations.removeAt(0);
210220

211221
if (expectation.response != null) {
@@ -216,7 +226,7 @@ class MockHttpBackend implements HttpBackend {
216226
}
217227

218228
for (var definition in definitions) {
219-
if (definition.match(method, url, data, headers != null ? headers : {})) {
229+
if (definition.match(method, url, data, headers != null ? headers : {}, withCredentials)) {
220230
if (definition.response != null) {
221231
// if $browser specified, we do auto flush all requests
222232
responses.add(wrapResponse(definition));
@@ -248,8 +258,8 @@ class MockHttpBackend implements HttpBackend {
248258
* an array containing response status (number), response data (string) and response headers
249259
* (Object).
250260
*/
251-
_Chain when(method, [url, data, headers]) {
252-
var definition = new MockHttpExpectation(method, url, data, headers),
261+
_Chain when(method, [url, data, headers, withCredentials = false]) {
262+
var definition = new MockHttpExpectation(method, url, data, headers, withCredentials),
253263
chain = new _Chain(respond: (status, data, headers) {
254264
definition.response = _createResponse(status, data, headers);
255265
});
@@ -364,8 +374,8 @@ class MockHttpBackend implements HttpBackend {
364374
* an array containing response status (number), response data (string) and response headers
365375
* (Object).
366376
*/
367-
_Chain expect(method, [url, data, headers]) {
368-
var expectation = new MockHttpExpectation(method, url, data, headers);
377+
_Chain expect(method, [url, data, headers, withCredentials = false]) {
378+
var expectation = new MockHttpExpectation(method, url, data, headers, withCredentials);
369379
expectations.add(expectation);
370380
return new _Chain(respond: (status, data, headers) {
371381
expectation.response = _createResponse(status, data, headers);
@@ -385,7 +395,8 @@ class MockHttpBackend implements HttpBackend {
385395
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
386396
* request is handled. See #expect for more info.
387397
*/
388-
_Chain expectGET(url, [headers]) => expect('GET', url, null, headers);
398+
_Chain expectGET(url, [headers, withCredentials = false]) => expect('GET', url, null, headers,
399+
withCredentials);
389400

390401
/**
391402
* @ngdoc method
@@ -399,7 +410,8 @@ class MockHttpBackend implements HttpBackend {
399410
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
400411
* request is handled.
401412
*/
402-
_Chain expectDELETE(url, [headers]) => expect('DELETE', url, null, headers);
413+
_Chain expectDELETE(url, [headers, withCredentials = false]) => expect('DELETE', url, null,
414+
headers, withCredentials);
403415

404416
/**
405417
* @ngdoc method
@@ -412,7 +424,8 @@ class MockHttpBackend implements HttpBackend {
412424
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
413425
* request is handled.
414426
*/
415-
_Chain expectJSONP(url, [headers]) => expect('JSONP', url, null, headers);
427+
_Chain expectJSONP(url, [headers, withCredentials = false]) => expect('JSONP', url, null, headers,
428+
withCredentials);
416429

417430
/**
418431
* @ngdoc method
@@ -427,7 +440,8 @@ class MockHttpBackend implements HttpBackend {
427440
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
428441
* request is handled.
429442
*/
430-
_Chain expectPUT(url, [data, headers]) => expect('PUT', url, data, headers);
443+
_Chain expectPUT(url, [data, headers, withCredentials = false]) => expect('PUT', url, data,
444+
headers, withCredentials);
431445

432446
/**
433447
* @ngdoc method
@@ -442,7 +456,8 @@ class MockHttpBackend implements HttpBackend {
442456
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
443457
* request is handled.
444458
*/
445-
_Chain expectPOST(url, [data, headers]) => expect('POST', url, data, headers);
459+
_Chain expectPOST(url, [data, headers, withCredentials = false]) => expect('POST', url, data,
460+
headers, withCredentials);
446461

447462
/**
448463
* @ngdoc method
@@ -457,7 +472,8 @@ class MockHttpBackend implements HttpBackend {
457472
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
458473
* request is handled.
459474
*/
460-
_Chain expectPATCH(url, [data, headers]) => expect('PATCH', url, data, headers);
475+
_Chain expectPATCH(url, [data, headers, withCredentials = false]) => expect('PATCH', url, data,
476+
headers, withCredentials);
461477

462478
/**
463479
* @ngdoc method

test/core_dom/http_spec.dart

+9
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ void main() {
9898
flush();
9999
}));
100100

101+
describe('backend', () {
102+
it('should pass on withCredentials to backend and use GET as default method',
103+
async(() {
104+
backend.expect('GET', '/url', null, null, true).respond('');
105+
http(url: '/url', method: 'GET', withCredentials: true);
106+
flush();
107+
}));
108+
});
109+
101110

102111
describe('params', () {
103112
it('should do basic request with params and encode', async(() {

test/mock/http_backend_spec.dart

+15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ void main() {
4343
expect(callback).toHaveBeenCalledOnce();
4444
});
4545

46+
it('should match when with credentials is set', () {
47+
hb.when('GET', '/url1').respond(200, 'content', {});
48+
hb.when('GET', '/url1', null, null, true).respond(201, 'another', {});
49+
50+
callback.andCallFake((status, response, _) {
51+
expect(status).toBe(201);
52+
expect(response).toBe('another');
53+
});
54+
55+
hb('GET', '/url1', callback, withCredentials: true);
56+
expect(callback).not.toHaveBeenCalled();
57+
hb.flush();
58+
expect(callback).toHaveBeenCalledOnce();
59+
});
60+
4661

4762
it('should respond with JSON', (Logger logger) {
4863
hb.when('GET', '/url1').respond(200, ['abc'], {});

0 commit comments

Comments
 (0)