티스토리 뷰
http / https 서버와의 통신을 위해 이전에는 HttpURLConnection과 같은 Object를 많이 사용했었습니다.
이번에는 RestTemplate을 사용하여 서버간 통신을 해보려고 합니다.
1. 개요
Spring 3.0부터 지원하는 템플릿이며 RESTful원칙을 지킵니다. 코드도 간소화됩니다.
동기방식을 지원합니다.
AsyncRestTemplate도 있습니다. Spring 4.0부터 지원하며, 비동기 방식을 지원하지만 5.0에서 deprecated 되었습니다.
WebClient도 있습니다. Spring 5.0부터 지원하며, 논블럭-비동기 방식을 지원합니다.
2. 이전에 사용했던 방식
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
public class HttpConnectionUtil {
private void sendApiUsingHttpConnection(Map param) {
try {
String domain = "http://domain.kr";
String url = domain + "/send/to/anywhere/";
StringBuilder sb = new StringBuilder();
sb.append("&userId=").append(param.get("userId"))
.append("&column1=").append(param.get("column1"))
.append("&column2=").append(param.get("column2"))
.append("&column3=").append(param.get("column3"))
.append("&sendto=").append(param.get("sendto"));
HttpURLConnection conn = (HttpURLConnection) new URL(url + URLEncoder.encode(sb.toString(), "UTF-8")).openConnection();
log.debug("Connection : " + conn);
log.debug("ResponseCode : " + conn.getResponseCode());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
문제점으로는 응답 코드에 따라 IOException이 호출됩니다.
또, 동기식이므로 타임아웃을 설정할 수 없습니다.
3. RestTemplate
사용하기 전에 Factory를 통한 생성이 가능합니다. 이것이 가능한 이유는 Spring Framework 내에 내장되어 있는 라이브러리이기 때문에 추상화가 가능하기 때문입니다. 즉, 기본적으로 제공하는 기본값들을 개발자가 설정할 수 있는 일종의 @Component가 가능하기 때문입니다.
@Component
public class RestTemplateFactory {
public RestTemplate getCustomizingRT() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(5000); // 읽기시간초과 기준 시간를 의미 단위는 ms
factory.setConnectTimeout(3000); // 연결시간초과 기준 시간를 의미 단위는 ms
factory.setHttpClient(
// 동기실행에 사용될 HttpClient 세팅
HttpClientBuilder.create()
.setMaxConnTotal(100) // 최대 Connection Pool Size를 의미
.setMaxConnPerRoute(5) // 라우터당 최대 Connection Size
.build());
RestTemplate restTemplate = new RestTemplate(factory);
return restTemplate;
}
}
사용법
기본적으로 제공되는 메소드는 다음과 같습니다.
RestTemplate Method | HTTP Method | Description | |
execute | Any | Request/Response콜백을 수정합니다 | restTemplate.execute(); |
exchange | Any | 헤더를 세팅해서 HTTP Method를 호출하고 원하는 클래스타입의 ResponseEntity 값으로 반환받습니다 | ResponseEntity<ClassName> result = restTemplate.exchange(uri.toString(), HttpMethod.GET, entity, ClassName.class); |
getForObject | GET | 주어진 URL 주소로 GET 메소드의 객체로 결과를 반환받습니다 | |
getForEntity | GET | 주어진 URL 주소로 GET 메소드로 ResponseEntity로 결과를 반환받습니다 | |
postForLocation | POST | POST 요청을 보내고 결과로 헤더에 저장된 URI를 결과로 반환받습니다 | |
postForObject | POST | POST 요청을 보내고 객체로 결과를 반환받습니다 | |
postForEntity | POST | POST 요청을 보내고 결과로 ResponseEntity로 반환받습니다 | |
put | PUT | 주어진 URL 주소로 HTTP PUT 메소드를 실행합니다 | |
delete | DELETE | 주어진 URL 주소로 HTTP DELETE 메소드를 실행합니다 | |
headForHeaders | HEAD | 헤더의 모든 정보를 얻을 수 있으면 HTTP HEAD 메소드를 사용합니다 | |
optionsForAllow | OPTIONS | 주어진 URL 주소에서 지원하는 HTTP 메소드를 조회합니다 |
아래 코드는 JUnit 테스트 코드입니다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class RestTemplateJUnitTestClass {
@Autowired RestTemplateFactory restTemplate;
@Test
public void testGetForObject() {
Employee employee = restTemplate.getForObject(BASE_URL + "/{id}", Employee.class, 25);
log.info("employee: {}", employee);
}
@Test
public void testGetForEntity() {
ResponseEntity<String> responseEntity = restTemplate.getForEntity(BASE_URL + "/{id}", String.class, 25);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());
}
@Test
public void testGetForEntitySendWithPathVariables() {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("name", "Frank Oh");
params.add("country", "US");
ResponseEntity<Employee> responseEntity = restTemplate.getForEntity(BASE_URL + "/{name}/{country}", Employee.class, params);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());
}
@Test
public void testPostForObjectSendExcludingHeader() {
Employee newEmployee = Employee.builder()
.name("Frank")
.address(Address.builder()
.country("US")
.build())
.build();
Employee employee = restTemplate.postForObject(BASE_URL + "/employee", newEmployee, Employee.class);
log.info("employee: {}", employee);
}
@Test
public void testPostForObjectSendWithHeader() {
Employee newEmployee = Employee.builder()
.name("Frank")
.address(Address.builder()
.country("US")
.build())
.build();
HttpHeaders headers = new HttpHeaders();
headers.set("headerTest", "headerValue");
HttpEntity<Employee> request = new HttpEntity<>(newEmployee, headers);
Employee employee = restTemplate.postForObject(BASE_URL + "/employee", request, Employee.class);
log.info("employee: {}", employee);
}
@Test
public void testPostForEntityReturnByString() {
Employee newEmployee = Employee.builder()
.name("Frank")
.address(Address.builder()
.country("US")
.build())
.build();
ResponseEntity<String> responseEntity = restTemplate.postForEntity(BASE_URL + "/employee", newEmployee, String.class);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());
}
@Test
public void testPostForLocation() {
Employee newEmployee = Employee.builder()
.name("Frank")
.address(Address.builder()
.country("US")
.build())
.build();
HttpEntity<Employee> request = new HttpEntity<>(newEmployee);
URI location = restTemplate.postForLocation(BASE_URL + "/employee/location", request);
log.info("location: {}", location);
}
@Test
public void testDelete() {
Map<String, String> params = new HashMap<>();
params.put("name", "Frank");
restTemplate.delete(BASE_URL + "/employee/{name}", params);
}
@Test
public void testPut() {
Map<String, String> params = new HashMap<>();
params.put("name", "Frank");
Address address = Address.builder()
.city("Columbus")
.country("US")
.build();
restTemplate.put(BASE_URL + "/employee/{name}", address, params);
}
@Test
public void testExchange() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<>("Hello World!", headers);
log.info("request: {}", request);
ResponseEntity<Employee> empEntity = restTemplate.exchange(BASE_URL + "/exchange/employee/{id}", HttpMethod.GET, request, Employee.class, 50);
log.info("empEntity: {}", empEntity);
}
@Test
public void testOptionsForAllow() {
final Set<HttpMethod> optionsForAllow = restTemplate.optionsForAllow(BASE_URL + "/employee");
log.info("optionsForAllow: {}", optionsForAllow);
}
@Test
public void testExecute() {
Address address = Address.builder()
.city("Columbus")
.country("US")
.build();
restTemplate.execute(BASE_URL + "/employee/{name}", HttpMethod.PUT, requestCallback(address), clientHttpResponse -> null, "frank");
}
}
요청 URL 방식
1. UriComponentBuilder로 파라미터를 붙일 수 있습니다.
public URI getURIstandardFormat() {
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
UriComponents uriComp = builder.scheme("http")
.host("localhost")
.port(8080)
.path("/users/{userId}/stores/{storeId}")
.build();
uriComp = uriComp.expand("333", "1").encode();
Uri uri = uriComp.toUri();
return uri;
}
public URI getURIbyChaining() {
URI uri = UriComponentsBuilder.newInstance()
.scheme("http")
.host("localhost")
.path("/users/{userId}/stores/{storeId}")
.build()
.toUri();
return uri;
}
2. String.format으로도 가능합니다.
String response = template.getForObject("http://localhost:8080/stores/{storesId}", String.class, "1");
3. REST 형식으로도 전달이 가능합니다.
String response = template.getForObject("http://../users/{userId}/stores/{storesId}", String.class, "333", "1");
4. 보내는 파라미터를 Map으로 하여 전송도 가능합니다.
Map<String, Object> params = new HashMap<>();
params.put("userId", "333");
params.put("storesId", "1");
String response = restTemplate.getForObject("http://../users/{userId}/stores/{storesId}", String.class, params);
에러 응답 처리
1. HttpStatusCodeException : 응답 코드가 에러인 경우 발생합니다.
2. ResourceAccessException : 네트워크 연결에 문제가 있는 경우 발생합니다.
3. UnknownHttpStatusCodeException : 알 수 없는 응답코드인 경우 발생합니다.
그 외 AsyncRestTemplate를 이용한 비동기 처리
RestTemplate와 같은 메소드를 지원합니다.
하지만 결과를 바로 받지 않고 ListenableFutureCallback으로 받아 처리하게 됩니다.
AsyncRestTemplate template = new AsyncRestTemplate();
template.getForEntity("http://www.naver.com", String.class)
.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
public void onSuccess(ResponseEntity<String> result) { //... }
public void onFailure(Throwable ex) { //... }
});
'Server' 카테고리의 다른 글
[Spring] Jedis 보다는 Lettuce를 쓰자 (2) | 2020.07.20 |
---|---|
Kotlin은 Lombok을 사용할 수 없다 (0) | 2020.07.20 |
[Java] 직렬화는 중요한 것일까 (0) | 2020.07.17 |
[Spring] Spring Framework 5와 WebFlux (0) | 2020.07.06 |
Kotlin의 상속성과 다형성 (0) | 2020.07.03 |
[Java] Files를 사용하여 입력하기 (0) | 2020.07.02 |