HTTP Client

calis_ws·2023년 7월 30일
0

클라이언트와 서버

클라이언트 (Client)

클라이언트는 사용자가 웹 브라우저를 통해 접속, 상호작용하는 기기 또는 소프트웨어를 의미한다.
사용자의 요청을 생성하고 서버에 전송하여 데이터를 요청하고 처리된 응답을 받는다.

서버 (Server)

서버는 클라이언트의 요청을 받아 처리하고, 요청에 따른 결과를 클라이언트에게 반환하는 컴퓨터 또는 소프트웨어를 의미한다.
요청을 받고 필요한 데이터베이스 조회, 계산, 파일 접근 등의 작업을 수행한 후 클라이언트에게 결과를 응답한다.

RestTemplate

Spring 프레임워크에서 제공하는 HTTP 클라이언트 라이브러리다.
웹 애플리케이션에서 서버와의 통신을 쉽게 처리할 수 있도록 도와준다.

RESTful 웹 서비스와의 상호작용에 사용되며, HTTP Method 를 사용하여 원격 서버로부터 데이터를 요청하고 응답을 받을 수 있다.

WebClient

Spring 프레임워크에서 제공하는 비동기적인 HTTP 클라이언트 라이브러리다.

RestTemplate의 대체제로 도입되었으며, Spring 5 버전부터 주요 HTTP 클라이언트로 사용되고 있다.

RestTemplate보다 WebClient가 선호되는 이유는?

  1. 비동기 처리

WebClient는 리액티브 스트림(Flux 및 Mono)을 사용하여 비동기적인 요청 처리를 지원한다. 즉, 여러 요청을 동시에 보낼 수 있고, 응답을 기다리는 동안 다른 작업을 수행할 수 있다. 이를 통해 애플리케이션의 성능과 확장성을 향상시킬 수 있다.

  1. 논블로킹 I/O

입출력 작업을 수행할 때, 해당 작업이 완료될 때까지 대기하지 않고 다른 작업을 계속 수행할 수 있다. 이는 리소스를 효율적으로 활용하여 애플리케이션이 더 많은 요청을 처리할 수 있고, 대기 시간이 줄어들어 응답성이 향상된다.

  1. 함수형 프로그래밍

WebClient는 Java 8의 람다식과 함께 사용할 수 있어 함수형 프로그래밍 스타일을 지원한다. 이는 코드의 가독성과 유지보수성을 높인다.

RestTemplate 요청/응답 받기

client 프로젝트 생성

RestTemplate 메서드의 구성

String responseBody = restTemplate.getForObject(url, String.class);

  • get~ : 어떤 메서드로 요청을 보낼 것인지 정함 (get, post 등)

  • ForObject : Body만 응답으로 받을지(ForObject) Header도 같이 응답으로 받을지(FotEntity) 결정

  • (url, String.class) :
    첫번째 인자 → 요청을 보낼 URL
    두번째 인자 → 응답을 어떤 객체로 역직렬화 할것인지 지정

GET 요청 보내기

  • GET 요청을 보내 응답의 Body만 String 타입으로 역직렬화 하기
public void getBeerObject() {
    // RestTemplate: Spring에서 제공하는 기본 HTTP Client
    RestTemplate restTemplate =new RestTemplate();
    String url = "https://random-data-api.com/api/v2/beers";

    // RestTemplate 로 GET 요청
    String responseBody = restTemplate.getForObject(url, String.class);
    log.info(responseBody);

GET 요청을 보내 응답의 Header와 Body 모두 받아 Body는 BeerDto 타입으로 역직렬화

public void getBeerEntity() {
    // RestTemplate: Spring에서 제공하는 기본 HTTP Client
    RestTemplate restTemplate =new RestTemplate();
    String url = "https://random-data-api.com/api/v2/beers";

    // RestTemplate 로 GET 요청
    ResponseEntity<BeerGetDto> responseBody = restTemplate.getForEntity(url, BeerGetDto.class);
    log.info(responseBody);
}

인증정보 같은 경우 Header에 포함되어있기에 헤더를 읽기 위한 용도로 사용되기도 함

Post 요청 보내기

  • Post 요청에 BeerPostDto를 보내고 응답은 Body만 String 타입으로 역직렬화하기
//postForObject
    public void postBeerObject() {
        RestTemplate restTemplate = new RestTemplate();
        String url = "http://localhost:8081/give-me-beer";

        BeerPostDto dto = new BeerPostDto();

        // post 요청을 보낼때 responseBody 같이 전달
        String responseBody = restTemplate.postForObject(
                url, // URL
                dto, // request BOdy
                String.class // 응답 해석 타입
        );
        log.info(responseBody);
    }

Delete 메서드와 같이 응답이 없는 요청을 보내는 방법(Void 클래스 사용)

url = "http://localhost:8081/givr-me-beer-204";
ResponseEntity response = restTemplate.postForEntity(
                url,
                dto,
                Void.class
        );
log.info(response.getStatusCode().toString());

WebClient 요청/응답 받기

GET 요청 보내기

@Service
@Slf4j
public class BeerClientService {
    public void getBeer() {
        WebClient webClient = WebClient.builder().build();
        // WebClient는 Builder 패턴처럼 사용

        String url = "https://random-data-api.com/api/v2/beers";
        // 어떤 HTTP 메소드로 요청을 보낼지를 get() post() 메소드 등으로 결정
        // 만일 다른 메소드를 쓰고 싶다면, method()
        String response = webClient.get()  // webClient.method(HttpMethod.GET)
                .uri(url)  // 요청 경로 설정
                .header("x-test", "header")  // 요청 헤더 추가
                // body도 메소드에 따라 추가
                .retrieve()  // 여기 전까지는 요청을 정의한 부분
                // 이제부터 정의하는건 응답을 어떻게 처리할 것인지
                .bodyToMono(String.class)  // 응답이 한번 돌아올것이며, 그 응답의 body를 String으로 해석
                .block();  // 동기식으로 처리하겠다.
        log.info(response);
    }
}

POST 요청 보내기

public void postBeer() {
        WebClient webClient = WebClient.builder().build();
        String url = "http://localhost:8081/give-me-beer";

        BeerPostDto dto = new BeerPostDto();
        // post 요청 시작
        MessageDto responseBody = webClient.post()
                .uri(url)  // url 정의
                .bodyValue(dto)  // requestBody 정의
                .retrieve()  // 응답 정의 시작
                .bodyToMono(MessageDto.class)  // 응답 데이터 정의
                .block();  // 동기식 처리

        log.info(responseBody.toString());
    }

    public void postBeer204(){
        WebClient webClient = WebClient.builder().build();
        String url = "http://localhost:8081/give-me-beer-204";

        BeerPostDto dto = new BeerPostDto();
        ResponseEntity<Void> response = webClient.post()
                .uri(url)
                .bodyValue(dto)
                .retrieve()
                .toBodilessEntity()  // 응답 Body가 없을 경우 사용
                .block();

        log.info(response.getStatusCode().toString());
    }

Interface 기반 DI

BeerClient 인터페이스

  • BeerClient 인터페이스를 구현한 BeerRestClient, BeerWebClient로 HTTP Client를 사용
  • BeerService에서는 BeerClient 인터페이스를 주입받아 HTTP Client가 변경돼도 BeerService 코드를 변경할 필요가 없다.
package com.example.client.client;

import com.example.client.dto.BeerGetDto;

public interface BeerClient {
    BeerGetDto getBeer();
}

BeerClient 인터페이스를 구현

  • BeerRestClient 클래스
import com.example.client.dto.BeerGetDto;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
@Qualifier("beerRestClient")
public class BeerRestClient {
		// BeerRestService -> getBeerObject() 메서드에서 return type만 BeerGetDto로 수정
    public BeerGetDto getBeer() {
        RestTemplate restTemplate = new RestTemplate();
        String url = "https://random-data-api.com/api/v2/beers";
        return restTemplate.getForObject(url, BeerGetDto.class);
    }
}
  • BeerWebClient같은 인터페이스의 구현체 클래스가 두 개 이상이 빈으로 등록 될 경우

    • @Primary : Spring 프레임워크에서 의존성을 주입할 때, 우선적으로 선택할 Bean을 지정하는 어노테이션

    • 우선적으로 BeerWebClient가 BeerService에 주입되도록 명시했다.

package com.example.client.client;

import com.example.client.dto.BeerGetDto;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

@Component
@Primary
@Qualifier("beerWebClient")
public class BeerWebClient implements BeerClient {
    public BeerGetDto getBeer() {
        WebClient webClient = WebClient.builder().build();
        String url = "https://random-data-api.com/api/v2/beers";

        return webClient.get()
                .uri(url)
                .retrieve()
                .bodyToMono(BeerGetDto.class)
                .block();
    }
}

BeerService에 BeerClient 인터페이스를 주입한다.

  • @Qualifier : 여러 구현체 중 우선순위를 지정해주는 어노테이션
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class BeerService {
    private final BeerClient client;

		public BeerService(@Qualifier("beerRestClient") BeerClient client) {
        this.client = client;
    }

    public void drinkBeer() {
        log.info("order beer");
				BeerGetDto result = client.getBeer();
        log.info("{}는 맛있다", result.getName());
    }
}

@Qualifier@Primary 어노테이션

Spring boot에서 어노테이션을 사용해 자동으로 빈을 등록하는 경우, 같은 인터페이스 구현체 클래스가 두 개 이상 빈으로 등록되면 의존성 주입 시 오류가 발생한다.

이 경우 Spring boot에 어떤 빈을 의존성 주입해야 하는지 명시해줘야 한다.

@Qualifier 사용 방법

  • @Qualifier 로 빈에 추가 구분자를 붙여주는 방법

  • 의존성을 주입할 클래스의 생성자에 @Qualifier로 해당 구분자를 명시해주면 그 구분자를 가진 빈을 주입해준다.

  • 명시한 구분자를 가진 빈이 없으면, 그 값과 동일한 이름을 가진 스프링 빈이 있는지 탐색한다. 하지만 코드의 명확성을 위해 빈 이름과 Qualifier는 별도로 관리하는 것이 좋다.

@Primary 사용 방법

  • 여러 빈이 있을 때, 기본적으로 의존성 주입될 빈에 @Primary 어노테이션으로 명시해주는 방법

  • 선택될 빈에 만 @Primary를 붙이면 된다.

두 방법을 같이 사용할 수 있다. (메인 기능의 빈에 @Primary, 서브 기능의 빈에 @Qualifier를 붙이는 방식)

출처 : 멋사 5기 백엔드 위키 9팀 9글, 15팀 1&&Only

profile
반갑습니다람지

0개의 댓글