Servlet 04: 클라이언트가 보낸 데이터 읽기 I
비트캠프 서초본원 엄진영 강사님의 수업을 듣고 정리했습니다.
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 요청이다.

웹 브라우저가 보낸 데이터 읽기
- 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
-
- URL(Uniform Resource Locator)
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요청이다.

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
...