[Spring] RestTemplate Get, Post, Patch, Delete 요청 보내는 방법

식빵·2022년 7월 6일
3

Spring Lab

목록 보기
12/34
post-thumbnail

🥝 잘 까먹는 RestTemplate 사용법

정말 쓸 때마다 사용법을 까먹어서 검색하고 시간을 허비한다.
도저히 안되겠다 싶어서 작정하고 글을 쓴다.

요청 보내는 방법을 알아보기 위해 아래와 같은 순서로 글이 진행된다.

  1. 스프링 부트 프로젝트 생성 (추가적으로 필요한 gradle 의존성도 주입)
  2. PostMan MockServer 를 사용한 테스트용 Rest Api Server 생성
  3. 해당 Rest Api Server 에 GET/POST/PATCH/DELETE 요청하는 Junit5 학습 테스트 코드 작성




🥝 스프링 부트 프로젝트 생성

일단 테스트를 위한 프로젝트 생성을 한다.

  • 언어: Java
  • 빌드툴: Gradle
  • 기본 dependencies 선택 : Lombok + Spring Web




🥝 gradle dependencies 수정

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 설정

혹시 PostMan MockServer 세팅법이 처음이라면 이 글을 참고하길 바란다.

모든 요청 결과의 반환값을 json 으로 하기 위해서 Headers 정보에
Content-Type: application/json 을 추가해줬다.

만약 Postman 을 사용하기 싫다면, Spring boot 로 간단하게 Rest API Server 를 만들어도 된다.


🥥 GET METHOD


🥥 POST METHOD


🥥 PATCH METHOD


🥥 DELETE METHOD




🥝 학습용 테스트 코드 작성

여기에서 작성된 코드는 모두 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());
    }

	//... 생략 ...
}



🥥 GET METHOD

참고: 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);

    }
}



🥥 POST METHOD

@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);



🥥 PATCH METHOD

@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);
    }
    
}



🥥 DELETE METHOD

@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);
    }
}




🥥 보충: UriComponentBuilder 사용 이유

현재 위 예제에서 사용하는 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()) 해도 마찬가지다!




profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글