Skip to content
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

feat: support multiple headers (Replace #1090) #1106

Closed
wants to merge 6 commits into from
Closed
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
26 changes: 13 additions & 13 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ analyzer:

linter:
rules:
- avoid_bool_literals_in_conditional_expressions
- avoid_classes_with_only_static_members
- avoid_private_typedef_functions
- avoid_returning_this
- avoid_unused_constructor_parameters
- cascade_invocations
- join_return_with_assignment
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_runtimeType_toString
- prefer_const_declarations
- prefer_expression_function_bodies
- use_string_buffers
- avoid_bool_literals_in_conditional_expressions
- avoid_classes_with_only_static_members
- avoid_private_typedef_functions
- avoid_returning_this
- avoid_unused_constructor_parameters
- cascade_invocations
- join_return_with_assignment
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_runtimeType_toString
- prefer_const_declarations
- prefer_expression_function_bodies
- use_string_buffers
1 change: 1 addition & 0 deletions pkgs/http/lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export 'src/base_response.dart';
export 'src/byte_stream.dart';
export 'src/client.dart' hide zoneClient;
export 'src/exception.dart';
export 'src/headers.dart';
export 'src/multipart_file.dart';
export 'src/multipart_request.dart';
export 'src/request.dart';
Expand Down
4 changes: 2 additions & 2 deletions pkgs/http/lib/retry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ final class RetryClient extends BaseClient {

/// Returns a copy of [original] with the given [body].
StreamedRequest _copyRequest(BaseRequest original, Stream<List<int>> body) {
final request = StreamedRequest(original.method, original.url)
final request = StreamedRequest(original.method, original.url,
headers: original.headers)
..contentLength = original.contentLength
..followRedirects = original.followRedirects
..headers.addAll(original.headers)
..maxRedirects = original.maxRedirects
..persistentConnection = original.persistentConnection;

Expand Down
22 changes: 10 additions & 12 deletions pkgs/http/lib/src/base_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,42 @@ import 'streamed_response.dart';
/// maybe [close], and then they get various convenience methods for free.
abstract mixin class BaseClient implements Client {
@override
Future<Response> head(Uri url, {Map<String, String>? headers}) =>
Future<Response> head(Uri url, {Object? headers}) =>
_sendUnstreamed('HEAD', url, headers);

@override
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
Future<Response> get(Uri url, {Object? headers}) =>
_sendUnstreamed('GET', url, headers);

@override
Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
{Object? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('POST', url, headers, body, encoding);

@override
Future<Response> put(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
{Object? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('PUT', url, headers, body, encoding);

@override
Future<Response> patch(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
{Object? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('PATCH', url, headers, body, encoding);

@override
Future<Response> delete(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
{Object? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('DELETE', url, headers, body, encoding);

@override
Future<String> read(Uri url, {Map<String, String>? headers}) async {
Future<String> read(Uri url, {Object? headers}) async {
final response = await get(url, headers: headers);
_checkResponseSuccess(url, response);
return response.body;
}

@override
Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) async {
Future<Uint8List> readBytes(Uri url, {Object? headers}) async {
final response = await get(url, headers: headers);
_checkResponseSuccess(url, response);
return response.bodyBytes;
Expand All @@ -71,12 +71,10 @@ abstract mixin class BaseClient implements Client {
Future<StreamedResponse> send(BaseRequest request);

/// Sends a non-streaming [Request] and returns a non-streaming [Response].
Future<Response> _sendUnstreamed(
String method, Uri url, Map<String, String>? headers,
Future<Response> _sendUnstreamed(String method, Uri url, Object? headers,
[Object? body, Encoding? encoding]) async {
var request = Request(method, url);
final request = Request(method, url, headers: headers);

if (headers != null) request.headers.addAll(headers);
if (encoding != null) request.encoding = encoding;
if (body != null) {
if (body is String) {
Expand Down
11 changes: 4 additions & 7 deletions pkgs/http/lib/src/base_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
// 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:collection';

import 'package:meta/meta.dart';

import '../http.dart' show ClientException, get;
import 'base_client.dart';
import 'base_response.dart';
import 'byte_stream.dart';
import 'client.dart';
import 'headers.dart';
import 'streamed_response.dart';
import 'utils.dart';

Expand Down Expand Up @@ -82,7 +81,7 @@ abstract class BaseRequest {
// TODO(nweiz): automatically parse cookies from headers

// TODO(nweiz): make this a HttpHeaders object
final Map<String, String> headers;
final Headers headers;

/// Whether [finalize] has been called.
bool get finalized => _finalized;
Expand All @@ -96,11 +95,9 @@ abstract class BaseRequest {
return method;
}

BaseRequest(String method, this.url)
BaseRequest(String method, this.url, {Object? headers})
: method = _validateMethod(method),
headers = LinkedHashMap(
equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
hashCode: (key) => key.toLowerCase().hashCode);
headers = Headers(headers);

/// Finalizes the HTTP request in preparation for it being sent.
///
Expand Down
19 changes: 11 additions & 8 deletions pkgs/http/lib/src/base_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'base_client.dart';
import 'base_request.dart';
import 'headers.dart';

/// The base class for HTTP responses.
///
Expand Down Expand Up @@ -47,20 +48,22 @@ abstract class BaseResponse {
/// by a single space. Leading and trailing whitespace in header values are
/// always removed.
// TODO(nweiz): make this a HttpHeaders object.
final Map<String, String> headers;
final Headers headers;

final bool isRedirect;

/// Whether the server requested that a persistent connection be maintained.
final bool persistentConnection;

BaseResponse(this.statusCode,
{this.contentLength,
this.request,
this.headers = const {},
this.isRedirect = false,
this.persistentConnection = true,
this.reasonPhrase}) {
BaseResponse(
this.statusCode, {
this.contentLength,
this.request,
Object? headers,
this.isRedirect = false,
this.persistentConnection = true,
this.reasonPhrase,
}) : headers = Headers(headers) {
if (statusCode < 100) {
throw ArgumentError('Invalid status code $statusCode.');
} else if (contentLength != null && contentLength! < 0) {
Expand Down
35 changes: 21 additions & 14 deletions pkgs/http/lib/src/browser_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:js_interop';

import 'package:web/helpers.dart';
Expand Down Expand Up @@ -62,14 +63,22 @@ class BrowserClient extends BaseClient {
..open(request.method, '${request.url}', true)
..responseType = 'arraybuffer'
..withCredentials = withCredentials;
for (var header in request.headers.entries) {
xhr.setRequestHeader(header.key, header.value);

// Set the headers.
for (final (name, value) in request.headers.entries()) {
xhr.setRequestHeader(name, value);
}

// Sets cookies.
for (final cookie in request.headers.getSetCookie()) {
xhr.setRequestHeader('Set-Cookie', cookie);
}

var completer = Completer<StreamedResponse>();

unawaited(xhr.onLoad.first.then((_) {
if (xhr.responseHeaders['content-length'] case final contentLengthHeader
if (xhr.responseHeaders.get('content-length')
case final contentLengthHeader
when contentLengthHeader != null &&
!_digitRegex.hasMatch(contentLengthHeader)) {
completer.completeError(ClientException(
Expand Down Expand Up @@ -118,12 +127,11 @@ class BrowserClient extends BaseClient {
}

extension on XMLHttpRequest {
Map<String, String> get responseHeaders {
// from Closure's goog.net.Xhrio.getResponseHeaders.
var headers = <String, String>{};
var headersString = getAllResponseHeaders();
var headersList = headersString.split('\r\n');
for (var header in headersList) {
Headers get responseHeaders {
final headers = Headers();
final lines = const LineSplitter().convert(getAllResponseHeaders());

for (var header in lines) {
if (header.isEmpty) {
continue;
}
Expand All @@ -132,14 +140,13 @@ extension on XMLHttpRequest {
if (splitIdx == -1) {
continue;
}

var key = header.substring(0, splitIdx).toLowerCase();
var value = header.substring(splitIdx + 2);
if (headers.containsKey(key)) {
headers[key] = '${headers[key]}, $value';
} else {
headers[key] = value;
}

headers.append(key, value);
}

return headers;
}
}
Loading