Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

블로그 이름 뭐하지

[Spring] RestTemplate과 OpenAPI 본문

Spring

[Spring] RestTemplate과 OpenAPI

가는말이고우면오는말은come 2024. 11. 15. 09:44

RestTemplate

보통은 Client와 Server의 입장에서 요청과 응답을 보내지만,

가끔 Server와 Server가 요청을 주고받을 때가 있다.

예를 들어 회원가입을 진행할 때, 사용자의 주소를 받아야한다면,

주소를 검색하는 기능을 직접 구현해도 되지만, 이미 만들어진 API를 가져와 사용하는 것이 쉽다.

이럴 때 우리(Server)는 Client의 입장이 되어 해당 주소 API 서버에 요청을 진행해야한다.

 

RestTemplate는 HTTP Client를 쉽게 사용할 수 있도록 도와주는 라이브러리로,

HTTP 요청과 응답을 쉽게 처리할 수 있으며 Restful API 서버와의 통신을 간편하게 구현할 수 있다.

 

RestTemplate 사용

RestTemplate를 사용해보기 위해 서버 2개를 만들어 예시코드를 작성해보자.

하나는 클라이언트의 입장이 될 서버, 하나는 서버의 입장이 될 서버이다.

두 서버의 설정은 동일하게 맞추고, port 번호만 다르게 설정한다.(8080-7070)

 

▶Client 입장의 서버 초기 설정

//1. controller > ItemController

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

//2. dto > UserRequestDto

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

//3. entity > Item

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

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

//4. service > ItemService

@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 입장의 서버 초기 설정

//1. controller > ItemController

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

//2. dto > UserRequestDto

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

//3. entity > Item

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

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

//4. service > ItemService

@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 요청

▶ Client 입장의 서버

RestTemplateService > RestTemplate 주입, getCallObject 메서드 수정

// RestTemplate 주입
private final RestTemplate restTemplate;

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

public ItemDto getCallObject(String query) {
    // 요청 URL 만들기(RestTemplate 사용)
    // 요청받은 검색어를 쿼리스트링 방식으로 Server입장의 서버로 요청
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/get-call-obj")
            .queryParam("query", query)
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

    //getForEntity : Get 방식으로 해당 URI의 서버에 요청을 진행한다.
    //첫번째 파라미터에는 uri,
    //두번째 파라미터에는 전달받은 데이터와 매핑하여 인스턴스화 할 클래스의 타입을 작성한다.
    //직접 JSON TO Object를 구현하지 않아도 자동으로 처리한다
    ResponseEntity<ItemDto> responseEntity = restTemplate.getForEntity(uri, ItemDto.class);

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

    // getBody()를 이용해 두번째 파라미터로 전달한 클래스 타입으로 자동 변환된 객체를 가져온다
    return responseEntity.getBody();
}

 

▶ Server 입장의 서버

ItemService > getCallObject 메서드 수정

public Item getCallObject(String query) {
    //itemList를 조회하여 요청받은 검색어에 맞는 Item을 반환한다
    for (Item item : itemList) {
        if(item.getTitle().equals(query)) {
            return item;
        }
    }
    return null;
}

 

!요청한 Item이 여러개일 경우

 

▶ Client 입장의 서버

1) build.gradle에 json 의존성 추가

// json
implementation 'org.json:json:20230227'

 

2) RestTemplateService > getCallList 메서드 수정, fromJSONtoItems 메서드 추가

public List<ItemDto> getCallList() {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/get-call-list")
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);
    
    //결과값이 다중 JSON으로 넘어오므로
    //JSON To Object를 사용하지 않고 일단 String 값을 가져온다
    ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);

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

    return fromJSONtoItems(responseEntity.getBody());
}

// Json처리를 도와주는 라이브러리 추가
// Json 형태의 String을 처리한다.
public List<ItemDto> fromJSONtoItems(String responseEntity) {
    //1. String을 JSONObject로 변환
    JSONObject jsonObject = new JSONObject(responseEntity);
    //2. JSONObject에서 items 배열 꺼내기
    JSONArray items  = jsonObject.getJSONArray("items");
    //3. JSONArray로 for문 돌면서 상품을 하나씩 ItemDto로 변환하기
    List<ItemDto> itemDtoList = new ArrayList<>();

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

    return itemDtoList;
}

 

3) ItemDto > 생성자 추가

@Getter
@NoArgsConstructor
public class ItemDto {
    private String title;
    private int price;
	
    //JSONObject를 ItemDto로 변환
    public ItemDto(JSONObject itemJson) {
        this.title = itemJson.getString("title");
        this.price = itemJson.getInt("price");
    }
}

 

▶ Server 입장의 서버

ItemService > getCallList 메서드 수정

public ItemResponseDto getCallList() {
    //ItemList를 ItemResponseDto에 담아 반환한다
    ItemResponseDto responseDto = new ItemResponseDto();
    for (Item item : itemList) {
        responseDto.setItems(item);
    }
    return responseDto;
}

 

 

RestTemplate의 Post 요청

▶ Client 입장의 서버

RestTemplateService > postCall 메서드 수정

public ItemDto postCall(String query) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/post-call/{query}")
            .encode()
            .build()
            .expand(query) //query 안의 값을 동적으로 처리할 수 있다.
            .toUri();
    log.info("uri = " + uri);

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

    //PostForEntity : Post 방식으로 해당 URI의 서버에 요청을 진행한다.
    //첫번째 파라미터에는 uri,
    //두번째 파라미터에는 HTTP Body에 넣을 데이터(Java 객체를 넣으면 자동으로 Json으로 변환),
    //세번째 파라미터에는 전달받은 데이터와 매핑하여 인스턴스화 할 클래스의 타입을 작성한다.
    ResponseEntity<ItemDto> responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class);

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

    return responseEntity.getBody();
}

 

▶ Server 입장의 서버

ItemService > postCall 메서드 수정

public Item postCall(String query, UserRequestDto userRequestDto) {
    //전달받은 HttpBody의 User 데이터를 확인한다
    System.out.println("userRequestDto.getUsername() = " + userRequestDto.getUsername());
    System.out.println("userRequestDto.getPassword() = " + userRequestDto.getPassword());

    return getCallObject(query);
}

 

 

RestTemplate의 exchange 요청

RestTemplate으로 보내는 요청 Header에 특정 정보를 같이 전달하고 싶을 때 사용한다.

 

▶ Client 입장의 서버

RestTemplateService > exchangeCall 메서드 수정

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);
    
    //exchange 메서드의 첫번째 파라미터에 RequestEntity 객체를 만들어 전달하면
    //uri, header, body의 정보를 한 번에 전달 할 수 있다
    ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);

    return fromJSONtoItems(responseEntity.getBody());
}

 

▶ Client 입장의 서버

ItemService > exchangeCall 메서드 수정

public ItemResponseDto exchangeCall(String token, UserRequestDto requestDto) {
    // 전달된 header와 body의 정보를 알 수 있다.
    System.out.println("token = " + token);
    System.out.println("requestDto.getUsername() = " + requestDto.getUsername());
    System.out.println("requestDto.getPassword() = " + requestDto.getPassword());

    return getCallList();
}

 

Open API

누구나 사용할 수 있도록 공개된 API를 뜻한다.

공공데이터 포탈이나, 네이버, 카카오, 구글 등의 디벨로퍼에서도 Open API를 제공하고 있고,

지도앱, 로그인, 검색, 데이터 분석 기능 등을 사용할 수 있다.

Open API가 Server 입장의 서버, Local 서버가 Client 입장의 서버가 되어 해당 기능을 사용한다.

 

예시로 들 것은 그 중에서도 네이버에서 제공하는 검색 기능이다.

 

1. API 이용 신청

 

NAVER Developers

네이버 오픈 API들을 활용해 개발자들이 다양한 애플리케이션을 개발할 수 있도록 API 가이드와 SDK를 제공합니다. 제공중인 오픈 API에는 네이버 로그인, 검색, 단축URL, 캡차를 비롯 기계번역, 음

developers.naver.com

네이버 디벨로퍼에서 Application > 애플리케이션 등록을 누른다.

 

이름은 마음대로 정하고, 사용 API는 검색, 환경은 WEB 설정에, URL은 localhost로 설정한다.

생성이 끝나면 내 애플리케이션에 설정한 이름의 애플리케이션이 등록되어있고,

해당 Client ID와 Client Secret을 사용해 API에 접근할 수 있다.

 

2. API 이해

 

PostMan에서 해당 검색 기능( https://openapi.naver.com/v1/search/shop.json?query= )을 확인하고,

가져올 정보의 key값을 살핀다.

 

검색 API 의 동작순서는 다음과 같다. 이에 따라 상품 검색 API를 구현한다.

 

3. API 구현

//1) naver > controller > NaverApiController

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

//2) naver > dto > ItemDto

@Getter
@NoArgsConstructor
public class ItemDto {
    //포스트맨에서 확인한 key값 중 필요한 것을 가져다가 입력
    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");
    }
}

// 3) naver > service > NaverApiService

@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") // 디벨로퍼의 clientId
                .header("X-Naver-Client-Secret", "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;
    }
}

 

4) Postman에서 정상 작동하는지 확인

'Spring' 카테고리의 다른 글

[Spring] Entity 연관 관계  (0) 2024.11.15
[Spring] Validation  (0) 2024.11.14
[Spring] Spring Security  (0) 2024.11.14
[Spring] 필터(Filter)  (0) 2024.11.14
[Spring] 쿠키와 세션, JWT  (0) 2024.11.13