我有一个Flutter应用程序,它正试图使用Dio包连接到服务器API。服务器使用自签名证书,这意味着使用Dio解决方案来验证证书,以便连接到服务器(请参阅https://pub.dev/packages/dio#https-证书验证(。
以下是我到目前为止所拥有的:
String url = 'https://...'; // Server API URL
Map<String, dynamic> data = ... // Request body
String PEM = "XXXXX"; // Certificate content
Dio dio = Dio();
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback=(X509Certificate cert, String host, int port){
if (cert.pem == PEM) {
// Verify the certificate
return true;
}
return false;
};
};
Response response = await dio.post(url, data: data);
然而,这会引发HttpException:
HttpException: Failed to parse HTTP, uri = https://... // Server API url
异常中没有显示其他信息。response.data
的值为null
。
需要注意的是,服务器实际上是用数据进行响应的,因此这似乎是Flutter/客户端的问题。服务器响应是字符串字段和值的标准对象:
{
"status": "ok",
"token": "...",
"field1": "...",
"field2": "...",
"field3": "...",
...
}
如何解决此问题?
该异常仅由http_parser.dart
文件中的两个函数引发:_expect
和_expectHexDigit
。这两个函数依次在几个不同的地方被调用,所有这些函数都在同一文件的_doParse
函数中。对从客户端-服务器连接流接收的字节调用此函数。
http_parser.dart
是Flutter的http API的一部分,因此问题不在于Dio或其他第三方软件包,也不在于您的应用程序代码,而在于您的服务器。
您可以通过使用Android Studio执行"在路径中查找"搜索(在Windows上为Ctrl+Shift+F(,并在下拉列表中选择"范围"one_answers"所有位置"的同时搜索"无法解析HTTP"来查找文件/函数。您还可以在抛出这些异常的行上放置断点,这将有助于将问题缩小到特定的失败。
您会注意到,大多数_expect
函数调用都在检查响应各个部分之间的"LF"或"CR"字符(换行符(,但确定问题所在的唯一方法是使用断点进行调试并深入挖掘。
编辑:_doParse
函数上面有以下注释:
// From RFC 2616.
// generic-message = start-line
// *(message-header CRLF)
// CRLF
// [ message-body ]
// start-line = Request-Line | Status-Line
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
// Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
// message-header = field-name ":" [ field-value ]
它只是期望您的服务器响应在响应分段和分段之间的换行方面符合RFC 2616。也许有一些工具可以用来检查你的服务器/端点是否符合RFC 2616?RFC 2616是HTTP/1.1协议,因此,例如,如果您的服务器使用HTTP/2.0,这将解释异常。
你在安卓系统上测试吗?如果是这样,默认情况下它只会连接到HTTPS端点,但您可以通过将android:usesCleartextTraffic="true"
添加到AndroidManifest.xml中进行测试来解决此问题。
颤振:
可能您的问题是由于证书在某些平台中无效而引起的,您可以使用以下方法:
if (Platform.isAndroid) {
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback = (X509Certificate cert, String host, int port) {
if (cert.pem == PEM) {
// Verify the certificate
return true;
}
return false;
};
};
}
安卓系统:
由于默认情况下不允许使用非SSL的Android 9.0 URL(请参阅有关ClearTextTraffic的信息(。
如果您的应用程序目标为API 28级,则您必须在应用程序内部仅使用https://
URL,或者您可以启用允许从应用程序中使用http://
,并在AndroidManifest.xml
:内部添加
android:usesCleartextTraffic=真正的
示例:
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application
...
android:usesCleartextTraffic="true"
...>
...
</application>
</manifest>