[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
https://yyman.tistory.com/1425
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)]
HomeController.zip
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)]
JSONController.zip
14. Model - Board.java
매우 간단한 게시판 구조에 대한 모델이다. 방명록도 어찌보면, 게시판의 한 종류가 될 수 있다.
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)]
Board.zip
15. Model - Greeting.java
사실 쉽고 간단한 코드가 더 어려운 코드라고 본다.
꼭 반드시 게시판 형태만 생각할 필요가 없다고 본다.
공부하는 데 있어서는 간단한 것이 사실 더 어려운 부분이라고 본다.
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)]
Greeting.zip
16. Model - FileInfo.java
경로: /src/main/java/com/example/restexample2/model/FileInfo.java
파일에 관한 명세이다.
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)]
FileInfo.zip
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)]
FileController.zip
비고: 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)]
BoardRestController.zip
19. Util - HttpUtil.java
경로: /src/main/java/com/example/restexample2/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)]
HttpUtil.zip
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)]
home.zip
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)]
upload.zip
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)]
sample-keyword.txt
sample-keyword.zip
* 맺음글(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 흐름에 대해서 살펴볼 수 있다. 물론 자바 코드에 직접 도움은 되는 건 아니지만 동일하게 구성될 수 있다는 아이디어를 제공해준다.