Skip to content
This repository has been archived by the owner on Feb 11, 2024. It is now read-only.

add support for user added headers in http requests #26

Merged
merged 4 commits into from
Aug 17, 2021
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.0.5

* Added support for adding user specified headers in `HttpClientRequest`.

## 0.0.4+4

* Added support for 32 bit architecture.
Expand Down
1 change: 1 addition & 0 deletions lib/cronet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export 'src/exceptions.dart';
export 'src/http_client.dart';
export 'src/http_client_request.dart' hide HttpClientRequestImpl;
export 'src/http_client_response.dart' hide HttpClientResponseImpl;
export 'src/http_headers.dart' hide HttpHeadersImpl;
export 'src/quic_hint.dart';
23 changes: 16 additions & 7 deletions lib/src/http_client_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'exceptions.dart';
import 'globals.dart';
import 'http_callback_handler.dart';
import 'http_client_response.dart';
import 'http_headers.dart';
import 'third_party/cronet/generated_bindings.dart';

/// HTTP request for a client connection.
Expand Down Expand Up @@ -61,6 +62,8 @@ abstract class HttpClientRequest implements io.IOSink {

/// The uri of the request.
Uri get uri;

HttpHeaders get headers;
}

/// Implementation of [HttpClientRequest].
Expand All @@ -70,6 +73,8 @@ class HttpClientRequestImpl implements HttpClientRequest {
final Pointer<Cronet_Engine> _cronetEngine;
final CallbackHandler _callbackHandler;
final Pointer<Cronet_UrlRequest> _request;
final _requestParams = cronet.Cronet_UrlRequestParams_Create();
late final HttpHeadersImpl _headers;

/// Holds the function to clean up after the request is done (if nessesary).
///
Expand Down Expand Up @@ -97,18 +102,18 @@ class HttpClientRequestImpl implements HttpClientRequest {
: _callbackHandler =
CallbackHandler(wrapper.SampleExecutorCreate(), ReceivePort()),
_request = cronet.Cronet_UrlRequest_Create() {
_headers = HttpHeadersImpl(_requestParams);
// Register the native port to C side.
wrapper.RegisterCallbackHandler(
_callbackHandler.receivePort.sendPort.nativePort, _request.cast());
}

// Starts the request.
void _startRequest() {
final requestParams = cronet.Cronet_UrlRequestParams_Create();
if (requestParams == nullptr) throw Error();
if (_requestParams == nullptr) throw Error();
// TODO: ISSUE https://github.com/dart-lang/ffigen/issues/22
cronet.Cronet_UrlRequestParams_http_method_set(
requestParams, _method.toNativeUtf8().cast<Int8>());
_requestParams, _method.toNativeUtf8().cast<Int8>());
wrapper.InitSampleExecutor(_callbackHandler.executor);
final cronetCallbacks = cronet.Cronet_UrlRequestCallback_CreateWith(
wrapper.addresses.OnRedirectReceived.cast(),
Expand All @@ -122,7 +127,7 @@ class HttpClientRequestImpl implements HttpClientRequest {
_request,
_cronetEngine,
_uri.toString().toNativeUtf8().cast<Int8>(),
requestParams,
_requestParams,
cronetCallbacks,
wrapper.SampleExecutor_Cronet_ExecutorPtr_get(_callbackHandler.executor)
.cast());
Expand All @@ -138,13 +143,14 @@ class HttpClientRequestImpl implements HttpClientRequest {
_callbackHandler.listen(_request, () => _clientCleanup(this));
}

/// Returns [Future] of [HttpClientResponse] which can be listened for server
/// response.
/// Closes the request for input.
///
/// Throws [UrlRequestError] if request can't be initiated.
/// Returns [Future] of [HttpClientResponse] which can be listened to the
/// server response. Throws [UrlRequestError] if request can't be initiated.
@override
Future<HttpClientResponse> close() {
return Future(() {
_headers.isImmutable = true;
_startRequest();
return HttpClientResponseImpl(_callbackHandler.stream);
});
Expand Down Expand Up @@ -235,4 +241,7 @@ class HttpClientRequestImpl implements HttpClientRequest {
write(object);
write('\n');
}

@override
HttpHeaders get headers => _headers;
unsuitable001 marked this conversation as resolved.
Show resolved Hide resolved
}
50 changes: 50 additions & 0 deletions lib/src/http_headers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2021, 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:ffi';

import 'package:ffi/ffi.dart';

import 'globals.dart';
import 'third_party/cronet/generated_bindings.dart';

/// Headers for HTTP requests.
///
/// In some situations, headers are immutable:
unsuitable001 marked this conversation as resolved.
Show resolved Hide resolved
/// [HttpClientRequest] have immutable headers from the moment the body is
/// written to. In this situation, the mutating methods throw exceptions.
///
/// For all operations on HTTP headers the header name is case-insensitive.
///
/// To set the value of a header use the `set()` method:
///
/// ```dart
/// request.headers.set('Content-Type',
/// 'application/json');
/// ```
abstract class HttpHeaders {
/// Sets the header [name] to [value].
void set(String name, Object value);
}

/// Implementation of [HttpHeaders].
class HttpHeadersImpl implements HttpHeaders {
final Pointer<Cronet_UrlRequestParams> _requestParams;
bool isImmutable = false;

HttpHeadersImpl(this._requestParams);

@override
void set(String name, Object value) {
if (isImmutable) {
throw StateError('Can not write headers in immutable state.');
}
final header = cronet.Cronet_HttpHeader_Create();
cronet.Cronet_HttpHeader_name_set(header, name.toNativeUtf8().cast());
cronet.Cronet_HttpHeader_value_set(
header, value.toString().toNativeUtf8().cast());
cronet.Cronet_UrlRequestParams_request_headers_add(_requestParams, header);
cronet.Cronet_HttpHeader_Destroy(header);
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.

name: cronet
version: 0.0.4+4
version: 0.0.5
homepage: https://github.com/google/cronet.dart
description: Experimental Cronet dart bindings.

Expand Down
49 changes: 49 additions & 0 deletions test/http_headers_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2021, 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:convert';
import 'dart:io' as io;

import 'package:cronet/cronet.dart';
import 'package:test/test.dart';

const host = 'localhost';
const sentData = 'Hello, world!';

void main() {
group('Header Tests', () {
late HttpClient client;
late io.HttpServer server;
late int port;
setUp(() async {
client = HttpClient();
server = await io.HttpServer.bind(io.InternetAddress.anyIPv6, 0);
port = server.port;
server.listen((io.HttpRequest request) {
request.response.write(request.headers.value('test-header'));
request.response.close();
});
});

test('Send an arbitrary http header to the server', () async {
final request = await client.getUrl(Uri.parse('http://$host:$port/'));
request.headers.set('test-header', sentData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also provide users a way to set multiple headers? What do you think?
I think a very popular use case would be to have a standard set of headers which is used in multiple requests to an API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will be handy indeed :).

We can plan to introduce 2 new apis in a later pr:

  • headers.setAll that will take a map of header name and value.
  • An API to set headers to HttpClient that all the initiated HttpClientRequest will use by default.

These 2 APIs aren't part of dart:io as of now. But, will be a really nice addition to this package.

Easy way of adding few common headers will be added via #27 to comply with dart:io apis.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. 😀

final resp = await request.close();
final dataStream = resp.transform(utf8.decoder);
expect(dataStream, emitsInOrder(<Matcher>[equals(sentData), emitsDone]));
});

test('Mutating headers after request.close throws error', () async {
final request = await client.getUrl(Uri.parse('http://$host:$port/'));
await request.close();
expect(
() => request.headers.set('test-header', sentData), throwsStateError);
});

tearDown(() {
client.close();
server.close();
});
});
}