[Spring-Framework] 20. Spring MVC - Spring Framework 5 REST, Jackson, Commons-FileUpload - (2)
2부에서는 Controller, Model, View, Util(한글 문제)을 집중적으로 소개하겠다.
1. [Spring-Framework] 19. Spring MVC - Spring Framework 5 REST, Jackson, Commons-FileUpload - (1), 2020-09-28
12. Controller - HomeController.java
초기 프로젝트를 생성하면 자동으로 만들어지는 HomeController.java이다.
REST 프로젝트 작업에 필요한 형태로 추가 작성 및 변형하였다.
- REST Client에 대해서 자세히 소개하였음. (CRUD - GET, POST, PUT, DELETE 클라이언트)
package com.example.restexample2.controller;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.example.restexample2.model.Board;
import com.example.restexample2.model.Greeting;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate.Param;
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);;
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
/**
* 파일 업로드 입력 화면
*/
@RequestMapping(value = "/fileUploadView", method = RequestMethod.GET)
public String fileUploadView(Locale locale, Model model) {
logger.info("Welcome FileUpload! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "file/upload";
}
/**
* GET 방식 - 클라이언트
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "/client/listMapGet", method = RequestMethod.GET)
public String helloClient(String regId, String time) throws UnsupportedEncodingException{
// Get 응답 방법론
String url = "http://localhost:8080/restexample2/testValue2";
String serviceKey = "서비스키";
String decodeServiceKey = URLDecoder.decode(serviceKey, "UTF-8");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); //Response Header to UTF-8
UriComponents builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("serviceKey", decodeServiceKey)
.queryParam("regId", regId)
.queryParam("tmFc", time)
.queryParam("_type", "json")
.build(false); //자동으로 encode해주는 것을 막기 위해 false
//Object response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, new HttpEntity<String>(headers), String.class);
// Object response = restTemplate.getForEntity(builder.toUriString(), List.class);
Object response = restTemplate.getForObject(builder.toUriString(), List.class);
if ( response != null) {
List<Integer> map = (List<Integer>) response;
System.out.println("map" + map);
}
//return response;
return "home";
}
/**
* POST 방식 - 클라이언트
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "/client/listMapPost")
public String helloClient2(String regId, String time) throws UnsupportedEncodingException{
// POST 응답 방법
//String url = "http://..............";
String url = "http://localhost:8080/restexample2/testValue2";
String serviceKey = "서비스키";
String decodeServiceKey = URLDecoder.decode(serviceKey, "UTF-8");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); //Response Header to UTF-8
/*
UriComponents builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("serviceKey", decodeServiceKey)
.queryParam("regId", regId)
.queryParam("tmFc", time)
.queryParam("_type", "json")
.build(false); //자동으로 encode해주는 것을 막기 위해 false
*/
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
parameters.add("servicekey", decodeServiceKey);
parameters.add("regId", regId);
parameters.add("tmFc", time);
parameters.add("_type", "json");
//Object response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, new HttpEntity<String>(headers), String.class);
// Object response = restTemplate.getForEntity(builder.toUriString(), List.class);
Object response = restTemplate.postForObject(url, parameters, List.class);
// Object response = restTemplate.postForEntity(url, parameters, List.class);
if ( response != null) {
List<Integer> map = (List<Integer>) response;
System.out.println("map" + map.get(0));
}
//return response;
return "home";
}
/**
* PUT 방식 - 클라이언트
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "/client/listMapPut/{boardIdx}")
public String helloClient3(String regId,
String time,
@PathVariable(name="boardIdx", required=true) int boardIdx)
throws UnsupportedEncodingException{
// PUT 방법
String url = "http://localhost:8080/restexample2/v1/api/board/2";
String serviceKey = "서비스키";
String decodeServiceKey = URLDecoder.decode(serviceKey, "UTF-8");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); //Response Header to UTF-8
// 파라메터
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
parameters.add("servicekey", decodeServiceKey);
parameters.add("regId", regId);
parameters.add("tmFc", time);
parameters.add("_type", "json");
//Object response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, new HttpEntity<String>(headers), String.class);
Board updatedBoard = new Board();
updatedBoard.setId(boardIdx);
updatedBoard.setSubject("앙하하");
updatedBoard.setName("수정했다.");
updatedBoard.setMemo("메모지");
// Object response = restTemplate.getForEntity(builder.toUriString(), List.class);
//Object response = restTemplate.postForObject(url, parameters, List.class);
restTemplate.put ( url, updatedBoard, parameters );
// 데이터 확인하기
url = "http://localhost:8080/restexample2/v1/api/board";
UriComponents builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("serviceKey", decodeServiceKey)
.build(false);
Board[] response = restTemplate.getForObject(builder.toUriString(), Board[].class );
// Object response = restTemplate.postForEntity(url, parameters, List.class);
if ( response != null) {
List<Board> listData = Arrays.asList(response);
System.out.println("list(Size):" + listData.size());
try {
if ( listData.size() != 0 && (boardIdx - 1) >= 0) {
System.out.println("boardIdx:" + (boardIdx - 1));
Board boardTmp = listData.get(boardIdx - 1);
System.out.println("board:" + boardTmp.getId() + "/" + boardTmp.getSubject());
}
}catch(Exception e) {
e.printStackTrace();
}
}
//return response;
return "home";
}
/**
* PUT 방식 - 클라이언트
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "/client/listMapDelete/{boardIdx}")
public String helloClient4(String regId,
String time,
@PathVariable(name="boardIdx", required=true) int boardIdx)
throws UnsupportedEncodingException{
// PUT 방법
String url = "http://localhost:8080/restexample2/v1/api/board/" + boardIdx;
String serviceKey = "서비스키";
String decodeServiceKey = URLDecoder.decode(serviceKey, "UTF-8");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); //Response Header to UTF-8
// 파라메터
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
parameters.add("servicekey", decodeServiceKey);
parameters.add("regId", regId);
parameters.add("tmFc", time);
parameters.add("_type", "json");
//Object response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, new HttpEntity<String>(headers), String.class);
// Object response = restTemplate.getForEntity(builder.toUriString(), List.class);
//Object response = restTemplate.postForObject(url, parameters, List.class);
restTemplate.delete ( url, parameters );
// 데이터 확인하기
url = "http://localhost:8080/restexample2/v1/api/board";
UriComponents builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("serviceKey", decodeServiceKey)
.build(false);
Board[] response = restTemplate.getForObject(builder.toUriString(), Board[].class );
// Object response = restTemplate.postForEntity(url, parameters, List.class);
if ( response != null) {
List<Board> listData = Arrays.asList(response);
System.out.println("list(Size):" + listData.size());
try {
if ( listData.size() != 0 && (boardIdx - 1) >= 0) {
System.out.println("boardIdx:" + (boardIdx - 1));
Board boardTmp = listData.get(boardIdx - 1);
System.out.println("board:" + boardTmp.getId() + "/" + boardTmp.getSubject());
}
}catch(Exception e) {
e.printStackTrace();
}
}
//return response;
return "home";
}
}
파일명: HomeController.java
[첨부(Attachments)]
13. Controller - JSONController.java
REST를 간단하게 입문하는 용도로 작성하였다.
package com.example.restexample2.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.restexample2.model.Greeting;
/**
* Handles requests for the application home page.
*/
@RestController
public class JSONController {
private static final Logger logger = LoggerFactory.getLogger(JSONController.class);
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
@GetMapping(value="/testValue")
public String getTestValue(){
String TestValue = "레스트컨트롤러 테스트";
return TestValue;
}
@GetMapping(value="/testValue2")
public List<Integer> getTestValue2(){
List<Integer> mList = new ArrayList<Integer>();
mList.add(1);
mList.add(2);
mList.add(3);
mList.add(4);
return mList;
/*
* Error: pom.xml의 jackson-databind, jackson-core 추가할 것
* onverter found for return value of type: class java.util.ArrayList]
*/
}
@PostMapping(value="/testValue2")
public List<Integer> getTestValue3(){
List<Integer> mList = new ArrayList<Integer>();
mList.add(1);
mList.add(2);
mList.add(3);
mList.add(4);
return mList;
/*
* Error: pom.xml의 jackson-databind, jackson-core 추가할 것
* onverter found for return value of type: class java.util.ArrayList]
*/
}
@GetMapping(value="/getMap")
public Map<String, Greeting> getMap(){
Map<String, Greeting> map = new HashMap<>();
map.put("First", new Greeting(1, "Hello"));
map.put("Second", new Greeting(2, "Rest"));
return map;
}
}
파일명: JSONController.java
[첨부(Attachments)]
매우 간단한 게시판 구조에 대한 모델이다. 방명록도 어찌보면, 게시판의 한 종류가 될 수 있다.
package com.example.restexample2.model;
public class Board {
private long id;
private String subject;
private String name;
private String memo;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMemo() {
return memo;
}
public void setMemo(String memo) {
this.memo = memo;
}
}
파일명: Board.java
[첨부(Attachments)]
공부하는 데 있어서는 간단한 것이 사실 더 어려운 부분이라고 본다.
package com.example.restexample2.model;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
파일명: Greeting.java
[첨부(Attachments)]
package com.example.restexample2.model;
import org.springframework.web.multipart.MultipartFile;
public class FileInfo {
private long num;
private String filename;
private long filesize;
private MultipartFile mediaFile;
public long getNum() {
return num;
}
public void setNum(long num) {
this.num = num;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public long getFilesize() {
return filesize;
}
public void setFilesize(long filesize) {
this.filesize = filesize;
}
public MultipartFile getMediaFile() {
return mediaFile;
}
public void setMediaFile(MultipartFile mediaFile) {
this.mediaFile = mediaFile;
}
}
파일명: FileInfo.java
[첨부(Attachments)]
17. Controller - FileController.java
경로: /src/main/java/com/example/restexample2/controller/FileController.java
파일 업로드에 대한 기능이다.
package com.example.restexample2.controller;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.example.restexample2.model.Board;
import com.example.restexample2.model.FileInfo;
import com.example.restexample2.util.HttpUtil;
@RestController
@RequestMapping ("/file")
public class FileController {
private static final Logger logger = LoggerFactory.getLogger(FileController.class);
/**
* 파일 멀티파트 업로드 Rest
* {/
* @param inputFile
* @return
* (주석 스타일 참고)
*/
@RequestMapping(value = "/uploadFileModelAttribute/new",
method = {RequestMethod.POST },
produces="text/plain;charset=UTF-8")
public String multiFileUpload(@ModelAttribute Board boardVO,
@RequestParam("mediaFile")MultipartFile[] files,
Model model,
HttpServletRequest req,
HttpServletResponse res) throws IOException {
boolean filechk = false;
//디스크상의 프로젝트 실제 경로얻기
//String contextRootPath = "c:" + File.separator + "upload";
// String charset = "UTF-8";
// req.setAttribute("charset", charset);
// req.setCharacterEncoding(charset);
// res.setContentType("text/html; charset=" + charset);
String dirName = "upload" ;
String contextRootPath = req.getSession().getServletContext().getRealPath("/") + dirName;
System.out.println("실제경로:" + contextRootPath);
//1. 메모리나 파일로 업로드 파일 보관하는 FileItem의 Factory 설정
DiskFileItemFactory diskFactory = new DiskFileItemFactory(); //디스크 파일 아이템 공장
diskFactory.setSizeThreshold(4096); //업로드시 사용할 임시 메모리
diskFactory.setRepository(new File(contextRootPath + "/WEB-INF/temp")); //임시저장폴더
//2. 업로드 요청을 처리하는 ServletFileUpload생성
ServletFileUpload upload = new ServletFileUpload(diskFactory);
upload.setSizeMax(3 * 1024 * 1024); //3MB : 전체 최대 업로드 파일 크기
// 한글 깨짐 해결(버그)
// String kor_a = new String(boardVO.getSubject().getBytes("8859_1"), "UTF-8");
System.out.println("게시물제목:" + HttpUtil.getISO8859toUTF8( boardVO.getSubject()) );
System.out.println("게시물작성자:" + HttpUtil.getISO8859toUTF8( boardVO.getName()) );
System.out.println("게시물내용:" + HttpUtil.getISO8859toUTF8( boardVO.getMemo()) );
System.out.println("파일(길이):" + files.length );
for(MultipartFile mFile : files) {
// 3. 파일 가져오기
if ( mFile.getOriginalFilename().isEmpty() &&
filechk == false ) {
String msg = "Please select at least one mediaFile.<br/>(미디어 파일을 하나 이상 선택하십시오.)";
model.addAttribute("msg", msg);
return model.getAttribute("msg").toString();
}
// 4. 파일명 - 현재시간으로 생성
String uploadedFileName = System.currentTimeMillis() + "";
if (!mFile.getOriginalFilename().isEmpty()) {
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(
new File( contextRootPath + File.separator + "upload" + File.separator, uploadedFileName )
));
System.out.println("파일명:" + mFile.getOriginalFilename());
outputStream.write(mFile.getBytes());
outputStream.flush();
outputStream.close();
filechk = true;
}
}
return "fileUploadForm";
}
}
파일명: FileController.java
[첨부(Attachments)]
비고: HttpUtil.java의 HttpUtil에 정의된 한글 출력 문제 등이 적용되어 있음.
18. Controller - BoardRestController.java
경로: /src/main/java/com/example/restexample2/controller/BoardRestController.java
게시판 시스템을 적용한 Rest 컨트롤러이다.
자료구조를 적절하게 재배치하여 실습에는 DB없이 가능한 수준으로 구현하였다.
package com.example.restexample2.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.restexample2.model.Board;
@RestController
@RequestMapping("/v1/api")
public class BoardRestController {
private static final Logger logger = LoggerFactory.getLogger(BoardRestController.class);
private static int num = 1;
private List<Object> tmpBoard = new ArrayList<Object>();
//@Autowired
//private BoardService boardService;
// 조회 = GET
// (전체 게시물)
@GetMapping("board")
public List<Object> listBoard(HttpServletRequest request, @ModelAttribute Board board) throws Exception {
logger.info("게시판 목록");
logger.info("----------------------------------");
Board createNode = new Board();
createNode.setId(num);
createNode.setName("홍길동");
createNode.setMemo("메모");
createNode.setSubject("주소지");
// tmpBoard.put(num, createNode);
// num = num + 1;
return tmpBoard;
//return this.boardService.selectBoardList(request, board);
}
// 조회 = GET
// (특정 세부 게시물)
@GetMapping("board/{boardIdx}")
public Board detailBoard(HttpServletRequest request,
@PathVariable(name="boardIdx", required=true) int boardIdx)
throws Exception {
logger.info("게시판 조회");
logger.info("----------------------------------");
logger.info("게시판 특정 게시물 번호" + boardIdx);
//return this.boardService.selectBoard(request, boardIdx);
return (Board) tmpBoard.get(boardIdx);
}
// 등록 = POST
@PostMapping("board/new")
public void insertBoard(HttpServletRequest request, @RequestBody Board board) throws Exception {
logger.info("게시판 삽입");
board.setId(num);
tmpBoard.add(board);
System.out.println(num);
num = num + 1;
//this.boardService.insertBoard(request, board);
}
// 수정 = PUT, PATCH (전송 방식)
// /member/{id} + body (json데이터 등)
@PutMapping("board/{boardIdx}")
@PatchMapping("board/{boardidx}")
public void updateBoard(HttpServletRequest request,
@PathVariable(name="boardIdx", required=true) int boardIdx,
@RequestBody Board board) throws Exception {
logger.info("게시판 수정");
if ( !tmpBoard.isEmpty() ) {
board.setId(boardIdx); // 고유키 그대로 유지할 것
tmpBoard.set(boardIdx - 1, board);
}
//board.setBoardIdx(boardIdx);
//this.boardService.updateBoard(request, board);
}
// 삭제 = DELETE(전송 방식)
@DeleteMapping("board/{boardIdx}")
public void deleteBoard(HttpServletRequest request,
@PathVariable(name="boardIdx",
required=true) int boardIdx) throws Exception {
logger.info("게시판 삭제");
try {
tmpBoard.remove(boardIdx);
}
catch(Exception e) {
logger.info("Null값");
e.getStackTrace();
}
//this.boardService.deleteBoard(request, boardIdx);
}
}
파일명: BoardRestController.java
[첨부(Attachments)]
19. Util - HttpUtil.java
package com.example.restexample2.util;
import java.io.UnsupportedEncodingException;
public class HttpUtil {
// 버그 개선: euc-kr 검증
public static boolean isEucKr(String s) {
int len = s.length();
char c;
for (int i = 0; i < len; i++) {
c = s.charAt(i);
/// System.out.println("" + c + " = " + toHex(c));
if (((c & 0xFFFF) >= 0xAC00) && ((c & 0xFFFF) <= 0xD7A3))
return true;
/// else if (((c & 0xFF00) != 0) && ((c & 0x00) == 0))
/// return false;
}
return false;
}
// 버그 개선: ISO8859-1 검증
public static boolean isISO8859(String s) {
int len = s.length();
char c;
for (int i = 0; i < len; i++) {
c = s.charAt(i);
/// System.out.println("" + c + " = " + toHex(c));
if ((c & 0xFF00) != 0)
return false;
}
return true;
}
public static String getISO8859toUTF8(String s) {
try {
return new String(s.getBytes("8859_1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return s;
}
}
}
파일명: HttpUtil.java
[첨부(Attachments)]
20. View - home.jsp
경로: /src/main/webapp/WEB-INF/views/home.jsp
기본 생성된 jsp파일이다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>
Hello world! - Rest(REST)
</h1>
<P>
</P>
</body>
</html>
파일명: home.jsp
[첨부(Attachments)]
21. View - upload.jsp
경로: /src/main/webapp/WEB-INF/views/file/upload.jsp
업로드 페이지에 관한 정의이다.
그림 33. login.jsp 파일 모습 - 사용자 인터페이스(User Interfaces)
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<title>다중 파일 업로드</title>
<meta charset="UTF-8">
</head>
<body>
<h3>다중 파일 업로드 및 다중 변수</h3>
<form action="file/uploadFileModelAttribute/new" method="POST"
enctype="multipart/form-data">
<table>
<tr>
<td>
제목:
<input type="text" name="subject" >
</td>
<td>
이름:
<input type="text" name="name" >
</td>
<td>
내용:
<input type="text" name="memo" >
</td>
</tr>
<tr>
<td>Select Files(파일을 선택하시오)</td>
<td>
<input type="file" name="mediaFile" multiple>
</td>
<td>
<input type="file" name="mediaFile" multiple>
</td>
</tr>
<tr>
<td colspan="3">
<button type="submit">Upload(업로드)</button>
</td>
</tr>
</table>
</form>
</body>
</html>
파일명: login.jsp
[첨부(Attachments)]
22. 의외로 어려운 REST 작업 - Client에서 작업하기
REST Client 앱을 설치하여 작업을 시도했을 때 명세 등을 정의하질 못해서 작업이 쉽게 이뤄지지 못한 경우도 있다.
그래서 하나 만들어보게 되었다.
비고: 작업할 때는 조금 많이 찍어봐야 한다. (힘들고 더딘 작업 중 하나이다.)
그림 34. REST Client에서 처리할 때 사용될 수 있는 정보들
이러한 정보는 그냥 나온 것은 아니고, 조금 코드로 셈플이 나오도록 찍어봐야 한다.
안 찍어보면, 어떤 구조인지 모른다. 알 길이 없다.
그림 35. 코드 등으로 객체를 찍어보는 형태로 변형해주기
셈플 코드가 출력될 수 있는 환경으로 코드를 변형해준다.
그림 36. 객체 정보의 체계 - 출력
이런 형태로 정보들이 나오면, 잘 대입해보고 정리해보기도 하고 그래야 한다.
(태스트 작업이 소요됨)
그림 37. YARC REST Client에서 사용하기
자료를 가공해서 입력한 후, Send Request를 누른다.
(구글 스토어 - 엣지, 크롬 등에서 지원함)
예를 들면, GET 관련 코드를 통해서 Send Request를 누른 후에 아래에서 결과를 찾아볼 수 있다.
그러면 아래처럼 관련 유추할 수 있는 정보 단위 코드를 출력해볼 수 있다.
"3":{"id":3,"subject":"주소지","name":"홍길동","memo":"메모"}}
(양식)
(응용 -> POST 등록 명령)
-> {"id":3,"subject":"주소지","name":"홍길동","memo":"메모"}
(응용 -> PUT, PATCH 수정 명령)
-> {"id":3,"subject":"주소지","name":"홍길동","memo":"메모"}
-----------------------------------------------------
POST /restexample2/file/uploadFileModelAttribute/new HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 24211
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarya1HOiexOytPpWx8U
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8080/restexample2/fileUploadView
Accept-Encoding: gzip, deflate, br
Accept-Language: ko,en;q=0.9,en-US;q=0.8,ja;q=0.7,de;q=0.6
Cookie: JSESSIONID=C1BB03C6629263A6B77084AA15CDF947
}
파일명: sample-keyword.txt
[첨부(Attachments)]
* 맺음글(Conclusion)
실전에서 REST를 쉽고 빠르게 적용할 수 있는 방법이 없는지 충분히 고민하였다.
시중 책도 정말 많이 사보고, 인터넷 정보 검색, 공식 메뉴얼 등 삽질을 많이 하였다.
그리하여 이 프로젝트가 작성된 것이다.
REST를 쉽고 빠르게 사용할 수 있었으면 하는 바람이다.
* 참고자료(References)
[RESTEasy - 프로젝트에 대한 것]
1. A Guide to RESTEasy, https://www.baeldung.com/resteasy-tutorial, Accessed by 2020-09-28, Last Modified 2020-02-12.
2. tutorials/resteasy at master · eugenp/tutorials · GitHub, https://github.com/eugenp/tutorials/tree/master/resteasy, Accessed by 2020-09-28, Last Modified .
3. jersey, resteasy(JBoss)
[비고]
-> resteasy는 현재 xml 출력을 못함. (POST, GET 등은 동작함)
-> jersey는 최신 버전도 인식을 못해버림.
[RestController, RESTful Service - Spring Framework ]
4. Building a RESTful Web Service, https://spring.io/guides/gs/rest-service/, Accessed by 2020-09-28, Last Modified .
[참고할 때 메모]
-> Library없이 가능한지 확인할 것
[비고]
-> Gradle 기반과 Spring Boot로 작성되었는데, Gradle 생략하고 Spring Framework 작업은 동일해서 많은 도움을 받았다.
5. RestController에 대해 알아보자, https://milkye.tistory.com/283, Accessed by 2020-09-28, Last Modified 2018-12-02.
[비고]
매우 간단하게 RestController를 사용하는 방법에 대해서 소개하고 있음.
6. [Spring Error] No converter found for return value of type: class java.util.ArrayList, https://keichee.tistory.com/274, Accessed by 2020-09-28, Last Modified 2016-11-27.
[비고]: jackson-bind의 적용 방법에 대해서 소개하고 있음. 클래스 변환에 대한 오류 해결은 미약함.
[다중 업로드]
7. Spring MVC - 유연한 다중 파일업로드 핸들러 만들기 (Multipart upload), https://galid1.tistory.com/684, Accessed by 2020-09-28, Last Modified 2020-01-29.
8. 스프링 파일 업로드 처리, https://advenoh.tistory.com/26, Accessed by 2020-09-28, Last Modified 2019-01-01.
[RESTful - 게시판 설계]
9. 4. springboot restful 방식으로 게시판 변경, https://linked2ev.github.io/gitlog/2019/12/28/springboot-restful-4-rest-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD/ , Accessed by 2020-09-28, Last Modified 2019-12-28.
[비고]: REST 기반의 게시판 설계 아이디어를 많이 얻었음.
[RESTTemplate 관련]
10. Spring REST API 생성, 호출, https://www.leafcats.com/173, Accessed by 2020-09-28, Last Modified 2017.
11. 스프링 (Spring) RestTemplate으로 POST 파라미터 (Parameter) 전송해보기, https://soshanstory.tistory.com/entry/스프링-Spring-RestTemplate으로-POST-파라미터-Parameter-전송해보기, Accessed by 2020-09-28, Last Modified 2014-08-31.
[비고]: Rest Template 사용방법을 매우 간단하게 잘 적어놨음.
[공부하면서 메모]
- XML, Java 설정 방식이 있다. (REST 셋팅 관련 - 범위가 방대해지므로 줄임)
12. RestTemplate (4가지 - 자바 내에서 클라이언트 작업), https://howtodoinjava.com/spring-boot2/resttemplate/spring-restful-client-resttemplate-example/, Accessed by 2020-09-28, Last Modified 2015-03.
[비고]: 추천하고 싶음. 매우 RestTemplate에 대해서 가장 깔끔하게 잘 정리하였음.
(공식적으로는 HTML에서는 POST, GET만 지원함.)
13. RestTemplate list 반환하기, https://luvstudy.tistory.com/52, Accessed by 2020-09-28, Last Modified 2018-11-16.
[REST 한글 깨짐 Response]
14. spring에서 json response 한글 깨짐, https://thswave.github.io/spring/2015/02/22/korean-json-response.html, Accessed by 2020-09-28, Last Modified 2015-02-22.
[POST, GET 이외의 문제]
15. Using PUT method in HTML form, https://stackoverflow.com/questions/8054165/using-put-method-in-html-form, Accessed by 2020-09-28, Last Modified 2012.
[비고]: 8년 전 문제이긴 하지만, 현재에도 해당되는 문제이다.
16. REST - HTML Form에서 GET/POST만 지원하는 이유, http://haah.kr/2017/05/23/rest-http-method-in-html-form/, Accessed by 2020-09-28, Last Modified 2017-05-23.
[비고]: 이론적으로 REST에 대해서 아주 잘 소개하고 있는 사이트이다.
17. HTML 양식에 PUT 및 DELETE 메소드가없는 이유는 무엇입니까?, https://qastack.kr/software/114156/why-are-there-are-no-put-and-delete-methods-on-html-forms, Accessed by 2020-09-28, Last Modified .
[비고]: 토론 형태로 문제에 대해서 의견을 공유하고 있다.
18. [REST] PUT, PATCH, DELETE 미지원 처리, https://velog.io/@ette9844/REST-PUT-PATCH-DELETE-%EB%AF%B8%EC%A7%80%EC%9B%90-%EC%B2%98%EB%A6%AC, Accessed by 2020-09-28, Last Modified 2020-05-19.
[비고]: 이 코드는 동작하지 않음. (이론적으로 동작되는 방법이라고 보는 게 타당함.)
[번외의 주제: Node.js - REST]
19. REST API 예제, https://hyun-am-coding.tistory.com/entry/REST-API-%EC%98%88%EC%A0%9C, Accessed by 2020-09-28, Last Modified 2019-11-17.
[비고]: REST Client와 Server 흐름에 대해서 살펴볼 수 있다. 물론 자바 코드에 직접 도움은 되는 건 아니지만 동일하게 구성될 수 있다는 아이디어를 제공해준다.