1. 개요
예전에 서비스 내에서 HTTP을 사용해 HR 시스템에서 정보를 가져오는 로직에 문제가 발생한 적이 있었다.
원인은 사용하는 HttpClient객체가 static으로 선언되어 있어 멀티 쓰레드 환경에서 통신이 꼬여버린 것이다.
해결방안으로 HttpClientBuilder를 사용해 PoolingHttpClientconnectionManager, requestConfig 객체를 주입받은 CloseableHttpClient 객체를 싱글톤으로 등록 후 호출할때마다 재사용하는 방식을 사용했다.
그런데 다른 프로젝트의 SM업무를 맡던 도중 HTTP 통신할때마다 DefaultHttpClient 객체를 생성하고 있었다. 또한 인스턴스를 통신할때마다 생성하는 부분은 있지만 close시키는 부분이 없어서 DefaultHttpClient가 어떤녀석인지, 그리고 CloseableHttpClient와 무슨 차이점이 있는지 궁금해졌다.
2. CloseableHttpClient와 DefaultHttpClient
이 두 클래스 모두 HttpClient 인터페이스의 구현클래스이다. 하지만 이녀석들의 차이에 대해 상세하게 정리된 내용을 찾지못해 실제 테스트를 통해 알아보기로 했다.
2.1. DefaultHttpClient, CloseableHttpClient 테스트코드 1.
각각의 클래스에 DefaultHttpClient, CloseableHttpClient 인스턴스 생성 후 HTTP GET 통신을 하는 코드를 작성하였다.
2.2. CloseableHttpClient
DefaultHttpClient를 사용하여 HTTP GET 통신을 하는 코드이다.
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
28
29
30
31
32
33
34
|
public class UseHttpClient {
private static final String URL = "http://www.naver.com";
public static void main(String[] args) {
HttpClient httpClient = null;
try {
httpClient = new DefaultHttpClient(); // httpClient 4.3버전 이후 deprecated 처리.
HttpGet httpGet = new HttpGet(URL);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(":: DefaultHttpClient Response ::");
System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String inputLine;
StringBuffer responseBuf = new StringBuffer();
while((inputLine = reader.readLine()) != null) {
responseBuf.append(inputLine);
}
reader.close();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
cs |
17번째 라인부터는 응답 데이터를 읽어오는 부분인데 네이버 홈페이지 코드가 방대하게 나와서 굳이 출력하진 않았다.
중요한건 DefaultHttpClient 인스턴스를 사용해 HTTP 통신을 한번 했다는 점이다.
2.2. CloseableHttpClient
CloseableHttpClient를 사용하여 HTTP GET 통신을 하는 코드이다.
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
28
29
30
31
32
33
34
35
36
37
38
39
|
public class UseCloseableHttpClient {
private static final String URL = "http://www.naver.com";
public static void main(String[] args) {
CloseableHttpClient httpClient = null;
try {
httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(URL);
CloseableHttpResponse response = httpClient.execute(httpGet);
System.out.println(":: CloseableHttpResponse ::");
System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String inputLine;
StringBuffer responseBuf = new StringBuffer();
while((inputLine = reader.readLine()) != null) {
responseBuf.append(inputLine);
}
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
|
cs |
마찬가지로 16번째 라인부터는 응답 데이터를 읽어오는 부분이다.
여기서도 중요한건 CloseableHttpClient 인스턴스를 사용해 한번 통신했다는 점이다.
2.3. 첫번째 테스트로 인해 알게된 차이점
첫번째 테스트로 인해 알게된 이 두 클래스의 차이점을 정리해보았다. 생성방식이 달랐고, CloseableHttpClient 클래스는 close 메서드가 있었다. 왜 있지?? 아직까지는 큰 차이를 느끼진 못해 다음 테스트를 진행하였다.
DefaultHttpClient | CloseableHttpClient | |
생성 | new DefaultHttpClient() | HttpClients.createDefault() |
close 여부 | X | O |
3.1. DefaultHttpClient, CloseableHttpClient 테스트코드 2.
close라는 부분이 눈에 밟혀 각각의 인스턴스에서 HTTP 통신 메서드인 execute를 두번씩 호출해보았다.
3.2. DefaultHttpClient
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
28
29
30
31
32
33
34
35
36
|
public class UseHttpClient {
private static final String URL = "http://www.naver.com";
public static void main(String[] args) {
HttpClient httpClient = null;
try {
httpClient = new DefaultHttpClient(); // httpClient 4.3버전 이후 deprecated 처리.
HttpGet httpGet = new HttpGet(URL);
HttpResponse response = httpClient.execute(httpGet);
HttpResponse response2 = httpClient.execute(httpGet); // 추가한 코드
System.out.println(":: DefaultHttpClient Response ::");
System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
System.out.println(":: response 2 result code : "+response2.getStatusLine().getStatusCode()); // 추가한 코드
BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String inputLine;
StringBuffer responseBuf = new StringBuffer();
while((inputLine = reader.readLine()) != null) {
responseBuf.append(inputLine);
}
reader.close();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
cs |
13번 라인과 17번 라인에 호출 및 응답 코드를 출력하는 코드를 추가하였다.
실행 결과 다음과 같은 Exception이 발생하였다.
예외 메시지는 "연결이 여전히 할당되어 있습니다. 다른 연결을 할당하기 전에 연결을 해제해야 합니다." 라는 뜻이다.
12번 라인에서 수행된 연결이 아직 끊기지 않은 상태에서 13번 라인의 execute 코드가 실행되어 그런 것같다. 그렇다면 과연 언제 끊기는 걸까? 생명주기가 궁금해졌지만 이는 더 파고들어야 알수있을 것 같다. 일단 여기서 확인된 점은 DefaultHttpClient 클래스는 생명주기가 끝나기 전까지 한번의 HTTP 요청을 수행할 수 있다는 것이다.
3.3. CloseableHttpClient
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
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public class UseCloseableHttpClient {
private static final String URL = "http://www.naver.com";
public static void main(String[] args) {
CloseableHttpClient httpClient = null;
try {
httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(URL);
CloseableHttpResponse response = httpClient.execute(httpGet);
CloseableHttpResponse response2 = httpClient.execute(httpGet); // 추가한 코드
System.out.println(":: CloseableHttpResponse ::");
System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
System.out.println(":: response 2 result code : "+response2.getStatusLine().getStatusCode()); // 추가한 코드
BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String inputLine;
StringBuffer responseBuf = new StringBuffer();
while((inputLine = reader.readLine()) != null) {
responseBuf.append(inputLine);
}
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
|
cs |
마찬가지로 11, 15번 라인에 통신 및 응답코드를 출력하는 코드를 추가하였다.
실행 결과, 예외가 발생하지 않으며, 두 요청 모두 response Code 200을 응답받았다.
그렇다면 이 CloseableHttpClient의 생명주기는 어떻게 될지가 궁금해졌다. 마침 close 메서드도 있으니 추가 테스트를 진행해보았다. 그런데 예상치 못한 예외 코드가 출력되었다.
3.4. CloseableHttpClient close()
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class UseCloseableHttpClient {
private static final String URL = "http://www.naver.com";
public static void main(String[] args) {
CloseableHttpClient httpClient = null;
try {
httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(URL);
CloseableHttpResponse response = httpClient.execute(httpGet);
httpClient.close(); // 추가한 코드
CloseableHttpResponse response2 = httpClient.execute(httpGet);
System.out.println(":: CloseableHttpResponse ::");
System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
System.out.println(":: response 2 result code : "+response2.getStatusLine().getStatusCode());
BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String inputLine;
StringBuffer responseBuf = new StringBuffer();
while((inputLine = reader.readLine()) != null) {
responseBuf.append(inputLine);
}
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
|
cs |
커넥션 풀이 종료됐다는 예외였다!
아. 생성한 CloseableHttpClient 인스턴스는 기본적으로 커넥션 풀을 지원한다는 것을 알게되었고, close 메서드는 이 인스턴스에 할당된 커넥션 풀을 close 시키는 것이었다.
3.2 테스트에서 2번을 연속으로 호출했을 때 예외가 발생하지 않았던 이유도 요청을 커넥션 풀을 통해 요청이 처리되기 때문이었다.
3.4. 두번째 테스트로 인해 알게된 차이점
DefaultHttpClient | CloseableHttpClient | |
생성 | new DefaultHttpClient() | HttpClients.createDefault() |
close 여부 | X | O |
HTTP 통신 횟수 | 1 | ConnectionPool 설정에 따라 다름 |
ConnectionPool | X | O |
이제 DefaultHttpClient에서 발생했던 예외의 원인과 생명주기는 무엇이고, CloseableHttpClient의 커넥션 풀이 어디서 설정되는지 확인해야한다. 이는 생성부분의 내부 코드를 확인해야한다.
이는 다음 포스팅에 정리하도록 하겠다.
- 혹, 글의 내용 중 맞지 않는 부분이나 수정할 사항이 있다면 꼭 댓글 부탁드립니다. 정말 감사히 받아드리겠습니다!
'백엔드 > JAVA' 카테고리의 다른 글
[Java] WHOIS OpenAPI를 사용한 해외 IP 차단 기능 구현 (1) (0) | 2022.02.13 |
---|---|
[JAVA] DefaultHttpClient와 CloseableHttpClient의 차이 2 (1) | 2021.08.25 |
[JAVA] 접근제어자, public, private, default, protected 정의, 예제 (0) | 2021.06.07 |
[Java] 예외처리, Throw, Throws란 / 예외와 에러의 차이 / 예제 (1) | 2021.06.04 |
[JAVA] String을 Json 으로 변환. String to Json (1) | 2021.02.03 |