WebClient와 RestTemplate

niz w·2025년 1월 9일

Spring

목록 보기
12/17

✨ 전개

소셜 로그인을 작업하다보니 참고하는 블로그마다 WebClientRestTemplate, HttpUrlConnection 등 다양하게 존재했다. 각각 어떻게 사용하는지, 차이점, 장단점을 비교해서 적용해보고자 정리하기 시작했다😉

일단 HttpURLConnection은 코드가 복잡하고 권장되지 않는다 하여... 후순위로 간단하게 적어보려고 한다.


간단히 비교부터 하고 들어가면...

특징 HttpURLConnection RestTemplate WebClient
레벨 낮은 수준 API 고수준 추상화 API 반응형 고수준 API
블로킹/논블로킹 블로킹 블로킹 논블로킹
비동기 지원 X X O
Spring Integration 직접 구현 필요 Spring에서 기본 제공 Spring 5+ 권장
권장 여부 권장되지 않음 Spring 5 이후 비권장 Spring 5+ 권장
코드 간결성 복잡함 간결함 간결함

📣 블로킹

  • 요청을 보낸 후 서버의 응답이 도착할 때까지 해당 thread가 멈춘 상태로 대기하는 것이다.
  • 동기식의 경우 블로킹관 연관되는데, 요청이 느리거나 네트워크 지원이 발생하면 리소스가 낭비될 수 있다.


    📣 논블로킹
  • 요청 보낸 후 즉시 thread가 반환된다.
  • 응답이 완료되면 별도의 callback 또는 반응형 데이터 스트림으로 처리한다.
  • 비동기와 연관되며, 자원을 효율적으로 사용할 수 있고 고성능이나 대규모 트래픽 처리 시 적합하다.



✨ HttpURLConnection


개념

  • Java의 기본 HTTP 통신 라이브러리로, java.net 패키지에 포함된다.
  • HTTP 요청과 응답 처리를 직접 구현해야 한다.
  • 설정과 처리가 복잡하고, 비동기 지원이 부족하다.
  • 간단한 작업에는 적합하지만 확장성과 사용성이 부족하다.

예시

1. GET 요청

public class HttpUrlConnectionEx {

	public static void main(String[] args) throws Exception {
    	// 호출하려는 API의 엔드포인트 URL 객체 생성
    	URL url = new URL("https://[그 외 url 주소]");
        
        HttpURLConnection comm = (HttpURLConnection) url.openConnection();
        // 요청 메서드 설정 - GET, POST 등
        conn.setRequestMethod("[해당 설정 값]");
        // header 부분 설정 (Content-Type이나 토큰 용 Authorization 등)
        conn.setRequestProperty("Content-Type", "[해당 설정 값]");
        conn.setRequestProperty("Authorization", "[해당 설정 값]"):
        
        /* body에 내용을 담아 전송하는 경우, 이 부분에 넣기!!*/
        
        // 서버로부터의 응답 코드 확인 (200: 성공, 404: 리소스 없음, 500: 서버 오류 등)
        int responseCode = conn.getResponseCode();
        
        if(responseCode == 200) {
        	// BufferedReader를 통해 받아온 데이터를 줄 단위로 읽기
        	BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            
            String inputLine;
            StringBuilder response = new StringBuilder();
            
            // 한 줄씩 읽어와서 StringBuilder 객체에 추가
            while((inputLine = br.readLine()) != null) {
            	response.append(inputLine);
            }
            
            System.out.println("Response : " + response.toString());
            br.close();
            
        } else {
        	System.out.println("Failed to fetch data. HTTP Response Code : " + responseCode);
        }
        
        conn.disconnection();
    }
}


2. POST 요청


카카오 로그인 당시 사용한 부분을 예시로 들면,
body 내용을 String을 만들어서 OutputStream을 통해 전송하면 된다.

일단 conn.setDoOutput(true);는 반드시 설정해주어야 전송 데이터를 넣을 수 있다.


1. String 형태는 위의 Content-Typeconn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 이렇게 설정해야 한다!!!

String body = "grant_type=authorization_code"
               + "&client_id=" + "your_client_id"
               + "&redirect_uri=" + "your_redirect_uri"
               + "&code=" + "your_code"
               + "&client_secret=" + "your_client_secret";

2. JSON 타입conn.setRequestProperty("Content-Type", "application/json");

요청 부분
{
     "grant_type": "authorization_code",
     "client_id": "your_client_id",
     "redirect_uri": "your_redirect_uri",
     "code": "your_code",
    "client_secret": "your_client_secret"
}

String body = response.toString();

3. 객체 타입conn.setRequestProperty("Content-Type", "application/json");

RequestDto requestDto = new RequestDto();
requestDto.setGrant_type("authorization_code");
requestDto.setClient_id("your_client_id");
requestDto.setRedirect_uri("your_redirect_uri");
requestDto.setCode("your_code");
requestDto.setClient_secret("your_client_secret");

위와 같이 객체를 코드 안에서 생성하거나, 파라미터로 들어온 경우에는

ObjectMapper objectMapper = new ObjectMapper();
String body = objectMapper.writeValueAsString(requestDto);

객체를 json 타입의 String으로 변환하여 넣어주면 된다!
2번의 json을 넣어주듯 작업하면 된다.



이렇게 설정된 String 타입의 body를 아래 내용에 담아 전송하면 된다!!

        // OutputStream을 사용하여 Body 데이터 전송
        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = body.getBytes("utf-8");
            os.write(input, 0, input.length);
        }



✨ RestTemplate


개념

  • Spring Framework에서 제공하는 동기식 HTTP 클라이언트이다.
  • API 요청과 응답을 추상화하여, 사용자가 쉽게 요청 보낼 수 있다.
  • 다양한 HTTP 메서드와 매핑을 지원한다.
  • 요청이 완료될 때까지 블로킹 상태이다.
  • 요청과 응답에 대해 객체 직렬화와 역직렬화를 자동 처리한다.
  • Spring 5.0 이후로는 비권장이므로, WebClient를 많이 사용한다고 한다.

예시

1. GET 요청

public class RestTemplateEx {
	public static void main(String[] args) {
    
    	RestTemplate restTemplate = new RestTemplate();
        String url = "https://[그 외 url 주소]";
        String response = restTemplate.getForObject(url, String.class);
        
   }
}

2. POST 요청

POSTHeader를 설정하고, Body를 구성하는 작업 때문에 코드가 더 길어진다.
여기서도 bodyJSON 형태라면 Content-Typeapplication/json으로 넣어주면 된다.

public class RestTemplateEx {
	public static void main(String[] args) {
    
    	// Header 구성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // Body 구성
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "authorization_code");
        body.add("client_id", clientId);
        body.add("redirect_uri", redirectUri);
        body.add("code", code);
        body.add("client_secret", clientSecret);

        // HTTP 생성
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
        
        // RestTemplate로 요청보내기
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                tokenUri,
                HttpMethod.POST,
                request,
                String.class
        );

        String responseBody = response.getBody();
        System.out.println("Response : " + responseBody);
    }
}

🚨 RestTemplate 요청 보내는 부분은 2가지 방식으로 작성이 가능하다!

첫 번째, exchange()는 다양한 설정을 직접 할 수 있다. 그래서 응답을 수동으로 처리할 때 많이 사용한다.

ResponseEntity response = rt.exchange(
     tokenUri,
     HttpMethod.POST,
     request,
     String.class
);

두 번째, postForEntity()는 POST 요청만을 처리하며, 요청과 응답이 단순한 경우에 간결하게 사용한다.

ResponseEntity responseEntity = restTemplate.postForEntity(
     tokenUri,
     requestEntity,
     String.class
);


위에서 사용한 LinkedMultiValueMap은 왜, 언제 쓰는지 해당 글에 간단히 적어두었다😉




✨ WebClient


개념

  • Spring WebFlux에서 제공하는 비동기식 HTTP 클라이언트이다.
  • 반응형 프로그래밍 기반으로 동작하며, 논블로킹 방식이다.
  • MonoFlux를 사용하여 데이터 스트림을 처리한다.
  • 효율적으로 자원을 사용하기에, 대규모 호출에서 유리하다.
  • 다양한 응답과 요청 처리 기능이 제공되며, Spring 5.0 이후로 권장되고 있다.

예시


1. GET 요청

public class WebClientEx {
    public static void main(String[] args) {
        WebClient webClient = WebClient.create("https://요청주소.com");

        String response = webClient.get()
                .uri("/posts/1")			// 그 외 url 경로
                .retrieve()					// 응답을 받아오기 위한 메서드
                .bodyToMono(String.class)	// 응답을 String으로 처리
                .block(); 					// 동기식으로 결과를 기다림
  
        System.out.println("Response: " + response);
    }
}

2. POST 요청

public class WebClientPostExample {
    public static void main(String[] args) {
        WebClient webClient = WebClient.create("https://요청주소.com");

        // POST 요청에 필요한 Body
        String requestBody = "{"
              + "\"grant_type\":\"authorization_code\","
              + "\"client_id\":\"your_client_id\","
              + "\"redirect_uri\":\"your_redirect_uri\","
              + "\"code\":\"your_code\","
              + "\"client_secret\":\"your_client_secret\""
              + "}";

        String response = webClient.post()
                .uri("/posts")
                .header("Content-Type", "application/json")
                .bodyValue(requestBody)		// JSON 형식의 BODY 추가
                .retrieve()
                .bodyToMono(String.class)
                .block();

        System.out.println("Response: " + response);
    }
}

🚨 URL에 직접 파라미터를 넣어야 한다면?!?
카카오 로그인 작업을 하다보니... body로 보내지 않고 parameter로 직접 전송하는 경우가 있었다.
이럴 때는

.uri("/oauth/token?grant_type=authorization_code&client_id=" + clientId
                        + "&redirect_uri=" + redirectUri
                        + "&code=" + code
                        + "&client_secret=" + clientSecret)

위와 같이 직접 해당 경로를 적어줘도 되지만, 유지보수가 어려울 수 있기에
UriBuilder를 적용할 것이다!

.uri(uriBuilder -> uriBuilder
                        .scheme("https")
                        .path("/oauth/token")
                        .queryParam("grant_type", authorizationGrantType)
                        .queryParam("client_id", clientId)
                        .queryParam("client_secret", clientSecret)
                        .queryParam("code", code)
                        .build(true))

이렇게 넣어주면 훨씬 깔끔하고 수정하기가 쉽다.
여기서 build 부분에 true특수문자 등을 인코딩 할지 여부이다.

띄어쓰기를 예로 들면 false는 띄어쓰기 그대로 적용되고, true는 %20으로 대체된다.


🚨 block()을 사용하는 이유?!?

  • 결과를 즉시 필요로 하는 작업에서는 비동기 방식으로 기다릴 수 없기에 block()으로 뽑아낸다.
  • 단순한 동기식 작업에 WebClient를 사용하는 경우에 쓴다.

🚨 비동기 방식으로 한다면..?!?

        // 비동기 방식
        Mono<String> responseMono = webClient.get()
                .uri("/posts/1")
                .retrieve()
                .bodyToMono(String.class);

        // 응답을 비동기적으로 기다림
        responseMono.subscribe(response -> System.out.println("Response: " + response));

        // 다른 작업도 동시에 수행 가능
        System.out.println("Other tasks can be performed...");

WebClient 객체 생성 부분 이후로 위의 코드와 같이 수정하면 된다.

  • subscribe() : 비동기 작업 결과가 완료되었을 때, 비동기적으로 응답을 처리한다.



동기 방식이어도 WebClient가 간결하고 다양한 옵션을 쉽게 설정할 수 있어서 많이 이용한다고 한다! 실제 업무에서 써본 적은 없지만... 소셜 로그인에 먼저 적용을 해볼 것이다!

0개의 댓글