[Web] 자바 웹 프로그래밍 Next Step 알게된 사실들
자바 웹 프로그래밍 Next Step 책 정리
웹서버 실습
실습을 위한 개발 환경 세팅
- https://github.com/slipp/web-application-server 프로젝트를 자신의 계정으로 Fork한다. Github 우측 상단의 Fork 버튼을 클릭하면 자신의 계정으로 Fork된다.
- Fork한 프로젝트를 eclipse 또는 터미널에서 clone 한다.
- Fork한 프로젝트를 eclipse로 import한 후에 Maven 빌드 도구를 활용해 eclipse 프로젝트로 변환한다.(mvn eclipse:clean eclipse:eclipse)
- 빌드가 성공하면 반드시 refresh(fn + f5)를 실행해야 한다.
웹 서버 시작 및 테스트
- webserver.WebServer 는 사용자의 요청을 받아 RequestHandler에 작업을 위임하는 클래스이다.
- 사용자 요청에 대한 모든 처리는 RequestHandler 클래스의 run() 메서드가 담당한다.
- WebServer를 실행한 후 브라우저에서 http://localhost:8080으로 접속해 “Hello World” 메시지가 출력되는지 확인한다.
각 요구사항별 학습 내용 정리
- 구현 단계에서는 각 요구사항을 구현하는데 집중한다.
- 구현을 완료한 후 구현 과정에서 새롭게 알게된 내용, 궁금한 내용을 기록한다.
- 각 요구사항을 구현하는 것이 중요한 것이 아니라 구현 과정을 통해 학습한 내용을 인식하는 것이 배움에 중요하다.
요구사항 1 - http://localhost:8080/index.html로 접속시 응답
- ServerSocket
서버 프로그램을 구현할 때 사용. 일반적인 서버 프로그램의 과정은 아래의 6단계를 거침.- 서버 소켓 생성, 포트 바인딩
- 클라이언트로부터의 연결을 기다리고(Connect Listen) 요청이 오면 수락
- 클라이언트 소켓에서 가져온 InputStream 을 읽음
- 응답이 있다면 OutputStream을 통해 클라이언트에 데이터를 보냄s
- 클라이언트와의 연결을 닫음
- 서버 종료
- Content-Type vs Accept
- Content-type
- 리소스의 미디어 타입(MIME 타입)을 나타냄
- HTTP 메시지(요청 & 응답)에 담겨 보내는 데이터 형식을 알려주는 헤더
- 응답 내에 있는 Content-Type 헤더는 클라이언트에게 반환된 컨텐츠의 컨텐츠 유형이 실제로 무엇인지를 알려줌
- POST, PUT 요청에서 클라이언트는 서버에게 어떤 유형의 데이터가 실제로 전송됐는지를 알려줌
- GET의 경우에는 Content-Type 헤더가 불필요. URI와 쿼리 파라미터로도 충분하기 때문.
- POST/PUT의 경우에는 데이터 형식이 xml, json 등 다양한 형태로 전달될 수 있기 때문에 필요.
- Accept
- MIME 타입으로 표현되는, 클라이언트가 이해 가능한 컨텐츠 타입이 무엇인지를 알려줌
- 서버는 제안 중 하나를 선택하고 사용해
Content-Type
응답 헤더로 클라이언트에게 선택된 타입을 알려줌
- Content-type
- /index.html 요청을 한번 보냈는데 여러 개의 추가 요청이 발생하는 이유
서버가 웹 페이지를 구성하는 모든 자원(HTML, CSS, Javascript, 이미지 등)을 한번에 응답으로 보내지 않기 때문.
응답을 받은 브라우저는 HTML 내용을 분석해 CSS, Javascript, image 등의 자원이 포함되어 있으면 서버에 해당 자원을 다시 요청함.
요구사항 2 - get 방식으로 회원가입
- GET 방식의 문제점
- 보안 측면 : 사용자가 입력한 데이터가 브라우저 URL에 노출
- 요청 라인의 길이 제한이 있음 : 사용자가 입력할 수 있는 데이터 크기에 제한
요구사항 3 - post 방식으로 회원가입
- HTTP Method
- GET
- POST
- HEAD
- PUT
- DELETE
- PATCH
- TRACE
- OPTIONS
- HTML의 모든 <a> 태그 링크, CSS, Javascript, 이미지 요청은 모두
GET
방식으로 요청을 보냄 - <form> 태그가 지원하는 method 속성은
GET
과POST
뿐 - HTTP Status Code
- 2XX : 성공. 클라이언트가 요청한 동작을 수신하여 이해했고 승낙했으며 성공적으로 처리
- 3XX : 리다이렉션. 클라이언트는 요청을 마치기 위해 추가 동작이 필요
- 4XX : 요청 오류. 클라이언트에 오류가 있음
- 5XX : 서버 오류. 서버가 유효한 요청을 명백하게 수행하지 못했음
요구사항 4 - redirect 방식으로 이동
- Redirection : 3XX 코드
- 하위부터는 김영한님의 HTTP 강의 참고 내용
- 3xx : 요청을 완료하기 위해 유저 에이전트(eg. 웹 브라우저)의 추가 조치 필요
- 300 Multiple Choices
- 301 Moved Permanently
- 302 Found
- 303 See Other
- 304 Not Modified
- 307 Temporary Redirect
- 308 Permanent Redirect
- 웹 브라우저는 3xx 응답의 결과에
Location
헤더가 있으면, Location 위치로 자동 이동
- 영구 리다이렉션
- 특정 리소스의 URI가 영구 이동
- 301 : 리다이렉트시 요청 메서드가
GET
으로 변함. 본문이 제거될 수 있음 - 308 : 301과 기능 동일. 리다이렉트시 요청 메서드와 본문 유지
- 일시 리다이렉션
- 일시적인 변경 : 검색 엔진 등에서 URL을 변경하면 안됨
- PRG : Post - Redirect - Get
- 302 : 요청 메서드가
GET
으로 변함. 본문이 제거될 수 있음 - 307 : 302와 기능 동일. 요청 메서드와 본문 유지.
- 303 : 302와 기능 동일. 요청 메서드가 GET으로 변경.
- 특수 리다이렉션
- 결과 대신 캐시 사용
- 304 : 캐시를 목적으로 사용
- 클라이언트에게 리소스 수정되지 않았음을 알려줌.
- 메시지 바디 포함 X
요구사항 5 - 로그인 기능. cookie
- HTTP는
무상태 프로토콜
- HTTP 1.1 부터는 한번 맺은 연결을 재사용함. 다만 각 요청 간의 상태 데이터를 공유할 수 없는 무상태 프로토콜의 특성을 가짐.
- HTTP의 무상태성으로 인한 행위에 대한 결과 기록 불가.
- →
쿠키
의 등장. - HTTP는 각 요청 간에 데이터를 공유할 방법이 없기 때문에 헤더를 통해 공유할 데이터를 매번 다시 전송하는 방식으로 데이터를 공유
- 보안 이슈 존재 → 단점을 보완하기 위해 💡세선이 등장
- 세션 역시 쿠키 기반
- 상태 데이터를 서버에 저장한다는 점에서 다름.
- →
요구사항 6 - 사용자 목록 출력. stylesheet 적용
- 요청과 응답 헤더는 각 요청과 응답이 포함하고 있는 본문 컨텐츠에 대한 정보를 제공.
메타 데이터
: 데이터에 대한 정보를 포함하고 있는 헤더 정보- Content-Type, Content-Length : 본문 컨텐츠에 대한 타입과 길이 정보
5장. 웹 서버 리팩토링, 서블릿 컨테이너와 서블릿의 관계
리팩토링 1단계 힌트 - 요청 데이터를 처리하는 로직을 별도의 클래스로 분리(HttpRequest)
- 테스트 코드 기반 개발의 효과
- 클래스에 버그가 있는지를 빨리 찾아 구현할 수 있다
- 수동 테스트 횟수를 줄일 수 있음
- 디버깅하기 쉬움
- 클래스에 대한 단위 테스트를 하는 것은 결과적으로 디버깅을 좀 더 쉽고 빠르게 할 수 있어 개발 생산성을 높임
- 마음 놓고 리팩토링을 할 수 있다
- 클래스에 버그가 있는지를 빨리 찾아 구현할 수 있다
- private 메소드인데 로직의 복잡도가 높아 추가적인 테스트가 필요하다고 생각하는 메소드 발생 시 해결 방법 2가지
- private 접근 제어자인 메소드를
default
메소드로 수정하고 메소드 처리 결과를 반환하도록 수정해 테스트 - 메소드 구현 로직을
새로운 클래스
로 분리- 반환해야 하는 상태 값이 한 개 보다 많은 경우 적용하기 좋음 → 리팩토링에 정답은 없음. 반드시 새로운 객체를 추가하는 것이 정답은 아님.
- private 접근 제어자인 메소드를
- 상수 값이 서로 연관되어 있는 경우
enum
사용하기 적합 - 객체를 추가했을 시, 값을 가지고 있는 객체에 메시지를 보내 일을 시키도록 연습!!!
- 객체지향 설계에서 중요한 연습은 요구사항을 분석해 객체로 추상화하는 부분
리팩토링 2단계 - 응답 데이터를 처리하는 로직을 별도의 클래스로 분리(HttpResponse)
다형성을 활용해 클라이언트 요청 URL에 대한 분기 처리를 제거
static 코드 블락
을 활용해서 Map<String, Controller> 를 초기화 : Spring framework의 소스 코드를 참조해서 반영- 기존구조의 문제점 : 기능이 추가될 때마다 새로운 else if 절이 추가되야 함
- OCP 위반
- OCP(Open-Closed Principle) : 소프트웨어 개체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
- 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 해야 함
- 해결책
- 시스템을 컴포넌트 단위로 분리
- 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조 형성
- OCP 위반
서블릿 컨테이너, 서블릿/JSP
서블릿
- HTTP의 클라이언트 요청과 응답에 대한 표준을 정해 놓은 것
서블릿의 생명주기
: 서블릿 생성, 초기화, 서비스, 소멸 과정을 거치는 전체 과정
서블릿 컨테이너
- 서블릿 표준에 대한 구현
- Tomcat, JBoss, Jetty 등
멀티스레드
로 동작- 모든 스레드가 같은 서블릿 인스턴스를 재사용ㅈ
- 역할
- 서블릿 클래스의 인스턴스 생성
- 요청 URL과 서블릿 인스턴스 매핑
- 클라이언트 요청에 해당하는 서블릿을 찾은 후 서블릿에 작업 위임
- 서블릿의 생명주기 관리
- 서블릿과 관련한 초기화(init)와 소멸(destroy) 작업
- 멀티쓰레딩 지원
- 설정 파일을 활요한 보안관리
- JSP 지원
- 서블릿 컨테이너 시작 ~ 종료 과정
- 서블릿 컨테이너 시작
- 클래스패스에 있는 Servlet 인터페이스를 구현하는 서블릿 클래스를 찾음
- @WebServlet 설정을 통해 요청 URL과 서블릿 매핑
- 서블릿 인스턴스 생성
- init() 메소드를 호출해 초기화
동작방식
- 서블릿 컨테이너는 서버 시작 시 서블릿 인스턴스 생성
- 요청 URL과 서블릿 인스턴스 연결
- 클라이언트에서 요청이 오면 요청 URL에 해당하는 서블릿을 찾아 서블릿에 모든 작업 위임
컴파일타입 Exception & 런타임 Exception 가이드라인
- API를 사용하는 모든 곳에서 이 예외를 처리해야 하는가? 예외가 반드시 메소드에 대한 반환 값이 되어야 하는가? 이 질문에 대한 답이 “예”일 경우 컴파일타임 Exception을 사용해 컴파일러의 도움을 받는다.
- API를 사용하는 소수 중 이 예외를 처리해야 하는가? 이 질문에 대한 답이 “예”일 경우 런타임 Exception으로 구현한다. API를 사용하는 모든 코드가 Exception을 catch하도록 강제하지 않는 것이 좋다.
- 무엇인가 큰 문제가 발생했는가? 이 문제를 복구할 방법이 없는가? 이 질문에 대한 답이 “예”라면 런타임 Exception으로 구현한다. API를 사용하는 코드에서 Exception을 catch하더라도 에러에 대한 정보를 통보 받는 것 외에 아무것도 할 수 있는 것이 없다.
- 아직도 불명확한가? 그렇다면 런타임 Exception으로 구현하라. Exception에 대해 문서화하고 API를 사용하는 곳에서 Exception에 대한 처리를 결정하도록 하라.
References
- 자바 웹 프로그래밍 Next Step, 박재성 지음
- Clean Architecture, 로버트 C. 마틴 지음