Skip to content

Commit e7a8e25

Browse files
authored
Add BaseResponseWithUrl.url (#1109)
1 parent c8f17a6 commit e7a8e25

File tree

9 files changed

+109
-14
lines changed

9 files changed

+109
-14
lines changed

pkgs/http/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## 1.2.0-wip
1+
## 1.2.0
22

33
* Add `MockClient.pngResponse`, which makes it easier to fake image responses.
4+
* Added the ability to fetch the URL of the response through `BaseResponseWithUrl`.
45
* Add the ability to get headers as a `Map<String, List<String>` to
56
`BaseResponse`.
67

pkgs/http/lib/http.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import 'src/streamed_request.dart';
1616

1717
export 'src/base_client.dart';
1818
export 'src/base_request.dart';
19-
export 'src/base_response.dart' show BaseResponse, HeadersWithSplitValues;
19+
export 'src/base_response.dart'
20+
show BaseResponse, BaseResponseWithUrl, HeadersWithSplitValues;
2021
export 'src/byte_stream.dart';
2122
export 'src/client.dart' hide zoneClient;
2223
export 'src/exception.dart';
@@ -25,7 +26,7 @@ export 'src/multipart_request.dart';
2526
export 'src/request.dart';
2627
export 'src/response.dart';
2728
export 'src/streamed_request.dart';
28-
export 'src/streamed_response.dart';
29+
export 'src/streamed_response.dart' show StreamedResponse;
2930

3031
/// Sends an HTTP HEAD request with the given headers to the given URL.
3132
///

pkgs/http/lib/src/base_request.dart

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,25 @@ abstract class BaseRequest {
132132
try {
133133
var response = await client.send(this);
134134
var stream = onDone(response.stream, client.close);
135-
return StreamedResponse(ByteStream(stream), response.statusCode,
136-
contentLength: response.contentLength,
137-
request: response.request,
138-
headers: response.headers,
139-
isRedirect: response.isRedirect,
140-
persistentConnection: response.persistentConnection,
141-
reasonPhrase: response.reasonPhrase);
135+
136+
if (response case BaseResponseWithUrl(:final url)) {
137+
return StreamedResponseV2(ByteStream(stream), response.statusCode,
138+
contentLength: response.contentLength,
139+
request: response.request,
140+
headers: response.headers,
141+
isRedirect: response.isRedirect,
142+
url: url,
143+
persistentConnection: response.persistentConnection,
144+
reasonPhrase: response.reasonPhrase);
145+
} else {
146+
return StreamedResponse(ByteStream(stream), response.statusCode,
147+
contentLength: response.contentLength,
148+
request: response.request,
149+
headers: response.headers,
150+
isRedirect: response.isRedirect,
151+
persistentConnection: response.persistentConnection,
152+
reasonPhrase: response.reasonPhrase);
153+
}
142154
} catch (_) {
143155
client.close();
144156
rethrow;

pkgs/http/lib/src/base_response.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import 'base_client.dart';
66
import 'base_request.dart';
7+
import 'client.dart';
8+
import 'response.dart';
9+
import 'streamed_response.dart';
710

811
/// The base class for HTTP responses.
912
///
@@ -71,6 +74,37 @@ abstract class BaseResponse {
7174
}
7275
}
7376

77+
/// A [BaseResponse] with a [url] field.
78+
///
79+
/// [Client] methods that return a [BaseResponse] subclass, such as [Response]
80+
/// or [StreamedResponse], **may** return a [BaseResponseWithUrl].
81+
///
82+
/// For example:
83+
///
84+
/// ```dart
85+
/// final client = Client();
86+
/// final response = client.get(Uri.https('example.com', '/'));
87+
/// Uri? finalUri;
88+
/// if (response case BaseResponseWithUrl(:final url)) {
89+
/// finalUri = url;
90+
/// }
91+
/// // Do something with `finalUri`.
92+
/// client.close();
93+
/// ```
94+
///
95+
/// [url] will be added to [BaseResponse] when `package:http` version 2 is
96+
/// released and this mixin will be deprecated.
97+
abstract interface class BaseResponseWithUrl implements BaseResponse {
98+
/// The [Uri] of the response returned by the server.
99+
///
100+
/// If no redirects were followed, [url] will be the same as the requested
101+
/// [Uri].
102+
///
103+
/// If redirects were followed, [url] will be the [Uri] of the last redirect
104+
/// that was followed.
105+
abstract final Uri url;
106+
}
107+
74108
/// "token" as defined in RFC 2616, 2.2
75109
/// See https://datatracker.ietf.org/doc/html/rfc2616#section-2.2
76110
const _tokenChars = r"!#$%&'*+\-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`"

pkgs/http/lib/src/browser_client.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@ class BrowserClient extends BaseClient {
7979
return;
8080
}
8181
var body = (xhr.response as JSArrayBuffer).toDart.asUint8List();
82-
completer.complete(StreamedResponse(
82+
var responseUrl = xhr.responseURL;
83+
var url = responseUrl.isNotEmpty ? Uri.parse(responseUrl) : request.url;
84+
completer.complete(StreamedResponseV2(
8385
ByteStream.fromBytes(body), xhr.status,
8486
contentLength: body.length,
8587
request: request,
88+
url: url,
8689
headers: xhr.responseHeaders,
8790
reasonPhrase: xhr.statusText));
8891
}));

pkgs/http/lib/src/io_client.dart

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:io';
66

77
import 'base_client.dart';
88
import 'base_request.dart';
9+
import 'base_response.dart';
910
import 'client.dart';
1011
import 'exception.dart';
1112
import 'io_streamed_response.dart';
@@ -46,6 +47,22 @@ class _ClientSocketException extends ClientException
4647
String toString() => 'ClientException with $cause, uri=$uri';
4748
}
4849

50+
class _IOStreamedResponseV2 extends IOStreamedResponse
51+
implements BaseResponseWithUrl {
52+
@override
53+
final Uri url;
54+
55+
_IOStreamedResponseV2(super.stream, super.statusCode,
56+
{required this.url,
57+
super.contentLength,
58+
super.request,
59+
super.headers,
60+
super.isRedirect,
61+
super.persistentConnection,
62+
super.reasonPhrase,
63+
super.inner});
64+
}
65+
4966
/// A `dart:io`-based HTTP [Client].
5067
///
5168
/// If there is a socket-level failure when communicating with the server
@@ -116,7 +133,7 @@ class IOClient extends BaseClient {
116133
headers[key] = values.map((value) => value.trimRight()).join(',');
117134
});
118135

119-
return IOStreamedResponse(
136+
return _IOStreamedResponseV2(
120137
response.handleError((Object error) {
121138
final httpException = error as HttpException;
122139
throw ClientException(httpException.message, httpException.uri);
@@ -127,6 +144,9 @@ class IOClient extends BaseClient {
127144
request: request,
128145
headers: headers,
129146
isRedirect: response.isRedirect,
147+
url: response.redirects.isNotEmpty
148+
? response.redirects.last.location
149+
: request.url,
130150
persistentConnection: response.persistentConnection,
131151
reasonPhrase: response.reasonPhrase,
132152
inner: response);

pkgs/http/lib/src/streamed_response.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,20 @@ class StreamedResponse extends BaseResponse {
2626
super.reasonPhrase})
2727
: stream = toByteStream(stream);
2828
}
29+
30+
/// This class is private to `package:http` and will be removed when
31+
/// `package:http` v2 is released.
32+
class StreamedResponseV2 extends StreamedResponse
33+
implements BaseResponseWithUrl {
34+
@override
35+
final Uri url;
36+
37+
StreamedResponseV2(super.stream, super.statusCode,
38+
{required this.url,
39+
super.contentLength,
40+
super.request,
41+
super.headers,
42+
super.isRedirect,
43+
super.persistentConnection,
44+
super.reasonPhrase});
45+
}

pkgs/http/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: http
2-
version: 1.2.0-wip
2+
version: 1.2.0
33
description: A composable, multi-platform, Future-based API for HTTP requests.
44
repository: https://github.com/dart-lang/http/tree/master/pkgs/http
55

pkgs/http/test/io/request_test.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,22 @@ void main() {
4646
final response = await request.send();
4747

4848
expect(response.statusCode, equals(302));
49+
expect(
50+
response,
51+
isA<http.BaseResponseWithUrl>()
52+
.having((r) => r.url, 'url', serverUrl.resolve('/redirect')));
4953
});
5054

5155
test('with redirects', () async {
5256
final request = http.Request('GET', serverUrl.resolve('/redirect'));
5357
final response = await request.send();
54-
5558
expect(response.statusCode, equals(200));
5659
final bytesString = await response.stream.bytesToString();
5760
expect(bytesString, parse(containsPair('path', '/')));
61+
expect(
62+
response,
63+
isA<http.BaseResponseWithUrl>()
64+
.having((r) => r.url, 'url', serverUrl.resolve('/')));
5865
});
5966

6067
test('exceeding max redirects', () async {

0 commit comments

Comments
 (0)