2013-10-08 2 views
33

HttpUrlConnection 및 POST 요청으로 안드로이드에 알려진 문제가 있는지 알고 싶습니다. Android 클라이언트에서 POST 요청을 할 때 간헐적 인 EOF 예외가 발생합니다. 같은 요청을 다시 시도하면 결국 작동합니다.안드로이드 HttpUrlConnection EOFException

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) 

가 스택 오버 플로우하는 많은 유사한 버그 리포트 및 게시물하지만 내가 만약 그렇다면, 안드로이드 어떤 버전이 영향을 정말 문제가있는 경우 이해 할 수없는 어떤 제안 수정 : 다음은 샘플 스택 추적입니다/해결 방법은 있습니다.

는 잠재적 인 안드로이드 프레임 워크는 내가 사전에 프로 요하지만, 이러한 문제의 연결 풀에 중독 연결에 문제가 있었다 알고

을 수정 프로그램입니다 새로운 ICS + 기기에서만 독점적으로 발생합니다. 최신 장치에 문제가있는 경우 문제의 공식 Android 설명서가 필요합니다.

+0

이 해결 방법은 무엇입니까? 거기에 문제가 있습니까? http://stackoverflow.com/a/17638671/609782 – Darpan

+0

@Darpan 스택 트레이스를 기반으로 관련이없는 것 같지만 그냥 시도해 볼 수 있습니다. – Kevin

답변

12

우리의 결론은 Android 플랫폼에 문제가 있다는 것입니다. 우리의 해결책은 EOFException을 잡아서 N 번 요청을 다시 시도하는 것입니다. 아래는 의사 코드입니다.

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 
} 
+2

안녕 Matt. 나는 이것을 시도하고 있지만 작동하지 않습니다. 의사 코드가 다음과 같은지 확인하고 싶습니다. 1. url.openConnection()을 통한 연결 열기. 2. getResponseCode()를 호출 할 때 EOF가 발생하면 연결을 끊고 HttpURLConnection을 null로 설정 한 다음 다시 연결하십시오. 3. 최대 재시도 횟수까지 시도하십시오. 당신이 한 일이 이것입니까? 고맙습니다. –

+1

예, 요청을 시도 할 때마다 HttpURLConnection을 다시 만들 것을 권합니다. 동일한 인스턴스를 다시 사용하지 마십시오. 네가 말하는대로 작동하지 않는다면 무슨 뜻이야? –

+1

답변 해 주셔서 감사합니다. 나는 당신이 말한대로 일을 시도한 것을 의미하며, 여전히 EOFException에 문제가 있습니다. –

6

HttpURLConnection 라이브러리는 내부적으로 Connections 풀을 유지 관리합니다. 따라서 요청이 전송 될 때마다 먼저 풀에 기존 연결이 있는지 여부를 확인합니다.이 연결을 기반으로 새 연결을 만듭니다.

이러한 연결은 소켓 일 뿐이며 기본적으로이 라이브러리는 이러한 소켓을 닫지 않습니다. 서버가 잠시 후 연결을 종료하도록 선택할 수 있으므로 현재 사용 중이 아니며 풀에있는 연결 (소켓)이 더 이상 사용되지 않을 수 있습니다. 이제는 서버에 의해 연결이 닫혀 있기 때문에 라이브러리는 그것에 대해 알지 못하고 연결/소켓이 여전히 연결되어 있다고 가정합니다. 따라서 이것은이 부실 연결을 사용하여 새 요청을 전송하므로 EOFException이 발생합니다.

이 문제를 처리하는 가장 좋은 방법은 보낼 요청이있을 때마다 응답 헤더를 확인하는 것입니다. 서버는 항상 연결 (HTTP 1.1)을 종료하기 전에 "연결 : 닫기"를 보냅니다. 따라서 getHeaderField()를 사용하여 "Connection"필드를 확인할 수 있습니다. 또 다른주의 사항은 서버가 연결을 종료하려고 할 때만이 연결 필드를 보냅니다.그래서, 당신은 (서버가 연결을 종료되지 않은 경우) 일반적인 경우에 "널 (null)"을 얻을 수있는 가능성이 문제를 코딩 할 필요가

+0

누군가 설명 할 수없는 명령을 내리는 것보다는 무슨 일이 일어나는지 설명해 주셔서 감사합니다! 매우 감사. [+1] – naki

4

이 해결 방법은 안정적이고 성능이 좋은 경향 :

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

이 구현은 서버로 열 수있는 기본 연결 수가 5 (Froyo - KitKat)이라는 사실에 의존합니다. 즉, 최대 5 개의 연결이 끊어 질 수 있으며 각 연결은 닫혀 야합니다.

각 시도가 실패한 후 Connection:close 요청 속성은 connection.disconnect()이 호출 될 때 기본 HTTP 엔진이 소켓을 닫게합니다. 최대 6 번 (최대 연결 + 1)까지 재 시도하여 마지막 시도가 항상 새 소켓이 제공되도록합니다.

연결이없는 경우 요청에 추가 대기 시간이 발생할 수 있지만 이는 확실히 EOFException보다 좋습니다. 이 경우 최종 송신 시도는 새로 연 열린 연결을 즉시 닫지 않습니다. 이것이 실제 최적화가 될 수있는 유일한 방법입니다.

마법 기본값 인 5 대신에 직접 시스템 속성을 구성 할 수 있습니다. 이 속성은 KitKat의 ConnectionPool.java에있는 정적 이니셜 라이저 블록을 통해 액세스되며 이전 Android 버전에서도 이와 비슷하게 작동합니다. 따라서 속성을 설정하기 전에 속성을 사용할 수 있습니다.

static final int MAX_CONNECTIONS = 5; 

static { 
    System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS)); 
} 
+1

당신을 감사하십시오 남자! 이것은 정답으로 확인되어야합니다! – Andranik

-5

connection.addRequestProperty ("Accept-Encoding", "gzip");

은 내가 여기에 결함이 서버 생각되는 답

1

이며, HttpURLConnection의 다른 구현만큼 관대하지 않다. 그게 내 EOFException의 원인이었습니다. 내 경우에는 이것이 간헐적이지 않을 것이라고 생각합니다 (N 재시도 해결 방법을 테스트하기 전에 수정 됨). 따라서 위의 답변은 다른 문제와 관련이 있으며 이러한 경우에는 올바른 해결책이 될 수 있습니다.

내 서버가 파이썬 SimpleHTTPServer을 사용하고 있었고, 난 잘못 내가 성공을 표시 할 필요가있는 모든 가정되었다이었다 다음

초기 응답 헤더 라인, 서버 및 날짜 헤더를 전송
self.send_response(200) 

하지만, 헤더를 추가로 보낼 수있는 상태의 스트림을 유지합니다. HTTP는 헤더가 끝난 후에 새로운 행을 추가해야합니다. 이 새로운 라인이 존재하지 않는 경우 HttpURLConnection을 사용하여 결과 본문 InputStream 또는 응답 코드 등을 얻으려고 할 때 EOFException을 던집니다 (실제적으로 생각하면 합리적입니다). 일부 HTTP 클라이언트는 짧은 응답을 받아들이고 HttpURLConnection에서 손가락을 가리키게하는 성공 결과 코드를보고했습니다. 그 코드

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

더 이상 EOFException는 :

내가 대신이 일을 내 서버를 변경했습니다.

0

이것은 나를 위해 일했습니다.

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

예. Android 플랫폼, 특히 Android libcore 버전 4.1-4.3에 문제가 있습니다.

문제는이 커밋에 도입된다 https://android.googlesource.com/platform/libcore/+/b2b02ac6cd42a69463fd172531aa1f9b9bb887a8

안드로이드 4.4은 HTTP lib 디렉토리를 전환이 문제가되지 않는다 "okhttp"로 설정합니다. 안드로이드 4.1-4.3에

, 당신이 URLConnection의/HttpURLConnection의 "ChunkedStreamingMode"또는을 설정 "FixedLengthStreamingMode"로 게시물에 사용하고, URLConnection의/HttpURLConnection의 할 수 없습니다 :

문제는 다음과 같이 설명 재사용 된 연결이 고갈 된 경우 자동 재 시도하십시오. 앞의 대답과 마찬가지로 POST 을 코드에서 "http.maxConnections + 1"번 다시 시도해야합니다.

관련 문제