Skip to content

Handle timeouts in BrowserClient #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c303aa8
Update URL for reporting bugs
benasocj Sep 27, 2016
899f456
Remove an outdated bug link in the README. (#48)
nex3 Dec 14, 2016
efc13de
fix analyzer
kevmoo Oct 7, 2016
c23bef7
Replicate Body from shelf into http
donny-dont Jan 12, 2017
0dac77f
Replicate UnmodifiableMap implementation from shelf
donny-dont Jan 12, 2017
2d3ed9c
Merge pull request #52 from donny-dont/master
nex3 Jan 12, 2017
f975718
Updating .gitignore to latest from GitHub (#53)
donny-dont Jan 17, 2017
3ae6d4b
Emulate shelf messaging (#54)
donny-dont Feb 14, 2017
5940bb2
Stop using dart:mirrors. (#55)
nex3 Feb 16, 2017
2d2738d
Correct the pubspec.yaml, we require 1.23.0 (#58)
matanlurey Mar 6, 2017
e9dbebd
Update Client interface (#56)
donny-dont Mar 6, 2017
128c0bc
Add back missing Request and Response fields (#78)
donny-dont May 23, 2017
11fca7b
Add a dart:io client (#82)
donny-dont May 24, 2017
2168f61
Add a dart:html client (#83)
donny-dont May 25, 2017
5d6ce28
Remove ByteStream (#84)
donny-dont May 25, 2017
5466bac
Adding middleware (#85)
donny-dont Jun 22, 2017
866628b
Add Pipeline (#87)
donny-dont Jun 23, 2017
dc9262e
Add tests for Message (#90)
donny-dont Jun 23, 2017
00a9780
Remove old unittest tests
donny-dont Jun 23, 2017
5141052
Fix accidental character
donny-dont Jun 23, 2017
2841cd7
Expose pipeline and handler
donny-dont Jun 23, 2017
49d7f4a
Merge pull request #92 from donny-dont/test/run
nex3 Jun 23, 2017
15cc5de
Remove MockClient (#93)
donny-dont Jun 23, 2017
f62d1a6
Adding client tests (#95)
donny-dont Jun 23, 2017
5098a28
Fix merge conflicts
donny-dont Jun 24, 2017
bceba84
Add .travis file
kevmoo Jun 23, 2017
8b3b6e9
Merge pull request #100 from donny-dont/merge/0.11.x
nex3 Jun 26, 2017
5056526
Merge remote-tracking branch 'origin/master' into travis
kevmoo Jun 26, 2017
673bf5b
test on vm and firefox
kevmoo Jun 26, 2017
b2b853f
nits
kevmoo Jun 26, 2017
4f96dca
Merge pull request #96 from dart-lang/travis
kevmoo Jun 26, 2017
40ccf46
Add Request tests (#102)
donny-dont Jun 27, 2017
ce2bdc6
Remove "'" from boundary characters (#106) (#108)
donny-dont Jul 29, 2017
e1533c4
Add http package prefix for import consistency (#111)
perlatus Sep 20, 2017
ebd0b33
Fix content-length-header (#110)
donny-dont Sep 20, 2017
5f21a3b
Rename file (#114)
donny-dont Sep 21, 2017
77d470a
Stop depending on stack_trace (#115)
nex3 Sep 28, 2017
4171b73
Use generic method syntax (#125)
donny-dont Oct 19, 2017
7b0eb3b
Add content type library (#124)
donny-dont Oct 20, 2017
ada4c08
Fixing test directory (#126)
donny-dont Oct 20, 2017
441963f
Cleanup contents of util (#127)
donny-dont Oct 20, 2017
f940c48
Add User-Agent to headers (#129)
donny-dont Oct 25, 2017
5983d3f
Implement multipart requests (#113)
donny-dont Oct 26, 2017
6d3d7c5
dartfmt (#131)
donny-dont Oct 26, 2017
fc32f51
Documentation cleanup (#132)
donny-dont Oct 26, 2017
29a0077
Modify context variable names and write docs (#133)
donny-dont Oct 31, 2017
155430c
Add timeout support in BrowserClient
donny-dont Oct 31, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
# Don’t commit the following directories created by pub.
# See https://www.dartlang.org/tools/private-files.html

# Files and directories created by pub
.buildlog
.packages
.project
.pub/
build/
packages
.packages
**/packages/

# Or the files created by dart2js.
# Files created by dart2js
# (Most Dart developers will use pub build to compile Dart, use/modify these
# rules if you intend to use dart2js directly
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
# differentiate from explicit Javascript files)
*.dart.js
*.js_
*.part.js
*.js.deps
*.js.map
*.info.json

# Directory created by dartdoc
doc/api/

# Include when developing application packages.
pubspec.lock
# Don't commit pubspec lock file
# (Library packages only! Remove pattern if developing an application package)
pubspec.lock
26 changes: 26 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
language: dart
sudo: false

dart:
- dev
- stable

dart_task:
- test: --platform vm,firefox

# Only run one instance of the formatter and the analyzer, rather than running
# them against each Dart version.
matrix:
include:
- dart: stable
dart_task: dartfmt
- dart: dev
dart_task: dartanalyzer

# Only building master means that we don't run two builds for each pull request.
branches:
only: [master]

cache:
directories:
- $HOME/.pub-cache
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.11.3+14

* Remove single quote ("'" - ASCII 39) from boundary characters.
Causes issues with Google Cloud Storage.

## 0.11.3+13

* remove boundary characters that package:http_parser cannot parse.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class UserAgentClient extends http.BaseClient {

UserAgentClient(this.userAgent, this._inner);

Future<StreamedResponse> send(BaseRequest request) {
Future<http.StreamedResponse> send(http.BaseRequest request) {
request.headers['user-agent'] = userAgent;
return _inner.send(request);
}
Expand Down
File renamed without changes.
74 changes: 41 additions & 33 deletions lib/browser_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,35 @@ import 'dart:async';
import 'dart:html';
import 'dart:typed_data';

import 'package:stack_trace/stack_trace.dart';
import 'package:async/async.dart';

import 'src/base_client.dart';
import 'src/base_request.dart';
import 'src/byte_stream.dart';
import 'src/exception.dart';
import 'src/streamed_response.dart';
import 'src/request.dart';
import 'src/response.dart';

// TODO(nweiz): Move this under src/, re-export from lib/http.dart, and use this
// automatically from [new Client] once sdk#24581 is fixed.

/// A `dart:html`-based HTTP client that runs in the browser and is backed by
/// XMLHttpRequests.
///
/// This client inherits some of the limitations of XMLHttpRequest. It ignores
/// the [BaseRequest.contentLength], [BaseRequest.persistentConnection],
/// [BaseRequest.followRedirects], and [BaseRequest.maxRedirects] fields. It is
/// also unable to stream requests or responses; a request will only be sent and
/// a response will only be returned once all the data is available.
/// This client inherits some of the limitations of XMLHttpRequest. It is
/// unable to directly set some headers, such as `content-length`. It is also
/// unable to stream requests or responses; a request will only be sent and a
/// response will only be returned once all the data is available.
///
/// You can control the underlying `dart:html` [HttpRequest] by adding values to
/// [Request.context]:
///
/// * `"http.html.with_credentials"` is a boolean that defaults to `false`. If
/// it's `true`, cross-site requests will include credentials such as cookies
/// or authorization headers. See also [HttpRequest.withCredentials].
///
/// * `"http.html.timeout"` is an integer that specifies the time in
/// milliseconds before a request is automatically terminated. It defaults to
/// `0` which prevents a request from timing out. See also
/// [HttpRequest.timeout].
class BrowserClient extends BaseClient {
/// The currently active XHRs.
///
Expand All @@ -34,23 +44,20 @@ class BrowserClient extends BaseClient {
/// Creates a new HTTP client.
BrowserClient();

/// Whether to send credentials such as cookies or authorization headers for
/// cross-site requests.
///
/// Defaults to `false`.
bool withCredentials = false;

/// Sends an HTTP request and asynchronously returns the response.
Future<StreamedResponse> send(BaseRequest request) async {
var bytes = await request.finalize().toBytes();
Future<Response> send(Request request) async {
var bytes = await collectBytes(request.read());
var xhr = new HttpRequest();
_xhrs.add(xhr);
_openHttpRequest(xhr, request.method, request.url.toString(), asynch: true);
xhr.responseType = 'blob';
xhr.withCredentials = withCredentials;

xhr
..responseType = 'blob'
..withCredentials = request.context['http.html.with_credentials'] ?? false
..timeout = request.context['http.html.timeout'] ?? 0;

request.headers.forEach(xhr.setRequestHeader);

var completer = new Completer<StreamedResponse>();
var completer = new Completer<Response>();
xhr.onLoad.first.then((_) {
// TODO(nweiz): Set the response type to "arraybuffer" when issue 18542
// is fixed.
Expand All @@ -59,30 +66,34 @@ class BrowserClient extends BaseClient {

reader.onLoad.first.then((_) {
var body = reader.result as Uint8List;
completer.complete(new StreamedResponse(
new ByteStream.fromBytes(body),
xhr.status,
contentLength: body.length,
request: request,
headers: xhr.responseHeaders,
reasonPhrase: xhr.statusText));
completer.complete(new Response(xhr.responseUrl, xhr.status,
reasonPhrase: xhr.statusText,
body: new Stream.fromIterable([body]),
headers: xhr.responseHeaders));
});

reader.onError.first.then((error) {
completer.completeError(
new ClientException(error.toString(), request.url),
new Chain.current());
StackTrace.current);
});

reader.readAsArrayBuffer(blob);
});

xhr.onTimeout.first.then((error) {
completer.completeError(
new ClientException(
'XMLHttpRequest timeout after ${xhr.timeout}ms.', request.url),
StackTrace.current);
});

xhr.onError.first.then((_) {
// Unfortunately, the underlying XMLHttpRequest API doesn't expose any
// specific information about the error itself.
completer.completeError(
new ClientException("XMLHttpRequest error.", request.url),
new Chain.current());
StackTrace.current);
});

xhr.send(bytes);
Expand All @@ -100,9 +111,6 @@ class BrowserClient extends BaseClient {
request.open(method, url, async: asynch, user: user, password: password);
}

/// Closes the client.
///
/// This terminates all active requests.
void close() {
for (var xhr in _xhrs) {
xhr.abort();
Expand Down
74 changes: 35 additions & 39 deletions lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@ import 'src/client.dart';
import 'src/response.dart';

export 'src/base_client.dart';
export 'src/base_request.dart';
export 'src/base_response.dart';
export 'src/byte_stream.dart';
export 'src/client.dart';
export 'src/exception.dart';
export 'src/handler.dart';
export 'src/io_client.dart';
export 'src/middleware.dart';
export 'src/multipart_file.dart';
export 'src/multipart_request.dart';
export 'src/pipeline.dart';
export 'src/request.dart';
export 'src/response.dart';
export 'src/streamed_request.dart';
export 'src/streamed_response.dart';

/// Sends an HTTP HEAD request with the given headers to the given URL, which
/// can be a [Uri] or a [String].
Expand All @@ -31,9 +28,11 @@ export 'src/streamed_response.dart';
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request, use [Request] instead.
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
Future<Response> head(url, {Map<String, String> headers}) =>
_withClient((client) => client.head(url, headers: headers));
_withClient((client) => client.head(url, headers: headers));

/// Sends an HTTP GET request with the given headers to the given URL, which can
/// be a [Uri] or a [String].
Expand All @@ -42,9 +41,11 @@ Future<Response> head(url, {Map<String, String> headers}) =>
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request, use [Request] instead.
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
Future<Response> get(url, {Map<String, String> headers}) =>
_withClient((client) => client.get(url, headers: headers));
_withClient((client) => client.get(url, headers: headers));

/// Sends an HTTP POST request with the given headers and body to the given URL,
/// which can be a [Uri] or a [String].
Expand All @@ -63,12 +64,13 @@ Future<Response> get(url, {Map<String, String> headers}) =>
///
/// [encoding] defaults to [UTF8].
///
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
Future<Response> post(url, {Map<String, String> headers, body,
Encoding encoding}) =>
_withClient((client) => client.post(url,
headers: headers, body: body, encoding: encoding));
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
Future<Response> post(url, body,
{Map<String, String> headers, Encoding encoding}) =>
_withClient((client) =>
client.post(url, body, headers: headers, encoding: encoding));

/// Sends an HTTP PUT request with the given headers and body to the given URL,
/// which can be a [Uri] or a [String].
Expand All @@ -87,12 +89,13 @@ Future<Response> post(url, {Map<String, String> headers, body,
///
/// [encoding] defaults to [UTF8].
///
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
Future<Response> put(url, {Map<String, String> headers, body,
Encoding encoding}) =>
_withClient((client) => client.put(url,
headers: headers, body: body, encoding: encoding));
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
Future<Response> put(url, body,
{Map<String, String> headers, Encoding encoding}) =>
_withClient((client) =>
client.put(url, body, headers: headers, encoding: encoding));

/// Sends an HTTP PATCH request with the given headers and body to the given
/// URL, which can be a [Uri] or a [String].
Expand All @@ -111,23 +114,22 @@ Future<Response> put(url, {Map<String, String> headers, body,
///
/// [encoding] defaults to [UTF8].
///
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
Future<Response> patch(url, {Map<String, String> headers, body,
Encoding encoding}) =>
_withClient((client) => client.patch(url,
headers: headers, body: body, encoding: encoding));
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
Future<Response> patch(url, body,
{Map<String, String> headers, Encoding encoding}) =>
_withClient((client) =>
client.patch(url, body, headers: headers, encoding: encoding));

/// Sends an HTTP DELETE request with the given headers to the given URL, which
/// can be a [Uri] or a [String].
///
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request, use [Request] instead.
Future<Response> delete(url, {Map<String, String> headers}) =>
_withClient((client) => client.delete(url, headers: headers));
_withClient((client) => client.delete(url, headers: headers));

/// Sends an HTTP GET request with the given headers to the given URL, which can
/// be a [Uri] or a [String], and returns a Future that completes to the body of
Expand All @@ -139,11 +141,8 @@ Future<Response> delete(url, {Map<String, String> headers}) =>
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request and response, use [Request]
/// instead.
Future<String> read(url, {Map<String, String> headers}) =>
_withClient((client) => client.read(url, headers: headers));
_withClient((client) => client.read(url, headers: headers));

/// Sends an HTTP GET request with the given headers to the given URL, which can
/// be a [Uri] or a [String], and returns a Future that completes to the body of
Expand All @@ -155,11 +154,8 @@ Future<String> read(url, {Map<String, String> headers}) =>
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request and response, use [Request]
/// instead.
Future<Uint8List> readBytes(url, {Map<String, String> headers}) =>
_withClient((client) => client.readBytes(url, headers: headers));
_withClient((client) => client.readBytes(url, headers: headers));

Future<T> _withClient<T>(Future<T> fn(Client client)) async {
var client = new Client();
Expand Down
Loading