Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion pkgs/http/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 1.1.3-wip
## 1.2.0

* Add `MockClient.pngResponse`, which makes it easier to fake image responses.
* Added the ability to fetch the URL of the response through `BaseResponseV2`.

## 1.1.2

Expand Down
2 changes: 1 addition & 1 deletion pkgs/http/lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export 'src/multipart_request.dart';
export 'src/request.dart';
export 'src/response.dart';
export 'src/streamed_request.dart';
export 'src/streamed_response.dart';
export 'src/streamed_response.dart' hide StreamedResponseV2;

/// Sends an HTTP HEAD request with the given headers to the given URL.
///
Expand Down
26 changes: 19 additions & 7 deletions pkgs/http/lib/src/base_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,25 @@ abstract class BaseRequest {
try {
var response = await client.send(this);
var stream = onDone(response.stream, client.close);
return StreamedResponse(ByteStream(stream), response.statusCode,
contentLength: response.contentLength,
request: response.request,
headers: response.headers,
isRedirect: response.isRedirect,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase);

if (response is BaseResponseV2) {
return StreamedResponseV2(ByteStream(stream), response.statusCode,
contentLength: response.contentLength,
request: response.request,
headers: response.headers,
isRedirect: response.isRedirect,
url: (response as BaseResponseV2).url,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase);
} else {
return StreamedResponse(ByteStream(stream), response.statusCode,
contentLength: response.contentLength,
request: response.request,
headers: response.headers,
isRedirect: response.isRedirect,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase);
}
} catch (_) {
client.close();
rethrow;
Expand Down
34 changes: 34 additions & 0 deletions pkgs/http/lib/src/base_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import 'base_client.dart';
import 'base_request.dart';
import 'client.dart';
import 'response.dart';
import 'streamed_response.dart';

/// The base class for HTTP responses.
///
Expand Down Expand Up @@ -68,3 +71,34 @@ abstract class BaseResponse {
}
}
}

/// Fields and methods that will be added to [BaseResponse] when `package:http`
/// version 2 is released.
///
/// [Client] methods that return a [BaseResponse] subclass, such as [Response]
/// or [StreamedResponse], **may** return a [BaseResponseV2].
///
/// For example:
///
/// ```dart
/// final client = Client();
/// final response = client.get(Uri.https('example.com', '/'));
/// Uri? finalUri;
/// if (response is BaseResponseV2) {
/// finalUri = (response as BaseResponseV2).uri;
/// }
/// // Do something with `finalUri`.
/// client.close();
/// ```
mixin BaseResponseV2 on BaseResponse {
/// The [Uri] of the response returned by the server.
///
/// If no redirects were followed, [url] will be the same as the requested
/// [Uri].
///
/// If redirects were followed, [url] will be the [Uri] of the last redirect
/// that was followed.
///
/// May be `null` when the response is for a special URL such as "about:".
abstract final Uri? url;
}
5 changes: 4 additions & 1 deletion pkgs/http/lib/src/browser_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,13 @@ class BrowserClient extends BaseClient {
return;
}
var body = (xhr.response as JSArrayBuffer).toDart.asUint8List();
completer.complete(StreamedResponse(
var responseUrl = xhr.responseURL;
var url = responseUrl.isNotEmpty ? Uri.parse(responseUrl) : null;
completer.complete(StreamedResponseV2(
ByteStream.fromBytes(body), xhr.status,
contentLength: body.length,
request: request,
url: url,
headers: xhr.responseHeaders,
reasonPhrase: xhr.statusText));
}));
Expand Down
21 changes: 20 additions & 1 deletion pkgs/http/lib/src/io_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:io';

import 'base_client.dart';
import 'base_request.dart';
import 'base_response.dart';
import 'client.dart';
import 'exception.dart';
import 'io_streamed_response.dart';
Expand Down Expand Up @@ -46,6 +47,21 @@ class _ClientSocketException extends ClientException
String toString() => 'ClientException with $cause, uri=$uri';
}

class _IOStreamedResponseV2 extends IOStreamedResponse with BaseResponseV2 {
@override
final Uri? url;

_IOStreamedResponseV2(super.stream, super.statusCode,
{super.contentLength,
super.request,
super.headers,
super.isRedirect,
this.url,
super.persistentConnection,
super.reasonPhrase,
super.inner});
}

/// A `dart:io`-based HTTP [Client].
///
/// If there is a socket-level failure when communicating with the server
Expand Down Expand Up @@ -116,7 +132,7 @@ class IOClient extends BaseClient {
headers[key] = values.map((value) => value.trimRight()).join(',');
});

return IOStreamedResponse(
return _IOStreamedResponseV2(
response.handleError((Object error) {
final httpException = error as HttpException;
throw ClientException(httpException.message, httpException.uri);
Expand All @@ -127,6 +143,9 @@ class IOClient extends BaseClient {
request: request,
headers: headers,
isRedirect: response.isRedirect,
url: response.redirects.isNotEmpty
? response.redirects.last.location
: request.url,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase,
inner: response);
Expand Down
16 changes: 16 additions & 0 deletions pkgs/http/lib/src/streamed_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,19 @@ class StreamedResponse extends BaseResponse {
super.reasonPhrase})
: stream = toByteStream(stream);
}

/// This class is private to `package:http` and will be removed when
/// `package:http` v2 is released.
class StreamedResponseV2 extends StreamedResponse with BaseResponseV2 {
@override
final Uri? url;

StreamedResponseV2(super.stream, super.statusCode,
{super.contentLength,
super.request,
super.headers,
super.isRedirect,
this.url,
super.persistentConnection,
super.reasonPhrase});
}
2 changes: 1 addition & 1 deletion pkgs/http/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: http
version: 1.1.3-wip
version: 1.2.0
description: A composable, multi-platform, Future-based API for HTTP requests.
repository: https://github.com/dart-lang/http/tree/master/pkgs/http

Expand Down
4 changes: 3 additions & 1 deletion pkgs/http/test/io/request_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@ void main() {
final response = await request.send();

expect(response.statusCode, equals(302));
expect(
(response as http.BaseResponseV2).url, serverUrl.resolve('/redirect'));
});

test('with redirects', () async {
final request = http.Request('GET', serverUrl.resolve('/redirect'));
final response = await request.send();

expect(response.statusCode, equals(200));
final bytesString = await response.stream.bytesToString();
expect(bytesString, parse(containsPair('path', '/')));
expect((response as http.BaseResponseV2).url, serverUrl.resolve('/'));
});

test('exceeding max redirects', () async {
Expand Down