본문 바로가기

STUDY/국비과정

[JAVA 웹 개발 공부] 국비지원 65일차 - JSP 파일 업로드 및 다운로드, enctype, Content-Type, JSON, API, REST API

서블릿과 JSP

 

JSP의 구동 방식은 JSP 파일을 서블릿으로 변환하여 서블릿을 실행하는 방식이다. 

JSP는 클라이언트에 보여지는 결과 페이지를 생성할 때 주로 쓰이며, 서블릿은 UI 요소가 없는 제어나 기타 처리 용도로 쓰인다.

서블릿 JSP
자바 코드 안에서 전체 HTML 페이지를 생성한다. HTML 코드 안에서 필요한 부분만 자바 코드를 스크립트 형태로 추가한다.
변수 선언 및 초기화가 반드시 선행되어야 한다. 자주 쓰이는 기능을 내장 객체로 제공하여 즉시 사용할 수 있다.
컨트롤러(Controller)를 만들 때 사용된다. 처리된 결과를 보여주는 뷰(View)를 만들 때 사용한다.

 

 

JSP 파일 업로드 및 다운로드

 

1. 이진데이터(이미지 파일) DB(MySQL)에 올리기

 

*fileupload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 업로드</title>
</head>
<body>
	<form action="./file/upload" method="post" enctype="multipart/form-data">
		<input type="file" name="upload" /> 
		<input type="submit" />
	</form>
</body>
</html>

 

*FileUploadServlet.java

package web06;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@WebServlet(urlPatterns = "/file/upload"
		, initParams = {
		@WebInitParam(name = "dest", value = "file")
		})
@MultipartConfig(location = "d:\\myfolder", fileSizeThreshold = 1024 * 1024 * 5, maxFileSize = 1024 * 1024 * 50)
public class FileUploadServlet extends HttpServlet {
	private static final Logger logger = LoggerFactory.getLogger(FileUploadServlet.class);
	private IFileSave repo;
	
	@Override
	public void init(ServletConfig config) throws ServletException {
		String setting = config.getInitParameter("dest");
		if (setting.equals("file")) {
			repo = new FileSaveToAppFolder(config.getServletContext().getRealPath(""));
		} else {
			repo = new FileSaveToMySQL();
		}
		
//		repo = new FileSaveToAppFolder(config.getServletContext().getRealPath(""));
		repo = new FileSaveToMySQL();
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		Part part = req.getPart("upload");
		
		logger.info(part.getName());
		logger.info(part.getSubmittedFileName());
		for (String name : part.getHeaderNames()) {
			logger.info(name + ":" + part.getHeader(name));
		}
		
		repo.save(part.getSubmittedFileName(), part.getInputStream());
	}
}

*IFileSave.java (인터페이스)

package web06;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;

public interface IFileSave {
	void save(String filename, InputStream is) throws IOException;
}

 

*FileSaveToAppFolder.java

package web06;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FileSaveToAppFolder implements IFileSave {
	private String rootPath;
	
	public FileSaveToAppFolder(String rootPath) {
		this.rootPath = rootPath;
	}
	
	@Override
	public void save(String filename, InputStream inputstream) throws IOException {
		Path uploadPath = Paths.get(rootPath, "upload");
		if (Files.exists(uploadPath)) {
			Files.createDirectories(uploadPath);
		}
		Files.copy(inputstream, uploadPath.resolve(filename), StandardCopyOption.REPLACE_EXISTING);
	}
}

 

*FileSaveToMySQL.java

package web06;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class FileSaveToMySQL implements IFileSave {
	@Override
	public void save(String filename, InputStream is) throws IOException {
		String sql = "INSERT INTO image (filename, image) VALUES (?, ?)"; 
		try (Connection conn = MyContextListener.getConnection();
				PreparedStatement stmt = conn.prepareStatement(sql)) {
			stmt.setString(1, filename);
			stmt.setBlob(2, is);
			
			stmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException();
		}
	}
}

 

*MyContextListener.java

package web06;

import java.sql.Connection;
import java.sql.SQLException;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

public class MyContextListener implements ServletContextListener {
	public static DataSource dataSource;
	
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("어플리케이션 시작시 발생하는 이벤트를 처리 가능합니다.");
		
		System.out.println("어플리케이션 전역에서 사용할 설정을 미리 수행합니다.");
		ServletContext context = sce.getServletContext();
		String jdbcUrl = context.getInitParameter("jdbcUrl");
		String jdbcClassname = context.getInitParameter("jdbcClassName");
		String jdbcUser = context.getInitParameter("jdbcUser");
		String jdbcPassword = context.getInitParameter("jdbcPassword");
		
		BasicDataSource ds = new BasicDataSource();
		ds.setDriverClassName(jdbcClassname);
		ds.setUrl(jdbcUrl);
		ds.setUsername(jdbcUser);
		ds.setPassword(jdbcPassword);
		dataSource = ds;
	}
	
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}
}

 

*web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
	version="4.0">
	<display-name>web06</display-name>

	<listener>
		<listener-class>web06.MyContextListener</listener-class>
	</listener>

	<context-param>
		<param-name>jdbcUrl</param-name>
		<param-value>jdbc:mysql://localhost:3306/my_db</param-value>
	</context-param>
	<context-param>
		<param-name>jdbcClassName</param-name>
		<param-value>com.mysql.cj.jdbc.Driver</param-value>
	</context-param>
	<context-param>
		<param-name>jdbcUser</param-name>
		<param-value>root</param-value>
	</context-param>
	<context-param>
		<param-name>jdbcPassword</param-name>
		<param-value>root</param-value>
	</context-param>
</web-app>

 

*pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>web06</groupId>
	<artifactId>web06</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.9.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.32</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.3.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>2.0.6</version>
		</dependency>
	</dependencies>

	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.2.3</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

2. DB에서 이미지 파일 가져오기

*filelist.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 목록</title>
</head>
<body>
	<ul>
		<c:forEach var="e" items="${ files }">
			<c:url var="u" value="./download">
				<c:param name="id" value="${ e.key }" />
			</c:url>
			<li><a href="${u}">${ e.value }</a></li>
		</c:forEach>
	</ul>
</body>
</html>

 

*FileListServlet.java

package web06;

import java.io.IOException;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/file/list")
public class FileListServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String query = "SELECT * FROM image";
		Map<Integer, String> files = new HashMap<>();
		try (Connection conn = MyContextListener.getConnection();
				PreparedStatement stmt = conn.prepareStatement(query);
				ResultSet rs = stmt.executeQuery()){
			while (rs.next()) {
				int id = rs.getInt("id");
				String filename = rs.getString("filename");
				
				files.put(id, filename);
			}
			req.setAttribute("files", files);
			req.getRequestDispatcher("/WEB-INF/filelist.jsp").forward(req, resp);
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

 

*FileDownloadServlet.java

package web06;

import java.io.IOException;
import java.net.URLEncoder;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@WebServlet("/file/download")
public class FileDownloadServlet extends HttpServlet {
	private static final Logger logger = LoggerFactory.getLogger(FileDownloadServlet.class);
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		logger.info("사용자 요청 파일 id: " + req.getParameter("id"));
		Integer id = Integer.valueOf(req.getParameter("id"));
		
		String query = "SELECT * FROM image WHERE id = ?";
		try (Connection conn = MyContextListener.getConnection();
				PreparedStatement stmt = conn.prepareStatement(query)) {
			stmt.setInt(1, id);
			
			try (ResultSet rs = stmt.executeQuery()) {
				if (rs.next()) {
					String filename = rs.getNString("filename");
					Blob image = rs.getBlob("image");
					
					resp.setHeader("Content-Type", "application/octet-stream");
					resp.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
					
					ServletOutputStream out = resp.getOutputStream();
					out.write(image.getBytes(1, (int) image.length()));
					out.flush();
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

 

 

<form> 태그의 enctype 속성

 

enctype 속성은 폼 데이터(form data)가 서버로 제출될 때 해당 데이터가 인코딩되는 방법을 명시한다.
이 속성은 <form> 요소의 method 속성값이 “post”인 경우에만 사용할 수 있다.

<form enctype="속성값">

 

속성값 설명
application/x-www-form-urlencoded 기본값으로, 모든 문자들은 서버로 보내기 전에 인코딩됨을 명시한다.
multipart/form-data 모든 문자를 인코딩하지 않음을 명시한다.
이 방식은 <form> 요소가 파일이나 이미지를 서버로 전송할 때 주로 사용한다.
text/plain 공백 문자(space)는 "+" 기호로 변환하지만, 나머지 문자는 모두 인코딩되지 않음을 명시한다.

 

 

Content-Type

 

Http 통신에서 전송되는 데이터의 타입을 명시하기 위해 header에 실리는 정보다. 즉, api 요청 시 request에 실어 보내는 데이터(body)의 타입 정보다.

Content-Type으로 요청 또는 응답의 데이터가 어떤 형식인지 판단 할 수 있다.
Content-Type Header가 없다면 특정 data(img, viedo 등)를 Content-Type없이 보내면 data를 받는 쪽에서는 단순 텍스트 데이터로 받는다.

HTTP메소드에서 GET방식은 value=text 형식으로 보내지기 때문에 Content-Type은 필요없다.
HTTP메소드에 POST, PUT처럼 Body에 data를 보낼때 Content-Type이 필요하다.

 

*웹 개발에서 자주 사용되는 Content-Type

Content-Type 설명
application/octet-stream 이 타입은 이진 파일의 기본 타입이다.
브라우저에서 보통 자동으로 실행하지 않거나 실행해야 할지 묻는다.
Content-Disposition값이 attachment와 함께 설정되면 브라우저는 파일을 저장 또는 다른이름으로 저장 여부를 체크 한다.
text/plain 텍스트 파일의 기본 타입이다.
image/png
image/Jpeg
image/gif
image/webp
브라우저는 해당 컨텐트를 이미지로 취급한다.
text/javascript 브라우저는 Content를 Javascript문서로 취급한다.
multipart/form-data <form>태그를 사용해 브라우저에서 서버로 전송할 때 사용 한다.

 

 

JSON

 

1. JSON 구조
JSON은 자바스크립트의 객체 표기법으로부터 파생된 부분 집합이다.
따라서 JSON 데이터는 다음과 같은 자바스크립트 객체 표기법에 따른 구조로 구성된다.

*JSON 데이터는 이름과 값의 쌍으로 이루어진다.
*JSON 데이터는 쉼표(,)로 나열된다.
*객체(object)는 중괄호({})로 둘러쌓아 표현한다.
*배열(array)은 대괄호([])로 둘러쌓아 표현한다.

 

2. JSON 데이터

JSON 데이터는 이름과 값의 쌍으로 구성됩니다.

이러한 JSON 데이터는 데이터 이름, 콜론(:), 값의 순서로 구성됩니다.

"데이터이름": 값

 

3. JSON 변환

 

(1) 자바 객체 → JSON

ObjectMapper 클래스의 writeValueAsString 및 writeValueAsBytes 메소드는 Java 객체에서 JSON을 생성하고 생성된 JSON을 문자열 또는 바이트 배열로 반환한다.

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(node);

*예제

package kr.co.greenart.testjson;

import java.time.LocalTime;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class App {
    public static void main( String[] args ) throws JsonProcessingException {
    	ObjectMapper mapper = new ObjectMapper();
    	ObjectNode node = mapper.createObjectNode();
    	node.put("name1", 100);
    	node.put("name2", 45.33);
    	node.put("name3", true);
    	node.put("name4", "문자열값");
    	
    	String json = mapper.writeValueAsString(node);
    	System.out.println(json);
    	
    	// 현재 시간을 표현가능한 json 자료 만들기
    	// json 필드는 hour, minute, second 각각의 값은 시간 정수 값
    	
    	LocalTime now = LocalTime.now();
    	ObjectNode timeNode = mapper.createObjectNode();
    	timeNode.put("hour", now.getHour());
    	timeNode.put("minute", now.getMinute());
    	timeNode.put("second", now.getSecond());
    	
    	String timeJson = mapper.writeValueAsString(timeNode);
    	System.out.println(timeNode);
    }
}
{"name1":100,"name2":45.33,"name3":true,"name4":"문자열값"}
{"hour":15,"minute":15,"second":44}

 

(2) JSON → 자바 객체

ObjectMapper mapper = new ObjectMapper();
Person p2 = mapper.readValue(strPerson, Person.class);

*예제

package kr.co.greenart.testjson;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class App2 {
	public static void main(String[] args) throws JsonProcessingException {
		Person p1 = new Person("둘리", 33);
		
		ObjectMapper mapper = new ObjectMapper();
		String json = mapper.writeValueAsString(p1);
		System.out.println(json);
		
		String strPerson = "{\"name\":\"도우너\",\"age\":34}";
		Person p2 = mapper.readValue(strPerson, Person.class);
		
		System.out.println(p2.getName());
		System.out.println(p2.getAge());
	}
}
{"name":"둘리","age":33}
도우너
34

 

4. JSON 라이브러리

Jackson Databind

https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind/2.14.2

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
</dependency>

 

 

API

 

API는 프로그램들이 서로 상호작용하는 것을 도와주는 매개체라고 할 수 있다.  데이터와 기능의 집합을 제공하여 컴퓨터 프로그램간 상호작용을 촉진하며, 서로 정보를 교환가능 하도록 하는 것이다. 웹 서비스 쪽에서는 대표적으로 카카오나 네이버에서 제공하는 지도 API가 있다. 간단한 몇 줄의 코드로 우리가 만든 웹 애플리케이션에 지도를 출력할 수 있게 해준다.

 

 

RESTful API

 

1. REST
HTTP를 잘사용하기 위한 아키텍쳐 스타일이다.
HTTP URI를 통해 자원(Resource)을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것을 의미한다.
애플리케이션 분리 및 통합을 위해 필요하다.
REST의 원칙을 지키면서 API의 의미를 표현하고 쉽고, 파악하기 쉽게 하는것을 Restful 하다고 한다.

 

*REST 구성 요소

자원(Resource): URI - 모든 자원에 고유한 ID가 존재하고, 이 자원은 Server에 존재한다.
- 자원을 구별하는 ID는 ‘/groups/:group_id’와 같은 HTTP URI 다.
- Client는 URI를 이용해서 자원을 지정하고 해당 자원의 상태(정보)에 대한 조작을 Server에 요청한다.
행위(Verb): HTTP Method - HTTP 프로토콜의 Method를 사용한다.
- HTTP 프로토콜은 GET, POST, PUT, DELETE 와 같은 메서드를 제공한다.
표현(Representation of Resource) - Client가 자원의 상태(정보)에 대한 조작을 요청하면 Server는 이에 적절한 응답(Representation)을 보낸다.
- REST에서 하나의 자원은 JSON, XML, TEXT, RSS 등 여러 형태의 Representation으로 나타내어 질 수 있다.
- JSON 혹은 XML를 통해 데이터를 주고 받는 것이 일반적이다.

 

*REST 특징
(1) Server-Client(서버-클라이언트 구조)
(2) Stateless(무상태)
(3) Cacheable(캐시 처리 가능)
(4) Layered System(계층화)
(5) Code-On-Demand(optional)
(6) Uniform Interface(인터페이스 일관성)

 

2. REST API
REST한 방식으로 데이터를 상호교환하게 설계된 API를 말한다.
HTTP를 잘사용하기위해, URI와 HTTP메소드를 사용해서, URL로 어떤 자원에 접근할 것인지, 메소드로 어떤 행위를 할것인지 표현하여 설계된 API를 말합니다.


3. RESTful API 조건
*클라이언트-서버 커뮤니케이션 - 요청 간에 클라이언트 정보가 저장되지 않으며, 각 요청이 분리되어 있고 서로 연결되어 있지 않다.
*Stateless(무상태) - 클라이언트와 서버간 종속적이지 않다.

 

 

MyFakeAPI

 

MyFakeAPI는 가짜 데이터가 필요할 때마다 사용할 수 있는 무료 온라인 REST API이다.

https://myfakeapi.com/

 

My Fake API - Free Fake Rest API for Developers

Free Fake Rest API for Developers. Kickstart new applications or services at light speed using our modular and free to use API mocking service that gives you all the essential API endpoints you would require to scaffold, mock and test your web applications

myfakeapi.com