Skip to content

Make package:http/http.dart support all platforms #198

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

Merged
merged 6 commits into from
Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## 0.12.0-dev

### New Features

* The regular `Client` factory constructor is now usable anywhere that `dart:io`
or `dart:html` are available, and will give you an `IoClient` or
`BrowserClient` respectively.
* The `package:http/http.dart` import is now safe to use on the web (or
anywhere that either `dart:io` or `dart:html` are available).

### Breaking Changes

* In order to use or reference the `IoClient` directly, you will need to import
the new `package:http/io_client.dart` import. This is typically only necessary
if you are passing a custom `HttpClient` instance to the constructor, in which
case you are already giving up support for web.

## 0.11.3+17

* Use new Dart 2 constant names. This branch is only for allowing existing
Expand Down
105 changes: 1 addition & 104 deletions lib/browser_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,107 +2,4 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:html';
import 'dart:typed_data';

import 'src/base_client.dart';
import 'src/base_request.dart';
import 'src/byte_stream.dart';
import 'src/exception.dart';
import 'src/streamed_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.
class BrowserClient extends BaseClient {
/// The currently active XHRs.
///
/// These are aborted if the client is closed.
final _xhrs = new Set<HttpRequest>();

/// 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();
var xhr = new HttpRequest();
_xhrs.add(xhr);
_openHttpRequest(xhr, request.method, request.url.toString(), asynch: true);
xhr.responseType = 'blob';
xhr.withCredentials = withCredentials;
request.headers.forEach(xhr.setRequestHeader);

var completer = new Completer<StreamedResponse>();
xhr.onLoad.first.then((_) {
// TODO(nweiz): Set the response type to "arraybuffer" when issue 18542
// is fixed.
var blob = xhr.response == null ? new Blob([]) : xhr.response;
var reader = new FileReader();

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));
});

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

reader.readAsArrayBuffer(blob);
});

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),
StackTrace.current);
});

xhr.send(bytes);

try {
return await completer.future;
} finally {
_xhrs.remove(xhr);
}
}

// TODO(nweiz): Remove this when sdk#24637 is fixed.
void _openHttpRequest(HttpRequest request, String method, String url,
{bool asynch, String user, String password}) {
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();
}
}
}
export 'src/browser_client.dart' show BrowserClient;
1 change: 0 additions & 1 deletion lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export 'src/base_response.dart';
export 'src/byte_stream.dart';
export 'src/client.dart';
export 'src/exception.dart';
export 'src/io_client.dart';
export 'src/multipart_file.dart';
export 'src/multipart_request.dart';
export 'src/request.dart';
Expand Down
5 changes: 5 additions & 0 deletions lib/io_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

export 'src/io_client.dart' show IOClient;
108 changes: 108 additions & 0 deletions lib/src/browser_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:html';
import 'dart:typed_data';

import 'base_client.dart';
import 'base_request.dart';
import 'byte_stream.dart';
import 'exception.dart';
import 'streamed_response.dart';

/// Used from conditional imports, matches the definition in `client_stub.dart`.
BaseClient createClient() => BrowserClient();

/// 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.
class BrowserClient extends BaseClient {
/// The currently active XHRs.
///
/// These are aborted if the client is closed.
final _xhrs = new Set<HttpRequest>();

/// 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();
var xhr = new HttpRequest();
_xhrs.add(xhr);
_openHttpRequest(xhr, request.method, request.url.toString(), asynch: true);
xhr.responseType = 'blob';
xhr.withCredentials = withCredentials;
request.headers.forEach(xhr.setRequestHeader);

var completer = new Completer<StreamedResponse>();
xhr.onLoad.first.then((_) {
// TODO(nweiz): Set the response type to "arraybuffer" when issue 18542
// is fixed.
var blob = xhr.response == null ? new Blob([]) : xhr.response;
var reader = new FileReader();

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));
});

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

reader.readAsArrayBuffer(blob);
});

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),
StackTrace.current);
});

xhr.send(bytes);

try {
return await completer.future;
} finally {
_xhrs.remove(xhr);
}
}

// TODO(nweiz): Remove this when sdk#24637 is fixed.
void _openHttpRequest(HttpRequest request, String method, String url,
{bool asynch, String user, String password}) {
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();
}
}
}
15 changes: 10 additions & 5 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import 'dart:typed_data';

import 'base_client.dart';
import 'base_request.dart';
import 'io_client.dart';
// ignore: uri_does_not_exist
import 'client_stub.dart'
// ignore: uri_does_not_exist
if (dart.library.html) 'browser_client.dart'
// ignore: uri_does_not_exist
if (dart.library.io) 'io_client.dart';
import 'response.dart';
import 'streamed_response.dart';

Expand All @@ -24,10 +29,10 @@ import 'streamed_response.dart';
abstract class Client {
/// Creates a new client.
///
/// Currently this will create an [IOClient] if `dart:io` is available and
/// throw an [UnsupportedError] otherwise. In the future, it will create a
/// [BrowserClient] if `dart:html` is available.
factory Client() => new IOClient();
/// Currently this will create an `IOClient` if `dart:io` is available and
/// a `BrowserClient` if `dart:html` is available, otherwise it will throw
/// an unsupported error.
factory Client() => createClient();

/// Sends an HTTP HEAD request with the given headers to the given URL, which
/// can be a [Uri] or a [String].
Expand Down
9 changes: 9 additions & 0 deletions lib/src/client_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'base_client.dart';

/// Implemented in `browser_client.dart` and `io_client.dart`.
BaseClient createClient() => throw UnsupportedError(
'Cannot create a client without dart:html or dart:io.');
3 changes: 3 additions & 0 deletions lib/src/io_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import 'base_request.dart';
import 'exception.dart';
import 'streamed_response.dart';

/// Used from conditional imports, matches the definition in `client_stub.dart`.
BaseClient createClient() => IOClient();

/// A `dart:io`-based HTTP client.
///
/// This is the default client when running on the command line.
Expand Down
19 changes: 8 additions & 11 deletions lib/src/multipart_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:async/async.dart';
import 'package:http_parser/http_parser.dart';
import 'package:path/path.dart' as path;

import 'byte_stream.dart';
import 'utils.dart';

// ignore: uri_does_not_exist
import 'multipart_file_stub.dart'
// ignore: uri_does_not_exist
if (dart.library.io) 'multipart_file_io.dart';

/// A file to be uploaded as part of a [MultipartRequest]. This doesn't need to
/// correspond to a physical file.
class MultipartFile {
Expand Down Expand Up @@ -87,14 +89,9 @@ class MultipartFile {
/// Throws an [UnsupportedError] if `dart:io` isn't supported in this
/// environment.
static Future<MultipartFile> fromPath(String field, String filePath,
{String filename, MediaType contentType}) async {
if (filename == null) filename = path.basename(filePath);
var file = new File(filePath);
var length = await file.length();
var stream = new ByteStream(DelegatingStream.typed(file.openRead()));
return new MultipartFile(field, stream, length,
filename: filename, contentType: contentType);
}
{String filename, MediaType contentType}) =>
multipartFileFromPath(field, filePath,
filename: filename, contentType: contentType);

// Finalizes the file in preparation for it being sent as part of a
// [MultipartRequest]. This returns a [ByteStream] that should emit the body
Expand Down
23 changes: 23 additions & 0 deletions lib/src/multipart_file_io.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:async/async.dart';
import 'package:http_parser/http_parser.dart';
import 'package:path/path.dart' as p;

import 'byte_stream.dart';
import 'multipart_file.dart';

Future<MultipartFile> multipartFileFromPath(String field, String filePath,
{String filename, MediaType contentType}) async {
if (filename == null) filename = p.basename(filePath);
var file = new File(filePath);
var length = await file.length();
var stream = new ByteStream(DelegatingStream.typed(file.openRead()));
return new MultipartFile(field, stream, length,
filename: filename, contentType: contentType);
}
14 changes: 14 additions & 0 deletions lib/src/multipart_file_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

import 'package:http_parser/http_parser.dart';

import 'multipart_file.dart';

Future<MultipartFile> multipartFileFromPath(String field, String filePath,
{String filename, MediaType contentType}) =>
throw UnsupportedError(
'MultipartFile is only supported where dart:io is available.');
5 changes: 4 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: http
version: 0.11.4-dev
version: 0.12.0-dev
author: "Dart Team <[email protected]>"
homepage: https://github.com/dart-lang/http
description: A composable, Future-based API for making HTTP requests.
Expand All @@ -14,3 +14,6 @@ dependencies:

dev_dependencies:
test: ^1.3.0

dependency_overrides:
package_resolver: 1.0.4
Loading