웹 애플리케이션 이해
웹 서버, 웹 애플리케이션 서버
- 웹 서버 (Web Server)
- HTTP 기반으로 동작
- 정적 리소스 제공, 기타 부가 기능
- 정적 HTML, CSS, JS, 이미지, 영상 등을 HTTP 응답으로 제공
- 웹 애플리케이션 서버 (WAS - Web Application Server)
- HTTP 기반으로 동작
- 웹 서버 기능 포함 + 정적 리소스 제공
- 프로그램 코드를 실행해서 애플리케이션 로직 수행
- 동적 HTML, HTTP API (JSON, XML, ..)
- related with Servlet, JSP, Thymeleaf, 스프링 MVC
- Tomcat, Jetty, Undertow
- WEB Server VS WAS
- 결국 웹 서버는 정적 리소스(파일)을 실행하는 서버, WAS는 애플리케이션 로직까지 실행하는 서버
- 보통 웹 시스템은 정적 리소스를 처리하는 Web Server + 애플리케이션 로직같은 동적인 처리를 하는 WAS + 데이터를 이용하는 DB 로 구성됨
- 이런 식으로 구성하게 되면 잘 죽는 WAS를 대신해서 오류화면을 정적인 Web Server로 보여줄 수 있게 됨.
- 결국 웹 서버는 정적 리소스(파일)을 실행하는 서버, WAS는 애플리케이션 로직까지 실행하는 서버
서블릿 (Servlet)
-
HTTP 요청과 응답의 모든 과정에서 비지니스 로직을 제외한 모든 과정을 자동으로 처리해줌
- HTTP 스펙을 매우 편리하게 사용 가능하게 해줌
- HTTP 요청 정보를 편리하게 사용할 수 있도록
HttpServletRequest
제공 - HTTP 응답 정보를 편리하게 제공할 수 있도록
HttpServletResponse
제공
- HTTP 요청 정보를 편리하게 사용할 수 있도록
-
HTTP 요청, 응답 흐름
(request, response는 요청이 들어올 때마다 다 다른 객체, but helloServlet이라는 서블릿 객체는 동일한 객체(싱글톤))
- WAS는 Request, Response 객체를 새로 만들어서 서블릿 객체 호출
- 개발자는 Request 객체에서 HTTP 요청 정보를 편리하게 꺼내서 사용
- 개발자는 Response 객체에 HTTP 응답 정보를 편리하게 입력
- WAS는 Response 객체에 담겨있는 내용으로 HTTP 응답 정보를 생성
- Servlet Container
- 이 안에서 서블릿 객체를 자동으로 생성 및 호출해줌
- WAS에 따른 생명주기 관리 (WAS가 시작될 때 생성하고, WAS가 종료될 때 같이 종료)
- 서블릿 객체는 싱글톤으로 관리
- 즉, 모든 요청은 동일한 서블릿 객체 인스턴스에 접근
- 동시 요청을 위한 멀티 쓰레드 처리 지원
- 멀티 쓰레드 (Multi Thread, 동시 요청)
- 쓰레드 (Thread)
- Process : 프로그램을 실행하는 것 ↔ Thread : 프로그램 안에서 실행하는 것
- 애플리케이션 코드를 하나하나 순차적으로 실행하는 것은 쓰레드
- 자바 메인 메서드를 처음 실행하면 main이라는 이름의 쓰레드가 실행
- 쓰레드가 없다면 자바 애플리케이션 실행이 불가능
- 쓰레드는 한번에 하나의 코드 라인만 수행
- 동시 처리가 필요하면 쓰레드를 추가로 생성
- 요청 마다 Thread를 생성한다면?
- 장점
- 동시 요청을 처리할 수 있다.
- 리소스(CPU, 메모리)가 허용할 때 까지 처리가능
- 하나의 쓰레드가 지연 되어도, 나머지 쓰레드는 정상 동작한다.
- 단점
- 쓰레드는 생성 비용은 매우 비싸다.
- 고객의 요청이 올 때 마다 쓰레드를 생성하면, 응답 속도가 늦어진다.
- 쓰레드는 컨텍스트 스위칭(하나에서 다른 thread로 이동하는) 비용이 발생한다.
- 쓰레드 생성에 제한이 없다. 즉, 고객 요청이 너무 많이 오면, CPU, 메모리 임계점을 넘어서 서버가 죽을 수 있다.
- 그래서 우린 Thread Pool을 이용해야 됨.
- 장점
-
쓰레드 풀 (Thread Pool)
- 특징
- 필요한 쓰레드를 쓰레드 풀에 보관하고 관리한다.
- 쓰레드 풀에 생성 가능한 쓰레드의 최대치를 관리한다. 톰캣은 최대 200개 기본 설정 (변경 가능)
- 사용
- 쓰레드가 필요하면, 이미 생성되어 있는 쓰레드를 쓰레드 풀에서 꺼내서 사용한다.
- 사용을 종료하면 쓰레드 풀에 해당 쓰레드를 반납한다.
- 최대 쓰레드가 모두 사용중이어서 쓰레드 풀에 쓰레드가 없으면? → 기다리는 요청은 거절하거나 특정 숫자만큼만 대기하도록 설정할 수 있다.
- 장점
- 쓰레드가 미리 생성되어 있으므로, 쓰레드를 생성하고 종료하는 비용(CPU)이 절약되고, 응답 시간이 빠르다.
- 생성 가능한 쓰레드의 최대치가 있으므로 너무 많은 요청이 들어와도 기존 요청은 안전하게 처리할 수 있다.
- WAS의 멀티 쓰레드 지원
- 멀티 쓰레드에 대한 부분은 WAS가 처리
- 개발자가 멀티 쓰레드 관련 코드를 신경쓰지 않아도 됨
- 개발자는 마치 싱글 쓰레드 프로그래밍을 하듯이 편리하게 소스 코드를 개발
- 멀티 쓰레드 환경이므로 싱글톤 객체(서블릿, 스프링 빈)는 주의해서 사용
- 특징
- 쓰레드 (Thread)
HTML, HTTP API, CSR, SSR
- HTML
- 정적 리소스 (Web Server) VS WAS(Web Application Server)
-
정적 리소스
고전된 HTML 파일, CSS, JS, 이미지, 영상 등을 제공
-
WAS
동적으로 필요한 HTML 파일을 생성해서 전달
-
- 정적 리소스 (Web Server) VS WAS(Web Application Server)
-
HTTP API
- HTML이 아닌 데이터(JSON, XML)를 전달
- 데이터만 주고 받음. 즉, UI 화면이 필요하면, 클라이언트 쪽에서 별도로 처리
- 다양한 시스템 연동
- 서버 to 서버
- 앱 클라이언트 (아이폰, 안드로이드, PC 앱)
- 웹 브라우저에서 자바스크립트를 통한 HTTP API 호출
- React, Vue.js 같은 웹 클라이언트
-
SSR - 서버 사이드 렌더링
- 동적으로 HTML 최종 결과를 서버에서 만들어서 웹 브라우저에 전달
- 주로 정적인 화면에 사용
- 관련기술: JSP, 타임리프 -> 백엔드 개발자
-
CSR - 클라이언트 사이드 렌더링
- HTML 결과를 자바스크립트를 사용해 웹 브라우저에서 동적으로 생성해서 적용 (서버에서 생성된 HTML 결과를 받아오는 것이 아닌)
- 주로 동적인 화면에 사용, 웹 환경을 마치 앱 처럼 필요한 부분부분 변경할 수 있음
- 예) 구글 지도, Gmail, 구글 캘린더
- 관련기술: React, Vue.js -> 웹 프론트엔드 개발자
서블릿
프로젝트 생성
- JSP 사용을 위해 War 파일로 선택
- Spring boot는 사용하지 않지만 (servlet만을 이용) 내장 서버 이용을 위해 Spring Boot로 생성
Servlet 사용하기
@ServletComponentScan
- Spring에서 Servlet을 사용하기 위함. 서블릿 자동 등록
- main application 에 해당 어노테이션을 달아주게 되면 이 하위 패키지에서 servlet을 사용할 수 있도록 해줌
@WebServlet(name="helloServlet", urlPatterns = “/hello”)
- 해당 class에서 servlet을 사용하겠다는 것.
- 이름은
helloServlet
, 요청은 hello url로 연결 - 해당 class는
extends HttpServlet
, HttpServlet을 상속받아야 함 → servlet을 사용하기 위함. - 그리고 해당 상속받은 객체의 service 메서드를 overriding. →
protected void service(HttpServletRequest request, HttpServletResponse response)
- HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는
service
메서드를 실행 - /hello 라는 url로 접속하게 되면 이 service가 작동하게 되는 것
- http 요청이 오면 WAS 가
request
,response
객체를 만들어서 servlet 에 던져줌 request
를 통해 요청정보를 얻어 올 수 있음- ! request는 요청이 오고 요청에 대한 응답이 나갈 때까지 생존 주기를 가짐.
request.getParameter("username");
→ 쿼리 파라미터(/hello?username=kim) 받기- 만약 http 스펙에 있는 파라미터를 다 파싱해서 읽는다고 하면 너무 복잡함. but servlet은 이걸 편하게 도움
-
response
를 통해 응답 메세지를 작성하고 보낼 수 있음response.setContentType("text/plain"); // 단순 문자 -> header 정보에 들어감 response.setCharacterEncoding("utf-8"); // 문자 인코딩 정보 -> header 정보에 들어감 response.getWriter().write("hello" + username); // http Body에 들어가게 됨. 즉, 정보를 넘겨주게 되는 것
- HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는
- HTTP 요청의 모든 정보(url, header, body, … ) 보기 → application.properties 에서
logging.level.org.apache.coyote.http11=debug
추가
-
WAS 요청 및 응답 구조
- 가장 먼저 spring boot가 내장 톰켓 서버를 통해 서블릿 컨테이너 안에 servlet 객체 (싱글톤)를 생성. (호출, 삭제 까지 담당)
- 그 후 요청이 들어오면 그 요청 당 하나의 request 객체와 response 객체를 생성하고 helloServlet의 service 메서드에 넣어주며 호출
- 그 후 request를 이용하여 필요한 비지니스 로직을 수행하고 response객체에 그 로직의 결과 정보를 담아 요청자에게 반환
HttpServletRequest
- 개념
- 개발자가 HTTP 요청을 직접 파싱하지 않도록 자동으로 파싱해주는 것이 바로 서블릿, 해당 파싱 결과의 요청(request) 부분이 HttpServletRequest
- Start Line (Http 메소드, URL, 쿼리 파라미터, 스키마, 프로토콜), Header(헤더), Body(form 파라미터 형식, message body, 데이터)를 편리하게 조회 가능하게 함
- request의 생존 기간(Http 요청에 대한 응답이 나가기 전까지) 동안 임시 저장소로써 사용 가능
- 저장 :
request.setAttribute(name, value);
- 조회 :
request.getAttribute(name);
- 저장 :
- 세션 관리도 가능
request.getSession(create: true);
- 사용 (Start Line, Header 정보 조회)
- if) GET 요청 URL 은 http://localhost:8080/request-header?username=hello
- Start Line 정보 조회
- 메서드 조회 :
request.getMethod()
⇒ GET - 프로토콜 조회 :
request.getProtocal()
⇒ HTTP/1.1 - 스키마 조회 :
request.getScheme()
⇒ http - URL 조회 :
request.getRequestURL()
⇒ http://localhost:8080/request-header - URI 조회 :
request.getRequestURI()
⇒ /request-header - 쿼리 파라미터 조회 :
request.getQueryString()
⇒ username=hello - 보안 조회 :
request.isSecure()
⇒ false
- 메서드 조회 :
- Header 정보 조회
- server name : request.getServerName() ⇒ localhost
- server port : request.getServerPort() ⇒ 8080
- 이 뿐만 아니라 더 다양한 헤더 정보 조회 가능
- 사용 (HTTP 요청 데이터 조회)
- 클라이언트에서 서버로 데이터를 전달하는 방법
- GET - 쿼리 파라미터
- /url?username=hello&age=20
- 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
- 예) 검색, 필터, 페이징등에서 많이 사용하는 방식
- POST - HTML Form
- content-type: application/x-www-form-urlencoded
- 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20 (그래서 데이터를 조회하는 방식은 쿼리 파라미터와 동일)
- 예) 회원 가입, 상품 주문, HTML Form 사용
- HTTP message body에 데이터를 직접 담아서 요청
- HTTP API에서 주로 사용, JSON, XML, TEXT 등
- POST, PUT, PATCH 방식 사용
- GET - 쿼리 파라미터
- “GET - 쿼리 파라미터” 조회
- 가정
- 전달 데이터가 “username = hello, age = 20” 이라면
- ?username=hello&age=20 형식으로 url을 요청
- 조회
- 단일 파라미터 조회 →
request.getParameter(”username”)
,request.getParameter(”age”)
- 모든 파라미터 조회 →
request.getParameterNames().asIterator().forEachRemaining(paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));
- 이름이 같은 복수 파라미터 조회 (?username=kim&username=park) →
request.getParameterValues("username");
값이 배열로 저장됨. 즉, 모두 가져올 수 있음. 여기서request.getParameter
를 사용하면 가장 첫번째 값만 가져오게 됨.
- 가정
- POST - HTML Form
- 가정
- content-type: application/x-www-form-urlencoded
- message body: username=hello&age=20
- 값이 보내지는 형식이 쿼리 파라미터와 동일하기 때문에 GET - 쿼리 파라미터 조회와 동일하게
request.getParameter
로 값을 조회할 수 있음 - 즉, request.getParameter() 는 GET URL 쿼리 파라미터 형식, POST HTML Form형식, 둘 다 지원.
- 물론 form은 message body로 넘겨지므로 body 자체로 접근해서 값을 파싱할 수 있음. but 이미 request.getParameter로 충분
- 가정
- HTTP message body 에 데이터를 직점 담아서 요청
-
API 메시지 바디 - 단순 텍스트 (
Content-Type : text/plain
)ServletInputStream inputStream = request.getInputStream(); // HTTP message body의 내용을 byte 코드로 받음 String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 인코딩 설정 (byte to String)
-
API 메시지 바디 - JSON
- content-type: application/json
- message body: {“username”: “hello”, “age”: 20}
- 단순 텍스트에서 진행했던 것과 같이 파싱하면 됨 → Json도 차피 똑같은 문자이기에.
- 즉, content type : json으로 설정하면 text로 받아오지만 text를 json 형태로 파싱할 수 있는 로직을 진행할 수 있게 되는 것.
- 이제 파싱한 해당 messge body를 기존에 설정한 Data에 맞게 파싱 진행 →
ObjectMapper
Class 사용 (JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환 →objectMapper.readValue()
) HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
- 결과 : HelloData → username = hello, age = 20 (Hellodata의 속성들은 json의 변수들과 이름이 같아야 됨)
-
- 클라이언트에서 서버로 데이터를 전달하는 방법
HttpServletResponse
- HTTP 응답 메시지 생성
- HTTP 응답코드 지정, 헤더 생성, 바디 생성
- Content-Type, 쿠키, Redirect 설정 가능
- Status Line
- 상태 설정 :
response.setStatus(HttpServletResponse.*SC_OK*);
- 상태 설정 :
- Header
- content-type 설정 :
response.setHeader("Content-Type", "text/plain;charset=utf-8");
- 쿠키 설정 :
response.setHeader("Cache-Control", "no-cache, no-store, mustrevalidate");
,response.setHeader("Pragma", "no-cache");
- 사용자 정의 header 설정 :
response.setHeader("my-header","hello");
- redirect 설정 :
response.sendRedirect("/basic/hello-form.html");
- content-type 설정 :
- Body
- 단순 text :
response.getWriter().println(”ok”);
- HTML
- Content-Type : text/html;charset=utf-8
response.setContentType("text/html"); // html로 반환 response.setCharacterEncoding("utf-8"); // encoding 정보 PrintWriter writer = response.getWriter(); writer.println("<html>"); writer.println("<body>"); writer.println(" <div>안녕?</div>"); writer.println("</body>"); writer.println("</html>"); // 직접 작성해 줘야 됨
- API JSON
- Content-Type: application/json
ObjectMapper
Class 사용 (자바 객체를 JSON으로 변환해서 사용할 수 있도록) →objectMapper.writeValueAsString()
response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); HelloData helloData = new HelloData(); helloData.setUsername("kim"); helloData.setAge(20); String result = objectMapper.writeValueAsString(helloData); // json string으로 작성 response.getWriter().write(result); // 이 자체를 보내줌
- 단순 text :