Posts try-with-resource 사용시 Socket closed 예외 발생
Post
Cancel

try-with-resource 사용시 Socket closed 예외 발생

상황


자바로 만든 간단한 웹 서버에서 404, 500과 같은 에러 페이지 처리를 위해 catch 절 내부에 에러 페이지를 응답하는 로직을 작성했다. 하지만 에러 발생시 에러 페이지가 응답되지 않고 java.net.SocketException: Socket closed가 발생했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HttpServer {
    ...

    public void start() throws IOException {
        ExecutorService pool = Executors.newFixedThreadPool(NUM_THREADS);
        try (ServerSocket server = new ServerSocket(port)) {
            Socket connection;
            while ((connection = server.accept()) != null) {
                Runnable r = new HttpPageProcessor(connection);
                pool.submit(r);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HttpPageProcessor implements Runnable {

    @Override
    public void run() {
        try (InputStream inputStream = connection.getInputStream(); OutputStream outputStream = connection.getOutputStream()){
            httpReq = HttpUtils.getHttpRequest(inputStream);
            httpResp = HttpUtils.getHttpResponse(outputStream);

            ... 요청에 따라 알맞은 html 페이지 응답

        } catch (Exception e) {
            ... 에러 페이지 응답
        }
    }
    ...
}

원인


  • 원인은 try-with-resource 사용으로 인해 Socket이 자동으로 닫히기 떄문이다.
    • 즉, catch절 내에서 에러 페이지 응답 로직을 수행하는데 필요한 소켓 커넥션이 이미 닫힌 상태이다.
  • 근데 여기서 한가지 의문이 들었다.
    • try() 내부에서 할당한 것은 InputStreamOutputStream인데 왜 소켓도 닫히는거지 ?
  • 해답은 각각의 구현체인 java.net.SocketInputStream, java.net.SocketOutputStream에 있는 close() 메서드를 보면 알 수 있다.
    • close() 메서드 내부에서 소켓이 닫혔는지 여부를 확인하고 socket.close()를 호출하는 로직이 있다.
    • try 내부에서 OutputStream이 더 늦게 할당되므로 SocketOutputStream.close() 메서드가 먼저 호출된다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        private boolean closing = false;
        public void close() throws IOException {
            // Prevent recursion. See BugId 4484411
            if (closing)
                return;
            closing = true;
            if (socket != null) {
                if (!socket.isClosed())
                    socket.close();
            } else
                impl.close();
            closing = false;
        }
      

해결 과정


try-catch-finally 사용

  • 이렇게 해결하는게 맞는지는 모르겠으나, 일단 생각나는게 이것밖에 없어서 이렇게 해결해보았다.
  • 추후에 더 나은 방법을 알게되면 업데이트 해야겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class HttpPageProcessor implements Runnable {
    ...

    private void init() throws IOException {
        InputStream inputStream = connection.getInputStream();
        OutputStream outputStream = connection.getOutputStream());

        ... 객체 초기화
    }

    @Override
    public void run() {
        try {
            init();
             ... 요청에 따라 알맞은 html 페이지 응답
        } catch (Exception e) {
            ... 에러 페이지 응답
        } finally {
            try {
                connection.close();
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
         }
    }
    ...
}

참고 자료


This post is licensed under CC BY 4.0 by the author.

객체 지향 언어의 특징 - 기능과 책임 분리

자바 IO, NIO