'스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 수업을 듣고 정리한 내용입니다.
스프링 부트 환경에서 서블릿을 등록하고 사용해보자.
참고
서블릿은 톰캣 같은 웹 애플리케이션 서버를 직접 설치하고, 그 위에 서블릿 코드를 클래스 파일로 빌드해서 올린 다음, 톰캣 서버를 실행하면 된다. 하지만 이 과정은 매우 번거롭다.
스프링 부트는 톰캣 서버를 내장하고 있으므로, 톰캣 서버 설치 없이 편리하게 서블릿 코드를 실행할 수 있다.
@ServletComponentScan
: 스프링 부트는 서블릿을 직접 등록해서 사용할 수 있도록 @ServletComponentScan
을 지원한다.
main/java/servlet/ServletApplication.java
package hello.servlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan //서블릿 자동 등록
@SpringBootApplication
public class ServletApplication {
public static void main(String[] args) {
SpringApplication.run(ServletApplication.class, args);
}
}
실행 화면
ServletApplicaiton
을 실행하였을 때 아무것도 뜨지 않는다. (아직 입력이 없다.)
서블릿 등록하기
처음으로 실제 동작하는 서블릿 코드를 등록해보자.
main/java/hello.servlet/basic/HelloServlet.java
package hello.servlet.basic;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// hello로 오면 이게 실행된다.
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HelloServlet.service");
System.out.println("request = " + request);
System.out.println("response = " + response);
String username = request.getParameter("username");
System.out.println("username = " + username);
}
}
실행 결과
localhost:8080/hello?username=chang
여기서
?username=chang
: 쿼리 파라미터 (서블릿을 편하게 읽는 것을 지원하다.)
@WebServlet 서블릿 애노테이션
@WebServlet
애노테이션은 해당 클래스를 서블릿으로 사용할 수 있게 해주는 애노테이션이다.
name
: 서블릿 이름urlPatterns
: URL 매핑name
과urlPatterns
는 겹치면 안된다.
HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 다음 메서드를 실행한다.
protected void service(HttpServletRequest request, HttpServletResponse response)
웹브라우저 실행
http://localhost:8080/hello?username=world
콘솔 실행 결과
HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@a8d60b1
response = org.apache.catalina.connector.ResponseFacade@63c4ff4b
username = chang
application.properties
에
logging.level.org.apache.coyote.http11=debug
을 추가
서버를 다시 시작하고, 요청해보면 서버가 받은 HTTP 요청 메시지를 출력하는 것을 확인할 수 있다.
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: ko,en-US;q=0.9,en;q=0.8,ko-KR;q=0.7
]
HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@51cb338d
response = org.apache.catalina.connector.ResponseFacade@43c45faf
username = world
2022-01-06 10:56:10.807 DEBUG 1362 --- [nio-8080-exec-1] o.a.coyote.http11.Http11InputBuffer : Before fill(): parsingHeader: [true], parsingRequestLine: [true], parsingRequestLinePhase: [0], parsingRequestLineStart: [0], byteBuffer.position(): [0], byteBuffer.limit(): [0], end: [732]
2022-01-06 10:56:10.807 DEBUG 1362 --- [nio-8080-exec-1] o.a.coyote.http11.Http11InputBuffer : Received []
2022-01-06 10:56:10.807 DEBUG 1362 --- [nio-8080-exec-1] o.apache.coyote.http11.Http11Processor : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@40cdcc4:org.apache.tomcat.util.net.NioChannel@28525a1e:java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:8080 remote=/0:0:0:0:0:0:0:1:50308]], Status in: [OPEN_READ], State out: [OPEN]
📝 참고
운영서버에 이렇게 모든 요청 정보를 다 남기면 성능저하가 발생할 수 있다. 개발 단계에서만 적용하자.
(1) 내장 톰캣 서버 생성
스프링 부트 내부에 포함된 내장 톰캣이 띄워지면서 가지고 있는 서블릿 컨테이너를 통해 서블릿을 생성하고 등록해준다.
(2) HTTP 요청, HTTP 응답 메시지
(3) 웹 애플리케이션 서버의 요청 응답 구조
request
, response
객체를 생성한다.request
, response
객체를 전달한다.response
를 기반으로 HTTP 응답 메시지를 생성해서 웹 브라우저로 전송한다.
📝 참고
HTTP 응답에서 Content-Length는 웹 애플리케이션 서버가 자동으로 생성해준다.
이외로 welcome 페이지 추가
main/webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body> <ul>
<li><a href="basic.html">서블릿 basic</a></li> </ul>
</body>
</html>
main/webapp/basic.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body> <ul>
<li>hello 서블릿
<ul>
<li><a href="/hello?username=servlet">hello 서블릿 호출</a></li>
</ul> </li>
<li>HttpServletRequest
<ul>
<li><a href="/request-header">기본 사용법, Header 조회</a></li> <li>HTTP 요청 메시지 바디 조회
<ul>
<li><a href="/request-param?username=hello&age=20">GET -
쿼리 파라미터</a></li> li>
<li><a href="/basic/hello-form.html">POST - HTML Form</a></
<li>HTTP API - MessageBody -> Postman 테스트</li> </ul>
</li> </ul>
</li>
<li>HttpServletResponse
<ul>
<li><a href="/response-header">기본 사용법, Header 조회</a></li> <li>HTTP 응답 메시지 바디 조회
<ul>
<li><a href="/response-html">HTML 응답</a></li>
<li><a href="/response-json">HTTP API JSON 응답</a></li>
</ul> </li>
</ul> </li>
</ul>
</body>
</html>
index.html
basic.html
HttpServletRequest 역할
서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다.
그리고 그 결과를HttpServletRequest
객체에 담아서 제공한다.
HttpServletRequest
를 사용하면 다음과 같은 HTTP 요청 메시지를 편리하게 조회할 수 있다.
POST /save HTTP/1.1
HOST: localhost:8080
Content-Type: application/x-ww-form-urlencoded
username=kim&age=20
(1) START LINE
- HTTP 메소드
- URL
- 쿼리 스트링
- 스키마, 프로토콜
(2) 헤더
- 헤더 조회
(3) 바디
- form 파라미터 형식 조회
- message body 데이터 직접 조회
HttpServletRequest
객체는 추가로 여러가지 부가기능도 함께 제공한다.
임시 저장소 기능
해당 HTTP 요청이 시작부터 끝날 때까지 유지되는 임시 저장소 기능
request.setAttribute(name, value)
request.getAttribute(name)
세션 관리 기능
request.getSession(create: true)
💡 중요
HttpServletRequest
,HttpServletResponse
를 사용할 때 가장 중요한 점은 이 객체들이 HTTP 요청 메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점이다. 따라서 이 기능에 대해서 깊이있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다.
HttpServletRequest가 제공하는 기본 기능들을 알아보자!
hello.servlet.basic.request.RequestHeaderServlet.java
package hello.servlet.basic.request;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
printStartLine(request);
printHeaders(request);
printHeaderUtils(request);
printEtc(request);
}
}
start-line 정보
private void printStartLine(HttpServletRequest request) {
System.out.println("--- REQUEST-LINE - start ---");
System.out.println("request.getMethod() = " + request.getMethod()); //GET
System.out.println("request.getProtocal() = " + request.getProtocol()); //
System.out.println("request.getScheme() = " + request.getScheme()); //http
// http://localhost:8080/request-header
System.out.println("request.getRequestURL() = " + request.getRequestURL());
// /request-test
System.out.println("request.getRequestURI() = " + request.getRequestURI());
//username=hi
System.out.println("request.getQueryString() = " +
request.getQueryString());
System.out.println("request.isSecure() = " + request.isSecure()); //https
System.out.println("--- REQUEST-LINE - end ---");
System.out.println();
}
실행 결과
--- REQUEST-LINE - start ---
request.getMethod() = GET
request.getProtocal() = HTTP/1.1
request.getScheme() = http
request.getRequestURL() = http://localhost:8080/request-header
request.getRequestURI() = /request-header
request.getQueryString() = username=hello
request.isSecure() = false
--- REQUEST-LINE - end ---
헤더 정보
//Header 모든 정보
private void printHeaders(HttpServletRequest request) {
System.out.println("--- Headers - start ---");
/*
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
System.out.println(headerName + ": " + request.getHeader(headerName));
}
*/
request.getHeaderNames().asIterator()
.forEachRemaining(headerName -> System.out.println(headerName + ":" + request.getHeader(headerName)));
System.out.println("--- Headers - end ---");
System.out.println();
}
실행 결과
--- Headers - start ---
host:localhost:8080
connection:keep-alive
cache-control:max-age=0
sec-ch-ua:" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile:?0
sec-ch-ua-platform:"macOS"
upgrade-insecure-requests:1
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site:none
sec-fetch-mode:navigate
sec-fetch-user:?1
sec-fetch-dest:document
accept-encoding:gzip, deflate, br
accept-language:ko,en-US;q=0.9,en;q=0.8,ko-KR;q=0.7
--- Headers - end ---
값을 간단하게 조회하고 싶을 때 : request.getHeader("host");
//Header 편리한 조회
private void printHeaderUtils(HttpServletRequest request) {
System.out.println("--- Header 편의 조회 start ---");
System.out.println("[Host 편의 조회]");
System.out.println("request.getServerName() = " +
request.getServerName()); //Host 헤더
System.out.println("request.getServerPort() = " +
request.getServerPort()); //Host 헤더
System.out.println();
System.out.println("[Accept-Language 편의 조회]");
request.getLocales().asIterator()
.forEachRemaining(locale -> System.out.println("locale = " + locale));
System.out.println("request.getLocale() = " + request.getLocale()); // getLocale() : 최상위가 자동으로 호출
System.out.println();
System.out.println("[cookie 편의 조회]");
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}
}
System.out.println();
System.out.println("[Content 편의 조회]");
System.out.println("request.getContentType() = " +
request.getContentType());
System.out.println("request.getContentLength() = " + request.getContentLength());
System.out.println("request.getCharacterEncoding() = " +
request.getCharacterEncoding());
System.out.println("--- Header 편의 조회 end ---");
System.out.println();
}
Postman에서 테스트
POST
의 Body
에 Hello!를 추가한다.
Headers
에 key
, values
가 추가된다.
Send
를 클릭하여 서버에 보낸다.
결과
--- Header 편의 조회 start ---
[Host 편의 조회]
request.getServerName() = localhost
request.getServerPort() = 8080
[Accept-Language 편의 조회]
locale = ko_KR
request.getLocale() = ko_KR
[cookie 편의 조회]
[Content 편의 조회]
request.getContentType() = text/plain
request.getContentLength() = 6
request.getCharacterEncoding() = UTF-8
--- Header 편의 조회 end ---
request.getContentLength()
결과 6을 확인할 수 있다.
기타 정보는 HTTP 메시지의 정보는 아니다.
private void printEtc(HttpServletRequest request) {
System.out.println("--- 기타 조회 start ---");
System.out.println("[Remote 정보]"); // 요청에 온 것에 대한 정보
System.out.println("request.getRemoteHost() = " +
request.getRemoteHost()); //
System.out.println("request.getRemoteAddr() = " +
request.getRemoteAddr()); //
System.out.println("request.getRemotePort() = " +
request.getRemotePort()); //
System.out.println();
System.out.println("[Local 정보]"); // 나의 서버에 대한 정보
System.out.println("request.getLocalName() = " +
request.getLocalName()); //
System.out.println("request.getLocalAddr() = " +
request.getLocalAddr()); //
System.out.println("request.getLocalPort() = " +
request.getLocalPort()); //
System.out.println("--- 기타 조회 end ---");
System.out.println();
}
결과
--- 기타 조회 start ---
[Remote 정보]
request.getRemoteHost() = 0:0:0:0:0:0:0:1
request.getRemoteAddr() = 0:0:0:0:0:0:0:1
request.getRemotePort() = 60605
[Local 정보]
request.getLocalName() = localhost
request.getLocalAddr() = 0:0:0:0:0:0:0:1
request.getLocalPort() = 8080
--- 기타 조회 end ---
📝 참고
로컬에서 테스트하면IPv6
정보가 나오는데,IPv4
정보를 보고 싶으면 다음 옵션을 VM option에 넣어주면 된다.
-Djava.net.preferIPv4Stack=true
지금까지 HttpServletRequest
를 통해서 HTTP 메시지의 start-line
, header
정보 조회 방법을 이해했다.
다음부터는 HTTP 요청 데이터를 어떻게 조회하는지 알아보자.
HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.
주로 다음 3가지 방법을 사용한다.
(1) GET - 쿼리 파라미터
/url
?username=hello&age=20
- 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
- ex) 검색, 필터, 페이징등에서 많이 사용하는 방식
(2) POST - HTML Form
content-type: application/x-www-form-urlencoded
- 메시지 바디에 쿼리 파라미터 형식으로 전달
username=hello&age=20
- 예) 회원 가입, 상품 주문, HTML Form 사용
POST
: HTML-Form 예시
(3) HTTP message body에 데이터를 직접 담아서 요청
- HTTP API에서 주로 사용, JSON, XML, TEXT등이 있다.
데이터 형식은 주로 JSON 사용
POST
,PUT
,PATCH
이제 하나씩 알아보자!
다음 데이터를 클라이언트에서 서버로 전송해보자.
username=hello
age=20
메시지 바디 없이, URL의 쿼리 파라미터를 사용해서 데이터를 전달하자
쿼리 파라미터는 URL에 다음과 같이 ?
를 시작으로 보낼 수 있다. 추가 파라미터는 &
로 구분하면 된다.
http://localhost:8080/request-param?username=hello&age=20
서버에서는 HttpServletRequest
가 제공하는 다음 메서드를 통해 쿼리 파라미터를 편리하게 조회할 수 있다.
쿼리 파라미터 조회 메서드
String username = request.getParameter("username"); //단일 파라미터 조회
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map 으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회
단일 파라미터 :
username=hello
로 조회
main.java.hello.servlet.request.RequestParamServlet
package hello.servlet.basic.request;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*
* 1. 파라미터 전송 가능
* http://localhost:8080/request-param?username=hello&age=20
*
* 2. 동일한 파라미터 전송 가능
* http://localhost:8080/request-param?username=hello&username=chang&age=20
*
* */
@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String paramName = parameterNames.nextElement();
System.out.println(paramName + "=" +request.getParameter(paramName));
}
*/
System.out.println("[전체 파라미터 조회] - start");
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName)));
System.out.println("[전체 파라미터 조회] - end");
System.out.println();
System.out.println("[단일 파라미터 조회] - start");
String username = request.getParameter("username"); System.out.println("request.getParameter(username) = " + username);
String age = request.getParameter("age");
System.out.println("request.getParameter(age) = " + age);
System.out.println("[단일 파라미터 조회] - end");
System.out.println();
response.getWriter().write("ok");
}
}
실행결과 - 파라미터 전송
서블릿을 만든 후 http://localhost:8080/request-param?username=hello&age=20
파라미터를 전송하면 다음과 같이 출력된다.
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회] - start
request.getParameter(username) = hello
request.getParameter(age) = 20
[단일 파라미터 조회] - end
복수 파라미터, 파라미터 이름은 하나이지만 값이 중복인 경우 :
username=hello&username=chang
위 코드에 추가
System.out.println("[이름이 같은 복수 파라미터 조회]");
System.out.println("request.getParameterValues(username)");
String[] usernames = request.getParameterValues("username");
for (String name : usernames) {
System.out.println("username=" + name);
}
response.getWriter().write("ok");
실행결과 - 동일 파라미터 전송
http://localhost:8080/request-param?username=hello&username=chang&age=20
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회] - start
request.getParameter(username) = hello
request.getParameter(age) = 20
[단일 파라미터 조회] - end
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello
username=chang
username=hello&username=chang
와 같이 파라미터 이름은 하나이지만, 값이 중복일때
request.getParameter()
: 하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용한다.request.getParameterValues()
: 지금처럼 값이 중복일 때 사용한다.- 이렇게 중복일 때
request.getParameter()
를 사용하면request.getParameterValues()
의 첫 번째 값을 반환한다.
이번에는 HTML의 Form을 사용해서 클라이언트에서 서버로 데이터를 전송해보자.
주로 회원가입, 상품 주문 등에서 사용하는 방식이다.
특징
content-type: application/x-www-form-urlencoded
- 메세지 바디에 쿼리 파라미터 형식으로 데이터를 전달한다.
username=hello&age=20
src/main/webapp/basic/hello-form.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/request-param" method="post">
username: <input type="text" name="username" /> age: <input type="text" name="age" /> <button type="submit">전송</button>
</form>
</body>
</html>
실행 결과
http://localhost:8080/basic/hello-form.html
💡 주의
웹 브라우저가 결과를 캐시하고 있어서, 과거에 작성했던 html 결과가 보이는 경우도 있다. 이때는 웹 브라우저의 새로 고침을 직접 선택해주면 된다.
물론 서버를 재시작 하지 않아서 그럴 수도 있다.
POST의 HTML Form을 전송하면 웹 브라우저는 다음 형식으로 HTTP 메시지를 만든다. (웹 브라우저 개발자 모드 확인)
http://localhost:8080/request-param
application/x-www-form-urlencoded
username=hello&age=20
application/x-www-form-urlencoded
형식은 앞서 GET
에서 살펴본 쿼리 파라미터 형식과 같다. (GET
쿼리 파라미터 조회 메서드를 그대로 사용하면 된다.)
클라이언트(웹 브라우저) 입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 둘의 형식이 동일하므로, request.getParameter()
로 편리하게 구분없이 조회할 수 있다.
실행
localhost:8080/basic/hello-form.html
username=lee&age=20]
[전체 파라미터 조회] - start
username=lee
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회] - start
request.getParameter(username) = lee
request.getParameter(age) = 20
[단일 파라미터 조회] - end
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=lee
📝 정리
request.getParameter()
는GET
URL 쿼리 파라미터 형식도 지원하고,POST
HTML Form 형식도 지원한다.
📌 참고
content-type은 HTTP 메시지 바디의 데이터 형식을 지정한다.
GET
URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없다.POST
HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 content-type을 꼭 지정해야 한다. 이렇게 폼으로 데이터를 전송하는 형식을application/x-www-form-urlencoded
라 한다.
이런 간단한 테스트에 HTML form을 만들기는 귀찮다.
이때는 Postman을 사용하면 된다.
Postman 테스트 주의사항
Post 전송시
Body
→x-www-form-urlencoded
선택- Headers에서 content-type:
application/x-www-form-urlencoded
로 지정된 부분 꼭 확인하기❗️
Postman에서 실행 결과
username=kim&age=20]
[전체 파라미터 조회] - start
username=kim
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회] - start
request.getParameter(username) = kim
request.getParameter(age) = 20
[단일 파라미터 조회] - end
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=kim