Last active
November 4, 2022 14:31
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
@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
You can use this adapter by assigning it to
httpClientAdapter
property of yourDio
client.