I try to send HTTP GET request to a server with a "client certificate" enabled in my Flutter app. The server allows client certificates because some of the clients (Desktop apps) use certificates for authorization with this server.
Expected results: HTTP response
Actual results: The GET request receives in the server but the Dart's HTTP GET method throws a ClientException with an empty message before a response is received.
Server configuration and details: OS: Windows Host: IIS HTTP version: 1.1 Minimum TLS version: 1.2 Runtime: ASP .NET Core 5
If I disable "client certificate" authorization on the server everything works fine. But this is not a good solution for me.
BTW: I manage to make a request and get a response via other in other platforms to the same endpoint in this server without any problem! I try C# console app, Xamrin Forms app and Postman and this issue exists only in Flutter!
Update: I still don't have a solution for this one. At your request I attached my code snipped, it's a simple HTTP service. Nothing fancy. And the error I get is ClientException without a message.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:retry/retry.dart';
import '../models/app_exception.dart';
import 'logs_service.dart';
class HttpService {
final _timeoutDuration = Duration(minutes: 1);
final _retryOptions = RetryOptions(maxAttempts: 3);
final _headers = {
HttpHeaders.acceptHeader: 'application/json; charset=UTF-8',
HttpHeaders.pragmaHeader: 'no-cache',
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
HttpHeaders.acceptCharsetHeader: 'UTF-8',
};
Future<dynamic> get({
required String url,
String? authorizationToken,
Map<String, String>? args,
String? basicAuthUsername,
String? basicAuthPassword,
}) {
return _retryOptions.retry(
() => _get(
url,
authorizationToken,
args,
basicAuthUsername,
basicAuthPassword,
).timeout(_timeoutDuration),
retryIf: (e) => e is NetworkException,
);
}
Future<dynamic> _get(
String baseUrl,
String? authorizationToken,
Map<String, String>? args,
String? basicAuthUsername,
String? basicAuthPassword,
) async {
try {
var responseJson;
final headers = Map<String, String>.from(_headers);
if (authorizationToken != null) {
headers[HttpHeaders.authorizationHeader] = authorizationToken;
}
if (basicAuthUsername != null && basicAuthPassword != null) {
headers[HttpHeaders.authorizationHeader] =
_getBasicAuthStr(basicAuthUsername, basicAuthPassword);
}
final argsStr = args.convertToQueryString;
final url = Uri.parse('$baseUrl$argsStr');
try {
final response = await http.get(
url,
headers: headers,
);
logger.info(
text:
'GET | url: $url | response status code: ${response.statusCode}');
responseJson = _returnResponse(response.body, response.statusCode);
} on SocketException {
throw NetworkException('No Internet connection');
}
return responseJson;
} catch (e) {
logger.error(error: e, text: 'GET | error | url: $baseUrl');
throw e;
}
}
dynamic _returnResponse(String responseBody, int statusCode) {
if (statusCode >= 200 && statusCode < 300) {
final responseJson = responseBody.isNotEmpty
? json.decode(responseBody)
: Map<String, dynamic>();
return responseJson;
}
switch (statusCode) {
case 400:
throw BadRequestException(responseBody);
case 401:
throw UnauthorizedException(responseBody);
case 403:
switch (responseBody) {
case '\"DeviceUnregistered\"':
throw DeviceUnregisteredException(responseBody);
case '\"NoAppLicense\"':
throw NoAppLicenseException(responseBody);
case '\"InvitationNotFound\"':
throw InvitationNotFoundException(responseBody);
case '\"InvitationExpired\"':
throw InvitationExpiredException(responseBody);
case '\"DeviceAlreadyRegistered\"':
throw DeviceAlreadyRegisteredException(responseBody);
}
throw ForbiddenException(responseBody);
case 404:
throw NotFoundException(responseBody);
case 500:
case 501:
case 502:
case 503:
throw ServerErrorException(
'status code: $statusCode | body: $responseBody');
default:
throw GeneralHttpErrorException(
'Error occurred while communication with server | Status code: $statusCode | body: $responseBody');
}
}
String _getBasicAuthStr(String userName, String password) =>
'Basic ' + base64Encode(utf8.encode('$userName:$password'));
}
extension StringMapeExtension on Map<String, String?>? {
String get convertToQueryString {
String query = '';
if (this == null) {
return query;
}
for (MapEntry e in this!.entries) {
query += '&${e.key}=${e.value}';
}
return ('?${query.substring(1)}');
}
}