[Spring Boot] Open API (Naver API)

JH·2023년 6월 13일

Spring Boot

목록 보기
4/4

1. TIL

A. OpenAPI

1. ResponseEntity

HTTP
https://developer.mozilla.org/ko/docs/Web/HTTP/Messages

HTTP 메시지는 3개로 구분이 가능(empty line은 제외) : start-line, HTTP headers, body

ResponseEntity

  • HttpEntity를 상속받는 결과 데이터와 HTTP 상태 코드를 제어할 수 있는 클래스

  • HttpRequest에 대한 응답 데이터 포함

  • 구조

    • HttpStatus (상태 확인)

    • HttpHeaders ()

    • HttpBody (resource 자원)


2. RestTemplate

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

스프링 프레임워크에서 제공하는 HTTP 통신을 간편하게 처리하기 위한 클래스

  • Http 프로토콜의 메서드에 맞는 여러 메서드 제공

  • RESTful 형식을 갖춘 템플릿

  • HTTP 요청 후 JSON,XML,문자열 등의 다영한 형식으로 응답받을수 있음

  • 블로킹 기반의 동기 방식 사용

  • 다른 API 호출할 때 HTTP 헤더에 다양한 값 설정 가능

  • <get,post,put,delete>ForEntity 메소드를 이용

  • 우리의 서버에서 다른 서버로 요청, 응답을 쉽게 보낼 수 있음

3. Object Mapper

Jackson 라이브러리에서 제공하는 클래스로, 자바 객체와 JSON 데이터 간의 변환을 처리하는 데 사용됨

  • JSON <-> Java 변환시 사용하는 Jackson 라이브러리의 클래스

  • 대표적인 메소드

    • writeValue() : Java -> JSON Serialization 시, 사용
    • readValue() : JSON -> Java Deserialization 시, 사용


B. Ex)

1. application.properties

server.port=8081
server.servlet.context-path=/

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

#Naver Search API-Book (보안 상의 이유로 참조해서 사용함)
naver.search.api.book.client.id=Test
naver.search.api.book.client.secret=Test

2. DTO

Book, Post, User, NaverResult DTO는 생략함 (특이 사항 없음)

기본 생성자로 query 필드를 제외한 나머지 필드의 기본값을 지정함 (유동적인 쿼리스트링 활용을 위해서)

package naver.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class NaverRequest {
	
	private String query;
	private Integer display;
	private Integer start;
	private String sort;
	
	public NaverRequest() {
		this.display = 10;
		this.start = 1;
		this.sort = "sim";
	}
}

3. Controller

@Value 어노테이션을 이용해서 OpenAPI의 properties의 id, secret을 참조함

URI, UriComponentsBuilder를 이용해서 url을 대체함

getForEntity 대신 exchange 메소드를 이용함 (RequestEntity를 사용하기 위해서)

getForEntity는 RequestEntity를 파라미터로 받을 수 없음

package naver.controller;

import java.io.IOException;
import java.net.URI;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.exc.StreamWriteException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import naver.dto.Book;
import naver.dto.NaverRequest;
import naver.dto.NaverResult;
import naver.dto.Post;
import naver.dto.User;

@Controller
public class BookController {
	
	// Naver Search API-Book
	@Value("${naver.search.api.book.client.id}")
	private String clientId;
	
	@Value("${naver.search.api.book.client.secret}")
	private String clientSecret;
	
	@GetMapping(value = "main")
	public String main() {
		return "main";
	}
	
	@GetMapping("/rest-template")
	public void getRestTemplate() {
		// https://jsonplaceholder.typicode.com/posts
		String url = "https://jsonplaceholder.typicode.com/posts";
		
		User user = new User("dev", 27);
		
		// RestTemplate 객체 생성
		RestTemplate restTemplate = new RestTemplate();
		ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
				
		// ObjectMapper
		ObjectMapper objectMapper = new ObjectMapper();
		
		// writeValue : java -> json
		// writeValue(결과 파일, java객체)
		
		// readValue : json -> java
		// readValue(대상 파일, java객체)
		
		
		try {
			String jsonObject = "{\"name\":\"dev\",\"age\":27}";
			String jsonArrayObject = "[{\"name\":\"dev\",\"age\":27}, {\"name\":\"devops\",\"age\":30}]";
			
			// java -> json
//			objectMapper.writeValue(new File("user.json"), user);
			
			// 문자열 -> java
			User convertedUser = objectMapper.readValue(jsonObject, User.class);
			
			// json -> java
//			User convertedUser = objectMapper.readValue(new File("user.json"), User.class);
//			System.out.println(convertedUser);
			
			// jsonArray -> java
			List<User> userList = objectMapper.readValue(jsonArrayObject, new TypeReference<List<User>>() {});
//			System.out.println(userList);
			
			// responseEntity.getBody() -> java
			List<Post> bookList = objectMapper.readValue(responseEntity.getBody(), new TypeReference<List<Post>>() {});
//			System.out.println(bookList);
			
		} catch (StreamWriteException e) {
			e.printStackTrace();
		} catch (DatabindException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@GetMapping("/api/search/book")
	public String naverSearchBookAPI(@ModelAttribute NaverRequest naverRequest,
									  Model model) {
		
//		String url = "https://openapi.naver.com/v1/search/book.json?query=" + query;
		
		// URI
		URI uri = UriComponentsBuilder
							.fromUriString("https://openapi.naver.com")
							.path("/v1/search/book.json")
							.queryParam("query", naverRequest.getQuery())
							.queryParam("display", naverRequest.getDisplay())
							.queryParam("start", naverRequest.getStart())
							.queryParam("sort", naverRequest.getSort())
							.encode()
							.build()
							.toUri();
		
		// requestEntity : header에 id, secret 지정하여 요청을 보내야 하기 때문!
		RequestEntity<Void> requestEntity = RequestEntity
											.get(uri)
											.header("X-Naver-Client-Id", clientId)
											.header("X-Naver-Client-Secret", clientSecret)
											.build();
		
		
		// restTemplate
		RestTemplate restTemplate = new RestTemplate();
		ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
		
		// ObjectMapper
		ObjectMapper objectMapper = new ObjectMapper();
		NaverResult naverResult = null;
		
		try {
			naverResult = objectMapper.readValue(responseEntity.getBody(), NaverResult.class);
			
		}catch(JsonMappingException e) {
			e.printStackTrace();
		}catch(JsonProcessingException e) {
			e.printStackTrace();
		}
		
		List<Book> bookList = naverResult.getItems();
		model.addAttribute("bookList", bookList);
		
		return "book";
	}
}

4. JSP

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c"   uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>Book List</title>
	</head>
	<body>
		<h1>Naver Search API : Book</h1>
		
		<table border="1">
            	<tr>
                <th>ISBN</td>
                <th>이미지</td>
                <th>도서명</td>
                <th>저자/출판사</td>
                <th>출판일</td>
            </tr>
            <c:forEach var="book" items="${bookList}">
	            <tr>
	                <td>${book.isbn}</td>
	                <td><img src="${book.image}" alt="${book.title}" width="80"></td>
	                <td><a href="${book.link}">${book.title}</a></td>
	                <td>${book.author} / ${book.publisher}</td>
	                <td>${book.pubdate}</td>
	            </tr> 
            </c:forEach>
		</table>
	</body>
</html>


2. 에러

문제 발생 1. :내 서버에서 naverAPI url로 요청을 보내면 id 미존재 예외가 발생함 (인증실패)

해결 : RequestEntity를 통해 header에 id, scrit를 담아서 보내야함

문제 발생 2. : 쿼리스트링에 특정 값의 유무에 따라 url이 달라지므로 이를 처리하는 과정을 합치거나 분할 시켜야 함

해결 : URI, RequestDTO (@ModelAttribute)를 사용해서 유동적인 url을 만들 수 있음



3. 느낀점

Builder 패턴을 점점 더 많이 사용하는 것 같음

클라이언트 --> 서버
서버 ---> 서버

위처럼 요청과 응답의 대상과 피대상을 잘 이해해야할 것으로 예상됨



profile
잘해볼게요

0개의 댓글