Android HttpUrlConnection EOFException

je voudrais savoir s'il y a des problèmes connus sur Android avec HttpUrlConnection et les requêtes POST. Nous faisons l'expérience de intermittent EOFExceptions lors de la réalisation de requêtes POST d'un client Android. Revenir sur la même demande finira par marcher. Voici une trace de la pile d'échantillons:

java.io.EOFException
at libcore.io.Streams.readAsciiLine(Streams.java:203)
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.java:579)
at libcore.net.http.HttpEngine.readResponse(HttpEngine.java:827)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:283)
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134)

il y a beaucoup de rapports de bogues et de messages similaires à empiler overflow mais je ne peux pas comprendre s'il y a vraiment un problème et si oui, quelles versions D'Android sont touchés et ce que le correctif/travail proposé est autour est.

voici quelques-uns des rapports similaires auxquels je fais référence:

Voici un potentiel Android cadre fix

je sais qu'il y avait un problème avec des connexions empoisonnées dans le pool de connexion à pré-Froyo mais ces problèmes se produisent sur de nouveaux appareils ICS+ exclusivement. S'il y avait un problème sur les appareils postérieurs Je m'attendrais à une sorte de la documentation officielle Android de la question.

33
demandé sur Community 2013-10-09 01:12:38

7 réponses

notre conclusion est qu'il y a un problème sur la plateforme Android. Notre solution était d'attraper L'EOFException et de réessayer la requête N nombre de fois. Ci-dessous le pseudo code:

private static final int MAX_RETRIES = 3;

private ResponseType fetchResult(RequestType request) {
    return fetchResult(request, 0);
}

private ResponseType fetchResult(RequestType request, int reentryCount) {
    try {
        // attempt to execute request
    } catch (EOFException e) {
        if (reentryCount < MAX_RETRIES) {
            fetchResult(request, reentryCount + 1);
        }
    }
    // continue processing response
}
12
répondu Matt Accola 2013-11-30 16:40:06
La bibliothèque

HttpURLConnection gère en interne un ensemble de connexions. Ainsi, chaque fois qu'une requête est envoyée, elle vérifie d'abord s'il y a une connexion existante déjà présente dans le pool, sur la base de laquelle elle décide d'en créer une nouvelle.

ces connexions ne sont rien d'autre que des sockets, et cette bibliothèque par défaut ne ferme pas ces sockets. Il peut parfois arriver qu'une connexion (socket) qui n'est pas actuellement utilisé et est présent dans la piscine n'est plus utilisable car le serveur peut décider de mettre fin à la connexion après un certain temps. Maintenant, puisque la connexion Même si elle est fermée par le serveur, la bibliothèque ne le sait pas et suppose que la connexion/socket est toujours connectée. Ainsi il envoie la nouvelle requête en utilisant cette connexion périmée et nous obtenons EOFException.

la meilleure façon de gérer cela est de vérifier les en-têtes de réponse après chaque demande que vous envoyez. Le serveur envoie toujours une "connexion: fermer" avant terminer une connexion (HTTP 1.1). Ainsi, vous pouvez utiliser getHeaderField() et vérifier le champ "Connection". Une autre chose à noter est que le serveur n'envoie ce champ de connexion que lorsqu'il est sur le point de mettre fin à la connexion. Ainsi, vous devez coder autour de cela avec la possibilité d'obtenir un "null" dans le cas normal (lorsque le serveur ne ferme pas la connexion)

7
répondu AllThatICode 2014-04-03 07:04:11

cette solution a tendance à être fiable et performante:

static final int MAX_CONNECTIONS = 5;

T send(..., int failures) throws IOException {
    HttpURLConnection connection = null;

    try {
        // initialize connection...

        if (failures > 0 && failures <= MAX_CONNECTIONS) {
            connection.setRequestProperty("Connection", "close");
        }

        // return response (T) from connection...
    } catch (EOFException e) {
        if (failures <= MAX_CONNECTIONS) {
            disconnect(connection);
            connection = null;

            return send(..., failures + 1);
        }

        throw e;
    } finally {
        disconnect(connection);
    }
}

void disconnect(HttpURLConnection connection) {
    if (connection != null) {
        connection.disconnect();
    }
}

cette implémentation repose sur le fait que le nombre par défaut de connexions qui peuvent être ouvertes avec un serveur est 5 (Froyo - KitKat). Cela signifie que jusqu'à 5 connexions obsolètes peuvent exister, chacun devra être fermé.

après chaque tentative ratée, la propriété Connection:close request fera que le moteur HTTP sous-jacent fermera la socket quand connection.disconnect() s'appelle. En renouvelant jusqu'à 6 fois (max connexions + 1), nous nous assurons que la dernière tentative sera toujours un nouveau socket.

la requête peut subir une latence supplémentaire si aucune connexion n'est vivante, mais c'est certainement mieux qu'un EOFException . Dans ce cas, la dernière tentative d'envoi ne fermera pas immédiatement la connexion fraîchement ouverte. C'est la seule optimisation des pratiques qui peuvent être faites.

au lieu de compter sur le magic valeur par défaut de 5 , vous pouvez être en mesure de configurer la propriété du système vous-même. Gardez à l'esprit que cette propriété est accessible par un bloc d'initialiseur statique dans le ConnectionPool de KitKat.java , et il fonctionne comme cela dans les anciennes versions Android aussi. En conséquence, la propriété peut être utilisée avant d'avoir une chance de le définir.

static final int MAX_CONNECTIONS = 5;

static {
    System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS));
}
4
répondu James Wald 2014-05-22 19:26:11

Oui. Il y a un problème dans la plate-forme Android, en particulier, dans Android libcore avec la version 4.1-4.3.

le problème est introduit dans cette commit: https://android.googlesource.com/platform/libcore/+ / b2b02ac6cd42a69463fd172531aa1f9b9bb887a8

Android 4.4 commuté lib http en "okhttp" qui n'a pas ce problème.

problème expliqué comme suit:

sur android 4.1-4.3, lorsque vous utilisez URLConnection/HttpURLConnection pour poster avec" ChunkedStreamingMode "ou" FixedLengthStreamingMode " set , URLConnection/HttpURLConnection ne sera pas faire un essai silencieux si la connexion réutilisée est périmée. Vous devez réessayer de poster au plus " http.maxConnections+1" fois dans votre code, comme le suggèrent les réponses précédentes.

4
répondu solrex 2016-04-29 03:18:35

je soupçonne que c'est peut-être le serveur qui est en faute ici, et la connexion Httpurl n'est pas aussi indulgente que les autres implémentations. C'était la cause de mon Exc Eptation. Je soupçonne dans mon cas ce ne serait pas intermittent (corrigé avant de tester la solution de contournement du pays), de sorte que les réponses ci-dessus se rapportent à d'autres questions et être une solution correcte dans ces cas.

mon serveur utilisait python SimpleHTTPServer et je supposais à tort que tout ce que j'avais à faire pour indiquer le succès fut le suivant:

self.send_response(200)

qui envoie la ligne d'en-tête de réponse initiale, un serveur et un en-tête de date, mais laisse le flux dans l'état où vous pouvez envoyer des en-têtes supplémentaires aussi. HTTP nécessite une nouvelle ligne supplémentaire après les en-têtes pour indiquer qu'ils sont finis. Il apparaît si cette nouvelle ligne n'est pas présente lorsque vous tentez d'obtenir le corps de résultat InputStream ou code de réponse etc avec HttpURLConnection alors il lance L'EOFException (qui est en fait raisonnable, à y penser). Certains clients HTTP ont accepté la réponse courte et ont rapporté le code de résultat de succès qui m'amène peut-être à pointer injustement le doigt sur HttpURLConnection.

j'ai changé mon serveur pour faire ceci à la place:

self.send_response(200)
self.send_header("Content-Length", "0")
self.end_headers()

plus D'exception avec ce code.

1
répondu tangobravo 2015-01-08 17:20:15

ça a marché pour moi.

public ResponseObject sendPOST(String urlPrefix, JSONObject payload) throws JSONException {
    String line;
    StringBuffer jsonString = new StringBuffer();
    ResponseObject response = new ResponseObject();
    try {

        URL url = new URL(POST_URL);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setReadTimeout(10000);
        connection.setConnectTimeout(15000);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Accept", "application/json");
        connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");

        OutputStream os = connection.getOutputStream();
        os.write(payload.toString().getBytes("UTF-8"));
        os.close();
        BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        while ((line = br.readLine()) != null) {
            jsonString.append(line);
        }
        response.setResponseMessage(connection.getResponseMessage());
        response.setResponseReturnCode(connection.getResponseCode());
        br.close();
        connection.disconnect();
    } catch (Exception e) {
        Log.w("Exception ",e);
        return response;
    }
    String json = jsonString.toString();
    response.setResponseJsonString(json);
    return response;
}
0
répondu Rajdeo Das 2015-12-15 10:19:14
"151900920 de la connexion".addRequestProperty ("Accept-Encoding"," gzip");

est la réponse

-5
répondu Nizzy 2014-06-30 10:36:17