본문 바로가기
IT기술 연구소

java.net.SocketTimeoutException: Read timed out (Jsoup)

by 문창모 2024. 7. 9.

Jsoup 라이브러리를 이용하여 특정 대외 API를 호출하는 처리를 수행하고 있었다.

평소처럼 서버의 오류로그를 모니터링 하는데 최근들어 심심찮게

java.net.SocketTimeoutException: Read timed out 메시지가 노출이 된다.

 

평소 아무 이상 없었던 처리인데 이런 오류가 심심치않게 발생하고 

현장쪽에서도 불편 접수가 되는듯하여 시간을 들여 내부로직을 확인해 보았다.

 

API 호출시에는 Jsoup 라이브러리(jsoup-1.13.1)를 사용하며 

timeout 값은 10000 즉 10초를 설정했었는데, 로그파일을 확인해 보면 딱 5초 시점에서 

Read timed out 오류가 발생하는 것이었다.

 

해당 라이브러리의 내부 소스를 확인하면 아래와 같이 구현이 되어있다.

    private static HttpURLConnection createConnection(Connection.Request req)
      throws IOException
    {
      HttpURLConnection conn = (HttpURLConnection)(req.proxy() == null ? 
        req.url().openConnection() : 
        req.url().openConnection(req.proxy()));

      conn.setRequestMethod(req.method().name());
      conn.setInstanceFollowRedirects(false);
      conn.setConnectTimeout(req.timeout());
      conn.setReadTimeout(req.timeout() / 2);

      if ((req.sslSocketFactory() != null) && ((conn instanceof HttpsURLConnection)))
        ((HttpsURLConnection)conn).setSSLSocketFactory(req.sslSocketFactory());
      if (req.method().hasBody())
        conn.setDoOutput(true);
      if (req.cookies().size() > 0)
        conn.addRequestProperty("Cookie", getRequestCookieString(req));
      for (Iterator localIterator1 = req.multiHeaders().entrySet().iterator(); localIterator1.hasNext(); ) { header = (Map.Entry)localIterator1.next();
        for (String value : (List)header.getValue())
          conn.addRequestProperty((String)header.getKey(), value);
      }
      Map.Entry header;
      return conn;
    }

 

결국 내부적으로는 HttpURLConnection 클래스를 사용하는데 

그중 ConnectTimeout은 Jsoup라이브러리의 Jsoup.Connect.method로 받은 값을 그대로 사용하고 

ReadTimeout 값은 입력받은 method의 절반값을 사용한다는 것. -> req.timeout() / 2 이 부분..

 

어차피 ConnectTimeout 보다 적은 값을 ReadTimeout 값으로 설정한다는 의미로 보면 이해는 된다. 결국 Timeout 값이라는 것이 절대적이고 명확한 값을 받아서 처리 한다기 보다는 운영시의 상황에 맞춰서 적절한 timeout 값을 찾아서 적용하라는 의미이긴 할듯..

 

어쨌든 이 timeout 값을 평소보다 더 늘려서 운영에 반영하고 

Read timed out 오류 발생시에는 해당 처리를 retry 하도록 처리를 추가 하였다. (feat. ChatGPT)

참고로 Read timed out 메시지는 Jsoup 내부 라이브러리에서 캐치할 경우에는 아래와 같이 오류 메시지가 출력이 되기도 한다. 

org.jsoup.UncheckedIOException: java.net.SocketTimeoutException: Read timeout

기본적으로는 java.net.SocketTimeoutException: Read timed out 오류 메시지가 발생한다.

 

retry 의사코드는 아래와 같다.

    private void execute(Map<String, Object> param) throws Exception {

        final int MAX_TRY_CNT = 3; // 최대 재시도
        int retries = MAX_TRY_CNT;
        
        while (retries > 0) {

            Gson gson = new Gson();
            
            String url         = "";
            int    timeout     = 10000;
            
            try {
                
                // some api call..
                
                break; // 처리 성공시 반복문 처리 중지.
                
            } catch (Exception e) {
                if (특정조건에서 재시도 하기 위한 조건문) {
                    retries--;
                    if (retries == 0) {
                        throw e;
                    }
                    Thread.sleep(timeout / 2); // 대기 후 재시도
                } else {
                    throw e;
                }
            }
            
        } // end while retries
    }

 

끝.