Spring 입문 4-1 (RestTemplate & Open API)

SJ.CHO·2024년 10월 10일
post-thumbnail

RestTemplate이란 무엇일까?

  • 개발을 진행하다보면 표준 라이브러리 만으로는 기능구현이 힘든 점이 존재.
  • Ex) 회원가입중 주소검색기능이 필요시 다른 서버의 주소검색 API를 사용한다면 구현이 쉬워짐.
  • Spring에서는 서버에서 다른 서버로 간편하게 요청할 수 있도록 RestTemplate 기능을 제공

RestTemplate Get 요청

클라이언트

private final RestTemplate restTemplate;

// RestTemplateBuilder의 build()를 사용하여 RestTemplate을 생성합니다.
public RestTemplateService(RestTemplateBuilder builder) {
    this.restTemplate = builder.build();
}
  • RestTemplate 을 주입.
  • 기존의 생성자 주입방식이 아닌 RestTemplateBuilder 를 통해 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();
}
  • UriComponentsBuilder 을 통해 서버에 보낼 URL을 제작하여 검색어를 만들어서 전송 (동적 URI 생성)
  • RestTemplate의 getForEntit을 통해 Get 방식으로 해당 URI에 요청을 진행.
  • 요청결과값은 RestTemplate이 JSON형태의 데이터를 객체형태로 자동변환 해줌.
    • response.getBody() 의 2번째 파라미터 DTO 형태로 변환.

서버 Service

public Item getCallObject(String query) {
    for (Item item : itemList) {
        if(item.getTitle().equals(query)) {
            return item;
        }
    }
    return null;
}
  • 받아온 query(Item 명) 을 통해서 해당 List내의 아이템 객체를 반환

특정 Object 가 아닌 List형태로 받아오기

클라이언트

// json
implementation 'org.json:json:20230227'
  • JSON을 다루기위한 JSON 라이브러리 추가.
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);

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

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

    return fromJSONtoItems(responseEntity.getBody());
}
  • 특정 DTO 객체형태가 아닌 전부 받아오기위해 String 형태로 받아옴.
JSON 형태의 String 문자열.
========================================================
{
"items":[
		{"title":"Mac","price":3888000},
		{"title":"iPad","price":1230000},
		{"title":"iPhone","price":1550000},
		{"title":"Watch","price":450000},
		{"title":"AirPods","price":350000}
	]
}
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;
}
  • 받아온 String을 분해하여 JSON의 배열명의 K:V 값을 분해하여 ItemDto 생성자를 통해 한개씩 객체화시켜줌.
@Getter
@NoArgsConstructor
public class ItemDto {
    private String title;
    private int price;

    public ItemDto(JSONObject itemJson) {
        this.title = itemJson.getString("title");
        this.price = itemJson.getInt("price");
    }
}
  • ItemDto 의 생성자를 통해 객체생성이 가능.

서버

public ItemResponseDto getCallList() {
    ItemResponseDto responseDto = new ItemResponseDto();
    for (Item item : itemList) {
        responseDto.setItems(item);
    }
    return responseDto;
}
  • 서버의 DTO에서 List를 생성해 직접 담아서 반환.

RestTemplate Post 요청

클라이언트

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} 안의 값을 동적으로 처리할 수 있음. (@PathVariable 방식)
  • RestTemplate의 postForEntity는 Post 방식으로 해당 URI의 서버에 요청을 진행
    • Post는 입력값도 존재할수 있기에 입력될 정보 객체를 파라미터로 삽입이 가능. (자동으로 JSON 형태로 변환)

서버

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

    return getCallObject(query);
}
  • 서버는 위의 Get 방식과 크게다르지 않은 구조로 동작.
  • 전달받은 객체를 RequestDto에 담아서 필드 출력.

RestTemplate의 exchange

  • 기존에는 BODY 부분에 정보를 넣어서 전달을 했음.
  • 특정정보를 Body 가 아닌 Header에 넣어서 전달방법.

클라이언트

    @GetMapping("/exchange-call")
    public List<ItemDto> exchangeCall(@RequestHeader("Authorization") String token) {
        return restTemplateService.exchangeCall(token);
    }
  • @RequestHeader :
    • header 부분의 원하는 데이터를 추출하여 꺼내 올 수 있다.
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의 정보를 한번에 전달 가능.

서버

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

OPEN API 사용해보기

  • OPEN API 를 사용하기 위해선 제공하는 문서에 대한 이해가 필요.

  • 요청 예시 및 응답예시등 문서화가 잘 되어져있다.

  • 요청에 맞지않는 데이터가 존재할시 예외발생.

동작순서

  • 서버 to 서버의 형태로 기존서버가 처리하기힘든일을 요청화해서 해당서버에게 전송 및 응답을받아 처리하는 형태.

컨트롤러

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

DTO

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

Service

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

  • 기존과 유사한 방법으로 JSON을 받아오되 URI를 NAVER에서 지정한 형태로 발송해주는게 포인트다.
profile
70살까지 개발하고싶은 개발자

0개의 댓글