본문 바로가기

🚀 부트캠프 - PLAYDATA/📒 수업 내용 정리

[Springboot] RestTemplate / 서버간 통신

chapter 12 . 서버 간 통신 

 

1. RestTemplate 

스프링에서 HTTP통신 기능을 손쉽게 사용하도록 설계된 템플릿 (다른 서버로 요청하고 응답받을 수 있도록 클래스를 제공) 

동기방식으로 처리된다. 

RestTemplate 현업에서 많이 쓰이나 지원 중단된 상태 - > webClient 로 대체 

 

 

 

 

2. RestTemplate 동작원리

애플리케이션에서 RestTemplate선언, url과 http메서드, body 등을 설정 

  1. 외부 api로 요청을 보내게되면, RestTemplate에서 HttpMessageConverter를 통해 RequestEntity를 요청 메시지로 변환 
  2. RestTemplate에서는 변환된 메시지를 ClientHttpRequstFactory를 통해 ClientHttpRequest로 가져온 후 외부 api로 요청을 보냄
  3. 외부에서 요청에 대한 응담을 받으면서 RestTemplate은 ResponseErrorHandler로 오류를 확인하고 , 오류가 있다면 ClientHttpResponse에서 응답 데이터를 처리함. 
  4. 받은 응답 데이터가 정상적이라면 다시 한 번 MessageConverter를 거쳐 자바 객체로 변환해서 애플리케이션으로 반환 

RestTemplate 대표적인 메서드 

메서드  HTTP형태  설명 
getForObject GET GET 형식으로 요청한 결과를 객체로 반환
getForEntity GET GET 형식으로 요청한 결과를 ResponseEntity 결과로 반환
postForLocation POST POST형식으로 요청한 결과를 헤더에 저장된 URI로 반환 
postForObject POST POST형식으로 요청한 결과를 객체로 반환 
postForEntity  POST POST형식으로 요청한 결과를 ResponseEntity형식으로 반환
delete DELETE DELETE 형식으로 요청
put PUT PUT 형식으로 요청
patchForObject  PATCH  PATCH 형식으로 요청한 결과를 객체로 반환
optionsForAllow OPTIONS 해당 URI에서 지원하는 HTTP 메서드를 조회
exchange any  HTTP 헤더를 임의로 추가할 수 있고, 어떤 메서드 형식에서도 사용할 수 있음
execute any 요청과 응답에 대한 콜백을 수정

 

RestTemplate 적용 

GET 형식의 RestTemplate 작성 

1) Controller 부분

@RestController
@RequestMapping("/rest-template")
public class RestTemplateController {
    private final RestTemplateService restTemplateService;

    public RestTemplateController(RestTemplateService restTemplateService){
        this.restTemplateService = restTemplateService;
    }

    // 아무 값을 받지 않는 메서드
    @GetMapping
    public String getName() {
        return restTemplateService.getName();
    }
    
    // 이름과 PathValiable 요청받은 메서드
    @GetMapping("/path-variable")
    public String getNameWithPathVariable(){
        return restTemplateService.getNameWithPathVariable();
    }

클래스 생성 

Service bean 주입

RestTemplateService 의 인스턴스를 주입 받아 RestTemplateController 에서 사용할 수 있도록 함

1. 아무 값을 받지 않는 메서드 생성 getName() : uri객체를 생성 

 

 

 

2. Service 부분

@Service
public class RestTemplateService {

    public String getName(){
        URI uri = UriComponentsBuilder	
                .fromUriString("http://localhost:9090")		// 서버주소
                .path("/api/v1/crud-api")	// 요청할 url
                .encode()					// 넣는 값이 없음
                .build()				// build 객체 생성
                .toUri();				// uri 객체로 변환하는 부분
	
//        RestTemplate restTemplate = new RestTemplate();
        RestTemplate restTemplate = restTemplate();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
        // getForEntity() : 파라미터로 전달, URI와 응답받는 타입을 매개변수로 사용 

        return responseEntity.getBody();
    }

    // UriComponentsBuilder : 여러 컴포넌트를 연결해서 URI 형식으로 만드는 기능을 수행
    public String getNameWithPathVariable(){
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api/{name}")
                .encode()
                .build()
                .expand("Flature")
                .toUri();
//        RestTemplate restTemplate = new RestTemplate();
        RestTemplate restTemplate = restTemplate();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);

        return responseEntity.getBody();
    }

 

RestTemplate를 생성하고 사용하는 방법 

: 가장 보편적인 방법은 uriComponentBuilder !!  

  스프링에서 제공하는 클래스 

  여러 파라미터를 연결해서 uri형식으로 만드는 기능을 수행 

  • fromUriString() : 호출부의 URL을 입력하고 이어서 path()에서 세부 경로 입력 
  • encode() : 인코딩 문자셋을 설정 / 인자를 전달안하면 기본 utf-8
  • path() : 메서드 내에 입력한 세부 uri중 중괄호 부분을 사용해 개발 단계에서 쉽게 이해할 수 있는 변수명을 입력
  • expend() : 순서대로 값을 입력하면 됨. 많은 경우 "," 활용
  • build() : 빌더 생성을 종료 
  • toUri() : uri타입으로 리턴. 만약 URI객체를 사용하지 않고 String타입의 URI사용한다면 toUriString()메서드로 대체하여 사용 
  • getForEntity() : 파라미터로 전달, URI와 응답받는 타입을 매개변수로 사용  
 public String getNameWithParameter(){
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api/param")
                .queryParam("name", "Flature")
                .encode()
                .build()
                .toUri();

//        RestTemplate restTemplate = new RestTemplate();
        RestTemplate restTemplate = restTemplate();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
        return responseEntity.getBody();
    }
  • queryParam() : (키, 값) 형식으로 파라미터를 추가

 

POST 형식의 RestTemplate 작성 

1. Controller 

@PostMapping
public ResponseEntity<MemberDto> postDto(){
    return restTemplateService.postWithParamAndBody();
}

@PostMapping("/header")
public ResponseEntity<MemberDto> postWithHeader(){
    return  restTemplateService.postWithHeader();
}

 

 

2. Service 

    public ResponseEntity<MemberDto> postWithParamAndBody(){
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/v1/crud-api")
                .queryParam("name", "Flature")
                .queryParam("email", "Flature@wikibooks.co.kr")
                .queryParam("organization", "Wikibooks")
                .encode()
                .build()
                .toUri();
		// 파라미터에 값을 담는 부분 
        MemberDto memberDto = new MemberDto();
        memberDto.setName("flature!!");
        memberDto.setEmail("flature@gmail.com");
        memberDto.setOrganization("Around Hub Studio");

		// body 에 값을 담는 부분 
//        RestTemplate restTemplate = new RestTemplate();
        RestTemplate restTemplate = restTemplate();
        ResponseEntity<MemberDto> responseEntity = restTemplate.postForEntity(uri, memberDto, MemberDto.class);

        return responseEntity;
    }

POST 형식으로

외부 API에 요청할 때 Body 값과 파라미터 값을 담는 방법 

  • postForEntity() : 서버의 API를 호출하면서 서버 프로젝트 콘솔 로그에는 RequestBody값이 출력, 파라미터 값은 결과값으로 리턴

 

1. GET 요청 (요청 파라미터 없는 경우) / 두번째 서버에서 Flature를 리턴함. 

 

2. GET 요청 (이름과 PathValiable 요청받은 메서드)

 

3. POST 요청 

 

@RequestBody MemberDto request,

@RequestBody 에 담은 DTO 객체의 값은 외부 API 로 가정한 서버 프로젝트 콘솔 창에 출력됨 

1) Controller 

@PostMapping("/header")
public ResponseEntity<MemberDto> postWithHeader(){
    return  restTemplateService.postWithHeader();
}

2) Service 

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("flature");
    memberDto.setEmail("flature@wikibooks.co.kr");
    memberDto.setOrganization("Around Hub Studio");

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

    //RestTemplate restTemplate = new RestTemplate();
    RestTemplate restTemplate = restTemplate();
    ResponseEntity<MemberDto> responseEntity = restTemplate.exchange(requestEntity, MemberDto.class);
    return responseEntity;
}
  • header() : 메서드에서 헤더의 키 이름과 값을 설정하는 코드 
  • exchange() : 모든 형식의 HTTP요청을 생성할 수 있음. 

♥ 헤더를 추가하는 예제

대부분의 외부 API는 토큰 키를 받아 서비스 접근을 인증하는 방식으로 작동함. 

이 때 토큰 값을 헤더에 담아 전달하는 방식이 가장 많이 사용됨. 

헤더를 설정하기 위해서는 RequestBody를 정의해서 사용하는 방법이 가장 편한 방법.

 

RequestEntity 를 생성하고 post()메서드로 URI를 설정한 후,

header() 메서드에서 헤더의 키 이름과 값을 설정하는 코드 

 

대체로 서버 프로젝트의 API명세에는 헤더에 필요한 키 값을 요구하면서 키 이름을 함께 제시하기 때문에 

그에 맞춰 헤더의 값을 설정하면 됨. 

 

 

RestTemplate 커스텀 설정 

RestTemplate는 HttpClient 를 추상화하고 있음. 

HttpClient는 종류에 따라 기능에 차이가 다소 있는데 가장 큰 차이는 Connection Pool

 

RestTemplate는 Connection Pool을 지원하지 않음. 

매번 호출할 때 마다 포트를 열어 커넥션을 생성하게 되는데 

TIME_WAIT 상태가 된 소켓은 다시 사용할 수 없음. 

 

따라서 이 기능을 활성화 하는 가장 대표적인 방법은 

HttpClient로 대체해서 사용하는 방식 

 

RestTemplate 커스텀 설정  

의존성 추가하기 

<!--Http Components  : restTemplate Custom -->
<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);
    factory.setReadTimeout(5000);

    RestTemplate restTemplate = new RestTemplate(factory);

    return restTemplate;
}

RestTemplate의 생성자를 보면 ClientHttpRequestFactory 를 매개변수로 받는 생성자가 존재함. 

-> 

 

ClientHttpRequestFactory는 함수형 인터페이스로

대표적인 구현체로서 SimpleClientHttpRequestFactory 와 HttpComponentsClientHttpRequestFactor가 있음. 

별도의 구현체를 설정해서 전달하지 않으면 HttpAccessor에 구현돼 있는 내용에 의해 SimpleClientHttpRequestFactory를 사용

 

 

HttpComponentsClientHttpRequestFactory를 사용해서 

ClientHttpRequestFactory를 사용하면 

RestTemplate의 Timeout 설정이 가능.

 

HttpComponentsClientHttpRequestFactory는 커넥션 풀을 설정하기 위해

HttpClient를 HttpComponentsClientHttpRequestFactory에 설정할 수 있다. 

 

HttpClient를 생성하는 방법은 두가지가 있는데 

 

 

 

 

 

 

 

 

<!--web flux : Web Client -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>