스프링 - RestTemplate & Open API

2024. 8. 19. 15:22스프링

2024/08/19

 

 

오늘 살펴볼 RestTemplate에 앞서
혹시 REST API가 무엇인지 모르는 분들은
이전 포스팅을 참고하시기 바랍니다.

 

 


 

※   RestTemplate에 대해 알아보자.

 

   ▶  RestTemplate이란?

          💡 Server to Server

 

                  ●  지금까지는 주로 Client 즉, 브라우저로부터 요청을 받는 서버의 입장에서 개발을 진행해왔다.
                  ●  서비스 개발을 진행하다보면 라이브러리 사용만으로는 구현이 힘든 기능들이 무수히 많이 존재한다.
                       예를 들어 우리의 서비스에서 회원가입을 진행할 때 사용자의 주소를 받아야 한다면❓
                         ➡️  주소를 검색할 수 있는 기능을 구현해야하는데 직접 구현을 하게되면 많은 시간과 비용이 들어간다.
                  ●  But, 카카오에서 만든 주소 검색 API를 사용한다면 해당 기능을 간편하게 구현할 수 있다.

 

 

                  ●  이럴 때 우리의 서버는 Client의 입장이 되어 Kakao 서버에 요청을 진행해야한다.
                  ●  Spring에서는 서버에서 다른 서버로 간편하게 요청할 수 있도록 RestTemplate기능을 제공하고 있다.

 

          💡  IntelliJ에서 실습하기

             ▼  프로젝트 생성

                  ●  Client 입장 서버 
                       Name: spring-resttemplate-client
                       Language: Java
                       Build system: Gradle - Groovy
                       JDK: 17
                       Dependencies - Spring web, Lombok 추가
                    
                  ●  Server 입장 서버 
                       Name: spring-resttemplate-server

                       Language: Java
                       Build system: Gradle - Groovy
                       JDK: 17
                       Dependencies - Spring web, Lombok 추가

 

             ▼   RestTemplate 사용방법

프로젝트 2개를 만들어서 Client 입장의 서버는 8080 port, Server 입장의 서버는 7070 port로 동시에 실행시킨다.

                  ●  Client 입장 서버 
                       controller > RestTemplateController

package com.sparta.springresttemplateclient.controller;

import com.sparta.springresttemplateclient.dto.ItemDto;
import com.sparta.springresttemplateclient.service.RestTemplateService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/client")
public class RestTemplateController {

    private final RestTemplateService restTemplateService;

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

    @GetMapping("/get-call-obj")
    public ItemDto getCallObject(String query) {
        return restTemplateService.getCallObject(query);
    }

    @GetMapping("/get-call-list")
    public List<ItemDto> getCallList() {
        return restTemplateService.getCallList();
    }

    @GetMapping("/post-call")
    public ItemDto postCall(String query) {
        return restTemplateService.postCall(query);
    }

    @GetMapping("/exchange-call")
    public List<ItemDto> exchangeCall(@RequestHeader("Authorization") String token) {
        return restTemplateService.exchangeCall(token);
    }
}


                       dto > ItemDto

package com.sparta.springresttemplateclient.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ItemDto {
    private String title;
    private int price;
}


                       entity > User

package com.sparta.springresttemplateclient.entity;

import lombok.Getter;

@Getter
public class User {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}


                       service > RestTemplateService

package com.sparta.springresttemplateclient.service;

import com.sparta.springresttemplateclient.dto.ItemDto;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class RestTemplateService {

    public ItemDto getCallObject(String query) {
        return null;
    }

    public List<ItemDto> getCallList() {
        return null;
    }

    public ItemDto postCall(String query) {
        return null;
    }

    public List<ItemDto> exchangeCall(String token) {
        return null;
    }
}

 

                  ●  Server 입장 서버 
                       controller > ItemController

package com.sparta.springresttemplateserver.controller;

import com.sparta.springresttemplateserver.dto.ItemResponseDto;
import com.sparta.springresttemplateserver.dto.UserRequestDto;
import com.sparta.springresttemplateserver.entity.Item;
import com.sparta.springresttemplateserver.service.ItemService;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/server")
public class ItemController {

    private final ItemService itemService;

    public ItemController(ItemService itemService) {
        this.itemService = itemService;
    }

    @GetMapping("/get-call-obj")
    public Item getCallObject(@RequestParam String query) {
        return itemService.getCallObject(query);
    }

    @GetMapping("/get-call-list")
    public ItemResponseDto getCallList() {
        return itemService.getCallList();
    }

    @PostMapping("/post-call/{query}")
    public Item postCall(@PathVariable String query, @RequestBody UserRequestDto requestDto) {
        return itemService.postCall(query, requestDto);
    }

    @PostMapping("/exchange-call")
    public ItemResponseDto exchangeCall(@RequestHeader("X-Authorization") String token, @RequestBody UserRequestDto requestDto) {
        return itemService.exchangeCall(token, requestDto);
    }
}

                       dto > UserRequestDto

package com.sparta.springresttemplateserver.dto;

import lombok.Getter;

@Getter
public class UserRequestDto {
    private String username;
    private String password;
}


                       dto > ItemResponseDto

package com.sparta.springresttemplateserver.dto;

import com.sparta.springresttemplateserver.entity.Item;
import lombok.Getter;

import java.util.ArrayList;
import java.util.List;

@Getter
public class ItemResponseDto {
    private final List<Item> items = new ArrayList<>();

    public void setItems(Item item) {
        items.add(item);
    }
}


                       entity > Item

package com.sparta.springresttemplateserver.entity;

import lombok.Getter;

@Getter
public class Item {
    private String title;
    private int price;

    public Item(String title, int price) {
        this.title = title;
        this.price = price;
    }
}


                       service > ItemService

package com.sparta.springresttemplateserver.service;

import com.sparta.springresttemplateserver.dto.ItemResponseDto;
import com.sparta.springresttemplateserver.dto.UserRequestDto;
import com.sparta.springresttemplateserver.entity.Item;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

@Service
public class ItemService {

    private final List<Item> itemList = Arrays.asList(
            new Item("Mac", 3_888_000),
            new Item("iPad", 1_230_000),
            new Item("iPhone", 1_550_000),
            new Item("Watch", 450_000),
            new Item("AirPods", 350_000)
    );

    public Item getCallObject(String query) {
        return null;
    }

    public ItemResponseDto getCallList() {
        return null;
    }

    public Item postCall(String query, UserRequestDto requestDto) {
        return null;
    }

    public ItemResponseDto exchangeCall(String token, UserRequestDto requestDto) {
        return null;
    }
}

 

   ▶  RestTemplate의 GET 요청

             ▼   GET 요청 방법

                  ●  Client 입장 서버 
                     1.  RestTemplate을 주입 받는다.

private final RestTemplate restTemplate;

// RestTemplateBuilder의 build()를 사용하여 RestTemplate을 생성
public RestTemplateService(RestTemplateBuilder builder) {
    this.restTemplate = builder.build();
}


                     2.  요청 받은 검색어를 Query String 방식으로 Server 입장의 서버로 RestTemplate를 사용하여 요청한다.

public ItemDto getCallObject(String query) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/get-call-obj")
            .queryParam("query", query)
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

    ResponseEntity<ItemDto> responseEntity = restTemplate.getForEntity(uri, ItemDto.class);

    log.info("statusCode = " + responseEntity.getStatusCode());

    return responseEntity.getBody();
}

                       Spring의 UriComponentsBuilder를 사용해서 URI를 손쉽게 만들 수 있다.
                       RestTemplate의 getForEntity는 GET 방식으로 해당 URI의 서버에 요청을 진행한다.
                       ✅  첫 번째 파라미터에는 URI, 두 번째 파라미터에는 전달 받은 데이터와 매핑하여 인스턴스화할
                              클래스의
타입을 주면된다.
                       요청의 결과값에 대해서 직접 JSON TO Object를 구현할 필요없이 RestTemplate을 사용하면 자동으로
                         처리해준다.
                       ✅  따라서 response.getBody() 를 사용하여 두 번째 파라미터로 전달한 클래스 타입으로 자동 변환된
                              객체를 가져올 수 있다.

                  ●  Server 입장 서버 
                     1.  Server 입장의 서버에서 itemList를 조회하여 요청받은 검색어에 맞는 Item을 반환한다.

public Item getCallObject(String query) {
    for (Item item : itemList) {
        if(item.getTitle().equals(query)) {
            return item;
        }
    }
    return null;
}

             🚩 도전해보기 – 요청한 Item이 여러 개라면?

 

   ▶  RestTemplate의 POST 요청

             ▼   POST 요청 방법

                  ●  Client 입장 서버 
                     1.  요청 받은 검색어를 Query String 방식으로 Server 입장의 서버로 RestTemplate를 사용하여 요청한다.

public ItemDto postCall(String query) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/post-call/{query}")
            .encode()
            .build()
            .expand(query)
            .toUri();
    log.info("uri = " + uri);

    User user = new User("Robbie", "1234");

    ResponseEntity<ItemDto> responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class);

    log.info("statusCode = " + responseEntity.getStatusCode());

    return responseEntity.getBody();
}

                       UriComponentsBuilder의 expand를 사용하여 {query} 안의 값을 동적으로 처리할 수 있다.
                      RestTemplate의 postForEntity는 POST 방식으로 해당 URI의 서버에 요청을 진행한다.
                       ✅  첫 번째 파라미터에는 URI, 두 번째 파라미터에는 HTTP Body에 넣어줄 데이터를 넣는다.
                        →  Java 객체를 두 번째 파라미터에 넣으면 자동으로 JSON 형태로 변환된다.
                       ✅  세 번째 파라미터에는 전달 받은 데이터와 매핑하여 인스턴스화할 클래스의 타입을 주면 된다.

                  ●  Server 입장 서버 
                     1.  Server 입장의 서버에서 itemList를 조회하여 요청받은 검색어에 맞는 Item을 반환한다.

public Item postCall(String query, UserRequestDto userRequestDto) {
    System.out.println("userRequestDto.getUsername() = " + userRequestDto.getUsername());
    System.out.println("userRequestDto.getPassword() = " + userRequestDto.getPassword());

    return getCallObject(query);
}

                       전달 받은 HTTP Body의 User 데이터를 확인한다.

 

   ▶  RestTemplate의 Exchange

             ▼  요청 Header에 정보를 추가하고 싶다면?

                  ●  RestTemplate으로 요청을 보낼 때 Header에 특정 정보를 같이 전달 하고 싶다면 어떻게 하면 될까?

                  ●  Client 입장 서버 
                     1.  RestTemplate의 exchange를 사용합니다.

public List<ItemDto> exchangeCall(String token) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/exchange-call")
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

    User user = new User("Robbie", "1234");

    RequestEntity<User> requestEntity = RequestEntity
            .post(uri)
            .header("X-Authorization", token)
            .body(user);

    ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);

    return fromJSONtoItems(responseEntity.getBody());
}

                        exchange 메서드의 첫 번째 파라미터에 RequestEntity 객체를 만들어 전달해주면 uri, header, body의
                          정보를 한번에 전달할 수 있다.

 

                  ●  Server 입장 서버 
                     1.  전달된 header와 body의 정보를 확인할 수 있다.

public ItemResponseDto exchangeCall(String token, UserRequestDto requestDto) {
    System.out.println("token = " + token);
    System.out.println("requestDto.getUsername() = " + requestDto.getUsername());
    System.out.println("requestDto.getPassword() = " + requestDto.getPassword());

    return getCallList();
}

 

 

   ▶  Naver Open API

             ▼  Naver Open API란?

📌  네이버 서비스를 코드로 이용할 수 있는 서비스

                  ●  아래 주소로 가면 네이버에서 제공하는 API 목록을 확인할 수 있다.

https://developers.naver.com/products/intro/plan/

             ▼  API 이용 신청하기

                  ●  신청 방법
                    1.  애플리케이션 등록을 누른다.
                    2.  네이버 로그인을 진행한다.
                    3.  아래 정보를 입력한 후 등록하기를 누른다.
                          애플리케이션 이름
                          사용 API : 검색
                          비로그인 오픈 API 서비스 환경 - WEB 선택 후 http://localhost 입력
                    4. Client ID, Client Secret이 생성된 것을 확인한다.

             ▼  실습에 사용할 API 살펴보기

                  ●  네이버 검색 > 쇼핑 API 주소

https://developers.naver.com/docs/serviceapi/search/shopping/shopping.md#%EC%87%BC%ED%95%91

             ▼  Postman으로 확인하기

                  ●    Naver 검색 API URL

https://openapi.naver.com/v1/search/shop.json?query=macbook

 

 

             ▼  '상품 검색 API' 동작 순서 이해하기

 

             ▼  상품 검색 API 구현하기

                  ●  검색한 상품을 반환하는 API 생성
                          naver > controller > NaverApiController

package com.sparta.springresttemplateclient.naver.controller;

import com.sparta.springresttemplateclient.naver.dto.ItemDto;
import com.sparta.springresttemplateclient.naver.service.NaverApiService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api")
public class NaverApiController {

    private final NaverApiService naverApiService;

    public NaverApiController(NaverApiService naverApiService) {
        this.naverApiService = naverApiService;
    }

    @GetMapping("/search")
    public List<ItemDto> searchItems(@RequestParam String query)  {
        return naverApiService.searchItems(query);
    }
}

                          naver > dto > ItemDto

package com.sparta.springresttemplateclient.naver.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;
import org.json.JSONObject;

@Getter
@NoArgsConstructor
public class ItemDto {
    private String title;
    private String link;
    private String image;
    private int lprice;

    public ItemDto(JSONObject itemJson) {
        this.title = itemJson.getString("title");
        this.link = itemJson.getString("link");
        this.image = itemJson.getString("image");
        this.lprice = itemJson.getInt("lprice");
    }
}

                          naver > service > NaverApiService

package com.sparta.springresttemplateclient.naver.service;

import com.sparta.springresttemplateclient.naver.dto.ItemDto;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

@Slf4j(topic = "NAVER API")
@Service
public class NaverApiService {

    private final RestTemplate restTemplate;

    public NaverApiService(RestTemplateBuilder builder) {
        this.restTemplate = builder.build();
    }

    public List<ItemDto> searchItems(String query) {
        // 요청 URL 만들기
        URI uri = UriComponentsBuilder
                .fromUriString("https://openapi.naver.com")
                .path("/v1/search/shop.json")
                .queryParam("display", 15)
                .queryParam("query", query)
                .encode()
                .build()
                .toUri();
        log.info("uri = " + uri);

        RequestEntity<Void> requestEntity = RequestEntity
                .get(uri)
                .header("X-Naver-Client-Id", "Client-Id")
                .header("X-Naver-Client-Secret", "Client-Secret")
                .build();

        ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);

        log.info("NAVER API Status Code : " + responseEntity.getStatusCode());

        return fromJSONtoItems(responseEntity.getBody());
    }

    public List<ItemDto> fromJSONtoItems(String responseEntity) {
        JSONObject jsonObject = new JSONObject(responseEntity);
        JSONArray items  = jsonObject.getJSONArray("items");
        List<ItemDto> itemDtoList = new ArrayList<>();

        for (Object item : items) {
            ItemDto itemDto = new ItemDto((JSONObject) item);
            itemDtoList.add(itemDto);
        }

        return itemDtoList;
    }
}

                  ●  Postman으로 확인하기
                          상품 검색 URL

http://localhost:8080/api/search?query=macbook

 

 

 

 

 

 

 

 

 

RestTemplate이 무엇이고 어떻게 사용하는지 다들 아시겠나요?
기본적인 내용을 살펴봤으니 앞으로 Rest API를 구현할 수 있길 바랍니다.
오늘 포스팅은 여기서 마무리 하겠습니다. 

 

 


※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.