Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(Http): Http service can make cross-site requests (get, post, put…
Browse files Browse the repository at this point in the history
…, etc.) which use credentials (such as cookies or authorization headers).

Closes #945
Closes #1026
  • Loading branch information
mvuksano authored and jbdeboer committed May 22, 2014
1 parent f35e490 commit 3ef9d8e
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 43 deletions.
60 changes: 35 additions & 25 deletions lib/core_dom/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ class Http {
* - headers: Map of strings or functions which return strings representing
* HTTP headers to send to the server. If the return value of a function
* is null, the header will not be sent.
* - withCredentials: True if cross-site requests should use credentials such as cookies or
* authorization headers; false otherwise. If not specified, defaults to false.
* - xsrfHeaderName: TBI
* - xsrfCookieName: TBI
* - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors]
Expand All @@ -422,6 +424,7 @@ class Http {
data,
Map<String, dynamic> params,
Map<String, dynamic> headers,
bool withCredentials: false,
xsrfHeaderName,
xsrfCookieName,
interceptors,
Expand Down Expand Up @@ -481,7 +484,8 @@ class Http {
var result = _backend.request(url,
method: method,
requestHeaders: config.headers,
sendData: config.data).then((dom.HttpRequest value) {
sendData: config.data,
withCredentials: withCredentials).then((dom.HttpRequest value) {
// TODO: Uncomment after apps migrate off of this class.
// assert(value.status >= 200 && value.status < 300);

Expand Down Expand Up @@ -535,15 +539,16 @@ class Http {
String data,
Map<String, dynamic> params,
Map<String, String> headers,
bool withCredentials: false,
xsrfHeaderName,
xsrfCookieName,
interceptors,
cache,
timeout
}) => call(method: 'GET', url: url, data: data, params: params,
headers: headers, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
cache: cache, timeout: timeout);
}) => call(method: 'GET', url: url, data: data, params: params, headers: headers,
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
timeout: timeout);

/**
* Shortcut method for DELETE requests. See [call] for a complete description
Expand All @@ -553,15 +558,16 @@ class Http {
String data,
Map<String, dynamic> params,
Map<String, String> headers,
bool withCredentials: false,
xsrfHeaderName,
xsrfCookieName,
interceptors,
cache,
timeout
}) => call(method: 'DELETE', url: url, data: data, params: params,
headers: headers, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
cache: cache, timeout: timeout);
}) => call(method: 'DELETE', url: url, data: data, params: params, headers: headers,
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
timeout: timeout);

/**
* Shortcut method for HEAD requests. See [call] for a complete description
Expand All @@ -571,15 +577,16 @@ class Http {
String data,
Map<String, dynamic> params,
Map<String, String> headers,
bool withCredentials: false,
xsrfHeaderName,
xsrfCookieName,
interceptors,
cache,
timeout
}) => call(method: 'HEAD', url: url, data: data, params: params,
headers: headers, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
cache: cache, timeout: timeout);
}) => call(method: 'HEAD', url: url, data: data, params: params, headers: headers,
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
timeout: timeout);

/**
* Shortcut method for PUT requests. See [call] for a complete description
Expand All @@ -588,15 +595,16 @@ class Http {
async.Future<HttpResponse> put(String url, String data, {
Map<String, dynamic> params,
Map<String, String> headers,
bool withCredentials: false,
xsrfHeaderName,
xsrfCookieName,
interceptors,
cache,
timeout
}) => call(method: 'PUT', url: url, data: data, params: params,
headers: headers, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
cache: cache, timeout: timeout);
}) => call(method: 'PUT', url: url, data: data, params: params, headers: headers,
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
timeout: timeout);

/**
* Shortcut method for POST requests. See [call] for a complete description
Expand All @@ -605,15 +613,16 @@ class Http {
async.Future<HttpResponse> post(String url, String data, {
Map<String, dynamic> params,
Map<String, String> headers,
bool withCredentials: false,
xsrfHeaderName,
xsrfCookieName,
interceptors,
cache,
timeout
}) => call(method: 'POST', url: url, data: data, params: params,
headers: headers, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
cache: cache, timeout: timeout);
}) => call(method: 'POST', url: url, data: data, params: params, headers: headers,
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
timeout: timeout);

/**
* Shortcut method for JSONP requests. See [call] for a complete description
Expand All @@ -623,15 +632,16 @@ class Http {
String data,
Map<String, dynamic> params,
Map<String, String> headers,
bool withCredentials: false,
xsrfHeaderName,
xsrfCookieName,
interceptors,
cache,
timeout
}) => call(method: 'JSONP', url: url, data: data, params: params,
headers: headers, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors,
cache: cache, timeout: timeout);
}) => call(method: 'JSONP', url: url, data: data, params: params, headers: headers,
withCredentials: withCredentials, xsrfHeaderName: xsrfHeaderName,
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
timeout: timeout);

/**
* Parse raw headers into key-value object
Expand Down
52 changes: 34 additions & 18 deletions lib/mock/http_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,23 @@ class _MockXhr {
* An internal class used by [MockHttpBackend].
*/
class MockHttpExpectation {
final method;
final url;
final String method;
final /*String or RegExp*/ url;
final data;
final headers;
final bool withCredentials;

var response;

MockHttpExpectation(this.method, this.url, [this.data, this.headers]);
MockHttpExpectation(this.method, this.url, [this.data, this.headers, withCredentials]) :
this.withCredentials = withCredentials == true;

bool match(method, url, [data, headers]) {
bool match(method, url, [data, headers, withCredentials]) {
if (method != method) return false;
if (!matchUrl(url)) return false;
if (data != null && !matchData(data)) return false;
if (headers != null && !matchHeaders(headers)) return false;
if (withCredentials != null && !matchWithCredentials(withCredentials)) return false;
return true;
}

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

bool matchWithCredentials(withCredentials) => this.withCredentials == withCredentials;

String toString() => "$method $url";
}

Expand All @@ -124,7 +129,7 @@ class MockHttpBackend implements HttpBackend {
* This function is called from [Http] and designed to mimic the Dart APIs.
*/
dart_async.Future request(String url,
{String method, bool withCredentials, String responseType,
{String method, bool withCredentials: false, String responseType,
String mimeType, Map<String, String> requestHeaders, sendData,
void onProgress(ProgressEvent e)}) {
dart_async.Completer c = new dart_async.Completer();
Expand All @@ -137,7 +142,7 @@ class MockHttpBackend implements HttpBackend {
}
};
call(method == null ? 'GET' : method, url, callback,
data: sendData, headers: requestHeaders);
data: sendData, headers: requestHeaders, withCredentials: withCredentials);
return c.future;
}

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

if (!expectation.matchWithCredentials(withCredentials))
throw ['Expected $expectation with different withCredentials\n'
'EXPECTED: ${prettyPrint(expectation.withCredentials)}\n'
'GOT: ${prettyPrint(withCredentials)}'];

expectations.removeAt(0);

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

for (var definition in definitions) {
if (definition.match(method, url, data, headers != null ? headers : {})) {
if (definition.match(method, url, data, headers != null ? headers : {}, withCredentials)) {
if (definition.response != null) {
// if $browser specified, we do auto flush all requests
responses.add(wrapResponse(definition));
Expand Down Expand Up @@ -248,8 +258,8 @@ class MockHttpBackend implements HttpBackend {
* an array containing response status (number), response data (string) and response headers
* (Object).
*/
_Chain when(method, [url, data, headers]) {
var definition = new MockHttpExpectation(method, url, data, headers),
_Chain when(method, [url, data, headers, withCredentials = false]) {
var definition = new MockHttpExpectation(method, url, data, headers, withCredentials),
chain = new _Chain(respond: (status, data, headers) {
definition.response = _createResponse(status, data, headers);
});
Expand Down Expand Up @@ -364,8 +374,8 @@ class MockHttpBackend implements HttpBackend {
* an array containing response status (number), response data (string) and response headers
* (Object).
*/
_Chain expect(method, [url, data, headers]) {
var expectation = new MockHttpExpectation(method, url, data, headers);
_Chain expect(method, [url, data, headers, withCredentials = false]) {
var expectation = new MockHttpExpectation(method, url, data, headers, withCredentials);
expectations.add(expectation);
return new _Chain(respond: (status, data, headers) {
expectation.response = _createResponse(status, data, headers);
Expand All @@ -385,7 +395,8 @@ class MockHttpBackend implements HttpBackend {
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
* request is handled. See #expect for more info.
*/
_Chain expectGET(url, [headers]) => expect('GET', url, null, headers);
_Chain expectGET(url, [headers, withCredentials = false]) => expect('GET', url, null, headers,
withCredentials);

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

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

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

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

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

/**
* @ngdoc method
Expand Down
9 changes: 9 additions & 0 deletions test/core_dom/http_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ void main() {
flush();
}));

describe('backend', () {
it('should pass on withCredentials to backend and use GET as default method',
async(() {
backend.expect('GET', '/url', null, null, true).respond('');
http(url: '/url', method: 'GET', withCredentials: true);
flush();
}));
});


describe('params', () {
it('should do basic request with params and encode', async(() {
Expand Down
15 changes: 15 additions & 0 deletions test/mock/http_backend_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ void main() {
expect(callback).toHaveBeenCalledOnce();
});

it('should match when with credentials is set', () {
hb.when('GET', '/url1').respond(200, 'content', {});
hb.when('GET', '/url1', null, null, true).respond(201, 'another', {});

callback.andCallFake((status, response, _) {
expect(status).toBe(201);
expect(response).toBe('another');
});

hb('GET', '/url1', callback, withCredentials: true);
expect(callback).not.toHaveBeenCalled();
hb.flush();
expect(callback).toHaveBeenCalledOnce();
});


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

0 comments on commit 3ef9d8e

Please sign in to comment.