Servlet으로 text file Reader 만들기

작성: 2021.03.14

수정: 2021.03.14

읽는시간: 00 분

Programming/Java

반응형

수업에서 배운 tmpl 파일구조를 이용합니다.

tmpl은 jsp 구조를 익히기 위해 임의로 만든 확장자입니다.

package kr.or.ddit.servlet01;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public abstract class AbstractUseTmplServlet extends HttpServlet {
    protected ServletContext application;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        application = config.getServletContext();
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 0. mime 결정
        setContentType(resp);
        // 1. tmpl 읽기
        StringBuffer tmplSrc = readTmpl(req);
        // 2. 데이터 만들기
        makeData(req);
        // 3. tmpl을 데이터로 치환
        StringBuffer html = replaceData(tmplSrc,req);
        // 4. 응답 데이터를 출력 try ~ with resource
        try(
            PrintWriter out = resp.getWriter();
        ){
            out.print(html);
        }
    }

    protected abstract void setContentType(HttpServletResponse resp);

    private StringBuffer replaceData(StringBuffer tmplSrc, HttpServletRequest req) {
        Pattern regex = Pattern.compile("%([a-zA-Z0-9_]+)%");
        Matcher matcher = regex.matcher(tmplSrc);
        StringBuffer html = new StringBuffer();
        while(matcher.find()) {
            String name = matcher.group(1);
            Object value = req.getAttribute(name);
            if(value != null) {
                matcher.appendReplacement(html, value.toString());
            }
        }
        matcher.appendTail(html);
        return html;
    }

    protected abstract void makeData(HttpServletRequest req);

    // 후크메서드 : 끌여당겨져 사용됨
    private StringBuffer readTmpl(HttpServletRequest req) throws IOException {
        String tmplPath = req.getServletPath();
        InputStream is = application.getResourceAsStream(tmplPath);
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader reader = new BufferedReader(isr);
        String temp = null;
        StringBuffer tmplSrc = new StringBuffer();
        while((temp = reader.readLine()) != null) {
            tmplSrc.append(String.format("%s\n",temp));
        }
        return tmplSrc;
    }

}

해당 tmpl을 읽을 수 있도록 만들어 둔 템플릿 입니다. 해당 템플릿을 상속해서 만들 예정입니다.

<html>
    <body>
        <h4>Text file viewer</h4>
        <select id='textFile' name='textFile'>
            <option>choose a file to read</option>
            %options%
        </select>
        <br><br>
        <!-- src 속성의 주소값 : ex ) textView.do?textFile=filename(path?) -->
        <iframe id="viewer" src="" style="width:500px;height:500px"></iframe>
<script type="text/javascript">
    var select = document.querySelector("#textFile")
    select.onchange = function(event){
        var fileName = this.value;
        if(fileName == 'choose file to read')
            return false;
        var frm = document.getElementById("viewer");
        frm.setAttribute("src","text.do?textFile="+fileName);
    }
</script>        
    </body>
</html>

textViewer.tmpl 파일입니다. 기본적으로 jsp 의 구조를 갖고 있습니다.

onChange 로 이벤트를 발생시키도록 하였습니다. 이벤트 발생시에 iframe의 src 속성을 변경시켜, 선택한 파일명을 인자로 넘겨 text.do에 매핑된 서블릿을 호출시켜 프레임에 출력하도록 했습니다.

package kr.or.ddit.servlet01;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;

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("/01/text.do")
public class textServlet extends HttpServlet{

    public void doGet(HttpServletRequest req, HttpServletResponse resp) 
    throws IOException, ServletException{
        String textFilename = req.getParameter("textFile");

        URL path = getClass().getResource("/datas");
        File folder = new File(path.getFile());

        if(textFilename == null || textFilename.isEmpty()) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        File textFile = new File(folder,textFilename);
        if(!textFile.exists()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        String mime = getServletContext().getMimeType(textFilename);
        if(mime==null || !mime.startsWith("text/")) {
            resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
            return;
        }

        resp.setContentType(mime);

        FileInputStream fis = new FileInputStream(textFile);


        OutputStream os = resp.getOutputStream();
        byte []buffer = new byte[1024];

        int pointer = -1;
        while((pointer = fis.read(buffer)) != -1){
            os.write(buffer, 0, pointer);
        }
    }

}

텍스트 파일을 읽어주는 서블릿입니다.

res 라는 이름의 소스폴더에 datas 라는 폴더를 만들어 거기에 데이터들을 계층 구조로 저장해 두었는데요,

src와 res의 파일들은 실행시에 같은 위치, 즉 classpath 로 이동됩니다.

정확히는 이동되는건 아니고, src 안의 파일들은 컴파일 되어 .class 파일이 classpath 에 저장이 되고, 컴파일이 필요 없는 res 안의 파일들은 그대로 저장됩니다.그래서 res 안에 있는 datas 폴더에 접근하기 위해 getClass().getResource("/datas"); 를 사용했습니다.

나머지는 입력받은 input에 대해 검사를 한 뒤에, OutputStream을 이용해 resp에 출력해주는 과정입니다.

텍스트 읽어주는 서블릿을 테스트할때는, text.do?textFile="[datas 폴더 내의 특정한 텍스트 파일명]" 을 주소창에 입력해서 잘 출력되는지 확인해보면 됩니다.

이제 정말 몇시간동안 끙끙대며 작성한 textFormServlet 코드입니다.

package kr.or.ddit.servlet01;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;

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

// 1. 텍스트 파일만 옵션으로 제공 할것
// 2. 텍스트 파일 체인지 이벤트로 , iframe에 그 내용이 출력 되도록

@WebServlet("/01/textViewer.tmpl")
public class textFormServlet extends AbstractUseTmplServlet{

    private static final long serialVersionUID = 1L;

    @Override
    protected void setContentType(HttpServletResponse resp) {
        resp.setContentType("text/html; charset=utf-8");
    }

    @Override
    protected void makeData(HttpServletRequest req) {

        URL path = getClass().getResource("/datas");
        File contents = new File(path.getFile());

        ArrayList<String> children = getChildren(contents);

        StringBuffer options = new StringBuffer();
        for(String child : children){
            options.append(String.format("<option>%s</option>",child));
        }
        req.setAttribute("options",options);

    }

    private ArrayList<String>  getChildren(File contents) {
        ArrayList<String> list = new ArrayList<>();

        File[] children = contents.listFiles();

        for(File child : children) {
            if(child.isDirectory()) {
                list.addAll(getChildren(child));
            }else {
                String mime = application.getMimeType(child.getName());

                if(mime!=null && mime.startsWith("text/"))
                    list.add(String.format("%s%s", getParent(child),child.getName()));
            }
        }
        return list;
    }

    private StringBuffer getParent(File file) {
        StringBuffer parent = new StringBuffer();

        if(!"datas".equals(file.getParentFile().getName())) {
            StringBuffer ancestors = getParent(file.getParentFile());
            StringBuffer parentDir = new StringBuffer(file.getParentFile().getName());
            parent.insert(0,ancestors.append(parentDir).append("/"));
        }
        return parent;
    }

}

textServlet과 마찬가지로

URL path = getClass().getResource("/datas");

File contents = new File(path.getFile());

를 이용해 컨텐츠들이 담긴 폴더를 찾아갑니다.

이제 폴더들을 하나 하나 수색해가며 텍스트 파일을 찾아야 하는데요,

해당 파일명들을 모아 담을 children 이라는 ArrayList를 선언했습니다.

getChildren 메서드를 따로 작성해서 파일들을 수색하며 다니도록 했습니다.

    private ArrayList<String>  getChildren(File contents) {
        ArrayList<String> list = new ArrayList<>();

        File[] children = contents.listFiles();

        for(File child : children) {
            if(child.isDirectory()) {
                list.addAll(getChildren(child));
            }else {
                String mime = application.getMimeType(child.getName());

                if(mime!=null && mime.startsWith("text/"))
                    list.add(String.format("%s%s", getParent(child),child.getName()));
            }
        }
        return list;
    }

contents 폴더에서 파일들의 목록을 listFiles로 받아 온 뒤에, 하나하나 검사해서

만약 해당 항목이 폴더라면 if(child.isDirectory())

getChildren 함수를 다시 재귀적으로 불러와서 불러온 리스트들을 list 라고 선언한 ArrayList에 전부 담도록(list.addAll) 했습니다.

만약 해당 항목이 폴더가 아니고 파일이라면 (else)

해당 파일의 mime 을 검사해서 , null도 아니고 mime이 text 파일일 경우에만 list에 add 하도록 했습니다.

그냥 list.add(child.getName()); 을 먼저 했었는데, 해당하는 파일의 계층 구조를 받아오지 못해서 문제가 있었는데요 .

해결을 위해 getParent라는 메서드를 따로 작성했습니다.

    private StringBuffer getParent(File file) {
        StringBuffer parent = new StringBuffer();

        if(!"datas".equals(file.getParentFile().getName())) {
            StringBuffer ancestors = getParent(file.getParentFile());
            StringBuffer parentDir = new StringBuffer(file.getParentFile().getName());
            parent.insert(0,ancestors.append(parentDir).append("/"));
        }
        return parent;
    }

해당 메서드 또한 재귀함수로 작동합니다. 부모 directory가 "datas"가 아닐 경우에는 계속해서 부모를 찾아가며 모든 경로를 읽어와 StringBuffer에 붙여 반환하도록 구현했습니다. 부모 경로는 파일명의 앞에 와야하기 때문에 append가 아닌 insert(0, 을 이용했습니다.

최종적으로 @WebServlet("/01/textViewer.tmpl")

해당 어노테이션을 붙여, textViewer.tmpl 을 불러오는 url들을 납치해 서블릿을 호출하도록 지정해줍니다.

서블릿 3.x 부터는 web.xml 에 맵핑을 해주지 않아도 어노테이션 만으로 가능합니다.

그렇게 만든 텍스트 뷰어를 불러왔습니다.

계층을 깊게 만들어보려고 deeper 라는 폴더를 만들어 그 안에 넣어 봤는데 모두 잘 읽어옵니다. 이미지 파일도 넣어 뒀는데 mime 에서 잘 걸러져서 텍스트 파일 목록만 잘 나오는 것을 확인 할 수 있습니다.

파일명을 선택 하면 이상 없이 잘 읽어 오는 것도 확인할 수 있습니다. 이상입니다.

 

 

 

 

+ 4월 3일 추가

시간이 생겨서 jsp 파일로 만들어 보았습니다. 코드가 간결해집니다.

<%@page import="java.net.URL"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.io.File"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>

	<body>
		<h4>Text file viewer</h4>
		<select id='textFile' name='textFile'>
			<option disabled selected>choose a file to read</option>
<%
	response.setContentType("text/html; charset=utf-8");
	ServletContext context = request.getServletContext();
	URL path = getClass().getResource("/datas");
	File contents = new File(path.getFile());
	ArrayList<String> children = getChildren(contents, context);
	
	for(String child : children){
		out.println(String.format("<option>%s</option>",child));
	}
%>
		</select>
		<br><br>
		<!-- src 속성의 주소값 : ex ) textView.do?textFile=filename(path?) -->
		<iframe id="viewer" src="" style="width:500px;height:500px"></iframe>
<script type="text/javascript">
	var select = document.querySelector("#textFile")
	select.onchange = function(event){
		var fileName = this.value;
		var frm = document.getElementById("viewer");
		frm.setAttribute("src","<%=request.getContextPath()%>/01/text.do?textFile="+fileName);
	}
</script>		
	</body>
</html>
<%!
private ArrayList<String> getChildren(File contents, ServletContext application) {
	ArrayList<String> list = new ArrayList<>();
	File[] children = contents.listFiles();
	
	for(File child : children) {
		if(child.isDirectory()) {
			list.addAll(getChildren(child, application));
		}else {
			String mime = application.getMimeType(child.getName());
			if(mime!=null && mime.startsWith("text/"))
				list.add(String.format("%s%s", getParent(child),child.getName()));
		}
	}
	return list;
}

private StringBuffer getParent(File file) {
	StringBuffer parent = new StringBuffer();
	
	if(!"datas".equals(file.getParentFile().getName())) {
		StringBuffer ancestors = getParent(file.getParentFile());
		String parentDir = file.getParentFile().getName();
		parent.insert(0,ancestors.append(parentDir).append("/"));
	}
	return parent;
}

%>

 

 

jsp에서 <%! %> Scriptlet으로 필요한 메서드를 선언해서 써보았는데요, 선언부 스크립트릿 안에서는 request를 사용할 수가 없었습니다.혼자 해볼때는 위와 같이 method의 Paramete로 request를 받아 사용했는데요,  선생님에게 질문을 하니,  메서드 안에서 어차피 getMimeType등을 사용할 수 있으니 위의상황에서는 굳이 받아오지 않아도 사용할 수 있다고 하셨습니다.  꼭 request를 사용해야 하는 경우에는 파라미터로 받는 방법도 있다곤 하셨습니다.  아니면 page scope를 사용하는것도 방법이라고 하셨습니다.

반응형