Book/스프링부트 핵심가이드

스프링부트를 이용한 서버 간의 통신(of. RestTemplate)

블로그 주인장 2023. 12. 1.

서버 간의 통신

다른 서버가 API를 호출해서 사용할 수 있게 구성되어 웹 요청을 보내고 응답 받을 수 있게 도와주는 "RestTemplate"에 대해 알아보겠습니다.


RestTemplate


  • 스프링에서 HTTP 통신 기능을 손쉽게 사용하도록 설계된 템플릿이다.
  • HTTP 서버와의 통신을 단순화한 해당 템플릿을 사용하면 RESTful 원칙을 따르는 서비스를 편리하게 만들 수 있다.
  • 기본적으로 동기 방식으로 처리되며, 비동식 방식으로 사용하고 싶은 경우 AsyncRestTemplate을 사용하면 된다.
  • 다만 현업에서는 많이 쓰이나 지원 중단된 상태라서 WebClient 방식도 함께 알아둘 것을 권장한다.

 

RestTemplate의 특징

  1. HTTP 프로토콜의 메서드에 맞는 여러 메서드를 제공한다
  2. RESTful 형식을 갖춘 템플릿이다
  3. HTTP 요청 후에 JSON, XML, 문자열 등의 다양한 형식으로 응답을 받을 수 있다.
  4. 블로킹(blocking) I/O 기반의 동기 방식을 사용한다.
  5. 다른 API를 호출할 때 HTTP 헤더에 다양한 값을 설정할 수 있다.

 

RestTemplate의 동작 원리

RestTemplate 동작 방식

  • 애플리케이션
    1. 우리가 직접 작성하는 애플리케이션 코드 구현부를 의미한다.
    2. 애플리케이션에서는 RestTemplate을 선언하고 URI 와 HTTP 메서드, Body 등을 설정한다.
  • RestTemplate
    1. 외부 API로 요청을 보내게 되면 HttpMessageConverter 를 통해 RequestEntity를 요청 메시지로 변환한다.
    2. 변환된 요청 메시지를 ClientHttpRequestFactory를 통해 ClientHttpRequest로 가져온 후 외부 API로 요청을 보낸다.
    3. 외부에서 요청에 대한 응답을 받으면 ResponseErrorHandler로 오류를 확인하고, 오류가 발생하면 ClientHttpResponse 에서 응답 데이터를 처리한다.
    4. 응답 데이터가 정상적이라면 다시 한 번 HttpMessageConverter를 거쳐 자바 객체로 변환 후 애플리케이션으로 반환한다.

 

RestTemplate 제공 메서드

 

RestTemplate 예시


@Controller
@RequestMapping("/api/v1/crud-api")
public class CrudController {

    @GetMapping
    public String getName() {
        return "crud-api";
    }

    @GetMapping(value = "/{variable}")
    public String getVariable(@PathVariable String variable) {
        return variable;
    }

    @GetMapping("/param")
    public String getNameWithParam(@RequestParam String name) {
        return "Hello" + name + "!";
    }

    @PostMapping
    public ResponseEntity<MemberDto> getMember(
            @RequestBody MemberDto request,
            @RequestParam String name,
            @RequestParam String email,
            @RequestParam String organization
    ) {
        System.out.println(request.getName());
        System.out.println(request.getEmail());
        System.out.println(request.getOrganization());

        MemberDto memberDto = new MemberDto();
        memberDto.setName(name);
        memberDto.setEmail(email);
        memberDto.setOrganization(organization);
        
        return ResponseEntity.status(HttpStatus.OK).body(memberDto);
    }
    
    @PostMapping("/add-header")
    public ResponseEntity<MemberDto> addHeader(
            @RequestHeader("my-header") String header,
            @RequestBody MemberDto memberDto
    ) {
        System.out.println(header);
        return ResponseEntity.status(HttpStatus.OK).body(memberDto);
    }
}

 

RestTemplate 구성


  1. RestTemplate은 별도의 유틸리티 클래스로 생성하거나 서비스 또는 비즈니스 계층으로 구현된다.
  2. 서버 프로젝트에 요청을 날리기 위해 서버의 역할을 수행하면서 다른 서버로 요청을 보내는 클라이언트 역할도 수행한다.
  3. 클라이언트는 서버를 대상으로 요청을 보내고 응답을 받는 역할을 한다.

 

GET 형식의 RestTemplate

@Service
public class RestTemplateService {

    public String getName() {
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api")
                .encode()
                .build()
                .toUri();

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);

        return responseEntity.getBody();
    }

    public String getNameWithPathVariable() {
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api/{name}")
                .encode()
                .build()
                .expand("test") //복수의 값을 넣어할 경우 (,) 추가하여 구분한다.
                .toUri();

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);

        return responseEntity.getBody();
    }

    public String getNameWithPathParameter() {
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api/param")
                .queryParam("name", "test")
                .encode()
                .build()
                .toUri();

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);

        return responseEntity.getBody();
    }
}
  • RestTemplate를 생성하고 사용하는 가장 보편적인 방법은 UriComponentsBuilder를 사용하는 방법이다.
  • UriComponentsBuilder
    1. 스프링 프레임워크에서 제공하는 클래스로 여러 파라미터를 연결해서 URI 형식으로 만드는 기능을 수행한다.
    2. UriComponentsBuilder는 빌더 형식으로 객체를 생성한다.
    3. fromUriString( ) 메서드에서는 호출부의 URL을 입력한다.
    4. path( ) 메서드에 세부 경로를 입력한다.
    5. encode( ) 메서드는 인코딩 문자셋을 설정할 수 있는데, 인자를 전달하지 못하면 기본적으로 UTF-8 형식으로 실행된다.
    6. getForEntity( ) 에 URI와 응답받는 타입을 매개변수로 사용한다.

 

POST 형식의 RestTemplate

@Service
public class RestTemplateService {

    public ResponseEntity<MemberDto> postWithParamAndBody() {
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api")
                .queryParam("name", "test")
                .queryParam("email", "test@gmail.com")
                .queryParam("organization", "notebook")
                .encode()
                .build()
                .toUri();

        MemberDto memberDto = new MemberDto();
        memberDto.setName("test!!");
        memberDto.setEmail("test@gmail.com");
        memberDto.setOrganization("studio");

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<MemberDto> responseEntity
                = restTemplate.postForEntity(uri, memberDto, MemberDto.class);

        return responseEntity;
    }

    public ResponseEntity<MemberDto> postWithHeader() {
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api/add-header")
                .encode()
                .build()
                .toUri();

        MemberDto memberDto = new MemberDto();
        memberDto.setName("test");
        memberDto.setEmail("test@gmail.com");
        memberDto.setOrganization("studio");

        RequestEntity<MemberDto> requestEntity = RequestEntity
                .post(uri)
                .header("my-header", "API")
                .body(memberDto);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<MemberDto> responseEntity
                = restTemplate.exchange(responseEntity, MemberDto.class);

        return responseEntity;
    }
}
  • POST 형식으로 외부 API에 요청하는 방법
    • 파라미터에 값을 추가하는 작업과 RequestBody에 값을 담는 작업이 있다.
    • RequestBody에 값을 담기 위해서는 데이터 객체를 생성한다.
    • postForEntity( ) 메서드를 사용하는 경우에는 파라미터로 데이터 객체를 넣으면 된다.
  • 대부분 외부 API는 토큰키를 받아 서비스 접근을 인증하는 방식으로 작동되는데, 이 때 토큰 값을 헤더에 담아 전달하는 방식으로 사용된다.
  • 헤더를 설정하기 위해서는 RequestEntity를 정의해서 사용하는 방법이 가장 편한 방법이다.
  • RequestEntity 를 생성하고 post( ) 메서드로 URI를 설정한 후에 header( ) 메서드에서 헤더의 키 이름과 값을 설정하면 된다.
  • RequestEntity 설정에서 post( ) 메서드 대신에 다른 형식의 메서드로 정의만 하면 exchange( ) 메서드로 쉽게 사용이 가능하다.

 

RestTemplate 커스텀 설정


  • RestTemplate은 HTTPClient를 추상화한다.
  • HttpClient의 종류에 따라 기능에 차이가 있는데 가장 큰 차이는 커넥션 풀(Connection Pool) 이다.
  • RestTemplate은 기본적으로 커넥션 풀을 지원하지 않는다. 
  • 지원하지 않으면 매번 호출할 때마다 포트를 열어 커넥션을 생성하게 된다.
  • TIME_WAIT 상태가 된 소켓을 다시 사용하려고 하면 재사용하지 못하게 된다.
  • 해당 기능을 활성화하는 가장 대표적인 방법은 아파치에서 제공하는 HttpClient로 대체하는 방법이다.

 

의존성 추가(Maven -> pom.xml)

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

 

커스텀 RestTemplate 객체 생성 메서드

public RestTemplate restTemplate() {
    HttpComponentsClientHttpRequestFactory factory
            = new HttpComponentsClientHttpRequestFactory();

    HttpClient client = HttpClientBuilder.create()
            .setMaxConnTotal(500)
            .setMaxConnPerRoute(500)
            .build();

    CloseableHttpClient httpClient = HttpClients.custom()
            .setMaxConnTotal(500)
            .setMaxConnPerRoute(500)
            .build();

    factory.setHttpClient(httpClient);
    factory.setConnectTimeout(2000);

    return new RestTemplate(factory);
}

 

반응형

댓글