Skip to content

Instantly share code, notes, and snippets.

@omeraydindev
Last active November 4, 2022 14:31
Show Gist options
  • Save omeraydindev/817cee930546af9b890e812e54130d19 to your computer and use it in GitHub Desktop.
Save omeraydindev/817cee930546af9b890e812e54130d19 to your computer and use it in GitHub Desktop.
A HttpClientAdapter subclass copied exactly from dio's io_adapter.dart (dio 4.0.4) to fix the "bug" where Dio lowercases all header names before sending a request. Search ***changed*** to see the changed parts of the code.
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
typedef OnHttpClientCreate = HttpClient? Function(HttpClient client);
/**
* Copied verbatim from dio's io_adapter.dart and
* fixed the case-insensitive header names bug. (marked with ***changed***)
*/
/// The default HttpClientAdapter for Dio.
class HeaderFixedHttpClientAdapter implements HttpClientAdapter {
/// [Dio] will create HttpClient when it is needed.
/// If [onHttpClientCreate] is provided, [Dio] will call
/// it when a HttpClient created.
OnHttpClientCreate? onHttpClientCreate;
HttpClient? _defaultHttpClient;
bool _closed = false;
// ***changed***: added a convenience constructor
HeaderFixedHttpClientAdapter({this.onHttpClientCreate});
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) async {
if (_closed) {
throw Exception(
"Can't establish connection after [HttpClientAdapter] closed!");
}
var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
var reqFuture = _httpClient.openUrl(options.method, options.uri);
void _throwConnectingTimeout() {
throw DioError(
requestOptions: options,
error: 'Connecting timed out [${options.connectTimeout}ms]',
type: DioErrorType.connectTimeout,
);
}
late HttpClientRequest request;
int timePassed = 0;
try {
if (options.connectTimeout > 0) {
var start = DateTime.now().millisecond;
request = await reqFuture
.timeout(Duration(milliseconds: options.connectTimeout));
timePassed = DateTime.now().millisecond - start;
} else {
request = await reqFuture;
}
//Set Headers
// ***changed***: fixed the case-sensitive header names bug
options.headers.forEach((k, v) {
// if header value is null, build BytesBuilder will crash,set preserveHeaderCase true to keep case
request.headers.set(k, v ?? "null", preserveHeaderCase: true);
});
} on SocketException catch (e) {
if (e.message.contains('timed out')) {
_throwConnectingTimeout();
}
rethrow;
} on TimeoutException {
_throwConnectingTimeout();
}
request.followRedirects = options.followRedirects;
request.maxRedirects = options.maxRedirects;
if (requestStream != null) {
// Transform the request data
await request.addStream(requestStream);
}
// [receiveTimeout] represents a timeout during data transfer! That is to say the
// client has connected to the server, and the server starts to send data to the client.
// So, we should use connectTimeout.
int responseTimeout = options.connectTimeout - timePassed;
var future = request.close();
if (responseTimeout > 0) {
future = future.timeout(Duration(milliseconds: responseTimeout));
}
late HttpClientResponse responseStream;
try {
responseStream = await future;
} on TimeoutException {
_throwConnectingTimeout();
}
var stream =
responseStream.transform<Uint8List>(StreamTransformer.fromHandlers(
handleData: (data, sink) {
sink.add(Uint8List.fromList(data));
},
));
var headers = <String, List<String>>{};
responseStream.headers.forEach((key, values) {
headers[key] = values;
});
return ResponseBody(
stream,
responseStream.statusCode,
headers: headers,
isRedirect:
responseStream.isRedirect || responseStream.redirects.isNotEmpty,
redirects: responseStream.redirects
.map((e) => RedirectRecord(e.statusCode, e.method, e.location))
.toList(),
statusMessage: responseStream.reasonPhrase,
);
}
HttpClient _configHttpClient(Future? cancelFuture, int connectionTimeout) {
var _connectionTimeout = connectionTimeout > 0
? Duration(milliseconds: connectionTimeout)
: null;
if (cancelFuture != null) {
var _httpClient = HttpClient();
_httpClient.userAgent = null;
if (onHttpClientCreate != null) {
//user can return a HttpClient instance
_httpClient = onHttpClientCreate!(_httpClient) ?? _httpClient;
}
_httpClient.idleTimeout = const Duration(seconds: 0);
cancelFuture.whenComplete(() {
Future.delayed(const Duration(seconds: 0)).then((e) {
try {
_httpClient.close(force: true);
} catch (e) {
//...
}
});
});
return _httpClient..connectionTimeout = _connectionTimeout;
}
if (_defaultHttpClient == null) {
_defaultHttpClient = HttpClient();
_defaultHttpClient!.idleTimeout = const Duration(seconds: 3);
if (onHttpClientCreate != null) {
//user can return a HttpClient instance
_defaultHttpClient =
onHttpClientCreate!(_defaultHttpClient!) ?? _defaultHttpClient;
}
_defaultHttpClient!.connectionTimeout = _connectionTimeout;
}
return _defaultHttpClient!;
}
@override
void close({bool force = false}) {
_closed = _closed;
_defaultHttpClient?.close(force: force);
}
}
@omeraydindev
Copy link
Author

You can use this adapter by assigning it to httpClientAdapter property of your Dio client.

return Dio()
      ...
      ..httpClientAdapter = HeaderFixedHttpClientAdapter();

@T-moz
Copy link

T-moz commented Nov 4, 2022

@MikeAndrson Great job !

Would you consider publish it to pubdev ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment