user2181948
user2181948

Reputation: 1888

Flutter 'HttpException: Failed to parse http' when connecting to server

I have a Flutter application which is attempting to connect to a server API using the Dio package. The server uses a self-signed certificate, which means using a Dio workaround to verify the certificate in order to connect to the server (see https://pub.dev/packages/dio#https-certificate-verification).

Below is what I have so far:

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);

However this throws an HttpException:

HttpException: Failed to parse HTTP, uri = https://... // Server API url

No other information is shown in the Exception. The value of response.data is null.

It is important to note that the server is in fact responding with data - so this appears to be a Flutter / client side issue. The server response is a standard object of String fields and values:

{
    "status": "ok",
    "token": "...",
    "field1": "...",
    "field2": "...",
    "field3": "...",
    ...
}

How do I fix this issue?

Upvotes: 1

Views: 4436

Answers (3)

Ovidiu
Ovidiu

Reputation: 8714

That exception is only thrown by 2 functions within the http_parser.dart file: _expect and _expectHexDigit. Both of those functions are in turn called in several different places, all of them within the _doParse function from the same file. This function is called on the bytes received from the client-server connection stream.

http_parser.dart is part of Flutter's http API, so the issue is not with Dio or other 3rd party packages, or with your app's code, but rather with your server.

You can find the file/functions by performing a 'Find in Path' search using Android Studio (Ctrl + Shift + F on Windows) and searching for 'Failed to parse HTTP' while having 'Scope' selected as well as 'All Places' from the dropdown. You can also place breakpoints on the lines where those exceptions are thrown, which should help you narrow down the issue to the specific failure.

You will notice that most of those _expect function invocations are checking for 'LF' or 'CR' characters (line breaks) between various sections of the response, but the only way to tell for sure what the issue is is by debugging with breakpoints and digging deeper.

Edit: The _doParse function has the following comments above it:

// 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 ]

It is simply expecting your server response to comply with RFC 2616 in terms of response segmentation and line breaks between segments. Maybe there is some tool out there that you could use to check if your server/endpoint is compliant with RFC 2616? RFC 2616 is the HTTP/1.1 protocol, so if for example your server is using HTTP/2.0, that would explain the exception.

Upvotes: 3

Jorgesys
Jorgesys

Reputation: 126445

Flutter:

Probably your problem is caused because the certificate is invalid in some platform, you can use this method:

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;
        };
    };
}

Android:

Since Android 9.0 urls that are not SSL are not allowed by default (see about ClearTextTraffic).

If your app is targeting to API Level 28, you must use inside your app only https:// urls or you can enable to allow the use of http:// from your app adding inside your AndroidManifest.xml :

android:usesCleartextTraffic=true

example:

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        ...
        android:usesCleartextTraffic="true"
        ...>
        ...
    </application>
</manifest>

Upvotes: 0

Mark Thomas
Mark Thomas

Reputation: 31

Are you testing on Android? If so it only will connect to HTTPS endpoints by default, but you can workaround this by adding android:usesCleartextTraffic="true" to you AndroidManifest.xml for testing.

Upvotes: 0

Related Questions