Skip to content

Return a customized StreamedResponse from CronetClient.send #1769

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
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
5 changes: 5 additions & 0 deletions pkgs/cronet_http/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.4.0-wip

* Add a new `CronetStreamedResponse` class that provides additional information
about the HTTP response.

## 1.3.4

* Cancel requests when the response stream is cancelled.
Expand Down
59 changes: 59 additions & 0 deletions pkgs/cronet_http/example/integration_test/client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// 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:io';

import 'package:cronet_http/cronet_http.dart';
import 'package:http/http.dart';
import 'package:http_client_conformance_tests/http_client_conformance_tests.dart';
import 'package:http_profile/http_profile.dart';
import 'package:integration_test/integration_test.dart';
Expand Down Expand Up @@ -52,8 +55,64 @@ Future<void> testConformance() async {
});
}

Future<void> testCronetStreamedResponse() async {
group('CronetStreamedResponse', () {
late HttpServer server;
late Uri serverUri;

setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.headers
.set('Cache-Control', 'public, max-age=30, immutable');
request.response.headers.set('etag', '12345');
await request.response.close();
});
serverUri = Uri.http('localhost:${server.port}');
});
tearDown(() {
server.close();
});

test('negotiatedProtocol', () async {
final client = CronetClient.defaultCronetEngine();

final response = await client.send(Request('GET', serverUri));
await response.stream.drain<void>();

expect(response.negotiatedProtocol, 'unknown');
});

test('receivedByteCount', () async {
final client = CronetClient.defaultCronetEngine();

final response = await client.send(Request('GET', serverUri));
await response.stream.drain<void>();

expect(response.receivedByteCount, greaterThan(0));
});

test('wasCached', () async {
final engine = CronetEngine.build(
cacheMode: CacheMode.memory, cacheMaxSize: 1024 * 1024);
final client = CronetClient.fromCronetEngine(engine);

final response1 = await client.send(Request('GET', serverUri));
await response1.stream.drain<void>();
final response2 = await client.send(Request('GET', serverUri));
await response2.stream.drain<void>();

expect(response1.wasCached, isFalse);
expect(response2.wasCached, isTrue);
});
});
}

void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

await testConformance();
await testCronetStreamedResponse();
}
56 changes: 51 additions & 5 deletions pkgs/cronet_http/lib/src/cronet_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,47 @@ class _StreamedResponseWithUrl extends StreamedResponse
super.reasonPhrase});
}

/// An HTTP response from the Cronet network stack.
///
/// The response body is received asynchronously after the headers have been
/// received.
class CronetStreamedResponse extends _StreamedResponseWithUrl {
final jb.UrlResponseInfo _responseInfo;

/// The protocol (for example `'quic/1+spdy/3'`) negotiated with the server.
///
/// It will be the empty string or `'unknown'` if no protocol was negotiated,
/// the protocol is not known, or when using plain HTTP or HTTPS.
String get negotiatedProtocol =>
_responseInfo.getNegotiatedProtocol().toDartString(releaseOriginal: true);

/// The minimum count of bytes received from the network to process this
/// request.
///
/// This count may ignore certain overheads (for example IP and TCP/UDP
/// framing, SSL handshake and framing, proxy handling). This count is taken
/// prior to decompression (for example GZIP) and includes headers and data
/// from all redirects. This value may change as more response data is
/// received from the network.
int get receivedByteCount => _responseInfo.getReceivedByteCount();

/// Whether the response came from the cache.
///
/// Is `true` for requests that were revalidated over the network before being
/// retrieved from the cache
bool get wasCached => _responseInfo.wasCached();

CronetStreamedResponse._(super.stream, super.statusCode,
{required jb.UrlResponseInfo responseInfo,
required super.url,
super.contentLength,
super.request,
super.headers,
super.isRedirect,
super.reasonPhrase})
: _responseInfo = responseInfo;
}

/// The type of caching to use when making HTTP requests.
enum CacheMode {
disabled,
Expand Down Expand Up @@ -148,7 +189,7 @@ Map<String, String> _cronetToClientHeaders(

jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
BaseRequest request,
Completer<StreamedResponse> responseCompleter,
Completer<CronetStreamedResponse> responseCompleter,
HttpClientRequestProfile? profile) {
StreamController<List<int>>? responseStream;
JByteBuffer? jByteBuffer;
Expand Down Expand Up @@ -186,9 +227,10 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
case final contentLengthHeader?:
contentLength = int.parse(contentLengthHeader);
}
responseCompleter.complete(_StreamedResponseWithUrl(
responseCompleter.complete(CronetStreamedResponse._(
responseStream!.stream,
responseInfo.getHttpStatusCode(),
responseInfo: responseInfo,
url: Uri.parse(
responseInfo.getUrl().toDartString(releaseOriginal: true)),
contentLength: contentLength,
Expand Down Expand Up @@ -219,9 +261,12 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(

if (!request.followRedirects) {
urlRequest.cancel();
responseCompleter.complete(StreamedResponse(
responseCompleter.complete(CronetStreamedResponse._(
const Stream.empty(), // Cronet provides no body for redirects.
responseInfo.getHttpStatusCode(),
responseInfo: responseInfo,
url: Uri.parse(
responseInfo.getUrl().toDartString(releaseOriginal: true)),
contentLength: 0,
reasonPhrase: responseInfo
.getHttpStatusText()
Expand Down Expand Up @@ -353,8 +398,9 @@ class CronetClient extends BaseClient {
requestMethod: request.method,
requestUri: request.url.toString());

/// Sends an HTTP request and asynchronously returns the response.
@override
Future<StreamedResponse> send(BaseRequest request) async {
Future<CronetStreamedResponse> send(BaseRequest request) async {
if (_isClosed) {
throw ClientException(
'HTTP request failed. Client is already closed.', request.url);
Expand Down Expand Up @@ -389,7 +435,7 @@ class CronetClient extends BaseClient {
final body = await stream.toBytes();
profile?.requestData.bodySink.add(body);

final responseCompleter = Completer<StreamedResponse>();
final responseCompleter = Completer<CronetStreamedResponse>();

final builder = engine._engine.newUrlRequestBuilder(
request.url.toString().toJString(),
Expand Down
2 changes: 1 addition & 1 deletion pkgs/cronet_http/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: cronet_http
version: 1.3.4
version: 1.4.0-wip
description: >-
An Android Flutter plugin that provides access to the Cronet HTTP client.
repository: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http
Expand Down