Http class
The Http service facilitates communication with the remote HTTP servers. It
uses dart:html's HttpRequest
and provides a number of features on top
of the core Dart library.
For unit testing, applications should use the MockHttpBackend
service.
General usage
The call method takes a number of named parameters and returns a
[Future
http(method: 'GET', url: '/someUrl')
.then((HttpResponse response) { .. },
onError: (HttpRequest request) { .. });
A response status code between 200 and 299 is considered a success status and
will result in the 'then' being called. Note that if the response is a redirect,
Dart's HttpRequest
will transparently follow it, meaning that the error callback will not be
called for such responses.
Shortcut methods
The Http service also defines a number of shortcuts:
http.get('/someUrl') is the same as http(method: 'GET', url: '/someUrl')
See the method definitions below.
Setting HTTP Headers
The Http service will add certain HTTP headers to requests. These defaults can be configured using the HttpDefaultHeaders object. The defaults are:
- For all requests:
Accept: application/json, text/plain, * / *
- For POST, PUT, PATCH requests:
Content-Type: application/json
Caching
To enable caching, pass a Cache object into the call method. The Http service will store responses in the cache and return the response for any matching requests.
Note that data is returned through a Future
, regardless of whether it
came from the Cache or the server.
If there are multiple GET requests for the same not-yet-in-cache URL while a cache is in use, only one request to the server will be made.
Interceptors
Http uses the interceptors from HttpInterceptors. You can also include interceptors in the call method.
Security Considerations
NOTE: < not yet documented >
class Http { Map<String, async.Future<HttpResponse>> _pendingRequests = <String, async.Future<HttpResponse>>{}; UrlRewriter _rewriter; HttpBackend _backend; HttpInterceptors _interceptors; /** * The defaults for [Http] */ HttpDefaults defaults; /** * Constructor, useful for DI. */ Http(UrlRewriter this._rewriter, HttpBackend this._backend, HttpDefaults this.defaults, HttpInterceptors this._interceptors); /** * DEPRECATED */ async.Future<String> getString(String url, {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) { return request(url, withCredentials: withCredentials, onProgress: onProgress, cache: cache).then((HttpResponse xhr) => xhr.responseText); } /** * Returns a [Future<HttpResponse>] when the request is fulfilled. * * Named Parameters: * - method: HTTP method (e.g. 'GET', 'POST', etc) * - url: Absolute or relative URL of the resource being requested. * - data: Data to be sent as the request message data. * - params: Map of strings or objects which will be turned to * `?key1=value1&key2=value2` after the url. If the values are * not strings, they will be JSONified. * - 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. * - xsrfHeaderName: TBI * - xsrfCookieName: TBI * - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors] * - cache: Boolean or [Cache]. If true, the default cache will be used. * - timeout: deprecated */ async.Future<HttpResponse> call({ String url, String method, data, Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout }) { if (xsrfHeaderName != null || xsrfCookieName != null || timeout != null) { throw ['not implemented']; } method = method.toUpperCase(); if (headers == null) { headers = {}; } defaults.headers.setHeaders(headers, method); // Check for functions in headers headers.forEach((k,v) { if (v is Function) { headers[k] = v(); } }); var serverRequest = (HttpResponseConfig config) { assert(config.data == null || config.data is String || config.data is dom.File); // Strip content-type if data is undefined if (config.data == null) { List<String> toRemove = []; headers.forEach((h, _) { if (h.toUpperCase() == 'CONTENT-TYPE') { toRemove.add(h); }; }); toRemove.forEach((x) => headers.remove(x)); } return request( null, config: config, method: method, sendData: config.data, requestHeaders: config.headers, cache: cache); }; var chain = [[serverRequest, null]]; var future = new async.Future.value(new HttpResponseConfig( url: url, params: params, headers: headers, data: data)); _interceptors.constructChain(chain); if (interceptors != null) { if (interceptors is HttpInterceptor) { interceptors = new HttpInterceptors.of([interceptors]); } assert(interceptors is HttpInterceptors); interceptors.constructChain(chain); } chain.forEach((chainFns) { future = future.then(chainFns[0], onError: chainFns[1]); }); return future; } /** * Shortcut method for GET requests. See [call] for a complete description * of parameters. */ async.Future<HttpResponse> get(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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); /** * Shortcut method for DELETE requests. See [call] for a complete description * of parameters. */ async.Future<HttpResponse> delete(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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); /** * Shortcut method for HEAD requests. See [call] for a complete description * of parameters. */ async.Future<HttpResponse> head(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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); /** * Shortcut method for PUT requests. See [call] for a complete description * of parameters. */ async.Future<HttpResponse> put(String url, String data, { Map<String, dynamic> params, Map<String, String> headers, 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); /** * Shortcut method for POST requests. See [call] for a complete description * of parameters. */ async.Future<HttpResponse> post(String url, String data, { Map<String, dynamic> params, Map<String, String> headers, 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); /** * Shortcut method for JSONP requests. See [call] for a complete description * of parameters. */ async.Future<HttpResponse> jsonp(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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); /** * Parse raw headers into key-value object */ static Map<String, String> parseHeaders(dom.HttpRequest value) { var headers = value.getAllResponseHeaders(); var parsed = {}, key, val, i; if (headers == null) return parsed; headers.split('\n').forEach((line) { i = line.indexOf(':'); if (i == -1) return; key = line.substring(0, i).trim().toLowerCase(); val = line.substring(i + 1).trim(); if (key != '') { if (parsed.containsKey(key)) { parsed[key] += ', ' + val; } else { parsed[key] = val; } } }); return parsed; } /** * Returns an [Iterable] of [Future] [HttpResponse]s for the requests * that the [Http] service is currently waiting for. */ Iterable<async.Future<HttpResponse> > get pendingRequests { return _pendingRequests.values; } /** * DEPRECATED */ async.Future<HttpResponse> request(String rawUrl, { HttpResponseConfig config, String method: 'GET', bool withCredentials: false, String responseType, String mimeType, Map<String, String> requestHeaders, sendData, void onProgress(dom.ProgressEvent e), /*Cache<String, HttpResponse> or false*/ cache }) { String url; if (config == null) { url = _rewriter(rawUrl); config = new HttpResponseConfig(url: url); } else { url = _buildUrl(config.url, config.params); } if (cache is bool && cache == false) { cache = null; } else if (cache == null) { cache = defaults.cache; } // We return a pending request only if caching is enabled. if (cache != null && _pendingRequests.containsKey(url)) { return _pendingRequests[url]; } var cachedValue = (cache != null && method == 'GET') ? cache.get(url) : null; if (cachedValue != null) { return new async.Future.value(new HttpResponse.copy(cachedValue)); } var result = _backend.request(url, method: method, withCredentials: withCredentials, responseType: responseType, mimeType: mimeType, requestHeaders: requestHeaders, sendData: sendData, onProgress: onProgress).then((dom.HttpRequest value) { // TODO: Uncomment after apps migrate off of this class. // assert(value.status >= 200 && value.status < 300); var response = new HttpResponse( value.status, value.responseText, parseHeaders(value), config); if (cache != null) { cache.put(url, response); } _pendingRequests.remove(url); return response; }, onError: (error) { if (error is! dom.ProgressEvent) { throw error; } dom.ProgressEvent event = error; _pendingRequests.remove(url); dom.HttpRequest request = event.currentTarget; return new async.Future.error( new HttpResponse(request.status, request.response, parseHeaders(request), config)); }); _pendingRequests[url] = result; return result; } _buildUrl(String url, Map<String, dynamic> params) { if (params == null) return url; var parts = []; new List.from(params.keys)..sort()..forEach((String key) { var value = params[key]; if (value == null) return; if (value is! List) value = [value]; value.forEach((v) { if (v is Map) { v = json.stringify(v); } parts.add(_encodeUriQuery(key) + '=' + _encodeUriQuery("$v")); }); }); return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); } _encodeUriQuery(val, {bool pctEncodeSpaces: false}) => Uri.encodeComponent(val) .replaceAll('%40', '@') .replaceAll('%3A', ':') .replaceAll('%24', r'$') .replaceAll('%2C', ',') .replaceAll('%20', pctEncodeSpaces ? '%20' : '+'); }
Static Methods
Map<String, String> parseHeaders(HttpRequest value) #
Parse raw headers into key-value object
static Map<String, String> parseHeaders(dom.HttpRequest value) { var headers = value.getAllResponseHeaders(); var parsed = {}, key, val, i; if (headers == null) return parsed; headers.split('\n').forEach((line) { i = line.indexOf(':'); if (i == -1) return; key = line.substring(0, i).trim().toLowerCase(); val = line.substring(i + 1).trim(); if (key != '') { if (parsed.containsKey(key)) { parsed[key] += ', ' + val; } else { parsed[key] = val; } } }); return parsed; }
Constructors
new Http(UrlRewriter _rewriter, HttpBackend _backend, HttpDefaults defaults, HttpInterceptors _interceptors) #
Constructor, useful for DI.
Http(UrlRewriter this._rewriter, HttpBackend this._backend, HttpDefaults this.defaults, HttpInterceptors this._interceptors);
Properties
HttpDefaults defaults #
The defaults for Http
HttpDefaults defaults
final Iterable<Future<HttpResponse>> pendingRequests #
Methods
Future<HttpResponse> call({String url, String method, data, Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout}) #
Returns a [Future
Named Parameters: - method: HTTP method (e.g. 'GET', 'POST', etc) - url: Absolute or relative URL of the resource being requested. - data: Data to be sent as the request message data. - params: Map of strings or objects which will be turned to
`?key1=value1&key2=value2` after the url. If the values are
not strings, they will be JSONified.
-
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.
- xsrfHeaderName: TBI
- xsrfCookieName: TBI
- interceptors: Either a HttpInterceptor or a HttpInterceptors
- cache: Boolean or Cache. If true, the default cache will be used.
- timeout: deprecated
async.Future<HttpResponse> call({ String url, String method, data, Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout }) { if (xsrfHeaderName != null || xsrfCookieName != null || timeout != null) { throw ['not implemented']; } method = method.toUpperCase(); if (headers == null) { headers = {}; } defaults.headers.setHeaders(headers, method); // Check for functions in headers headers.forEach((k,v) { if (v is Function) { headers[k] = v(); } }); var serverRequest = (HttpResponseConfig config) { assert(config.data == null || config.data is String || config.data is dom.File); // Strip content-type if data is undefined if (config.data == null) { List<String> toRemove = []; headers.forEach((h, _) { if (h.toUpperCase() == 'CONTENT-TYPE') { toRemove.add(h); }; }); toRemove.forEach((x) => headers.remove(x)); } return request( null, config: config, method: method, sendData: config.data, requestHeaders: config.headers, cache: cache); }; var chain = [[serverRequest, null]]; var future = new async.Future.value(new HttpResponseConfig( url: url, params: params, headers: headers, data: data)); _interceptors.constructChain(chain); if (interceptors != null) { if (interceptors is HttpInterceptor) { interceptors = new HttpInterceptors.of([interceptors]); } assert(interceptors is HttpInterceptors); interceptors.constructChain(chain); } chain.forEach((chainFns) { future = future.then(chainFns[0], onError: chainFns[1]); }); return future; }
Future<HttpResponse> delete(String url, {String data, Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout}) #
Shortcut method for DELETE requests. See call for a complete description of parameters.
async.Future<HttpResponse> delete(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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);
Future<HttpResponse> get(String url, {String data, Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout}) #
Shortcut method for GET requests. See call for a complete description of parameters.
async.Future<HttpResponse> get(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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);
Future<String> getString(String url, {bool withCredentials, void onProgress(ProgressEvent e), Cache cache}) #
DEPRECATED
async.Future<String> getString(String url, {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) { return request(url, withCredentials: withCredentials, onProgress: onProgress, cache: cache).then((HttpResponse xhr) => xhr.responseText); }
Future<HttpResponse> head(String url, {String data, Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout}) #
Shortcut method for HEAD requests. See call for a complete description of parameters.
async.Future<HttpResponse> head(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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);
Future<HttpResponse> jsonp(String url, {String data, Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout}) #
Shortcut method for JSONP requests. See call for a complete description of parameters.
async.Future<HttpResponse> jsonp(String url, { String data, Map<String, dynamic> params, Map<String, String> headers, 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);
Future<HttpResponse> post(String url, String data, {Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout}) #
Shortcut method for POST requests. See call for a complete description of parameters.
async.Future<HttpResponse> post(String url, String data, { Map<String, dynamic> params, Map<String, String> headers, 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);
Future<HttpResponse> put(String url, String data, {Map<String, dynamic> params, Map<String, String> headers, xsrfHeaderName, xsrfCookieName, interceptors, cache, timeout}) #
Shortcut method for PUT requests. See call for a complete description of parameters.
async.Future<HttpResponse> put(String url, String data, { Map<String, dynamic> params, Map<String, String> headers, 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);
Future<HttpResponse> request(String rawUrl, {HttpResponseConfig config, String method: 'GET', bool withCredentials: false, String responseType, String mimeType, Map<String, String> requestHeaders, sendData, void onProgress(ProgressEvent e), cache}) #
DEPRECATED
async.Future<HttpResponse> request(String rawUrl, { HttpResponseConfig config, String method: 'GET', bool withCredentials: false, String responseType, String mimeType, Map<String, String> requestHeaders, sendData, void onProgress(dom.ProgressEvent e), /*Cache<String, HttpResponse> or false*/ cache }) { String url; if (config == null) { url = _rewriter(rawUrl); config = new HttpResponseConfig(url: url); } else { url = _buildUrl(config.url, config.params); } if (cache is bool && cache == false) { cache = null; } else if (cache == null) { cache = defaults.cache; } // We return a pending request only if caching is enabled. if (cache != null && _pendingRequests.containsKey(url)) { return _pendingRequests[url]; } var cachedValue = (cache != null && method == 'GET') ? cache.get(url) : null; if (cachedValue != null) { return new async.Future.value(new HttpResponse.copy(cachedValue)); } var result = _backend.request(url, method: method, withCredentials: withCredentials, responseType: responseType, mimeType: mimeType, requestHeaders: requestHeaders, sendData: sendData, onProgress: onProgress).then((dom.HttpRequest value) { // TODO: Uncomment after apps migrate off of this class. // assert(value.status >= 200 && value.status < 300); var response = new HttpResponse( value.status, value.responseText, parseHeaders(value), config); if (cache != null) { cache.put(url, response); } _pendingRequests.remove(url); return response; }, onError: (error) { if (error is! dom.ProgressEvent) { throw error; } dom.ProgressEvent event = error; _pendingRequests.remove(url); dom.HttpRequest request = event.currentTarget; return new async.Future.error( new HttpResponse(request.status, request.response, parseHeaders(request), config)); }); _pendingRequests[url] = result; return result; }