Servlet 04: 클라이언트가 보낸 데이터 읽기 I

업데이트:
9 분 소요

비트캠프 서초본원 엄진영 강사님의 수업을 듣고 정리했습니다.


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

1. GET 요청 데이터 읽기

GET 요청

  • 웹 브라우저에 URL을 입력한 후 엔터를 치면 GET 요청을 보낸다.
  • 웹 페이지에서 링크를 클릭하면(자바스크립트 처리하지 않은 상태) GET 요청을 보낸다.
  • 웹 페이지의 폼(method=GET 일 때)에서 전송 버튼을 클릭하면 GET 요청을 보낸다.

테스트

  • http://localhost:9999/bitcamp-wep-project/ex04/test01.html 실행

  • test01.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test01</title>
</head>
<body>
<h1>GET 요청1</h1>
<p>
웹 브라우저 주소 창에 'http://localhost:9999/bitcamp-web-project/ex04/s1?name=홍길동&age=20' 
입력한 후 엔터를 친다. 
</p>

<h1>GET 요청2</h1>
<p>
다음 링크를 클릭한다.<br>
<a href="http://localhost:9999/bitcamp-web-project/ex04/s1?name=홍길동&age=20">GET 요청</a>
</p>

<h1>GET 요청3</h1>
<p>
다음 입력폼에 값을 넣고 '전송' 버튼을 클릭한다.
</p>
<form action="s1" method="get">
  이름: <input type="text" name="name"><br>
  나이: <input type="number" name="age"><br>
  <input type="submit" value="전송">
</form>
</body>
</html>
  • <form action="s1" method="get">와 같이 get 요청임을 명시하거나 method를 생략하면 get 요청이다.

GET 요청

웹 브라우저가 보낸 데이터 읽기

  • ServletRequest.getParameter(“파라미터이름”)
@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);
  }
}

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

1) 서블릿 클래스를 실행하고 싶을 때

  • 서블릿 클래스의 실제 위치
    • 톰캣배치폴더/wtpwebapps/eomcs-java-web/WEB-INF/classes/com/eomcs/web/ex04/Servlet01.class
  • 요청: 해당 서블릿을 서버에 등록할 때 사용한 URL을 지정해야 한다.
    • 예) http://localhost:9999/eomcs-java-web/ex04/s1

2) HTML, CSS, JavaScript, JPEG 등 정적 파일을 받고 싶을 때

  • 정적 파일(static resource)의 실제 위치
    • 톰캣배치폴더/wtpwebapps/eomcs-java-web/ex04/test01.html
  • 요청: 실제 위치를 URL에 적는다.
    • 예) http://localhost:9999/eomcs-java-web/ex04/test01.html

3) /WEB-INF/ 폴더에 있는 정적 파일을 받고 싶을 때

  • 정적 파일의 실제 위치
    • 톰캣배치폴더/wtpwebapps/eomcs-java-web/WEB-INF/ex04/test01.html
  • 요청: WEB-INF 폴더 아래에 있는 파일은 클라이언트에서 요청할 수 없다
    • 웹 애플리케이션의 정보(사용자 암호 등)를 두는 폴더이다.
    • 따라서 클라이언트가 접근하지 못하도록 막혀있다.

HTTP 요청 형식

  • request line + header + CRLF + message-body
    method sp request-URI sp http_version CRLF
    *(general header | request header | entity header) CRLF
    CRLF
    message-body
    
  • URI : Uniform Resource Identifier, 통합 자원 식별자
  • URL : Uniform Resource Locator, 파일 식별자

GET 요청 HTTP 프로토콜 예)

  • GET 요청은 데이터를 request-URI에 붙여서 보낸다.
  • request-URI 예)
    • /java-web/ex04/s1?name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=20
    • %ED%99%8D%EA%B8%B8%EB%8F%99는 한글이 URL 인코딩된 형태이다.
  • request-URI : URL + Query String
  • 서블릿 URL
    • /java-web/ex04/s1
  • 데이터(Query String) : 물음표 다음에 있는 데이터
    • name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=20
  • 데이터 형식
    • 이름=값&이름=값&이름=값

URL 인코딩

  • 데이터를 보낼 때 7bit 네트워크 장비를 거치면 8비트 데이터가 깨진다.
  • 이를 방지하고자 보내는 데이터를 7비트로 변환한다.
  • 어떻게? 원래 코드 값을 아스키(ASCII) 문자 코드로 변환한다.
  • ASCII 코드는 7비트이기 때문에 데이터를 주고 받을 때 깨지지 않을 것이다.
  • URL 인코딩이란? 문자 코드의 값을 ASCII 코드화시키는 것이다.
  • 예) “ABC가각”을 전송한다고 가정하자.
    • “ABC가각”의 문자 코드(UTF-8) 값 : 414243EAB080EAB081
  • 7비트 장비를 통과:
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 문자코드로 변환
  • 코드 값이 이미 ASCII 라면 그대로
    • 41 ⇒ 41
    • 42 ⇒ 42
  • 코드 값이 ASCII 가 아니라면 각 4비트 값을 아스키 문자라 간주하고 코드로 변환한다.
    • E ⇒ ‘E’ ⇒ 45
    • A ⇒ ‘A’ ⇒ 41
  • 이렇게 변경한 후, URL 인코딩 값임을 표시하기 위해 앞에 ‘%’ 코드를 붙인다.
    • EA ⇒ 25 45 41 ⇒ 사람이 보는 문자로 표현하면 ⇒ %EA ⇒
    • %EA 문자를 받은 쪽에서는 원래의 값을 변환(URL 디코딩)한다.
    • %EA(3바이트) ⇒ 1110 1010(1바이트)
  • 예)
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 빈 줄

HTTP 응답 프로토콜 형식

status-line(HTTP프로토콜 상태코드 간단한문구) CRLF
*(general header | response header | entity header) CRLF
CRLF
message-body
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:lex:eu:council:directive:2010-03-09;2010-19-UE

2. POST 요청 데이터 읽기

POST 요청

  • 웹 페이지의 폼(method=’POST’ 일 때)에서 전송 버튼을 클릭하면 POST 요청을 보낸다.
@WebServlet("/ex04/s2")
public class Servlet02 extends GenericServlet {
  private static final long serialVersionUID = 1L;

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

    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);

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

테스트

  • http://localhost:9999/bitcamp-wep-project/ex04/test02.html 실행

test02.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test02</title>
</head>
<body>
<h1>POST 요청</h1>
<p>
다음 입력폼에 값을 넣고 '전송' 버튼을 클릭한다.
</p>
<form action="/bitcamp-web-project/ex04/s2" method="post"> 
  이름: <input type="text" name="name"><br>
  나이: <input type="number" name="age"><br>
  <input type="submit" value="전송">
</form>
</body>
</html>
  • <form action="s2" method="post">와 같이 post 요청임을 명시해야하만 post 요청이다. 생략하면 get요청이다.

POST 요청

  • action="/bitcamp-web-project/ex04/s2"와 같이 절대 경로를 명시하면, 이후 URL을 변경하고 싶을 때마다 모든 html 문서를 찾아 변경해야 한다. 상대 경로를 적으면 쉽게 수정할 수 있다.
<form action="s2" method="post">
http://localhost:9999/bitcamp-web-project/ex04/test02.html
					 ^					 ^
				 server root       context root
  • 웹 브라우저가 사용하는 루트는 Server Root → 웹 브라우저에게 던져주는 루트는 모두 Server Root
  • 서블릿 컨테이너가 사용하는 루트는 Context Root(Web Application Root) → 서블릿 컨테이너에게 던져주는 루트는 Context Root
  • 웹 브라우저가 읽어야 하는 루트는 서버 루트, 서블릿 컨텍스트가 읽어야 하는 루트는 컨텍스트 루트

웹 브라우저가 보낸 데이터 읽기

  • 서버에서 데이터를 읽을 때는 GET 요청과 POST 요청이 같다.
    • ServletRequest.getParameter("파라미터이름")
    • 리턴 값은 String이다.
  • POST 요청으로 보낸 데이터는 기본으로 영어(ISO-8859-1)라고 간주한다.
  • 그래서 ISO-8859-1 문자 코드를 UCS2(UTF-16) 문자 코드로 변환한다.
  • 예) “ABC가각”을 보낸다고 가정하자.

    실제 웹 브라우저가 "ABC가각" 문자열을 보낼 때 다음과 같이 UTF-8 코드를 보낸다.

    41 42 43 EA B0 80 EA B0 81

    그런데 서블릿에서는 이 코드 값을 ISO-8859-1 코드라고 간주한다.

    그래서 getParameter()를 호출하여 값을 꺼내면 위의 코드를 UTF-16으로 바꾼 값을 리턴한다.

    즉 각 바이트에 그냥 00을 붙여 문자열을 만든 후 리턴한다.

    왜? ISO-8859-1 문자 코드를 2바이트 유니코드로 바꿀 때 그냥 앞에 00 1바이트를 붙이면 되기 때문이다.

    위의 코드를 UTF-16으로 바꾸면 다음과 같다.

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

    이렇게 변환된 코드를 화면에 출력하면 다음 폰트로 보인다.

    ABC가각

클라이언트가 보낸 한글을 읽을 때 깨지는 문제 해결?

  • getParameter()를 최초로 호출하기 전에 먼저 클라이언트 보낸 데이터의 인코딩 형식이 어떤 문자표로 되어 있는지 알려줘야 한다.
  • 반드시 getParamter()를 최초로 호출하기 전이어야 한다. 한 번 getParameter()를 호출한 후에는 소용없다.
request.setCharacterEncoding("UTF-8");
  • 숫자나 영문 데이터만을 꺼낼 때는 안해도 된다.
POST /java-web/ex04/s2 HTTP/1.1 
Host: localhost:8080 
Connection: keep-alive 
Content-Length: 33
Pragma: no-cache 
Cache-Control: no-cache 
Origin: http://localhost:8080 
Upgrade-Insecure-Requests: 1 
Content-Type: application/x-www-form-urlencoded 
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, 
Referer: http://localhost:8080/java-web/ex04/test02.html 
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 
빈 줄
name=ABC%EA%B0%80%EA%B0%81&age=20

GET 요청 vs POST 요청

1) 전송 데이터 용량

  • GET
    • 대부분의 웹서버가 request-line과 헤더의 크기를 8KB로 제한하고 있다.
    • 따라서 긴 게시글과 같은 큰 용량의 데이터를 GET 방식으로 전송할 수 없다.
  • POST
    • HTTP 요청 헤더 다음에 message-body 부분에 데이터를 두기 때문에 용량의 제한 없이 웹 서버에 전송할 수 있다.
    • 즉 웹 서버가 제한하지 않는 한 전송 데이터의 크기에 제한이 없다.
    • 웹 서버가 제한한다?
      • 특정 사이트에서는 게시글의 크기나 첨부파일의 크기에 제한을 둔다.
    • 용도
      • 게시글 조회나 검색어 입력 같은 간단한 데이터 전송에는 GET 요청으로 보낸다.
      • 게시글 등록이나 첨부파일 같은 큰 데이터 전송에는 POST 요청으로 보낸다.

2) 바이너리 데이터 전송

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

3) 보안

  • GET
    • URL에 전송 데이터가 포함되어 있기 때문에 사용자 아이디나 암호 같은 데이터를 GET 방식으로 전송하는 것은 위험하다.
    • 모든 웹 브라우저는 주소 창에 입력한 값을 내부 캐시에 보관해 두기 때문이다.
    • 그러나 게시물 번호 같은 데이터는 URL에 포함되어야 한다.
    • 그래야 다른 사람에게 URL과 함께 데이터를 보낼 수 있다.
  • POST
    • mesage-body 부분에 데이터가 있기 때문에 웹 브라우저는 캐시에 보관하지 않는다.
    • 또한 주소 창에도 보이지 않는다.
    • 사용자 암호 같은 데이터를 전송할 때는 특히 이 방식으로 보내는 것이 바람직 하다.
    • 즉 보내는 데이터를 웹 브라우저의 캐시 메모리에 남기고 싶지 않을 때는 POST 방식을 사용한다.
    • 거꾸로 특정 페이지를 조회하는 URL일 경우 POST 방식을 사용하면 URL에 조회하려는 정보의 번호나 키를 포함할 수 없기 때문에 이런 상황에서는 POST 방식이 적절하지 않다. 오히려 GET 방식이 적합하다.
  • gradle eclipse를 한 후에 webapp 폴더를 생성했다면, 다시 gradle eclipse를 실행해야 한다.

3. 파일 업로드 처리하기

POST 요청으로 파일 전송하기

  • 파일을 업르도하여 서버에 전송한다.
  • multipart/form-data 형식으로 데이터를 전송하지 않으면 첨부 파일의 데이터는 받을 수 없다.
  • 반드시 Post 요청으로 전송해야 한다.

테스트

    • http://localhost:8080/java-web/ex04/test03.html 실행

test03.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test03</title>
</head>
<body>
<h1>파일 업로드</h1>
<p>
'파일 선택(Browse)' 버튼을 클릭하여 사진 파일을 선택한 후 '전송' 버튼을 누른다.
</p>

<h2>GET 요청으로 파일 업로드</h2>
<form action="s3" method="get">
  이름: <input type="text" name="name"><br>
  나이: <input type="number" name="age"><br>
  사진: <input type="file" name="photo"><br>
  <input type="submit" value="GET 전송">
</form>
<p>
GET 으로 파일을 전송할 수 없다. 단지 파일 이름만 전송한다. 
</p>

<h2>POST 요청으로 파일 업로드</h2>
<form action="/eomcs-java-web/ex04/s3" method="post">
  이름: <input type="text" name="name"><br>
  나이: <input type="number" name="age"><br>
  사진: <input type="file" name="photo"><br>
  <input type="submit" value="POST 전송">
</form>
<p>
현재 form의 데이터 인코딩 형식은 "application/x-www-form-urlencoded"이다.
즉 "이름=값&이름=값&이름=값" 형식으로 서버에 데이터를 보낸다.
이 방식 또한 첨부한 파일의 데이터를 보내지 못한다.
단지 파일 이름만 보낸다.
</p>

<h2>POST 요청 + multipart/form-data 형식으로 파일 업로드</h2>
<form action="s3" enctype="multipart/form-data" method="post">
  이름: <input type="text" name="name"><br>
  나이: <input type="number" name="age"><br>
  사진: <input type="file" name="photo"><br>
  <input type="submit" value="POST 전송">
</form>
<p>
파일을 업로드 하려면, 반드시 multipart/form-data 형식으로 POST 요청해야 한다.
그래야 파일의 이름 뿐만아니라 파일 데이터도 전송한다.
</p>
</body>
</html>

파일 업로드

@WebServlet("/ex04/s3")
public class Servlet03 extends GenericServlet {
  private static final long serialVersionUID = 1L;

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

    req.setCharacterEncoding("UTF-8");

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

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

    // 파일의 데이터를 전송하려면,
    // <form> 태그에 enctype 속성을 "multipart/form-data"로 설정해야 한다.
    //
    // 단 멀티파트 형식으로 데이터가 넘어온 경우에는
    // getParameter()로 그 값을 꺼낼 수 없다.
  }
}
  • GET 요청이나 일반 POST 요청을 한 경우에는 파일이 이름만 넘어오고 파일 데이터는 넘어오지 않는다.
  • 파일의 데이터를 전송하려면, <form> 태그에 enctype 속성을 "multipart/form-data"로 설정해야 한다.

1) GET 요청 예

GET /eomcs-java-web/ex04/s3?name=ABC%EA%B0%80%EA%B0%81&age=20&photo=actors.jpg HTTP/1.1
Host: 192.168.1.10:9999
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/80.0.3987.149 Safari/537.36
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
Referer: http://192.168.1.10:9999/eomcs-java-web/ex04/test03.html
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6,cs;q=0.5
Connection: keep-alive

2) 일반 POST 요청 예

  • form의 기본 데이터 전송 형식은 “application/x-www-form-urlencoded”이다.
  • 즉 “이름=값&이름=값” 형태로 전송한다.
  • 다음 요청 프로토콜에서 “Content-Type” 헤더를 확인해 보라!
POST /eomcs-java-web/ex04/s3 HTTP/1.1
Host: 192.168.1.10:9999
Content-Length: 50
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.1.10:9999
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/80.0.3987.149 Safari/537.36
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
Referer: http://192.168.1.10:9999/eomcs-java-web/ex04/test03.html
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6,cs;q=0.5
Connection: keep-alive

name=ABC%EA%B0%80%EA%B0%81&age=20&photo=actors.jpg

3) 멀티파트 POST 요청 예

  • 멀티 파트 방식으로 전송하면, 파일명이 텍스트로도 전송되고 바이너리 데이터도 전송된다.
  • Servlet Container에서는 이 바이너리 데이터를 읽어들이기 위한 별도의 처리과정이 필요하다.
POST /eomcs-java-web/ex04/s3 HTTP/1.1
Host: 192.168.1.10:9999
Content-Length: 248900
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.1.10:9999
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryT1G23U6fYMK0zZxx
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
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
Referer: http://192.168.1.10:9999/eomcs-java-web/ex04/test03.html
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6,cs;q=0.5
Connection: keep-alive

------WebKitFormBoundaryT1G23U6fYMK0zZxx
Content-Disposition: form-data; name="name"

ABC가각
------WebKitFormBoundaryT1G23U6fYMK0zZxx
Content-Disposition: form-data; name="age"

20
------WebKitFormBoundaryT1G23U6fYMK0zZxx
Content-Disposition: form-data; name="photo"; filename="actors.jpg"
Content-Type: image/jpeg

...

태그:

카테고리:

업데이트: