본문 바로가기
Backend/Spring | SpringBoot

[Servlet] HttpServletRequest, HttpServletResponse

by 2245 2023. 8. 17.

목차

     

    서론

    Servlet이 제공하는 HttpServletRequest와 HttpServletResonse에 대해 알아봅시다.

     

     

    HttpServletRequest

    HttpServletRequest의 역할

    HTTP 요청 메시지는 텍스트 형태로 되어 있습니다. 

    이를 개발자가 직접 파싱해서 원하는 데이터를 뽑아낼 수도 있지만, 매우 번거롭습니다. 

     

    서블릿은 개발자를 대신하여 HTTP 요청 메시지를 파싱합니다. 그리고 그 결과를 HttpServletRequest 객체에 담아 제공합니다. 

    HttpServletRequest를 이용하여 다음과 같은 HTTP 요청 메시지를 편리하게 사용할 수 있습니다.

     

    HTTP 요청 메시지

    POST /save HTTP/1.1    //Start-line
    Host: localhost:8080      //헤더
    Content-Type: application/x-www-form-urlencoded    //헤더

    username=kim&age=20     //바디
    • Start-line
      • HTTP 메서드
      • URL
      • 쿼리 스트링
      • 스키마, 프로토콜
    • 헤더
    • 바디
      • Form 파라미터 형식 조회
      • message body 데이터 직접 조회 

     

    HttpServletRequest 부가 기능

    임시 저장소 기능

    해당 HTTP 요청의 시작부터 끝까지 유지되는 임시 저장소 기능입니다.

    • 저장 : request.setAttribute(name, value)
    • 조회 : request.getAttribute(name)

     

    세션 관리 기능

    request.getSession(create: true)

     

    중요 HTTP 의 요청, 응답 메시지 자체를 이해해야 한다. 
    HttpServletRequest, HttpServletResponse 를 사용할 때 가장 중요한 점은 이 객체들이 HTTP 요청 메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점입니다.
    따라서 이 기능에 대해서 깊이 있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 합니다. 
    참고 HttpSession
    HttpSession은 웹 애플리케이션에서 사용자의 상태를 유지하기 위한 서버 측 세션 관리 객체입니다. Java Servlet API에서 제공되며, HTTP 프로토콜을 통해 사용자와 서버 간의 상태를 유지하고 관리합니다.웹 애플리케이션에서 사용자 세션을 추적하고 세션 데이터를 저장하며, 주로 로그인 상태, 장바구니 정보, 선호 설정 등을 저장하는 데 사용됩니다.
    (웹 브라우저에서 제공되는 클라이언트 측 저장소인 SessionStorage와 다른 개념입니다.)

     

     

    HttpServletRequest 사용법

    HttpServletRequest가 제공하는 기본 기능들을 알아봅시다.

     

    @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);
    
            response.getWriter().write("ok");
        }
    }

     

    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.getProtocol() = " + request.getProtocol()); //HTTP/1.1
        System.out.println("request.getScheme() = " + request.getScheme()); //http
    
        System.out.println("request.getRequestURL() = " + request.getRequestURL()); //http://localhost:8080/request-header
        System.out.println("request.getRequestURI() = " + request.getRequestURI()); // /request-header
        System.out.println("request.getQueryString() = " + request.getQueryString());   //username=hi
        System.out.println("request.isSecure() = " + request.isSecure());   //https 사용 유무
    
        System.out.println("--- REQUEST-LINE - end ---");
        System.out.println();
    }

     

    --- REQUEST-LINE - start ---
    request.getMethod() = GET
    request.getProtocol() = HTTP/1.1
    request.getScheme() = http
    request.getRequestURL() = http://localhost:8080/request-header
    request.getRequestURI() = /request-header
    request.getQueryString() = username=hi
    request.isSecure() = false
    --- REQUEST-LINE - end ---

     

    Request Header

    //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));
        }
    
        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", "Google Chrome";v="115", "Chromium";v="115"
    sec-ch-ua-mobile : ?0
    sec-ch-ua-platform : "Windows"
    upgrade-insecure-requests : 1
    user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 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.7
    sec-fetch-site : none
    sec-fetch-mode : navigate
    sec-fetch-user : ?1
    sec-fetch-dest : document
    accept-encoding : gzip, deflate, br
    accept-language : ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
    --- Headers - end ---

     

    Request 헤더 편리한 조회

    //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();
    
        // -> 오류 (자바 8에서 asIterator() 사용 불가)
        System.out.println("[Accept-Language 편의 조회]");
        request.getLocales().asIterator()
                .forEachRemaining(locale -> System.out.println("locale = " + locale));
        System.out.println("request.getLocale() = " + request.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();
    }
    --- Header 편의 조회 start ---
    [Host 편의 조회]
    request.getServerName() = localhost
    request.getServerPort() = 8080

    [Accept-Language 편의 조회]
    locale = ko
    locale = en_US
    locale = en
    locale = ko_KR
    request.getLocale() = ko

    [cookie 편의 조회]

    [Content 편의 조회]
    request.getContentType() = null //Get 방식이기 때문에 null
    request.getContentLength() = -1
    request.getCharacterEncoding() = UTF-8
    --- Header 편의 조회 end ---

     

    기타 정보

    기타 정보는 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() = 64370

    [Local 정보]
    request.getLocalName() = 0:0:0:0:0:0:0:1
    request.getLocalAddr() = 0:0:0:0:0:0:0:1
    request.getLocalPort() = 8080
    --- 기타 조회 end ---

     

    참고
    로컬에서 테스트하면 IPv6 정보가 나오는데, IPv4 정보를 보고 싶으면 다음 옵션을 VM options에 넣어주면 됩니다.
    Djava.net.preferIPv4Stack=true

     

     

     

    지금까지 HttpServletReqeust를 통해 HTTP 메시지의 Start-line, header 정보 조회 방법을 이해했습니다.

    이제 본격적으로 HTTP 요청 데이터를 어떻게 조회하는지 알아봅시다.

     

     

    HTTP 요청 데이터 3가지 조회

    HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법에 대해 알아보고, HttpServletRequest를 통해 조회하는 방법을 알아봅시다.

    다음의 3가지에서 벗어나지 않습니다.

     

    1. GET - 쿼리 파라미터
      • /url?username=hello&age=20
      • 메시지 바디없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달합니다.
      • 검색, 필터, 페이징 등에서 많이 사용하는 방식입니다.
    2. POST - HTML Form
      • content-type: application/x-www.form-urlencoded
      • 메시지 바디에 쿼리 파라미터 형식으로 전달합니다. (username=hello&age=20)
      • 회원 가입, 상품 주문 등 HTML Form 에서 사용합니다.

    3. HTTP Message Body

    • HTTP API에서 주로 사용합니다.
    • JSON, XML, TEXT 형태로 주고 받습니다.
    • 데이터 형식은 주로 JSON 을 사용합니다.
    • POST, PUT, PATCH

     

    1. HTTP 요청 데이터 - GET 쿼리 파라미터

    다음 데이터를 클라이언트에서 서버로 전송한다고 해봅시다.

    전달 데이터
    - username=hello
    - age=20
    • 단, 메시지 바디 없이, URL의 쿼리 파라미터를 사용해서 데어터를 전송해야 합니다.
    • 검색, 필터, 페이징 등에서 많이 사용하는 방식입니다.

     

    http://localhost:8080/request-param?username=hello&age=20
    • 쿼리 파라미터는 다음과 같이 '?' 를 시작으로 보낼 수 있습니다.
    • 추가 파라미터는 '&' 으로 구분합니다.

     

    HttpServletRequest 조회

    서버에서는 HttpServletRequest가 제공하는 다음 메서드를 통해 쿼리 파리미터의 요청 데이터를 편리하게 조회할 수 있습니다.

    String username = request.getParameter("username");  //단일 파라미터 조회
    Enumeration<String> parameterNames = request.getParameterNames();  //파라미터 이름들 모두 조회
    Map<String, String[]> parameterMap = request.getParamterMap();  //파라미터를 Map으로 조회
    String[] usernames = request.getParamterValues("username");  //복수 파라미터 조회
    /**
     * 1. 파라미터 전송 기능 
     * http://localhost:8080/request-param?username=hello&age=20
     * 
     * 2. 동일한 파라미터 전송 기능 
     * http://localhost:8080/request-param?username=hello&username=hello2&age=20
     */
    @WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
    public class RequestParamServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("[전체 파라미터 조회] - start");
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String paramName = parameterNames.nextElement();
                System.out.println(paramName + "=" + request.getParameter(paramName));
            }
            /*
            request.getParameterNames().asIterator()
                    .forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName)));
             */
            System.out.println("[전체 파라미터 조회] - end");
            System.out.println();
    
            System.out.println("[단일 파라미터 조회]");
            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();
    
            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=hello2&age=20

     

    결과

    [전체 파라미터 조회] - start
    username=hello
    age=20
    [전체 파라미터 조회] - end

    [단일 파라미터 조회]
    request.getParameter(username) = hello
    request.getParameter(age) = 20

    [이름이 같은 복수 파라미터 조회]
    request.getParameterValues(username)
    username=hello
    username=hello2

     

     

    2. HTTP 요청 데이터 - POST HTML Form

    이번에는 HTML의 Form을 사용해서 클라이언트에서 서버로 데이터를 전송해봅시다.

    주로 회원가입, 상품 주문 등에서 사용하는 방식입니다.

     

    특징

    • content-type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파라미터 형식으로 데이터를 전달합니다. username=hello&age=20

     

    src/main/webapp/basic/hello-form.html 생성

    <!DOCTYPE html>
    <html lang="en">
    <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

     

    전송 버튼 클릭 시 HTTP Request 메시지 구조

    • 요청 URL: http://localhost:8080/request-param
    • content-type : application/x-www-form-urlencoded
    • message body : username=hello&age=20

     

    HttpServletRequest 조회

    application/x-www-form-urlencoded 형식은 앞서 GET 쿼리 파라미터 형식과 같습니다. 
    따라서, 쿼리 파리미터 조회 메서드를 그대로 사용하면 됩니다.

     

    클라이언트(웹 브라우저) 입장에서는 두 방식이 차이가 있지만, 서버 입장에서는 둘의 형식이 동일하므로, request.getParamter()로 편리하게 조회할 수 있습니다.

     

    정리하면 request.getParameter()는 GET 쿼리 파라미터 형식도 지원하고, POST HTML Form 형식 둘 다 지원합니다.

    참고 Content-type
    Content-type은 HTTP 메시지 바디의 데이터 형식을 지정합니다. 
    GET URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없습니다. 
    반면에, POST HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 content-type을 꼭 지정해야 합니다.
    이렇게 Form으로 데이터를 전송하는 형식을 application/x-www-form-urlencoded라 합니다. 

     

    참고로, HTML Form 을 만들 필요 없이, Postman을 사용하면 간단히 테스트할 수 있습니다. 

     

     

    3. HTTP 요청 데이터 - API 메시지 바디

    • HTTP message body에 데이터를 직접 담아서 요청하는 방법입니다.
    • HTTP API에서 주로 사용합니다.
    • JSON, XML, TEXT 형태를 주고 받습니다.
    • 데이터 형식은 주로 JSON을 사용합니다.
    • POST, PUT, PATCH

     

    1) 단순 텍스트

    먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아 전송하고 서버에서 읽어봅시다.

    HTTP 메시지 바디의 데이터를 InputStream을 사용해 읽을 수 있습니다.

    HttpServletRequest 조회

    @WebServlet(name = "requestBodyStringServlet", urlPatterns = "/request-body-string")
    public class RequestBodyStringServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            ServletInputStream inputStream = request.getInputStream();  //바이트코드
            String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);  //바이트를 문자로 변환
    
            System.out.println("messageBody = " + messageBody);
    
            response.getWriter().write("ok");
        }
    }

     

    Postman을 사용한 테스트

    • POST 선택, localhost:8080/request-body-string
    • Content-type: text/plain
    • Message Body: hello
    • 출력 : messageBody = hello

    참고 byte코드, UTF_8
    inputStream은 byte 코드를 반환합니다. byte 코드는 우리가 읽을 수 있는 문자(String)으로 변환하려면 문자표(charset)을 지정해줘야 합니다.
    여기서는 UTF_8 Charset으로 지정해주었습니다. 

     

     

    2) JSON

    이번에는 HTTP API에서 주로 사용하는 JSON 형식으로 데이터를 전송하고, 서버에서 읽어봅시다.

    JSON 형식 전송

     

    JSON 형식 파싱 추가

    JSON 형식으로 파싱할 수 있도록 객체를 하나 생성합시다.

    package hello.servlet.basic;
    
    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class HelloData {
        private String username;
        private int age;
    }

     

    HttpServletRequest 조회

    @WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
    public class RequestBodyJsonServlet extends HttpServlet {
    
        private ObjectMapper objectMapper = new ObjectMapper();
    
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            ServletInputStream inputStream = request.getInputStream();
            String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    
            System.out.println("messageBody = " + messageBody);
    
            HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
            System.out.println("helloData.username = " + helloData.getUsername());
            System.out.println("helloData.age = " + helloData.getAge());
    
            response.getWriter().write("ok");
        }
    }

     

    Postman으로 실행

     

    출력

     

     

    참고 JSON 변환 라이브러리
    JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환 라이브러리를 추가해서 사용해야 합니다. 스프링부트로 Spring MVC를 선택하면 기본으로 Jackson 라이브러리 (ObjectMapper)를 함께 제공합니다.
    참고
    HTML Form 데이터도 메시지 바디로 전송되므로, JSON 객체로 변환하여 읽을 수 있지만, 편리한 파라미터 조회 메서드(request.getParamter(...))을 이미 제공하기 때문에 파라미터 조회 기능을 사용하면 됩니다. 

     

     

     

    HttpServletResponse

    HttpServletResponse 역할

    • HTTP 응답 메시지를 생성합니다.
      • HTTP 응답 코드
      • 헤더
      • 바디
    • 편의 기능을 제공합니다.
      • Content-Type
      • 쿠키
      • Redirect

     

    HttpServletResponse 사용법

    @WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
    public class ResponseHeaderServlet extends HttpServlet {
    
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //[status-line]
            response.setStatus(HttpServletResponse.SC_OK);  //200
    
            //[response-headers]
            response.setHeader("Content-Type", "text/plain;charset=utf-8");
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");  //캐시 무효화
            response.setHeader("Pragma", "no-cache");
            response.setHeader("my-header", "hello");
    
            //[Header 편의 메서드]
            content(response);
            cookie(response);
            redirect(response);
    
            //[message body]
            PrintWriter writer = response.getWriter();
            writer.println("ok");
        }
    
        //Content 편의 메서드
        private void content(HttpServletResponse response) {
            //Content-Type: text/plain;charset=utf-8
            //Content-Length: 2
            //response.setHeader("Content-Type", "text/plain;charset=utf-8");
            response.setContentType("text/plain");
            response.setCharacterEncoding("utf-8");
            //response.setContentLength(2); //(생략시 자동 생성)
        }
    
        //쿠키 편의 메서드
        private void cookie(HttpServletResponse response) {
            //Set-Cookie: myCookie=good; Max-Age=600;
            //response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
            Cookie cookie = new Cookie("myCookie", "good");
            cookie.setMaxAge(600); //600초
            response.addCookie(cookie);
        }
    
        //redirect 편의 메서드
        private void redirect(HttpServletResponse response) throws IOException {
            //Status Code 302
            //Location: /basic/hello-form.html
            //response.setStatus(HttpServletResponse.SC_FOUND); //302
            //response.setHeader("Location", "/basic/hello-form.html");
            response.sendRedirect("/basic/hello-form.html");
        }
    }

     

    Header 직접 셋팅 응답 헤더

     

     

     

    HTTP 응답 데이터 생성

    HTTP 응답 메시지는 주로 다음 내용을 담아서 전달합니다.

    • 단순 텍스트 응답 (writer.println("ok");)
    • HTML 응답
    • HTTP API - Message Body JSON 응답

     

    2. HTML 응답

    HTTP 응답으로 HTML을 반환할 때는 Content-type을 text/html 로 지정해야 합니다. 

    @WebServlet(name = "responseHtmlServlet", urlPatterns = "/response-html")
    public class ResponseHtmlServlet extends HttpServlet {
    
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //Content-Type: text/html;charset=utf-8
            response.setContentType("text/html");
            response.setCharacterEncoding("utf-8");
    
            PrintWriter writer = response.getWriter();
            writer.println("<html>");
            writer.println("<body>");
            writer.println(" <div>안녕?</div>");
            writer.println("</body>");
            writer.println("</html>");
        }
    }

    실행

    http://localhost:8080/response-html

     

     

    3. API JSON 응답

    • HTTP 응답으로 JSON을 반환할 때는 Content-type을 application/json 으로 지정해야 합니다.
    • Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString() 을 사용하면 객체를 JSON 문자로 변경할 수 있습니다. 
    @WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
    public class ResponseJsonServlet extends HttpServlet {
    
        private ObjectMapper objectMapper = new ObjectMapper();
    
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //Content-Type: application/json
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
    
            HelloData data = new HelloData();
            data.setUsername("kim");
            data.setAge(20);
    
            //{"username":"kim", "age":20}
            String result = objectMapper.writeValueAsString(data);
            response.getWriter().write(result);
        }
    }

    실행

    http://localhost:8080/response-json

     

     

    참고
    application/json은 스펙상 utf-8 형식을 사용하도록 정의되어 있습니다. 그래서 스펙에서 charset=utf-8과 같은 추가 파라미터를 지원하지 않습니다.
    따라서 application/json 이라고만 사용해야지 application/json;charset=utf-8 이라고 전달하는 것은 의미 없는 파라미터를 추가한 것이됩니다.
    만약 response.getWriter()를 사용하면 추가 파라미터를 자동으로 추가해버립니다. 이때는 response.getOutputStream() 으로 출력하면 그런 문제가 없습니다. 

     

     


    출처

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

     

    스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

    웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

    www.inflearn.com