SpringBoot - Rest Template

dragonappear·2021년 12월 19일
0

HTTP

목록 보기
2/3


RestTemplate이란?

HTTP 메소드에 의한 평범한 기능 템플릿을 제공해주고, 더 나아가 특별한 케이스를 지원하는 exchangeexecute 메소드를 제공해준다.

  • Spring 4.x부터 지원하는 Spring의 HTTP 통신 템플릿
  • HTTP 요청 후 Json,xml,String과 같은 응답을 받을 수 있는 템플릿
  • Blocking I/O 기반의 Synchronous API (비동기를 지원하는 AsyncRestTemplate 도 있음)
  • ResponseEntity와 Server to Server 통신하는데 자주 쓰임
  • 또는 Header, Content-Type등을 설정하여 외부 API 호출
  • Http request를 지원하는 HttpClient를 사용함

RestTemplate을 사용할 때 주의할 점

RestTemplate은 기본적으로 connection pool을 사용하지 않기 때문에 매 요청마다 handshake를 수행한다.

이를 방지하기 위해 다음과 같은 설정으로 Custom RestTemplate을 빈으로 등록하여 사용할 수 있다.

@Configuration
public class HttpConnectionConfig {

    @Bean
    public RestTemplate getCustomRestTemplate() {
        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setConnectTimeout(2000);
        httpRequestFactory.setReadTimeout(3000);
        CloseableHttpClient httpClient = HttpClientBuilder.create()
                .setMaxConnTotal(200)
                .setMaxConnPerRoute(20)
                .build();
        httpRequestFactory.setHttpClient(httpClient);
        return new RestTemplate(httpRequestFactory);
    }
}

httpClient를 사용하기 위해 아파치 의존을 추가해야 한다

// https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient
	implementation 'org.apache.httpcomponents:httpclient:4.5.13'

HttpComponentsClientHttpRequestFactory: HttpClient를 사전구성 할 수 있는 메서드 제공

  • public void setConnectTimeout(int timeout)
  • public void setConnectionRequestTimeout(int connectionRequestTimeout)
  • public void setReadTimeout(int timeout)
  • public void setBufferRequestBody(boolean bufferRequestBody): requestBody에 대해 버퍼링을 할지 하지 않을지. 공식 문서에 의하면, Default는 true이나 매우 큰 응답 바디가 들어오는 경우 false로 세팅하기를 권장한다.

HttpClient에 connection pool 설정

        CloseableHttpClient httpClient = HttpClientBuilder.create()
                .setMaxConnTotal(200)
                .setMaxConnPerRoute(20)
                .build();

MaxConnTotalconnection pool의 갯수이고,
MaxConnPerRoute는 IP, Port 하나 당 연결 제한 갯수이다.

RestTemplate을 사용하기 위한 준비는 끝났고 주요 메소드는 다음과 같다.
메소드 명으로도 알 수 있다시피 Restful을 준수하는 템플릿이다.

RestTemplate 주요 메서드

  • execute
  • exchange
  • getForObject: get요청 후 응답은 Object로
  • getForEntity: get요청 후 응답은 Entity로
  • postForObject: post요청 후 응답은 Object로
  • putForObject
  • delete

Get

일반 JSON 가져오기

아래 예제는 getForEntity() API를 사용하는 예이다.


public class RestTemplateBasicLiveTest {
    private RestTemplate restTemplate;
    private static final String fooResourceUrl = "http://localhost:" + APPLICATION_PORT + "/foos";

    @BeforeEach
    public void init() {
        restTemplate = new RestTemplate();
    }

    @Test
    public void givenResourceUrl_whenSendGetForRequestEntity_thenStatusOk()  throws Exception{
        //given

        //when
        ResponseEntity<String> response = restTemplate.getForEntity(fooResourceUrl, String.class);
        //then
        Assertions.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    }

}

HTTP 응답에 대한 전체 엑세스 권한이 있으므로 상태 코드를 확인하여 작업이 성공했는지 확인하거나 응답의 실제본문으로 작업하는 것과 같은 작업을 수행할 수 있다.

ObjectMapper mapper = new ObjectMapper();
        JsonNode root = mapper.readTree(response.getBody());
        JsonNode name = root.path("name");
        Assertions.assertThat(name.asText()).isNotNull();

여기에서는 응답 본문을 표준 문자열로 사용하고 Jackson(및 Jackson이 제공하는 JSON 노드 구조)을 사용하여 일부 세부 정보를 확인한다.

JSON 대신 POJO 검색

응답을 Resource DTO에 직접 매핑할수도 있다.

public class Foo implements Serializable {
    private Long id;
    private String name;
    
    // 기본생성자, Getter, Setter 필수

이제 템플릿에서 getForObjectAPi를 간단히 사용할수 있다.

@Test
    public void givenResourceUrl_whenRetrievingResource_thenCorrect() throws Exception{
        //given

        //when
        Foo foo = restTemplate.getForObject(fooResourceUrl, Foo.class);
        //then
        assertThat(foo.getName()).isEqualTo("yyh");
        assertThat(foo.getId()).isEqualTo(1L);
    }

Header 검색

더 일반적인 방법으로 이동하기 전에 HEAD를 사용하는 방법을 간단히 살펴보자.

여기서에서는 headForHeaders() API를 사용해보자

@Test
    public void givenFooService_whenCallHeadForHeaders_thenReceiveAllHeadersForThatResource() throws Exception{
        //given

        //when
        HttpHeaders httpHeaders = restTemplate.headForHeaders(fooResourceUrl);
        //then
        assertThat(MediaType.APPLICATION_JSON).isIn(httpHeaders.getContentType());
    }

Post

새 리소스를 생성하기 위해 postForLocation(),postForObject() 또는 postForEntity() API를 사용할 수 있다.

첫번째는 새로 생성된 리소스의 URI를 반환하고 두번째는 리소스 자체를 반환한다.

postForObject()

@Test
    public void givenFooService_whenPostForObject_thenCreatedObjectIsReturned() throws Exception{
        //given
        HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
        //when
        Foo foo = restTemplate.postForObject(fooResourceUrl, request, Foo.class);
        //then
        assertThat(foo).isNotNull();
        assertThat(foo.getName()).isEqualTo("bar");
    }

postForLocation()

전체 리소스를 반환하는 대신 새로 생성된 리소스의 위치만 반환하는 작업을 살펴보자.

@Test
    public void givenFooService_whenPostForLocation_thenCreatedLocationIsReturned() throws Exception{
        //given
        HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
        //when
        URI location = restTemplate.postForLocation(fooResourceUrl, request);
        //then
        assertThat(location).isNotNull();
    }

exchange()

모든 HTTP 요청 메소드를 지원하며 원하는 서버에 요청시켜주는 메소드

 @Test
    public void givenFooService_whenExchange_thenItIsPosted() throws Exception{
        //given
        HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
        //when
        ResponseEntity<Foo> response = restTemplate.exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);
        //then
        Foo foo = response.getBody();
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(foo.getName()).isEqualTo("bar");
    }
  1. Body 만들기

Body는 보통 key, value의 쌍으로 이루어지기 때문에 자바에서 제공해주는 MultiValueMap 타입을 사용해야한다.

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add('')

MultiValueMap 타입으로 만들어준 변수에 add()를 사용해 보낼 데이터를 추가해준다.

  1. Header 만들기

HTTP POST를 요청할때 보내는 데이터(Body)를 설명해주는 헤더(Header)도 만들어서 같이 보내줘야 한다.

HttpHeaders headers = new HttpHeaders();
headers.add("");

Spring Framework에서 제공해주는 HttpHeaders 클래스는 Header를 만들어준다.

add()를 사용해 Header에 들어갈 내용을 추가해주자.

  1. 헤더와 바디 합치기
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);

Spring Framework에서 제공해주는 HttpEntity 클래스는 Header와 Body를 합쳐준다.

  1. Post 요청
RestTemplate rt = new RestTemplate();

ResponseEntity<String> response = rt.exchange(
                "https://{요청할 서버 주소}", //{요청할 서버 주소}
                HttpMethod.POST, //{요청할 방식}
                entity, // {요청할 때 보낼 데이터}
                String.class {요청시 반환되는 데이터 타입}
);

Spring Framework에서는 서버에 요청을 편하게 하기 위한 RestTemplate 클래스를 제공해준다.

Submit Form Data

다음으로 POST 메서드를 사용하여 데이터를 submit 하는 방법을 살펴보자

먼저 HTTP의 Content-Type 헤더를 application/x-www-form-urlencoded로 설정해야 한다.

이렇게 하면 &로 구분된 이름/값 쌍을 포함하는 큰 쿼리 문자열을 서버로 보낼 수 있다.

HttpHeaders headers = new HttpHeaders();     
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

양식 변수를 LinkedMultiValueMap으로 매핑할수있다.

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("id", "1");

다음으로 HttpEntity 인스턴스를 사용하여 요청을 작성한다.

HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

마지막으로 restTemplate.postForEntity()를 호출하여 Rest 서비스에 연결한다.

 ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl, request, String.class);
        //then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);

정리

@Test
    public void givenFooService_whenFormSubmit_thenResourceIsCreated() throws Exception{
        //given
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("id", "1");
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
        //when
        ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl, request, String.class);
        //then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);

    }

Put

template.put API 가 매우 간단하기 때문에 이 예제에서 구체적으로 exchange() API를 살펴보자.

단순 PUT with exchange()

API에 대한 간단한 PUT 작업으로 시작하고 작업이 클라이언트에 본문을 반환하지 않는다는 점을 염두에 두십시오.

@Test
    public void givenFooService_whenPutExistingEntity_thenItIsUpdated() throws Exception{
        final HttpHeaders headers = prepareBasicAuthHeaders();
        final HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"), headers);

        // Create Resource
        final ResponseEntity<Foo> createResponse = restTemplate.exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);

        // Update Resource
        final Foo updatedInstance = new Foo("newName");
        updatedInstance.setId(createResponse.getBody()
                .getId());
        final String resourceUrl = fooResourceUrl;
        final HttpEntity<Foo> requestUpdate = new HttpEntity<>(updatedInstance, headers);
        restTemplate.exchange(resourceUrl, HttpMethod.PUT, requestUpdate, Void.class);

        //then
    }

PUT exchange() 및 요청 콜백 포함

다음으로 요청 콜백을 사용하여 PUT을 실행할 것이다.

필요한 모든 헤더와 요청 본문을 설정할 수 있는 콜백을 준비해야 한다.


private RequestCallback requestCallback(final Foo updatedInstance) {
        return clientHttpRequest -> {
            final ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(clientHttpRequest.getBody(), updatedInstance);
            clientHttpRequest.getHeaders()
                    .add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            clientHttpRequest.getHeaders()
                    .add(HttpHeaders.AUTHORIZATION, "Basic " + getBase64EncodedLogPass());
        };
    }

다음으로 POST 요청으로 리소스를 생성한다.

 final HttpHeaders headers = prepareBasicAuthHeaders();
        final HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"), headers);

        // Create entity
        ResponseEntity<Foo> response = restTemplate.exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);

그런 다음 리소스를 업데이트 한다.

// Update entity
        final Foo updatedInstance = new Foo("newName");
        updatedInstance.setId(response.getBody()
                .getId());
        final String resourceUrl = fooResourceUrl + '/' + response.getBody()
                .getId();
        restTemplate.execute(resourceUrl, HttpMethod.PUT, requestCallback(updatedInstance), clientHttpResponse -> null);

Delete

기존 리소스를 제거하기 위해 delete() API를 빠르게 사용해보자.

String entityUrl = fooResourceUrl + "/" + existingResource.getId();
restTemplate.delete(entityUrl);

시간 초과 구성

ClientHttpRequestFactory 를 사용하여 RestTemplate 이 시간 초과되도록 구성해보자.

RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());

private ClientHttpRequestFactory getClientHttpRequestFactory() {
    int timeout = 5000;
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
      = new HttpComponentsClientHttpRequestFactory();
    clientHttpRequestFactory.setConnectTimeout(timeout);
    return clientHttpRequestFactory;
}

그리고 추가 구성 옵션에 HttpClient를 사용할 수 있다.

private ClientHttpRequestFactory getClientHttpRequestFactory() {
    int timeout = 5000;
    RequestConfig config = RequestConfig.custom()
      .setConnectTimeout(timeout)
      .setConnectionRequestTimeout(timeout)
      .setSocketTimeout(timeout)
      .build();
    CloseableHttpClient client = HttpClientBuilder
      .create()
      .setDefaultRequestConfig(config)
      .build();
    return new HttpComponentsClientHttpRequestFactory(client);
}

지원 중단

Spring Framework 5부터 WebFlux 스택과 함께 SpringWebClient 라는 새로운 HTTP 클라이언트를 도입했습니다 .

WebClientRestTemplate에 대한 현대적인 대체 HTTP 클라이언트 입니다. 기존의 동기 API를 제공할 뿐만 아니라 효율적인 비차단 및 비동기 접근 방식도 지원합니다.

즉, 새 응용 프로그램을 개발하거나 이전 응용 프로그램을 마이그레이션하는 경우 WebClient 를 사용하는 것이 좋습니다 . 앞으로 RestTemplate 은 향후 버전에서 더 이상 사용되지 않습니다.


참고

https://www.baeldung.com/rest-template
https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-resttemplate
https://withseungryu.tistory.com/116
https://a1010100z.tistory.com/125

0개의 댓글