Posts 학원 #88일차: 서블릿 프로그래밍
Post
Cancel

학원 #88일차: 서블릿 프로그래밍

com.eomcs.web.ex04

클라이언트가 보낸 데이터 읽기

GET 요청 데이터 읽기

  • 웹 브라우저에 URL을 입력한 후 엔터를 치면 GET 요청이다.
  • 웹 페이지에서 링크를 클릭하면(자바스크립트 처리하지 않은 상태) GET 요청을 보낸다.
  • 웹 페이지의 폼(method=’GET’일 때)에서 전송 버튼을 클릭하면 GET 요청을 보낸다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/ex04/s1")
public class Servlet01 extends GenericServlet {

  private static final long serialVersionUID = 1L;

  @Override
  public void service(ServletRequest req, ServletResponse res)
      throws ServletException, IOException {

    String name = req.getParameter("name");
    String age = req.getParameter("age");

    res.setContentType("text/plain;charset=UTF-8");
    PrintWriter out = res.getWriter();
    out.printf("이름=%s\n", name);
    out.printf("나이=%s\n", age);
  }
}

웹브라우저에서 웹서버의 자원을 요청하는 방법

서블릿 컨테이너는 서블릿의 생성에서 실행에서 소멸까지를 관리한다. html 파일은 서블릿이 아니다. 톰캣 서버에는 웹 브라우저의 요청을 받아서 던지는(처리하는) Web Server가 있고, 서블릿의 생성-실행-소멸까지를 관리하는 Servlet Container가 있다. HTML 문서는 이중 Web Server가 읽어서 리턴한다. 다시말해, Servlet은 Servlet Container가 실행하고, HTML 파일과 같은 정적 파일은 Web Server에서 관리한다.

사실상 JSP와 같은 템플릿도 서블릿이다. 반면 HTML은 서버에서 실행하는 것이 아니라 그대로 읽어서 리턴한다. 자바스크립트 같은 경우는 서버에서 실행하는 것이 아니라 서버가 웹 브라우저에 던져주면 웹 브라우저에서 실행한다.

여기서 프론트엔드와 백엔드가 나뉘게 된다. 프론트엔드 개발자는 브라우저에서 실행하는 코드를 작성하는 개발자이고, 백엔드 개발자는 서버에서 실행하는 코드즉 서블릿을 작성하는 개발자를 말한다.

서블릿 클래스를 실행하고 싶을 때
  • 서블릿 클래스의 실제 위치

    톰캣배치폴더/wtpwebapps/eomcs-java-web/WEB-INF/classes/com/web/ex04/Servlet01.class

  • 요청: 해당 서블릿을 서버에 등록할 때 사용한 URL을 지정해야 한다.

    http://localhost:9999/eomcs-java-web/ex04/s1

서버에 저 주소를 보낸다. 웹 서버는 먼저 해당되는 폴더로 가서 ex04 밑에 s1가 있는지 없는지 찾아볼 것이다. 없으면 그럼 서블릿 컨테이너에게 위임할 것이다. 그래도 못찾는다면 404 에러를 발생시킨다.

찾는 순서는 순서는 배포 폴더에서 먼저 있나 없나 찾아보고 없으면 서블릿 커테이너로 간다. 항상 웹 서버에서 먼저 찾고 없으면 서블릿 컨테이너에서 찾는다.

image

정적 리소스를 처리하는 건 NginX 웹서버나 아파치 웹서버가 훨씬 빠르다. 따라서 웹 프로젝트를 분산배포해야 한다. 정적 리소스는 웹 서버에서 읽어서 다이렉트로 보내고, 서블릿과 같은 동적 리소스는 서블릿 컨테이너가 관리한다.

HTML, CSS, JavaScript, JPEG 등 정적 파일을 받고 싶을 때
  • 정적 파일의 실제 위치

    톰캣배치폴더/wtpwebapps/eomcs-java-web/ex04/test01.html

  • 요청: http://localhost:9999/eomcs-java-web/ex04/test01.html

/WEB-INF/ 폴더에 있는 정적 파일을 받고 싶을 때
  • 정적 파일의 실제 위치

    톰캣배치폴더/wtpwebapps/eomcs-java-web/WEB-INF/ex04/test01.html

  • 요청

    /WEB-INF 폴더 아래에 있는 파일은 클라이언트에서 요청할 수 없다! 웹 애플리케이션의 정보를 두는 폴더이기 때문이다. 이 폴더에 클라이언트가 알아서는 안되는 DB 관련 정보나 실행에 관련한 정보를 둔다. 따라서 WEB-INF 폴더에 있는 파일은 클라이언트가 요청하지 않도록 만들었다.

포트번호 바꾸는 방법: Server > examples-config > server.xml

프로젝트에 적용

BoardService 객체를 생성해서 context에 담는 것은 DataHandlerListener에서 한다. 그러면 이제 context에서 담겨진 Service 객체를 getAttribute를 사용해서 꺼낸다.

1
2
3
ServletContext ctx = request.getServletContext();
BoardService boardService =
  (BoardService) ctx.getAttribute("boardService");

클라이언트가 웹주소와 동봉된 데이터(Query String: qs)를 읽는다. 여기서 request.setCharacterEncoding("UTF-8")을 하지 않아도 되는데, 그 이유는 무엇일까? 숫자는 인코딩을 신경쓰지 않아도 되기 때문이다.

배포된 것 같은데 metadata에서 확인해봤을 때 배포가 되지 않았을때는 어떻게 해야 할까? webapp이 html을 저장하는 폴더인데 누락이 되어서 navigator에 가서 .classpath 경로로 가면 webapp 경로가 없다. 이 문제를 해결하기 위해서는 gradle clean, gradle cleanEclipse, gradle eclipse를 해준다. 지금 설정파일은 webapp을 만들기 전의 파일이기 때문이다.

1
2
3
4
5
6
// 테스트용 객체
Member loginUser = new Member();
loginUser.setNo(1);
board.setWriter(loginUser);
//Member loginUser = (Member) session.getAttribute("loginUser");
//board.setWriter(loginUser);

테스트용 객체를 목업 객체라고 한다.

자바스크립트로 프로그램을 짜지 않는 이상 브라우저가 delete 요청을 보내는 방법이 없다. 브라우저에서 보내는 것은 GET 아니면 POST이다. form에서 DELETE 메서드를 주면 안되는건가? 그러나 form 태그의 method 속성값은 get 아니면 post 만 가능하다.

그러나 꼭 삭제에 대해서 POST를 할 필요는 없다. 그냥 번호를 쿼리스트링으로 전달하는 링크로 delete 버튼을 만들면 된다.

무한스크롤 구현 방법

서버에 그다음 화면을 요청하고 출력한다. 사용자가 화면을 스크롤하면 자바스크립트로 서버에 그 다음 화면을 요청하게 만든다. 그리고 이어서 그 화면을 출력한다. 무조건 자바 스크립트로 요청하고 결과 받아오고 출력한다. 요청하지 않으면 서버는 응답하지 않는다.

mapper 수정하면 서버를 재시작해야 한다. 자동으로 되게 만들면 안된다. 보안 문제 때문이다.

HTTP 요청 프로토콜

1
2
3
4
5
6
GET /java-web/ex04/s1?name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=20 HTTP/1.1 Host: localhost:8080
Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/73.0.3683.86 Safari/537.36 Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng, Accept-Encoding:
gzip, deflate, br Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6 빈 줄
  • GET 요청 HTTP 프로토콜 예)

    GET 요청은 데이터를 request-URI에 붙여서 보낸다.

    request-URI 예) /java-web/ex04/s1?name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=20

    • 서블릿 URL : /java-web/ex04/s1
    • 데이터(Query String) : name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=20
  • 데이터 형식: 이름=값&이름=값&이름=값

URL 인코딩

한글 코드가 7bit 네트워크 장비를 지날 때 문제점

image

데이터를 보낼 때 7bit 네트워크 장비를 거치면 8비트 데이터가 깨진다. 이를 방지하고자 보내는 데이터를 7비트로 변환한다. 이는 원래 코드 값을 아스키(ASCII) 문자 코드로 변환하면 된다. ASCII 코드는 7비트이기 때문에 데이터를 주고 받을 때 깨지지 않을 것이다 즉 URL 인코딩이란 문자 코드의 값을 ASCII 코드화시키는 것을 말한다.

예를 들어 “ABC가각”을 전송한다고 가정하자. “ABC가각”의 문자 코드(UTF-8) 값은 414243EAB080EAB081이다. 7비트 장비를 통과하면 다음과 같이 변한다.

1
2
3
4
41 => 0100 0001 => [7비트 장비] => 100 0001 => [8비트로 복원] => 0100 0001
42 => 0100 0010 => [7비트 장비] => 100 0001 => [8비트로 복원] => 0100 0010
43 => 0100 0011 => [7비트 장비] => 100 0011 => [8비트로 복원] => 0100 0011
EA => 1110 1010 => [7비트 장비] => 110 1010 => [8비트로 복원] => 0110 1010

코드 값이 이미 ASCII 라면(ABC) 그대로 데이터가 전송된다.

코드 값이 ASCII 가 아니라면 각 4비트 값을 아스키 문자라 간주하고 코드로 변환한다.

1
2
E ==> 'E' ==> 45
A ==> 'A' ==> 41

이렇게 변경한 후, URL 인코딩 값임을 표시하기 위해 앞에 ‘%’ 코드를 붙인다.

1
EA ==> 25 45 41 ==> 사람이 보는 문자로 표현하면 ==> %EA ==>

%EA 문자를 받은 쪽에서는 원래의 값을 변환(URL 디코딩)한다.

1
%EA(3바이트) ==> 1110 1010(1바이트)

HTTP 응답 프로토콜

형식

1
2
3
4
status-line(HTTP프로토콜 상태코드 간단한문구) CRLF
*(general header | response header | entity header) CRLF
CRLF
message-body

1
2
HTTP/1.1 200 OK Content-Type: text/plain;charset=UTF-8 Content-Length: 27 Date: Thu, 28 Mar 2019
05:46:08 GMT CRLF 이름=홍길동 나이=20

URI(Uniform Resource Identifier)

  • 웹 자원의 위치를 가리키는 식별자

  • URL(Uniform Resource Locator)
    • scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
    • 예) http://localhost:8080/ex04/s1?name=홍길동&age=20
  • URN(Uniform Resource Name)
    • <URN> ::= "urn:" <NID> ":" <NSS>
    • 예) urn:lex:eu:council:directive:2010-03-09;2010-19-*UE*

POST 요청 데이터 읽기

웹 페이지의 폼(method=’POST’ 일 때)에서 전송 버튼을 클릭하면 POST 요청을 보낸다.

데이터를 읽을 때는 GET 요청과 POST 요청이 같다. ServletRequest.getParameter("파라미터이름") 메서드를 사용해서 데이터를 읽는다.

한편 POST 요청으로 보낸 데이터는 기본으로 영어(ISO-8859-1)라고 간주한다. 그래서 영어 코드를 자바에서 사용하는 UTF-8로 변환한다. 실제 웹 브라우저가 “ABC가각” 문자열을 보낼 때 다음과 같이 UTF-8 코드를 보낸다.

1
414243EAB080EAB081

그런데 서블릿에서는 이 코드 값을 ISO-8859-1 코드라고 간주한다. 그래서 getParameter()를 호출하여 값을 꺼내면 위의 코드를 UTF-16으로 바꾼 값을 리턴한다. 즉 각 바이트에 그냥 00을 붙여 문자열을 만든 후 리턴한다. 영어를 2바이트 유니코드로 바꿀 때 그냥 앞에 00 1바이트를 붙이면 되기 때문이다. 위의 코드를 UTF-16으로 바꾸면 다음과 같다.

1
0041 0042 0043 00EA 00B0 0080 00EA 00B0 0081

이렇게 변환된 코드를 화면에 출력하면 한글이 깨져보인다.

이렇게 클라이언트가 보낸 한글을 읽을 때 깨지는 문제는 어떻게 해결할 수 있을까?

image

getParameter()를 최초로 호출하기 전에 먼저 클라이언트가 보낸 데이터의 인코딩 형식이 어떤 문자표로 되어 있는지 알려줘야 한다(req.setCharacterEncoding("UTF-8"). 단 반드시 getParameter()를 최초로 호출하기 전이어야 한다. 한 번 getParameter()를 호출한 후에는 소용 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
req.setCharacterEncoding("UTF-8");

String age = req.getParameter("age");
String name = req.getParameter("name");

res.setContentType("text/plain;charset=UTF-8");
PrintWriter out = res.getWriter();
out.printf("이름=%s\n", name);
out.printf("나이=%s\n", age);
out.println("-------------------");

char[] chars = name.toCharArray();
for (char c : chars) {
  out.printf("%x\n", (int) c);
}

GET 요청 vs POST 요청

전송 데이터 용량

  • GET

    대부분의 웹서버가 request-line과 헤더의 크기를 8KB로 제한하고 있다. 따라서 긴 게시글과 같은 큰 용량의 데이터를 GET 방식으로 전송할 수 없다.

  • POST

    HTTP 요청 헤더 다음에 message-body 부분에 데이터를 두기 때문에 용량의 제한 없이 웹 서버에 전송할 수 있다. 즉 웹 서버가 제한하지 않는 한 전송 데이터의 크기에 제한이 없다.

    웹 서버가 제한한다? 특정 사이트에서는 게시글의 크기나 첨부파일의 크기에 제한을 둔다.

게시글 조회검색어 입력 같은 간단한 데이터 전송에는 GET 요청으로 보내고 게시글 등록이나 첨부파일 같은 큰 데이터 전송에는 POST 요청으로 보낸다.

바이너리 데이터 전송

  • GET

    request-URI가 텍스트로 되어 있다. 따라서 바이너리 데이터를 request-URI에 붙여서 전송할 수 없다. 그럼에도 꼭 GET 요청으로 바이너리 데이터를 보내고자 한다면 바이너리 데이터를 텍스트로 변환하면 된다. 예를 들어 바이너리 데이터를 Base64로 인코딩하여 텍스트를 만든 후에 GET 요청 방식대로 이름=값 으로 보내면 된다. 그래도 결국 용량 제한 때문에 바이너리 데이터를 GET 요청으로 전송하는 것은 바람직하지 않다.

  • POST

    이 방식에서도 이름=값 형태로는 바이너리 값을 전송할 수 없다. multipart 형식을 사용하면 바이너리 데이터를 보낼 수 있다. 보통 파일 업로드를 구현할 때 이 multipart 전송 방식으로 사용한다.

보안

  • GET

    URL에 전송 데이터가 포함되어 있기 때문에 사용자 아이디나 암호 같은 데이터를 GET 방식으로 전송하는 것은 위험하다. 웹브라우저는 주소창에 입력한 값을 내부 캐시에 보관해두기 때문이다. 그러나 게시물 번호 같은 데이터는 URL에 포함되어야 한다. 그래야 다른 사람에게 URL과 함께 데이터를 보낼 수 있다.

  • POST

    message-body 부분에 데이터가 있기 때문에 웹브라우저는 캐시에 보관하지 않는다. 또한 주소창에도 보이지 않는다. 사용자 암호 같은 데이터를 전송할 때는 특히 이 방식으로 보내는 것이 바람직 하다. 꺼꾸로 특정 페이지를 조회하는 URL일 경우 POST 방식을 사용하면 URL에 조회하려는 정보의 번호나 키를 포함할 수 없기 때문에 이런 상황에서는 POST 방식이 적절하지 않다. 오히려 GET 방식이 적합하다.

This post is licensed under CC BY 4.0 by the author.

학원 #87일차: HTML 태그 사용법

학원 #89일차: 로그인과 사진 업로드 구현

Loading comments from Disqus ...