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

[스프링 부트 핵심 가이드] 12 서버 간 통신

2023. 10. 8. 22:18
목차
  1. 목차
  2. 12.1  RestTemplate이란?
  3. 12.2 RestTemplate 사용하기
  4. 12.3 WebClient란?
  5. 12.4 WebClient 사용하기
  6.  

목차

12 서버 간 통신

ㄴ12.1  RestTemplate이란?

    ㄴ12.1.1  RestTemplate의 동작원리

    ㄴ12.1.2  RestTemplate의 대표적인 메서드

ㄴ12.2 RestTemplate 사용하기

    ㄴ12.2.1 서버 프로젝트 생성하기

    ㄴ12.2.2  RestTemplate 구현하기

    ㄴ12.2.3  RestTemplate 커스텀 설정

ㄴ12.3 WebClient란?

    ㄴ12.3.1 WebClient 구성

ㄴ12.4 WebClient 사용하기

    ㄴ12.4.1 WebClient 구현

ㄴ12.5 정리

 

 

현재 개발되는 서비스들은 MSA(Micro Service Architecture)를 주로 채택하여 내가 만들고 있는 서비스가 요청을 받는 서버가 될 수 있고 다른 서비스로 요청을 할 수 있는 클라이언트가 되는 경우도 많다. 이럴 때 각 애플리케이션은 자신이 가진 API 기능을 외부에 노출하고, 다른 서버에서 그렇게 노출된 API를 호출해서 사용하게 된다.

이렇게 다른 서버로 웹 요청을 보내고 응답을 받을 수 있게 도와주는 것들이 Client 라이브러리 or Rest Client라고 불리운다.

그 종류로는 RestTemplate, WebClient, 현재는 HTTP interface 등이 나와있다.

 

  • RestTemplate
    • Spring 3부터 지원, REST API 호출이후 응답을 받을 때까지 기다리는 동기 방식
  • AsyncRestTemplate
    • Spring 4에 추가된 비동기 RestTemplate
  • WebClient
    • Spring 5에 추가된 논블럭, 리엑티브 웹 클라이언트로 동기, 비동기 방식을 지원

현재 RestTemplate는 완전히 deprecated된 상태라고 볼 순 없으나 WebClient를 더 지향하라고 하는 추세긴 하나,

 각 현업에서 사용하는 것을 잘 숙지하고 사용하면 될 듯하다. 

 

12.1  RestTemplate이란?

RestTemplate 특징

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

 

RestTemplate의 동작 원리

 

  • 애플리케이션에서 RestTemplate 선언 한 후 URI, HTTP 메서드, Body를 설정한뒤 외부 API로 요청보냄 
  • RestTemplate에서 HttpMessageConverter를 통해 RequestEntity를 요청 메시지로 변환함
  • 변환된 요청 메세지를 ClientHttpRequestFactory를 통해 ClientHttpRequest로 가져온 후 외부 API로 요청을 보냄.
  • 외부에서 요청에 대한 응답을 받으면 ResponseErrorHandler로 오류를 확인하고 오류가 있다면 ClientHttpResponse에서 응답 데이터를 처리함
  • 응답데이터에 오류가 없다면 HttpMessageConverter를 거쳐 자바 객체로 변환한 후 애플리케이션으로 반환한다.

 

RestTemplate의 대표적인 메서드

Method HTTP Method Return Type 설명
getForObject() GET Object GET 요청에 대한 결과를 객체로 반환합니다
getForEntity() GET ResponseEntity GET 요청에 대한 결과를 ResponseEntity로 반환합니다
postForLocation() POST URI POST 요청에 대한 결과로 헤더에 저장된 URI 반환합니다
postForObject() POST Object POST 요청에 대한 결과를 객체로 반환합니다
postForEntity() POST ResponseEntity POST 요청에 대한 결과를 ResponseEntity로 반환합니다
put() PUT void PUT 요청을 실행합니다
patchForObject() PATCH Object PATCH 요청을 실행하고 결과를 객체로 반환합니다
delete() DELETE void DELETE 요청을 실행합니다
headForHeaders() HEADER HttpHeaders 헤더 정보를 추출하고 HTTP HEAD 메서드를 사용합니다
optionsForAllow() OPTIONS Set<HttpMethod> 지원되는 HTTP 메서드를 추출합니다
exchange() any ResponseEntity 헤더를 생성하고 모든 요청 방법을 허용합니다
execute() any T 요청/응답 콜백을 수정합니다

 

12.2 RestTemplate 사용하기

의존성 추가

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-web'

 

실습을 할 때에는 한 컴퓨터에 프로젝트 2개를 가동시켜야하기 때문에 한 프로젝트의 톰캣 포트를 변경해야한다.

serer.port=9090

 

컨트롤러

> PUT, DELETE는 GET, POST 형식과 구성방식이 같으므로 생략했으며 GET, POST 메서드만 구현함.

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

    @GetMapping
    public String getName(){
        return "Flature";
    }

    @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(value = "/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);
    }
}

 

MemberDto

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class MemberDto {

    private String name;
    private String email;
    private String organization;
    
     @Override
    public String toString() {
        return "MemberDto{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", organization='" + organization + '\'' +
                '}';
    }

}

 

 

RestTemplate 구현하기

 

일반적으로 RestTemplate은 별도의 유틸리티 클래스로 생성하거나, 서비스 또는 비지니스 계층에 구현된다.

앞서 생성한 서버 프로젝트에 요청을 날리기 위해 서버의 역할을 수행하면서 다른 서버로 요청하는 클라이언트 역할도 수행 할 수 있는 새로운 프로젝트를 생성한다.

 

  • 클라이언트로부터 요청을 받는 컨트롤러
  • RestTemplate을 활용해 다른 서버에 통신 요청을 하는 서비스

이 두가지 계층을 작성한다.

 

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("Flature") // 복수의 값을 넣어야 할 경우 ,를 추가하여 구분
                .toUri();

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

        return responseEntity.getBody();
    }

    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();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);

        return responseEntity.getBody();
    }
}

 

POST 형식의 RestTemplate 작성

@Service
public class RestTemplateService {

//
    
    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");

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

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

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

        return responseEntity;
    }
    
}

 

API 호출을 쉽게 사용하기 위해 Swagger를 설정한다.

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

    @Bean
    public Docket api(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.springboot.rest"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("Spring Boot Open API Test with Swagger")
                .description("설명 부분")
                .version("1.0.0")
                .build();
    }
}

 

클라이언트로부터 요청 받고 위의 서비스 코드를 연결하는 컨트롤러 코드 예제 >

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

    @GetMapping("/path-variable")
    public String getNameWithPathVariable(){
        return restTemplateService.getNameWithPathVariable();
    }

    @GetMapping("/parameter")
    public String getNameWithParameter(){
        return restTemplateService.getNameWithParameter();
    }

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

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

}

 

 

RestTemplate도 커스텀 설정이 가능한데 보통 아파치에서 제공하는 HttpClient로 대체해서 사용하는 방식을 사용한다.

HttpClient를 사용하기 위해 의존성을 추가해야한다.

 

12.3 WebClient란?

WebClient 특징

  • 논블로킹(Non-blocking), I/O 기반의 동기 방식 사용
  • 리액티브 스트림(Reactive Streams)의 백 프레셔(Back Pressure)를 지원
  • 적은 하드웨어 리소스로 동시성을 지원
  • 함수형 AI를 지원
  • 동기, 비동기 상호작용을 지원
  • 스트리밍을 지원

 

WebClient를 사용하기 위해서는 WebFlux 모듈에 대한 의존성을 추가해야한다.

의존성 추가

<dependencies>

	<dependency>
    	<groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependecy>

</dependencies>

12.4 WebClient 사용하기

WebClient를 생성하는 방법

  • create() 메서드를 이용한 생성
  • builcer()를 이용한 생성

 

WebClient를 활용한 GET 요청 ex>

@Service
public class WebClientService {
    public String getName() {
        WebClient webClient = WebClient.builder()
                .baseUrl("http://localhost:9090")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();

        return webClient.get()
                .uri("/api/v1/crud-api")
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }

    public String getNameWithPathVariable(){
        WebClient webClient = WebClient.create("http://localhost:9090");

        ResponseEntity<String> responseEntity = webClient.get()
                .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/{name}")
                        .build("Flature"))
                .retrieve().toEntity(String.class).block();

        return responseEntity.getBody();
    }

    public String getNameWithParameter(){
        WebClient webClient = WebClient.create("http://localhost:9090");

        return webClient.get().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                .queryParam("name", "Flature")
                .build())
                .exchangeToMono(clientResponse -> {
                    if(clientResponse.statusCode().equals(HttpStatus.OK)){
                        return clientResponse.bodyToMono(String.class);
                    } else{
                        return clientResponse.createException().flatMap(Mono::error);
                    }
                })
                .block();
    }
}

 

 

WebClient를 활용한 POST요청 ex>

public ResponseEntity<MemberDto> postParamAndBody(){
        WebClient webClient = WebClient.builder()
                .baseUrl("http://localhost:9090")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();

        MemberDto memberDto = new MemberDto();
        memberDto.setName("flature!!");
        memberDto.setEmail("flature@gmail.com");
        memberDto.setOrganization("Around Hub Studio");

        return webClient.post().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                .queryParam("name", "Flature")
                .queryParam("email", "flature@wikibooks.co.kr")
                .queryParam("organization", "Wikibooks")
                .build())
                .bodyValue(memberDto)
                .retrieve()
                .toEntity(MemberDto.class)
                .block();
    }

    public ResponseEntity<MemberDto> postWithHeader(){
        WebClient webClient = WebClient.builder()
                .baseUrl("http://localhost:9090")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();

        MemberDto memberDto = new MemberDto();
        memberDto.setName("flature!!");
        memberDto.setEmail("flature@gmail.com");
        memberDto.setOrganization("Around Hub Studio");

        return webClient
                .post()
                .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/add-header")
                        .build())
                .bodyValue(memberDto)
                .header("my-header", "Wikibooks API")
                .retrieve()
                .toEntity(MemberDto.class)
                .block();
    }

 

 

자세한 WebClient의 사용법은 공식 문서에서 확인 가능 

> https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html

 

WebClient :: Spring Framework

Spring WebFlux includes a client to perform HTTP requests with. WebClient has a functional, fluent API based on Reactor, see Reactive Libraries, which enables declarative composition of asynchronous logic without the need to deal with threads or concurrenc

docs.spring.io

 

 

반응형

'BookStudy > 스프링 부트 핵심 가이드' 카테고리의 다른 글

스프링 부트 핵심 가이드 13. 서비스의 인증과 권한 부여  (1) 2023.10.15
[스프링 부트 핵심 가이드] 11 액추에이터 활용하기  (1) 2023.10.08
[스프링 부트 핵심 가이드] 10 유효성 검사와 예외 처리  (0) 2023.10.01
[스프링 부트 핵심 가이드] 09 연관관계 매핑  (0) 2023.09.23
[스프링 부트 핵심 가이드] 08. Spring Data JPA 활용  (0) 2023.09.17
  1. 목차
  2. 12.1  RestTemplate이란?
  3. 12.2 RestTemplate 사용하기
  4. 12.3 WebClient란?
  5. 12.4 WebClient 사용하기
  6.  
'BookStudy/스프링 부트 핵심 가이드' 카테고리의 다른 글
  • 스프링 부트 핵심 가이드 13. 서비스의 인증과 권한 부여
  • [스프링 부트 핵심 가이드] 11 액추에이터 활용하기
  • [스프링 부트 핵심 가이드] 10 유효성 검사와 예외 처리
  • [스프링 부트 핵심 가이드] 09 연관관계 매핑
three von
three von
어려워 보이는 프로그래밍 언어를 쉽게 정복하는 블로그
반응형
three von
LangEASY : 프로그래밍 언어를 쉽게 정복하는 공간
three von
전체
오늘
어제
  • 분류 전체보기 (89)
    • BackEnd (5)
    • JAVA (5)
      • 기초개념 (5)
    • 자료구조 & 알고리즘 (7)
      • 기초수학 (0)
      • 선형 자료구조 (4)
      • 비선형 자료구조 (1)
      • 알고리즘 (1)
    • CS (18)
      • 컴퓨터구조 (0)
      • 운영체제 (3)
      • 시스템 소프트웨어 (0)
      • 네트워크 (4)
      • 디자인패턴 (10)
    • 데이터베이스 (4)
    • Spring (4)
    • Project (2)
      • 팀프로젝트 (1)
      • 토이프로젝트 (1)
    • 회고 (0)
    • Git&Github (8)
    • IntelliJ (5)
    • 코테 (16)
      • 프로그래머스 (10)
      • 백준 (6)
    • BookStudy (12)
      • 스프링 부트 핵심 가이드 (12)
    • C++ (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자바 자료구조 힙
  • githubTest
  • 코테
  • vi/vim에디터사용
  • java heap 자료구조
  • 윈도우에서 리눅스 명령어
  • InteliJ에서 gitbash사용
  • 백엔드공부
  • 백엔드 스쿨
  • 제로베이스백엔드스쿨
  • github
  • vi/vim
  • 리눅스 명령어 윈도우 cmd창에서 가능
  • 개발자
  • 자바 자바해시맵
  • 제로베이스
  • 깃 이슈관리
  • Java
  • IntelliJ 자동화
  • heap 자료구조
  • github이슈관리
  • 백엔드
  • LiveTemplate사용
  • 제로베이스백엔드스쿨미니과제
  • 명령어변환
  • 인텔리제이에서 gitbash로 vi vim 에디터 사용하는법
  • spring
  • windowcmd창
  • 백엔드스쿨
  • 자바 선형자료구조

최근 댓글

최근 글

hELLO · Designed By 정상우.
three von
[스프링 부트 핵심 가이드] 12 서버 간 통신
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.