정말 쓸 때마다 사용법을 까먹어서 검색하고 시간을 허비한다.
도저히 안되겠다 싶어서 작정하고 글을 쓴다.
요청 보내는 방법을 알아보기 위해 아래와 같은 순서로 글이 진행된다.
일단 테스트를 위한 프로젝트 생성을 한다.
Java
Gradle
Lombok
+ Spring Web
build.gradles
파일에서 아래와 같이 수정한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// [추가] HttpComponentsClientHttpRequestFactory 를 사용하기 위해서 추가
// PATCH METHOD 를 사용하기 위해서는 필요하다.
implementation 'org.apache.httpcomponents:httpclient'
// [추가] 테스트에서 롬복 사용하기 위해 아래 2줄 추가
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
혹시 PostMan MockServer 세팅법이 처음이라면 이 글을 참고하길 바란다.
모든 요청 결과의 반환값을 json
으로 하기 위해서 Headers
정보에
Content-Type: application/json
을 추가해줬다.
만약 Postman 을 사용하기 싫다면, Spring boot 로 간단하게
Rest API Server
를 만들어도 된다.
여기에서 작성된 코드는 모두 Junit5 코드이며, 간단한 로그를 찍어서 눈으로만 확인하는
학습용 테스트이다. 그래서 assertThat 같은 코드는 작성하지 않는다.
그리고 사용 중인 jdk 17 이므로, 현재 이 글을 보시는 분들에게는 없는 기능을 사용할 수도 있다는 점 감안하고 봐주시길!
package hello.dailycode.rest;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
@Slf4j
class RestTemplateTest {
// REST API 를 요청하는 주체
private static final RestTemplate restTemplate;
// REST API 반환값에 대한 변환을 위해서 미리 선언
private static final ObjectMapper mapper
= new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 요청 URL
private static final String API_URL
= "postman_url_작성하면 됩니다";
// 요청 URL 로 UriComponents 생성
private static final UriComponents URI_COMPONENTS
= UriComponentsBuilder.fromUriString(API_URL)
.build();
// *** static 초기화 부분은 너무 길어서 이후에는 표기 안합니다! ***
static {
// 참고로 Patch Method 를 쓰기 위해서는
// HttpComponentsClientHttpRequestFactory를 사용해야 한다!
HttpComponentsClientHttpRequestFactory factory
= new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(5000);
factory.setBufferRequestBody(false);
restTemplate = new RestTemplate(factory);
restTemplate.getMessageConverters()
.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
// Response 한글 깨짐 방지
}
// 부가 기능 메소드 작성 - ResponseEntity 결과 내용들을 출력함
private void printEntity(ResponseEntity<?> entity) {
log.info("result entity status code : {}", entity.getStatusCode());
log.info("result entity headers : {}",entity.getHeaders());
log.info("result entity body : {}",entity.getBody());
}
//... 생략 ...
}
참고: RestTemplate 가장 보편적인 요청 메소드는 크게 2가지다.
getForEntity
(또는getForObject
)exchange
그래서 아래 예제 모두 이 2가지 방식으로 코드가 진행된다.
@Slf4j
class RestTemplateTest {
// ...기본 세팅 코드 생략...
@Test
@DisplayName("GET METHOD Test")
void getTest() throws URISyntaxException {
ResponseEntity<JsonNode> entity = restTemplate.getForEntity(
URI_COMPONENTS.toUri(),
JsonNode.class
);
printEntity(entity);
// 복잡한 POJO 를 반환 받고 싶을 때는 exchange 메소드의
// ParameterizedTypeReference 인자를 잘 활용하자.
ResponseEntity<List<Map<String, String>>> exchange
= restTemplate.exchange(
complexUrl.toUri(),
HttpMethod.GET,
null, new ParameterizedTypeReference<List<Map<String, String>>>() {}
);
printEntity(entity);
}
}
@Slf4j
class RestTemplateTest {
// ...기본 세팅 코드 생략...
@Test
@DisplayName("POST METHOD Test")
void postTest() throws URISyntaxException {
// POST METHOD 는 주로 HTTP BODY 에 전송할 데이터를 담아서 보낸다.
// 그래서 HTTP BODY 에 넣을 JSON 을 한번 만들어 보겠다.
ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode();
jsonNodes.put("good", "job");
jsonNodes.put("nice", "work");
// {"good":"job","nice":"work"}
ResponseEntity<JsonNode> postResult
= restTemplate.postForEntity(
URI_COMPONENTS.toUri(),
jsonNodes,
JsonNode.class
);
printEntity(postResult);
HttpEntity<JsonNode> requestEntity = new HttpEntity<>(jsonNodes);
// 복잡한 반환 값은 exchage 메소드의 ParameterizedTypeReference
// 인자값을 활용!
ResponseEntity<Map<String, JsonNode>> result
= restTemplate.exchange(URI_COMPONENTS.toUri(),
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<Map<String, JsonNode>>() {}
);
printEntity(result);
}
}
참고 (1):
HTTP HEADERS
도 같이 던지려면 2 번째 인자로MultiValueMap
사용// 참고로 org.springframework.http.HttpHeaders 는 MultiValueMap // 을 구현한 객체라서 이걸 쓰면 된다. HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<JsonNode> requestEntity2 = new HttpEntity<>(jsonNodes, headers);
참고 (2): 파일 전송도 될까? 된다!
MultipartBodyBuilder builder = new MultipartBodyBuilder(); builder.part("id", "dailycode"); builder.part("thumbNailFile", new FileSystemResource("c:/study/thumbnail.png")); // 만약 상세하게 part 의 header 를 작성하고 싶다면 아래처럼... builder.part("uploadfile", new ByteArrayResource(fileContent)) .header("Content-Disposition", "form-data; name=uploadfile; filename=daily.jpg"); // 최종적으로 build 호출하여 전송할 PayLoad 생성 MultiValueMap<String, HttpEntity<?>> payLoad = builder.build(); // RestTemplate 을 통해서 최종적으로 전송하면 된다. // body 내용이 딱히 없으므로 Void.class 타입으로 반환 template.postForObject("http://me.dailycode/upload", payLoad, Void.class);
@Slf4j
class RestTemplateTest {
// ...기본 세팅 코드 생략...
@Test
@DisplayName("PATCH METHOD Test")
void patchTest() {
// body 로 넣은 Json 생성
ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode();
jsonNodes.put("userId", "dailycode");
jsonNodes.putPOJO(
"hrHistory",
Map.of("sales", "1992-04-11", "develop", "2000-02-02")
);
// {
// "userId":"dailycode",
// "hrHistory":{"sales":"1992-04-11","develop":"2000-02-02"}
// }
// patchForEntity 같은 메소드는 없다!
Map map
= restTemplate.patchForObject(
URI_COMPONENTS.toUri(),
jsonNodes,
Map.class
);
System.out.println("map = " + map);
HttpEntity<JsonNode> requestEntity = new HttpEntity<>(jsonNodes);
ResponseEntity<Map<String, Object>> result
= restTemplate.exchange(
URI_COMPONENTS.toUri(),
HttpMethod.PATCH,
requestEntity,
new ParameterizedTypeReference<Map<String, Object>>() {}
);
printEntity(result);
}
}
@Slf4j
class RestTemplateTest {
// ...기본 세팅 코드 생략...
@Test
@DisplayName("Delete Method Test")
void deleteTest() {
// delete 메소드는 반환값이 없다!
restTemplate.delete(URI_COMPONENTS.toUri());
// exchange 를 해야 반환값을 받을 수 있다.
ResponseEntity<Map> result
= restTemplate.exchange(
URI_COMPONENTS.toUri(),
HttpMethod.DELETE,
HttpEntity.EMPTY,
Map.class
);
printEntity(result);
}
}
현재 위 예제에서 사용하는 URL은 굉장히 간단해서 굳이 UriComponentBuilder 를
쓸 필요가 없다. 하지만 경험상 개발 시에는 복잡한 URL 을 작성하는 경우가
매우 많기 때문에 예제 코드에서 UriComponentBuilder 을 사용한 것이다.
(참고로 ServletComponentBuilder 도 매우 유용하다)
UriComponents complexUrl = UriComponentsBuilder
.fromUriString("http://localhost:8080/users/{nickname}")
.uriVariables(Map.of("nickname", "dailycode"))
.queryParam("userName", "한글이름")
.queryParam("userId", "nobody")
.queryParam("age", 30)
// String 뿐만 아니라 int, boolan, double 등의 값도 사용할 수 있다!
.encode().build();
// Tip: encode() 해주면 toUriString 사용시 한글에 대한 URL 엔코딩도 해준다.
System.out.println("complexUrl.toUriString() = " + complexUrl.toUriString());
// 출력 결과:
// http://localhost:8080/users/dailycode?
// userName=%ED%95%9C%EA%B8%80%EC%9D%B4%EB%A6%84&userId=nobody&age=30
// 만약 .encode() 를 안 하면 ...?
// http://localhost:8080/users/dailycode?userName=한글이름&userId=nobody&age=30
// 이것은 System.out.println(complexUrl.toUri()) 해도 마찬가지다!