Notice
Recent Posts
Recent Comments
Link
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 73일차(15주차5일) - Servlet(URL 절대 경로와 상대 경로) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/DBMS, SQL, JDBC, Servlet

[비트캠프] 73일차(15주차5일) - Servlet(URL 절대 경로와 상대 경로)

끈기JK 2023. 2. 17. 18:13

 

서블릿을 만드는 다양한 방법

 

《interface》Servlet 을 HelloServlet 이 구현하려면, 요청에 대한 작업을 수행하는 service() 뿐만 아니라, init(), destroy(), getServletInfo(), getServletConfig() 를 모두 구현해야 한다.

 

service() 만 구현하고 싶으면 《interface》Servlet 을 상속받은 《abstract》GenericServlet 을 구현체를 상속받아 HelloServlet 을 생성한다.

GenericServlet 은 init(), destroy(), getServletInfo(), getServletConfig() 를 구현해 놓았고 service() 만 미구현하였다. 이를 HelloServlet 이 상속받아 service() 를 구현한다.

 

service() 를 HTTP method 별로 구현하려면 GenericServlet 을 상속받은 《abstract》HttpServlet 을 상속받은 HelloServlet 을 생성한다.

HttpServlet 은 service() 안에서 GET 요청시 doGet(), POST 요청시 doPost() 를 실행하도록 정의하였다.

이를 HelloServlet 이 상속받아 doGet(), doPost() 등을 구현한다.

 

 

 

서버 이중화

 

C1, C2 가 Tomcat Server 1(active) 에 접속한다. 여기서 HelloServlet 실행한다.

C3, C4 가 Tomcat Server 2(active) 에 접속한다. 이때 ① 서버에 문제 발생! 하면

Tomcat Server(standby) 로 이전 한다.

 

Tomcat Server 2 가 실행한 HelloServlet 을 serialize (인스턴스를 그대로 유지한 상태로 다른 서버로 이전 = 인스턴스 값 유지한 상태로 이)해서 바이트 배열로 바꾼다. 이를 deserialize 해서 Tomcat Server(standby) 가 실행한 HelloServlet 로 이전한다.

 

*서블릿 객체를 다른 서버로 이전할 경우를 고려하여 GenericServlet 클래스는 java.io.Serializable 인터페이스를 구현한다.

 

 

 

Web Project 개발과 테스팅 흐름

 

Web Project(프로젝트 폴더)의 index.html 을 ① 편집 및 저장한다. Eclipse IDE 가 tmp0/wtpwebapps/myapp-server/ (톰캣 테스트 환경의 배치 폴더) 로 ② 자동 복사한다. 그 후 Web Browser 에서 index.html 을 ③ 다시 요청(refresh) 하면 ④ 응답(전송) 받는다. 페이지가 ⑤ 갱신 된다.

 

http://localhost:8080/web/ 만 입력하면 자동으로 root 에서 index.html 을 다운받아 보여준다.

 

 

 

서블릿 출력과 한글 문자 코드 인코딩

 

out.println("AB가각") 을 기본 문자 집합 ISO-8859-1 을 사용하여 인코딩한다.

'A'   'B'   '가'   '각'    = 0041 0042 AC00 AC01  ← JVM 기본 문자집합 (UTF-16)

                                    41     42     3F     3F   ← ISO-8859-1 문자집합 으로 변환하여 전송한다.

Web Browser 화면 →  A       B       ?       ?

*ISO-8859-1 character set 에는 한글 문자에 대한 코드 값이 정의되어 있지 않다. 따라서 한글 문자는 '?' (못 바꾸겠다는 의미로)로 변환한다.

 

↓ 해결책

 

response.setContentType("text/html; charset=utf-8");

text/html : MIME Type. 응답 콘텐트가 무엇인지 웹브라우저에게 알려주는 용도

charset=utf-8 : 콘텐트의 문자 인코딩 지정. UTF16 변환하여 UTF8 로 출력

↓ 설정한 이후에

PrintWriter out = response.getWriter();

out.println("AB가각");

↓ UTF16 을 UTF8 규칙에 따라 변환(인코딩)

  'A'     'B'     '가'    '각'

0041   0042     AC00       AC01    ← UTF-16

  41       42     EAB080  EAB081  ← UTF-8 을 전송

   A         B           가            각      ← web browser 화면

 

 

 

요청 파라미터와 한글 인코딩

 

이름: AB가각

GET 요청 (UTF-8) 로 보내면 41 42 EA B0 80 EA B0 81 이고 이를 전송(UTF-8) 한다.

서버에서 getParameter("name") 통해 UTF-16 으로 값을 꺼내면 0041 0042 AC00 AC01 → A B 가 각

POST 요청 (UTF-8) 로 보내면 41 42 EA B0 80 EA B0 81 이고 이를 전송(ISO-8859-1) 한다.

서버에서 getParameter("name") 통해 UTF-16 으로 값을 꺼내면 00 을 앞에 다 붙여 → 0041 0042 00EA 00B0 0080 00EA 00B0 0081 이다. "€ê°.."

 

post 요청으로 전송 받은 문자열이 UTF-8 로 인코딩 되었음을 알려줘야 한다.

→ request.setCharacterEncoding("UTF-8")

반드시 getParameter() 를 호출하기 전에

 

41 42 EA B0 80 EA B0 81

↓ 이를 적용해 전송(UTF-8) 하면

getParameter("name")

0041  0042  AC00  AC01 → "AB가각"

 

 

 

URL 과 getServletPath(), getPathInfo()

 

http://localhost:8080/web/board/list?menu=1

  • /web : context root  → getContextPath() = 웹 애플리케이션 경로
  •  /board : servlet path  → getServletPath()
    *Servlet Path?
    @WebServlet("/board/*")  // 서블릿 경로
  • /list : 서블릿의 하위 경로  → getpathInfo()
  • menu=1 : QueryString  → getQueryString

 

 

 

URL 절대 경로와 상대 경로

 

웹브라우저 주소창 : http://localhost:8080/web/board 에서 /web/ 은 현재 경로, board 는 자원이다.

<a href="/list">...</a>  → http://localhost:8080/list

<a href="list">...</a>  여기서 list 는 자원  → http://localhost:8080/web/list 에서 http://localhost:8080/web/ 는 현재 경로, list 는 자원

 <a href="board/list">...</a>  → http://localhost:8080/web/board/list  board/list 는 자원

 

 

 웹브라우저 주소창 : http://localhost:8080/web/board/index.html  에서 /web/board/ 는 현재 경로, index.html 은 자원이다.

<a href="/list">...</a>  → http://localhost:8080/list

<a href="list">...</a>  → http://localhost:8080/web/board/list  에서 http://localhost:8080/web/board/ 는 현재 경로, list 는 자원

<a href="board/list">...</a>  → http://localhost:8080/web/board/board/list  board/list 는 자원

 

 

 

Command Design Pattern

 

BoardServlet 메서드

form()  ← board/form 요청

insert()  ← board/insert 요청

list()  ← board/list 

view()

update()

delete()

service()  ← board/*

만약 board/search 요청을 추가로 처리해야 한다면 BoardServlet 클래스를 변경해야 한다.

 

- 한 명령 → 한 메서드

- 향후 명령 추가할 가능성 있음

   ↓ Command 패턴 방식으로 설계를 변경

- 한 명령 → 한 클래스

- 명령 추가 → 클래스 추가

   ↓ 

기존 클래스는 손대지 않는다.

 

BoardFormServlet

BoardInsertServlet

BoardListServlet

BoardViewServlet

BoardUpdateServlet

BoardDeleteServlet

BoardSearchServlet

 

 

### 44. Web Application Server 구조로 전환하기
- 웹 기술 도입  - 웹 기술을 도입하여 애플리케이션 서버를 구현하는 방법

 

BoardHandler 파일명을 BoardServlet 으로 변경한다.

Servlet 패키지를 만들고 안에 board 패키지 만들어서 파일 이동한다.

그 안에서 Form, Insert, List, View, Update, Delete, Search 로 클래스를 나눈다. (GoF Design Pattern : Command Pattern)

 

 목록 읽어들일 때 한글 깨짐 방지하기 위해 response.setContentType("text/html;charset=UTF-8"); 입력한다.

@WebServlet("/board/list")
public class BoardListServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  private BoardDao boardDao;

  public BoardListServlet() {
    try {
      InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
          "bitcamp/myapp/config/mybatis-config.xml");
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      BitcampSqlSessionFactory sqlSessionFactory = new BitcampSqlSessionFactory(
          builder.build(mybatisConfigInputStream));
      boardDao = new DaoGenerator(sqlSessionFactory).getObject(BoardDao.class);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<meta charset='UTF-8'>");
    out.println("<title>비트캠프 - NCP 1기</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>게시판</h1>");

    out.println("<div><a href='form'>새 글</a></div>");

    out.println("<table border='1'>");
    out.println("<tr>");
    out.printf("<th>번호</th><th>제목</th><th>작성일</th><th>조회수</th>");
    out.println("</tr>");

    List<Board> boards = this.boardDao.findAll();
    for (Board b : boards) {
      out.println("<tr>");
      out.printf("<td>%d</td><td><a href='view?no=%d'>%s</a></td><td>%s</td><td>%d</td>",
          b.getNo(), b.getNo(), b.getTitle(), b.getCreatedDate(), b.getViewCount());
      out.println("</tr>");
    }
    out.println("</table>");

    out.println("</body>");
    out.println("</html>");
  }
}

 

@WebServlet("/board/view")
public class BoardViewServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  private BoardDao boardDao;

  public BoardViewServlet() {
    try {
      InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
          "bitcamp/myapp/config/mybatis-config.xml");
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      BitcampSqlSessionFactory sqlSessionFactory = new BitcampSqlSessionFactory(
          builder.build(mybatisConfigInputStream));
      boardDao = new DaoGenerator(sqlSessionFactory).getObject(BoardDao.class);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    int boardNo = Integer.parseInt(request.getParameter("no"));

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<meta charset='UTF-8'>");
    out.println("<title>비트캠프 - NCP 1기</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>게시판</h1>");

    Board b = this.boardDao.findByNo(boardNo);

    if (b == null) {
      out.println("<p>해당 번호의 게시글 없습니다.</p>");
    } else {
      this.boardDao.increaseViewCount(boardNo);

      out.println("<form id='board-form' action='update' method='post'>");

      out.println("<table border='1'>");

      out.println("<tr>");
      out.println("  <th>번호</th>");
      out.printf("   <td><input type='text' name='no' value='%d' readonly></td>", b.getNo());
      out.println("</tr>");

      out.println("<tr>");
      out.println("  <th>제목</th>");
      out.printf("   <td><input type='text' name='title' value='%s'></td>", b.getTitle());
      out.println("</tr>");

      out.println("<tr>");
      out.println("  <th>내용</th>");
      out.printf("   <td><textarea name='content' rows='10' cols='60'>%s</textarea></td>", b.getContent());
      out.println("</tr>");

      out.println("<tr>");
      out.println("  <th>암호</th>");
      out.println("   <td><input type='password' name='password'></td>");
      out.println("</tr>");

      out.println("<tr>");
      out.println("  <th>등록일</th>");
      out.printf("   <td>%s</td>", b.getCreatedDate());
      out.println("</tr>");

      out.println("<tr>");
      out.println("  <th>조회수</th>");
      out.printf("   <td>%d</td>", b.getViewCount());
      out.println("</tr>");

      out.println("</table>");
    }

    out.println("<div>");
    out.println("  <button id='btn-list' type='button'>목록</button>");
    out.println("  <button>변경</button>");
    out.println("  <button id='btn-delete' type='button'>삭제</button>");
    out.println("</div>");

    out.println("</form>");

    out.println("<script>");
    out.println("document.querySelector('#btn-list').onclick = function() {");
    out.println("  location.href = 'list';");
    out.println("}");
    out.println("document.querySelector('#btn-delete').onclick = function() {");
    out.println("var form = document.querySelector('#board-form');");
    out.println("  form.action = 'delete';");
    out.println("  form.submit();");
    out.println("}");
    out.println("</script>");

    out.println("</body>");
    out.println("</html>");
  }
}

 

 

요청시 한글 깨짐 방지 위해 출력 보다 위에 request.setCharacterEncoding("UTF-8"); 입력한다.

 

1초 후 페이지 이동하기 위해 (또는 원하는 시간 이후 이동하기 위해)

1. response.setHeader("Refresh", "1;url=list") 화면 출력하고 1초 후 화면 이동시킨다. 숫자 1에 원하는 값 입력한다.

@WebServlet("/board/update")  // @WebServlet("/board/*")
public class BoardUpdateServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  private BoardDao boardDao;

  public BoardUpdateServlet() {
    try {
      InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
          "bitcamp/myapp/config/mybatis-config.xml");
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      BitcampSqlSessionFactory sqlSessionFactory = new BitcampSqlSessionFactory(
          builder.build(mybatisConfigInputStream));
      boardDao = new DaoGenerator(sqlSessionFactory).getObject(BoardDao.class);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");

    Board board = new Board();
    board.setNo(Integer.parseInt(request.getParameter("no")));
    board.setTitle(request.getParameter("title"));
    board.setContent(request.getParameter("content"));
    board.setPassword(request.getParameter("password"));


    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<meta charset='UTF-8'>");
    out.println("<title>비트캠프 - NCP 1기</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>게시판</h1>");

    Board old = boardDao.findByNo(board.getNo());

    if (old == null) {
      out.println("<p>해당 번호의 게시글이 없습니다.</p>");

    } else if (!old.getPassword().equals(board.getPassword())) {
      out.println("<p>암호가 맞지 않습니다!</p>");

    } else {
      boardDao.update(board);
      out.println("<p>변경했습니다.</p>");
    }

    out.println("</body>");
    out.println("</html>");

    // 웹브라우저가 화면 출력을 완료한 후 1초 후에 다시 목록 화면을 호출하도록 명령한다.
    // 어떻게? 응답 헤더에 Refresh 를 추가한다.
    response.setHeader("Refresh", "1;url=list");
  }
}

 

2. <head> 태그 사이에 <meta http-equiv='Refresh' content='1;url=list'>"); 입력한다.

@WebServlet("/board/insert")
public class BoardInsertServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  private BoardDao boardDao;

  public BoardInsertServlet() {
    try {
      InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
          "bitcamp/myapp/config/mybatis-config.xml");
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      BitcampSqlSessionFactory sqlSessionFactory = new BitcampSqlSessionFactory(
          builder.build(mybatisConfigInputStream));
      boardDao = new DaoGenerator(sqlSessionFactory).getObject(BoardDao.class);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");  // getParameter 호출하기 전에 두어야 한다.

    Board board = new Board();
    board.setTitle(request.getParameter("title"));
    board.setContent(request.getParameter("content"));
    board.setPassword(request.getParameter("password"));

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<meta charset='UTF-8'>");
    out.println("<meta http-equiv='Refresh' content='1;url=list'>");
    out.println("<title>비트캠프 - NCP 1기</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>게시판</h1>");

    boardDao.insert(board);
    out.println("<p>등록했습니다.</p>");

    out.println("</body>");
    out.println("</html>");
  }
}

 

@WebServlet("/board/form")
public class BoardFormServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  private BoardDao boardDao;

  public BoardFormServlet() {
    try {
      InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
          "bitcamp/myapp/config/mybatis-config.xml");
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      BitcampSqlSessionFactory sqlSessionFactory = new BitcampSqlSessionFactory(
          builder.build(mybatisConfigInputStream));
      boardDao = new DaoGenerator(sqlSessionFactory).getObject(BoardDao.class);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<meta charset='UTF-8'>");
    out.println("<title>비트캠프 - NCP 1기</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>게시판</h1>");
    out.println("<form action='insert' method='post'>");
    out.println("<table border='1'>");
    out.println("<th>제목</th>");
    out.println("<td><input type='text' name='title'></td></tr>");
    out.println("<tr>");
    out.println("<th>내용</th>");
    out.println("<td><textarea name='content' rows='10' cols='60'></textarea></td></tr>");
    out.println("<tr>");
    out.println("<th>암호</th>");
    out.println("<td><input type='password' name='password'></td>");
    out.println("</tr>");
    out.println("</table>");
    out.println("<div>");
    out.println("<button>등록</button>");
    out.println("<button id='btn-cancel' type='button'>취소</button>");
    out.println("</div>");
    out.println("</form>");
    out.println("<script>");
    out.println("document.querySelector('#btn-cancel').onclick = function() {");
    out.println("location.href = 'list';");
    out.println("}");
    out.println("</script>");
    out.println("</body>");
    out.println("</html>");

  }
}

 

@WebServlet("/board/delete")
public class BoardDeleteServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  private BoardDao boardDao;

  public BoardDeleteServlet() {
    try {
      InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
          "bitcamp/myapp/config/mybatis-config.xml");
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      BitcampSqlSessionFactory sqlSessionFactory = new BitcampSqlSessionFactory(
          builder.build(mybatisConfigInputStream));
      boardDao = new DaoGenerator(sqlSessionFactory).getObject(BoardDao.class);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    int BoardNo = Integer.parseInt(request.getParameter("no"));
    String password = request.getParameter("password");

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<meta charset='UTF-8'>");
    out.println("<title>비트캠프 - NCP 1기</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>게시판</h1>");

    Board old = boardDao.findByNo(BoardNo);

    if (old == null) {
      out.println("<p>해당 번호의 게시글이 없습니다.</p>");

    } else if (!old.getPassword().equals(password)) {
      out.println("<p>암호가 맞지 않습니다!</p>");

    } else {
      boardDao.delete(BoardNo);
      out.println("<p>삭제했습니다.</p>");
    }

    out.println("</body>");
    out.println("</html>");

    response.setHeader("Refresh", "1;url=list");
  }
}

 

 

 


 

조언

 

*실무는 6시간 HTML, CSS, Javascript 작성하고 2시간 SQL 작성한다.

 

 


 

과제

 

StudentServlet, TeacherServlet 만들기