참고자료
https://jung-story.tistory.com/133
https://jung-story.tistory.com/130
개요
위의 링크에서는 Okhttp3을 이용하여 외부 API를 호출하였었습니다.
물론 그런 방법으로 해도 되지만, Okhttp3은 보통 안드로이드에서 많이 사용되고 가끔 서버에 jar파일로 묶어서 올릴 때 jar파일에 해당 라이브러리가 빌드가 안될 경우가 있습니다.
RestTemplate를 이용하여 외부 API를 연동하였을 경우 서버에 jar파일로 묶어서 올릴 때 jar파일에 해당 라이브러리가 잘 빌드되는 모습을 확인할 수 있었습니다.
그렇기 때문에 이번에는 RestTemplate와 MultiValueMap, HttpEntity를 이용하여 외부 API를 연동해 보도록 하겠습니다.
RestTemplate 란?
Spring 3.0 부터 지원을 하고 있습니다.
Spring에서 자체적으로 제공하는 http 통신 템플릿 중에서는 가장 유용하다고 볼 수 있다. URLConnection, HttpClient 등등 보다 훨씬 더 간단하게 사용할 수 있다는 장점이 있습니다.
RestTemplate는 HTTP 서버와의 통신을 단순화하고 RESTful 원칙을 지고 있습니다. 또한 jdbcTemplate처럼 기계적이고 반복적인 코드들을 깔끔하게 정리해주고 있습니다. (밑에서 코드를 보면 엄청 간결한 걸 알 수 있음.)
특징
- 기계적이고 반복적인 코드를 최대한 줄여준다.
- RESTful 형식에 맞춤.
- Json, xml 의 마크업 언어를 쉽게 응답 받음.
RestRemplate의 동작원리
org.springframwork.http.client 패키지에 있습니다.
HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이고, RestTemplate는 HttpClient를 추상화(HttpEntity의 json, xml 등) 해서 제공해줍니다. 따라서 내부 통신 (HTTP 커넥션에 있어서는 Apache HttpComponents를 사용합니다.
만약 RestTemplate 가 없었다면, 직접 json, xml 라이브러리를 사용해서 변환해야 했을 것입니다.
설명
- Application이 RestTemplate를 생성하고, URL, HTTP 메소드 등의 헤더를 담아 요청합니다.
- RestTemplate는 HttpMessageConverter를 사용하여 requestEntity를 요청메세지로 변환합니다.
- RestTemplate는 ClientHttpRequestFactory로부터 ClientHttpRequest를 가져와서 요청을 보냅니다.
- ClientHttpRequest는 요청 메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신합니다.
- RestTemplate는 ResponseErrorHandler로 오류를 확인하고 있다면 처리 로직을 태웁니다.
- ResponseErrorHandler는 오류가 있다면 ClientHttpResponse에서 응답 데이터를 가져와서 처리합니다.
- RestTemplate는 HttpMessageConverter를 이용해서 응답 메시지를 java object(Class responseType)로 변환합니다.
- Application에 반환됩니다.
RestTemplate 사용
기본 생성
RestTemplate restTemplate = getRestTempalte();
설정 생성
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000); //타임아웃 설정 5초 읽기시간초과
factory.setReadTimeout(5000); //타임아웃 설정 5초 연결시간초과
// Connection pool 적용
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(100) // connection pool 적용
.setMaxConnPerRoute(5) // connection pool 적용
.build();
factory.setHttpClient(httpClient); // 동기실행에 사용될 HttpClient 세팅
RestTemplate restTemplate = new RestTemplate(factory);
connection pool 적용
참조 문서 : https://stackoverflow.com/questions/31869193/using-spring-rest-template-either-creating-too-many-connections-or-slow/
RestTemplate는 기본적으로 connection pool을 사용하지 않습니다.
따라서 연결할 때마다, 로컬 포트를 열고 TCP connection을 맺습니다. 이때 문제는 close() 이후에 사용된 소켓은 TIME_WAIT 상태가 되는데, 요청량이 많다면 이런 소켓들을 재사용하지 못하고 소켓이 다 떨어지고, 응답이 지연될 것입니다.
이런 경우 connection pool을 사용해서 해결할 수 있는데, 위의 코드처럼 HttpClient를 만들고 setHttpClient()를 해주면 RestTemplate에 connection pool이 적용이 됩니다.
- setMaxConnPerRoute : IP, 포트 1쌍에 대해 수행할 연결 수를 제한합니다.
- setMaxConnTotal : 최대 오픈되는 커넥션 수를 제한합니다.
주요 메소드
RestTemplate Method | HTTP Method | 설명 |
execute | Any | |
exchange | Any | 헤더셋팅해서 HTTP Method로 요청보내고 ResponseEntity로 반환 받음. |
getForObject | GET | get 요청을 보내고 java object로 매핑받아서 반환받음. |
getForEntity | GET | get 요청을 보내고 ResponseEntity로 반환받음. |
postForLocation | POST | post 요청을 보내고 java.net.URI로 받환받음. |
postForObject | POST | post 요청을 보내고 ResponseEntity로 반환받음. |
put | PUT | |
delete | DELETE | |
headForHeaders | HEAD | |
optionsForAllow | OPTIONS |
에러 처리
- DefaultResponseErrorHandler를 사용하여 HTTP Error를 제어할 수 있다.
- restTemplate.setErrorHandler를 통해 커스텀 핸들러를 등록할 수 있다.
예제
MutilValueMap parameters에 파라메터 값들을 담고
HttpHeaders headers에 Content-Type 정보와 클라이언트 키값(api key)을 담는 경우도 있다.
Content-Type 은 두 가지로 나뉘는데 RestTemplate을 사용하면 알아서 정해진다고 한다.
headers.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
headers.add("Content-Type", "multipart/form-data; boundary=----WebKitFormB~~3");
parameters와 headers를 HttpEntity <> request에 담는다.
try {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000); //타임아웃 설정 5초
factory.setReadTimeout(5000);//타임아웃 설정 5초
RestTemplate restTemplate = new RestTemplate(factory);
HttpHeaders header = new HttpHeaders();
//headers.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); //전부다 String형일 때. RestTemplate 때문에 생략가능
//headers.add("Content-Type", "multipart/form-data; boundary=----WebKitFormB~~3"); //multipart/form-data 있는 경우 사용. RestTemplate 때문에 생략 가능
String url = "http://123.33.33/TESTAPI"; // 외부 API URL
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
// 통으로 JSON을 넣어 줄 경우
string user_id = "test_id"
parameters.add("testjson", "{\"TDATA1\":\"aageee\",\"TDATA2\":\"" + user_id + "\",\"TDATA3\":\"tttee\",\"TDATA4\": \"Test1234\" }");
log.debug("parameters ====" + parameters.toString());
// parameters 에 .add 로 계속해서 추가하여 넣어 줄경우
parameters.add("USER_ID", "test_id");
parameters.add("phone_num", "010-2222-2222");
parameters.add("message", "abcdefg");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(parameters, header);
String result = restTemplate.postForObject(new URI(url), request, String.class);
// result로 응답받은 결과를 출력
JSONObject jsonObj = new JSONObject(result);
log.debug("jsonObj=======" + jsonObj);
}
catch(HttpClientErrorException | HttpServerErrorException e) {
System.out.println(e.toString());
}
catch(Exception e) {
System.out.println(e.toString());
}
예제2
전송하고자 하는 API에 값을 JSON 형식으로 가공해서 전송해주기 위해서는 아래와 같이 사용합니다.
JSONObject와 JSONArray를 이용하여 데이터를 가공할 수 있습니다.
API에 보내야 하는 데이터형 식이 {"REQ":"agent_forwarding","AGENT_ID":"User01","EXTENSION_NO":"00110","FKEY":"SP_CODE","FDATA":["Apple","banana","good","take"]}
일 경우 아래와 같이 사용.
try {
String aspCustKeyArray[] = {"Apple","banana","good","take"};
String aspUserId = "User01"
String inLineNo = "00110"
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000); //타임아웃 설정 5초
factory.setReadTimeout(5000);//타임아웃 설정 5초
RestTemplate restTemplate = new RestTemplate(factory);
HttpHeaders header = new HttpHeaders();
// 전송할 API주소
String url = "http://333.33.33.333:60080/API/";
// JSON 객체를 생성.
JSONObject jsonObject = new JSONObject();
jsonObject.put("REQ", "agent_forwarding");
jsonObject.put("AGENT_ID", aspUserId);
jsonObject.put("EXTENSION_NO", inLineNo);
jsonObject.put("FKEY", "SP_CODE");
// 배열의 길이만큼 JSONArray에 데이터삽입.
JSONArray req_array = new JSONArray();
for(int i = 0; i < aspCustKeyArray.length; i++) {
//data.put(null, aspCustKeyArray[i]);
req_array.add(aspCustKeyArray[i]);
}
// JSON객체의 JSONArray를 삽입
jsonObject.put("FDATA", req_array);
log.debug("jsonObject =====================" + jsonObject);
// JSON형식이니까 Object로 변경
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
parameters.add("tplexParam", jsonObject);
log.debug("parameters ====" + parameters.toString());
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(parameters, header);
String result = restTemplate.postForObject(new URI(url), request, String.class);
JSONObject jsonObj = JSONObject.fromObject(result);
String ResultCode = jsonObj.getString("result_code");
String ErrorMsg = "";
// API로 부터 온 응답에 따른 오류처리
switch(ResultCode)
{
case "1":
ErrorMsg = "등록되지 않은 상담사 ID 입니다.";
break;
case "2":
ErrorMsg = "중복된 큐가 있습니다.";
break;
case "3":
ErrorMsg = "등록되지 않은 큐 입니다.";
break;
case "4":
ErrorMsg = "상담사가 로그인 상태입니다.";
break;
case "5":
ErrorMsg = "FDATA 값이 없습니다.";
break;
case "6":
ErrorMsg = "FKEY 값이 없습니다.";
break;
case "7":
ErrorMsg = "중복된 업체코드가 있습니다.";
break;
case "8":
ErrorMsg = "등록되지 않은 업체코드가 있습니다.";
break;
}
log.debug("jsonObj=======" + jsonObj);
}
catch(HttpClientErrorException | HttpServerErrorException e) {
System.out.println(e.toString());
}
catch(Exception e) {
System.out.println(e.toString());
}
return mjsonParams;
}
결론
이번 코드 작성과 외부 API 인터페이스 태우는 방법을 배웠기 때문에 다음에 오픈 API나 , 외부 API를 호출하여 유효성 체크 및 Return 값에 따른 로직을 처리할 수 있을 것입니다.
다음에는 오픈 API를 호출하여 결과를 받아오는 방법을 알아보도록 하겠습니다.
'서버 > Spring' 카테고리의 다른 글
[Spring] Builder 패턴 생성 및 장점 사용이유 설명 (코드 생성) (0) | 2021.09.29 |
---|---|
[Spring] 외부 API 연동 (Java REST API방식과 Ajax 방식) 코드 및 설명 (0) | 2021.09.28 |
[Spring] - Service를 Interface로 생성 하는 이유. (AOP, 결합도) (2) | 2021.08.11 |
[Spring] 생명주기 분석 및 - 생명주기에 따른 코드 (Ajax, Json 사용) (0) | 2021.08.06 |
[Spring] Mybatis (Interface생성 및 xml 파일과 Mapping) (1) | 2021.08.03 |