본문 바로가기
web/springboot

[springboot] RestTemplate (RestTemplate 기초, RestTemplate으로 카카오 API 호출하기)

by fien 2020. 11. 26.

 

목차

  • RestTemplate
    • RestTemplate 동작
    • RestTemplate 설정
      • Connection Pool
      • Message Converter
    • RestTemplate Method
      • GET
      • POST
      • PUT
      • DELETE
      • Exchange
      • 컬렉션 객체를 결과로 받기
    • Contenty Type과 Message Converter
  • 카카오 번역 API 호출하기
    • TranslationService 구현
    • Controller 구현

 

카카오 번역 API를 사용하기 위해 Spring RestTemplate을 공부한 내용입니다.


개발 환경

os : window10

ide : intellij IDEA Ultimate 2020.2

java : jdk 11

build tool : maven

spring boot 2.3


RestTemplate

Spring에서 제공하는 HTTP Client로 REST API를 호출을 위한 함수를 제공하는 클래스.

고수준의 API를 제공해서 API endpont 호출을 간단하게 처리해준다.

  • Spring 4.x 부터 지원하는 Spring의 HTTP 통신 템플릿
  • HTTP 요청 후 Json, xml, String 과 같은 응답을 받을 수 있는 템플릿
  • RESTful 형식을 지원
  • 반복적인 코드를 줄여줌
  • Blocking I/O 기반의 Synchronous API (비동기 방식은 WebClient 사용)

RestTemplate 이외의 다른 httpClient 로는 HttpURLConnection, HttpClient(HttpComponent), WebClient 등이 있다.

URLConnection, HttpClient 참고 → https://sjh836.tistory.com/141

RestTemplate 동작

애플리케이션에서 RestTemplate으로 요청을 보낼 때 어떻게 동작하는지 살펴보겠습니다.

  1. 애플리케이션 내부에서 REST API에 요청하기 위해 RestTemplate의 메서드를 호출한다.
  2. RestTemplate은 MessageConverter를 이용해 java object를 request body에 담을 message(JSON etc.)로 변환한다. 메시지 형태는 상황에 따라 다름
  3. ClientHttpRequestFactory에서 ClientHttpRequest을 받아와 요청을 전달한다.
  4. 실질적으로 ClientHttpRequest가 HTTP 통신으로 요청을 수행한다.
  5. RestTemplate이 에러핸들링을 한다.
  6. ClientHttpResponse에서 응답 데이터를 가져와 오류가 있으면 처리한다.
  7. MessageConverter를 이용해 response body의 message를 java object로 변환한다.
  8. 결과를 애플리케이션에 돌려준다.

주목할 점은 RestTemplate이 통신 과정을 ClientHttpRequestFactory(ClientHttpRequest, ClientHttpResponse)에 위임한다는 것입니다. ClientHttpRequestFactory의 실체는 HttpURLConnection, Apache HttpComponents HttpClient 와 같은 HTTP Client 입니다.

RestTemplate 설정

즉, RestTemplate은 HTTP Client의 일종이지만 실제 커넥션을 만들기 위해 내부에서는 URLConnection나 apache가 제공하는 HttpClient와 같은 HTTP Client를 사용합니다.

** HTTP Client 는 http 통신에서 클라이언트 단을 구현할 수 있게 기능을 모아놓은 통신 템플릿으로 이해하면 될 것 같습니다.

 

RestTemplate을 생성할 때 어떠한 HTTP Client를 사용할 것인지 ClientHttpRequestFactory를 전달하여 지정할 수 있습니다. 기본 생성자의 경우 내부적으로는 ClientHttpRequestFactory의 구현체인 SimpleClientHttpRequestFactory를 사용하여 초기화 합니다. 이 경우 jdk가 기본으로 제공하는 HttpURLConnection을 통해 ClientHttpRequest 객체를 생성합니다. 만약, apache에서 제공하는 HttpClient를 사용하고 싶다면 HttpComponentsClientHttpRequestFactory를 생성자에 넘겨주면 됩니다. HttpClient 사용을 위해서는 Apache HttpClient 라이브러리를 포함하고 있어야 합니다.

 

또한, 팩토리를 이용해 타임아웃 등의 설정을 할 수 있습니다.

 

RestTemplate은 자동으로 빈 등록되지 않기 때문에 직접 등록해줘야 합니다.

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(){
        // HttpURLConnection을 사용해 request 생성
        // 기본생성자로 초기화시 SimpleClientHttpRequestFactory 사용
        RestTemplate restTemplate = new RestTemplate();

        // apache HttpClient를 사용해 request 생성
        //RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
        return restTemplate;
    }
}

restTemplate.getRequestFactory().getClass()로 로그로 찍어보면 기본 생성자는 SimpleClientHttpRequestFactory를 사용함을 확인 할 수 있습니다.

 

 

Connection Pool

앞에서 RestTemplate이 HTTPClient를 추상화 하고 있다는 것을 살펴봤습니다. HTTPClient의 종류에 따라 성능과 기능에 차이가 있는데 그 중 하나가 바로 Connection Pooling 입니다.

 

RestTemplate은 기본적으로 Connection Pooling을 지원하지 않기 때문에 호출 할 때마다 로컬 포트를 열고 tcp connection을 만들어 사용합니다. 사용한 소켓은 TIME_WAIT 상태가 되어 요청이 많은 시점에도 재 사용하지 못하고 장애의 요소가 됩니다. 이를 방지하기 위해 connection pool을 사용해 커넥션을 재 사용하고 제한 할 필요가 있습니다. RestTemplate이 기본적으로 사용하는 URLConnection 대신 apache에서 제공하는 HttpClient를 사용하면 connection pooling이 가능합니다.

 

이때, Pooling은 서버가 keep-alive해야 사용 가능합니다. (HTTP1.1 은 기본적으로 활성화 되어 있음)

 

Apache HttpComponents HttpClient사용을 위한 HttpComponents dependency 추가

HttpClient 사용을 위해 pom.xml에 디펜던시를 추가

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>

 

Connection Pool 설정

public RestTemplate restTemplate(){
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();

    factory.setReadTimeout(5000); // read timeout
    factory.setConnectTimeout(3000); // connection timeout

    //Apache HttpComponents HttpClient
    HttpClient httpClient = HttpClientBuilder.create()
                                .setMaxConnTotal(50)//최대 커넥션 수
                                .setMaxConnPerRoute(20).build();
                                //각 호스트(IP와 Port의 조합)당 커넥션 풀에 생성가능한 커넥션 수
    factory.setHttpClient(httpClient);


    RestTemplate restTemplate = new RestTemplate(factory);
    return restTemplate;
}

 

Message Converter

Message Converter는 자바 객체를 요청과 응답 body로 변환할 때 사용 합니다. Body의 타입을 Http Message에서 Content-type으로 명시합니다. 타입에 따라 필요한 메시지 컨버터를 찾게 됩니다.

 

하지만 명시하지 않더라도 body의 타입에 맞춰 필요한 메시지 컨버터를 찾을 수 있고 컨버터가 적절히 content-type을 설정 합니다.

 

스프링은 클라이언트 측 RestTemplate과 서버 측 RequestMehtodHandlerAdater에 메시지 컨버터를 자동으로 등록해줍니다. 아래는 스프링이 기본적으로 포함하고 있는 HttpMessagConverter 입니다.

 

HttpMessageConverter(default)

Class name Description support type media type
ByteArrayHttpMessageConverter HTTP body (text or binary data) ⇔ Byte array byte[] all media types (* / *)
StringHttpMessageConverter HTTP body (text) 
⇔ String
String all text media types (text/*)
ResourceHttpMessageConverter HTTP body (binary data) ⇔ Resource object of Spring org.springframework.core.io.Resource all media types (* / *)
SourceHttpMessageConverter HTTP body (XML) 
⇔ XML source object
javax.xml.transform.Source text/xml,application/xml,application/*-xml
FormHttpMessageConverter HTTP body ⇔ 
MultiValueMap object
MultiValueMap<String, String> application/x-www-form-urlencoded

아래는 관련 라이브러리가 클래스 패스에 존재하는 경우 자동으로 등록하는 메시지 컨버터 입니다.

 

HttpMessageConverter

Class name Description support type media type
MappingJackson2HttpMessageConverter  HTTP body (JSON) 
⇔ JavaBean
Object (JavaBean),
Map
application/json,application/*+json
MappingJackson2XmlHttpMessageConverter  HTTP body (XML) 
⇔ JavaBean
Object (JavaBean),
Map
text/xml,application/xml,application/*-xml
Jaxb2RootElementHttpMessageConverter HTTP body (XML) 
⇔ JavaBean
Object (JavaBean) text/xml,application/xml,application/*-xml

MappingJackson2HttpMessageConverter 는 jackson 라이브러리에 포함된 ObjectMapper를 이용하여 JSON을 읽고 쓸 수 있게 해줍니다.

springboot starter에 포함되어있어 별도로 설치하지 않아도 됩니다. controller에서 리퀘스트 파라미터를 Object로 선언해도 잘 바인딩이 되었던 것이 바로 이 때문입니다.

 

RestTemplate 빈 설정시 Message Converter를 추가하거나 세팅해서 사용하면 됩니다. 다만, 추가(add)하지 않고 세팅(set)하면 위에 언급한 메시지 컨버터는 사용하지 않고 선언한 것만 사용합니다.

RestTemplate restTemplate = new RestTemplate();
//set
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML));
converters.add(mappingJackson2HttpMessageConverter);
converters.add(new StringHttpMessageConverter());
restTemplate.setMessageConverters(converters);
//add 스프링이 등록해주는 컨버터도 그대로 사용
restTemplate .getMessageConverters()
        .add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));

 

Message Converter 예제 코드는 메서드 파트 아래에 있습니다.

 

RestTemplate Method

요청을 처리하는 메서드를 살펴보겠습니다.

  • get 요청에서 헤더를 적용하고 싶을 경우 exchange 메서드를 사용한다.
  • 응답 타입을 지정하면 자동으로 object 매핑을 해준다.
  • exchange는 ResponseEntity를 반환하는데 ResponseEntity안에 담긴 응답 바디의 타입을 지정할 수 있다.

ResponseEntity

  • HttpEntity : 요청과 응답을 위한 HttpHeader와 HttpBody를 포함하는 클래스
  • ResponseEntity는 HttpEntity를 상속받아 구현한 클래스로써 HttpStatus를 포함하고
  • RequestEntity는 HttpMethod를 포함한다.

URI생성

  • RestTemplate은 uri를 String 타입과 URI 타입으로 받을 수 있다.
  • UriComponentsBuilder 로 url과 파라미터를 조립하여 빌드 할 수 있다.
 
UriComponents builder = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("name","한길동")
                .build()
                //.encode(StandardCharsets.UTF_8);
builder.toUri();
builder.toString();

 

각각에 대해서 예제 코드를 통해 확인해보겠습니다. 테스트 코드를 돌리기 위해서는 애플리케이션을 실행 시킨 후 테스트 해야합니다.

 

GET

  • getForObject()
  • GET 요청을 하고 결과를 객체 타입으로 변환해서 반환한다.
  • getForEntity()상태 코드와 결과 데이터를 받을 수 있다. 결과는 지정한 타입으로 변환해서 반환한다.
  • GET 요청을 하고 결과를 ResponseEntity객체로 반환한다.

get 메서드는 헤더를 추가 할 수 없기 때문에 이를 위해선 exchange 메서드를 사용해야 합니다.

 

test code

private RestTemplate restTemplate;

@Before
public void setUp() throws Exception {
    restTemplate = new RestTemplate();
}

@Test
public void test_getFor() throws Exception {
    String URI = "http://localhost:8080/api/test/{memberId}?name={name}";
    Map<String, String> params = new HashMap<String, String>();
    params.put("memberId","10");
    params.put("name","김태연★");
    Member member =restTemplate.getForObject(URI,Member.class,params);
    log.info("member : {}",member);
}

@Test
public void test_getFor_uriBuilder() throws Exception {
    String URI = "http://localhost:8080/api/test";
    Map<String, String> param = new HashMap<String, String>();
    param.put("memberId","10");
    UriComponents builder = UriComponentsBuilder.fromHttpUrl(URI)
            .path("/{memberId}")
            .queryParam("name","김태연★")
            .encode() //UTF-8 encoding해줌 toUri()로 URI타입을 넘기는 경우 사용
            .buildAndExpand(param); 
    Member member = restTemplate.getForObject(builder.toUri(),Member.class);
    //Member member = restTemplate.getForEntity(builder.toUri(),Member.class).getBody();
        
    log.info("member : {}",member);
}

@Test
public void test_uri() throws Exception {
    String URI = "한글 포함된 URI";
    UriComponents builder = UriComponentsBuilder.fromHttpUrl(URI).build(); 
    Member member = restTemplate.getForObject(builder.toString(),Member.class);
        // 이경우 URI 타입을 넘기면 한글을 인코딩 하지 않은 채 전달해서 깨짐
    Member member = restTemplate.getForObject(builder.toUri(),Member.class);
}
 

rest controller

@GetMapping(value = "/test/{memberId}" )
public ResponseEntity<Member> testapi(@PathVariable Long memberId, @RequestParam String name){
    Member member = new Member("id","noname","pwd");
    member.setMemberId(memberId);
    member.setName(name);
    return new ResponseEntity<Member>(member,HttpStatus.OK);
}

getFor**는 인자로 URI, response type, uri variables를 받습니다.

 

restTemplate에서 String type과 URI type의 URI 차이

 

uri 작성 연습을 위해 path variable과 query parameter가 존재하는 요청을 만들었습니다. 그리고 URI를 String타입과 UriComponentBuilder를 통해 만든 URI타입으로 전달하는 메서드를 각각 작성했습니다. 참고로, Builder를 사용한 방식도 toString() 메소드를 통해 String타입으로 값을 전달할 수 있습니다. 여기서 한 가지 주의해야 할 것이 있습니다. RestTemplate에 URI를 String 타입으로 전달하면 내부적으로 인코딩 작업을 수행한다는 점입니다. 한글을 포함하는 경우라면 builder와 RestTemplate에서 이중 인코딩하지 않도록 유의해야 합니다. 반면, URI타입을 반환하는 경우 buider에서 encoding 처리를 해줘야 합니다. encode()에 인자 값을 지정하지 않으면 기본적으로 UTF-8로 인코딩 작업을 수행합니다.

 

getForEntity의 경우 결과 객체를 ResponseEntity가 감싸고 있는데 결과 뿐만 아니라 상태 코드(Status Code)를 포함하고 있습니다. getBody()나 getStatusCode() 메소드를 통해 각각을 확인 할 수 있습니다.

 

컨트롤러에서 ResponseEntity를 반환해도 getForObject로 결과 데이터만 받아올 수 있습니다.

 

POST

  • postForObject()
  • postForEntity()

get메서드와 거의 동일합니다. 대신, post는 request body와 header를 포함해서 요청을 보낼 수 있습니다. HttpEntity를 통해 headers와 body를 같이 전달 할 수 있습니다.

 

test code

@Test
public void test_postFor(){
    String URI = "http://localhost:8080/api/test";
    Member member = new Member("gg_ty","kimty","0309");
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization","test key");
    Member result ;
    // 1. getForObject without headers
    //result = restTemplate.postForObject(URI,member,Member.class);

    // 2. getForObject with headers
    result = restTemplate.postForObject(URI,new HttpEntity<>(member,headers),Member.class);

    // 3. postForEntity without headers
    //ResponseEntity<Member> responseEntity = restTemplate.postForEntity(URI, member, Member.class);

    // 4. postForEntity with headers
    //ResponseEntity<Member> responseEntity = restTemplate.postForEntity(URI, new HttpEntity<>(member,headers), Member.class);
    //result = responseEntity.getBody();

    log.info("member : {}",result);
}

 

rest controller

@PostMapping(value = "/test" )
public ResponseEntity<Member> testPost(@RequestBody Member member,
                                       @Nullable @RequestHeader("Authorization") String key){
    log.info("member {}",member);
    log.info("header Authorization {}",key);
    //자원 생성 작업
    member.setMemberId(11L);
    return new ResponseEntity<Member>(member,HttpStatus.CREATED);
}
 

요청과 응답의 바디는 클라이언트와 서버단의 Message Converter에 의해 java object 에서 body 타입으로 body 타입에서 java object로 변환합니다. 이 코드에서는 별도로 명시하지 않았지만 body에 전달하는 값이 Obejct(java bean)이기 때문에 MappingJackson2HttpMessageConverter가 application/json 타입으로 변환 해줍니다.

 

PUT

자원 수정을 위해 body에 데이터를 담을 수 있습니다. post와 마찬가지로 헤더를 포함 할 수 있습니다. 결과를 따로 반환 받지 않습니다. 상태 코드를 받고 싶다면 exchange를 활용해야 합니다.

test code

@Test
public void test_put(){
    String URI = "http://localhost:8080/api/test/{memberId}";
    Member member = new Member("gg_ty","kimty","0309");
    restTemplate.put(URI,member,11L);
}

 

rest controller

@PutMapping(value = "/test/{memberId}")
public ResponseEntity testPut(@PathVariable Long memberId, 
                              @RequestBody Member member){
    log.info("memberId {}",memberId);
    log.info("member {}",member);
    //자원 수정 작업
    return new ResponseEntity<>(HttpStatus.ACCEPTED);
}

 

Delete

자원을 제거할 때 사용합니다. put과 마찬가지로 별도로 반환 받는 값이 없습니다.

test code

@Test
public void test_delete(){
    String URI = "http://localhost:8080/api/test/{memberId}";
    restTemplate.delete(URI,11L);
}

rest controller

@DeleteMapping(value = "/test/{memberId}")
public ResponseEntity testDelete(@PathVariable Long memberId){
    log.info("memberId {}",memberId);
    //자원 제거 작업
    return new ResponseEntity<>(HttpStatus.ACCEPTED);
}

Exchange

exchange는 HTTP 메서드를 지정해서 요청을 보낼 수 있습니다. 앞서 설명했던 메서드를 exchange 하나로 대체할 수 있습니다.

exchange메서드로 POST요청과 PUT요청을 작성해보겠습니다.

test code

@Test
public void test_exchange_post(){
    String URI = "http://localhost:8080/api/test";
    Member member = new Member("gg_ty","김태연","0309");
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization","test key");
    Member result = restTemplate.exchange(URI, HttpMethod.POST, new HttpEntity<>(member,headers),Member.class).getBody();
    log.info("member : {}",result);
}

@Test
public void test_exchange_put(){
    String URI = "http://localhost:8080/api/test/{memberId}";
    Member member = new Member("gg_ty","김태연","0309");
    Map<String,String> params = new HashMap<>();
    params.put("memberId","11");
    ResponseEntity<String> result = restTemplate.exchange(URI, HttpMethod.PUT, new HttpEntity<>(member), String.class, params);
    log.info("member : {}",result);
}

컨트롤러는 위의 POST 예제와 동일합니다.

exchange를 활용하면 GET 요청에 headers를 포함할 수 있고 PUT 요청 시 상태 코드를 결과로 받을 수 있습니다.

 

 

컬렉션 객체를 결과로 받기

exchange 메서드로 컬렉션 데이터를 받아올 수 있습니다. 요청을 보낼 때 응답 타입을 ParameterizedTypeReference로 감싸서 지정하면 됩니다.

test code

@Test
public void test_exchange_get(){
    String URI = "http://localhost:8080/api/test";
    List<Member> memberList= restTemplate.exchange(URI, HttpMethod.GET,null
                                        ,new ParameterizedTypeReference<List<Member>>(){}).getBody();
    log.info("member List: {}",memberList);
}

 

rest controller

@GetMapping(value = "/test" )
public ResponseEntity<List<Member>> testGet(){
    List<Member> memberList = new ArrayList<>();
    memberList.add(new Member("id1","name1","1111"));
    memberList.add(new Member("id2","name2","1111"));
    log.info("member List : {}",memberList);
}

 

메서드 관련해서 다음 글을 많이 참고 했습니다. 훨씬 정리가 잘 되어있지만 직접 해보면서 정리를 다시 해봤습니다.

링크

 

Content Type과 Message Converter

위의 코드들은 암시적으로 application/json 타입으로 메시지를 주고 받고 있습니다. 이번에는 form 타입을 건네 주는 코드를 작성해보겠습니다.

@PostMapping(value = "/test/message",consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Member> testPost_message(@RequestBody Member member,
                                       @RequestHeader("content-type") String contentType){
    log.info("member {}",member);
    log.info("header content-type {}",contentType);
    member.setName("탱구");
    return new ResponseEntity<Member>(member,HttpStatus.CREATED);
}

 

컨트롤러에서 읽어 들일 수 있는 메시지 타입을 application/x-www-form-urlencoded 로 설정했습니다. 내부 로직은 중요하지 않기 때문에 멤버의 필드 값을 수정에 다시 돌려주도록 했습니다.

@Test
public void test_messageConversion(){
    String URI = "http://localhost:8080/api/test/message";
    Member member = new Member("gg_ty","김태연","0309");
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization","test key");

    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    Member result = restTemplate.exchange(URI, HttpMethod.POST, new HttpEntity<>(member,headers),Member.class).getBody();
    log.info("member : {}",result);
}

 

테스트 코드에서 restTemplate으로 POST 요청을 보내니 415 UnsupportedMediaType 에러가 뜹니다.

application/json 타입으로 메세지를 보내고 있습니다.

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

헤더를 설정해 content-type을 서버와 일치시킵니다. 이제 해결 된 것 일까요?

테스트를 동작시키니 이번엔 메시지 컨버터 에러가 발생합니다.

요청 바디에 전달한 Member타입을 application/x-www-form-urlencoded 타입으로 변환할 메시지 컨버터가 없다고 뜹니다.

내용을 알면 당연한 오류이지만 미디어 타입의 구분을 잘 인지하지 못하면 해맬 수 밖에 없는 부분입니다.

application/x-www-form-urlencoded 은 form 데이터를 읽고 쓰기 위해 사용하는 미디어 타입입니다.

RestTemplate에서는 이러한 form 데이터를 요청으로 보내기 위해서 MultiValueMap객체를 사용합니다.

MultiValueMap<String,String> body = new LinkedMultiValueMap<>();
body.add("userId","gg_ty");
body.add("name","김태연");
body.add("password","0309");
Member result = restTemplate.exchange(URI, HttpMethod.POST, new HttpEntity<>(body,headers),Member.class).getBody();

Member 타입의 객체 대신 MultiValueMap에 key-value로 값을 담아 전달해봅니다.

 

컨트롤러도 마찬가지로 application/x-www-form-urlencoded 타입을 받기 때문에 RequestBody로 Object 타입(Member)가 아닌 MultiValueMap타입을 받아야 합니다. 이렇게 하면 정상적으로 동작합니다.

 

한 가지가 더 남았습니다. 매번 Object를 key-value로 옮겨 넣는다면 번거로울 뿐 아니라 타이핑 오류가 발생할 위험이 큽니다.

이 때, ObjectMapper를 사용해서 Object를 MultiValueMap로 쉽게 변환 할 수 있습니다. ObjectMapper는 spring-boot-stater-json내의 jackson 라이브러리에 들어있어서 Autowried로 주입받아 사용하면 됩니다.

 

이렇게 해서 완성한 코드는 아래와 같습니다.

@PostMapping(value = "/test/message",consumes =MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Member> testPost_message(@RequestBody MultiValueMap<String,String> data,
                                               @RequestHeader("content-type") String contentType){
    log.info("member {}",data);
    log.info("header content-type {}",contentType);
    Member member = objectMapper.convertValue(data.toSingleValueMap(), Member.class);
    member.setName("탱구");
    return new ResponseEntity<Member>(member,HttpStatus.CREATED);
}

 

@Test
public void test_messageConversion(){
    String URI = "http://localhost:8080/api/test/message";
    Member member = new Member("gg_ty","김태연","0309");
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization","test key");
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    
        // object to MultiValueMap
    MultiValueMap<String,String> body = new LinkedMultiValueMap<>();
    Map<String, String> maps = objectMapper.convertValue(member, new TypeReference<Map<String, String>>() {});
    body.setAll(maps);

    ResponseEntity<Member> responseEntity = restTemplate.exchange(URI, HttpMethod.POST, new HttpEntity<>(body, headers), Member.class);
    Member result = responseEntity.getBody();
    log.info("member : {}",responseEntity);
}

(Member 객체를 주고 받는 코드라서 application/json을 사용하는게 맞는 것 같지만 타입에 따른 코드 작성을 위한 코드니까 대충 넘어가주세요 ~~)

 

컨트롤러에서 produce 타입이 다르다면 restTemplate의 responseType이나 Accept 헤더도 변경해야합니다.

 

공부하기 이전에는 컨텐츠 타입을 헤더에 설정하기만 하면 어떤 타입이든 변환이 되는 줄 알았습니다. 하지만 변환은 java object 타입과 http body 타입에 해당하는 Message Converter가 있어야 가능합니다. 그렇기 때문에 content-type과 전달하는 object가 매핑되는지 확인해야 합니다.

 

참고로 jquery나 ajax의 기본 컨텐츠 타입은 application/json이 아니라 application/x-www-form-urlencoded 입니다.

 

 


카카오 API 호출하기

위에서 학습한 내용을 바탕으로 kakao 번역 api를 호출하는 코드를 작성하겠습니다.

우선 카카오 번역 api의 구조를 확인합니다.

  • method : GET/POST
  • request parameter : query, src_lang, target_lang
  • header : Authorization
  • encoding : form-urlencoded
  • response : transleted_text

이러한 사항들에 맞춰 요청을 만들어 주면 됩니다.

 

TranslationService 구현

아래는 GET 방식으로 요청하는 코드입니다.

@Slf4j
@Service
@RequiredArgsConstructor
public class TranslationService {
    private final RestTemplate restTemplate;
    private String url="https://dapi.kakao.com/v2/translation/translate";
    private String key="발급 받은 키 값";

    public String Translate(String targetLang,String targetText) {
        String result = null;
        UriComponents builder = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("src_lang","kr")
                .queryParam("target_lang",targetLang)
                .queryParam("query",targetText)
                                .encode()
                .build(); //자동으로 인코딩해주는걸 막아줌

        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization","KakaoAK "+key);
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        ResponseEntity<String> response = restTemplate.exchange(builder.toUri(), HttpMethod.GET, new HttpEntity<>(headers), String.class);
                
        //json 형태의 결과를 파싱
        //{ "translated_text": [[ "abcd", "efg","hijk"]] }
        // 결과가 Array로 오기 때문에 스트림을 사용해 String으로 변환
        try {
            JSONParser jsonParser = new JSONParser();
            JSONObject jsonObject = (JSONObject) jsonParser.parse(response.getBody());
            JSONArray translatedText =(JSONArray) jsonObject.get("translated_text");
            List<String> str = (List<String>) translatedText.get(0);
            result = str.stream()
                            .collect(Collectors.joining(" "));
            log.info("translatedText : "+ result);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return  result;
    }
}

 

JsonParser

 

결과 데이터를 string 형식으로 받았기 때문에 파싱을 해줍니다.

스프링부트가 포함하고 있는 JSONParser는 디폴트 생성자가 없으니 당황하지 마시고 아래의 dependency를 추가합니다.

패키지명을 꼭 확인하세요.

<!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple -->
<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1.1</version>
</dependency>
import org.json.simple.JSONArray;
import org.json.simple.parser.JSONParser;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;

Controller 구현

TranslationService를 사용하는 controller도 작성합니다.

번역할 Post의 id와 번역할 언어를 전달받습니다. Post의 id를 통해 db를 조회하여 content를 받아오고 TranslationService에 전달해 번역된 결과를 담아 응답하는 구조입니다.

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/post")
public class PostController {
    private final PostMapper postMapper;
    private final TranslationService translationService;

    @GetMapping("/{postId}/{lang}")
    public Post translatePost(@PathVariable Long postId, @PathVariable String lang){
        Post post = postMapper.findOneById(postId);
        String translatedContent = translationService.Translate(lang, post.getContent());
        post.setContent(translatedContent);
        return post;
    }
}
public class Post {
    private Long postId;
    private Long memberId;
    private String content;
    private LocalDateTime createdAt;
}

postman으로 요청을 보내면 잘 돌아가는 것을 확인 할 수 있습니다.

 

 


 

에러나 로깅 처리 등의 내용을 추가하면 좋을 것 같지만 ㅎㅎㅎ

이렇게 카카오 api 까지 호출해보는 것으로 RestTemplate에 대한 정리는 마치겠습니다

댓글