새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
최범균의 JSP ch.4 이후학습
목표 D-day : 92 일
리마인드 카운트 : 14번
오늘 학습 목표
- 최범균의 JSP 프로그래밍 ch 4
학습 방법
- 키워드 정리
- 키워드 위주로 학습
키워드
- Redirect
URLEncoder.encode()
가 왜 필요한지 알아야합니다.
학습내용
rediect 기능
웹 서버가 웹 브라우저에게 다른 페이지로 이동하라고 응답하는 기능입니다.
특정 페이지를 실행 후 지정한 페이지로 이동하기 원할 때 리다이렉트 기능을 하용한다.
실질적으로 웹 브라우저는 2번의 웹 요청을 시도한다.
웹 서버가 웹 브라우저에게 다음 행선지를 알려준다.
reponse.rediect("이동할 페이지");
Redirect 주의사항
웹 서버에 전송할 파라미터 값은 알맞은 인코딩을 해야합니다.
www.naver.com?name=자바
로 웹 서버에 요청할 때 웹 서버가 사용하는 캐릭터 셋으로 인코딩 후 전송해야합니다.
www.naver.com?name=%EC%9E%90%EB%B0%94
로 변경하여 Rediect를 시도해합니다.
여기서 주의사항이 있습니다.
response.sendRediect(url)
url에서 쿼리 스트링으로 한글이 들어갈 경우 자동으로 인코딩되지 않습니다.
왜 자동 인코딩이 되지 않을까?
웹 브라우저는 리디렉션 응답을 받을 때, 해당 URL을 그대로 사용합니다. 만약 URL에 한글이나 특수 문자가 인코딩되지 않은 채로 포함되어 있다면, 브라우저가 이를 올바르게 처리하지 못할 수 있습니다. 따라서, 서버 측에서 URL을 수동으로 인코딩해주어야 합니다.
따라서, 자바가 제공하는 URLEncoder.encode()
을 사용하여 인코딩이 필요합니다.
String encode = URLEncoder.encode('자바','utf-8');
response.sendRediect("www.naver.com?name="+encode);
필수 이해 요소
키워드
- JSP의 요청 처리 과정
- 출력 버퍼
- 웹 어플리케이션 폴더 구조
- war 파일
catalina.sh
JAVA_HOME
환경 변수를 설정하여 JDK의 설치 경로를 지정하는 것은 필수입니다.
내부에서 .java
를 .class
파일로 컴파일을 하며, .class
파일을 실행하기 위해 필요하기 때문입니다.
# JAVA_HOME Must point at your Java Development Kit installation.
# Required to run the with the "debug" argument.
WAS 실행 과정
-
/startup.bat
로 톰캣을 실행합니다. -
내부에서 JVM을 실행하여
main-class
로 작성된 클래스(TomcatServer
)의main()
을 실행하여 톰캣이 실행됩니다.public class TomcatServer { public static void main(String[] args) { ServerSocket serverSocket = new ServerSocket(8080); // 서버 소켓 생성 ExecutorService executor = Executors.newFixedThreadPool(10); // 스레드 풀 생성 while (true) { Socket clientSocket = serverSocket.accept(); executor.submit(new RequestHandler(clientSocket)); // 스레드 풀에서 스레드 할당 } } }
-
내부에
connection pool
을 만들고 웹 요청을 대기합니다. -
웹 요청이 들어오면 소켓을 읽고
HTTP
규칙에 맞게 정보를 분리합니다.public class RequestHandler implements Runnable { private Socket clientSocket; public RequestHandler(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { try { // 요청 읽기 InputStream input = clientSocket.getInputStream(); // 응답 쓰기 OutputStream output = clientSocket.getOutputStream(); // 서블릿 요청 처리 HttpServletRequest request = createRequest(input); HttpServletResponse response = createResponse(output); // 서블릿 매핑 및 호출 HttpServlet servlet = getServlet(request); servlet.service(request, response); } catch (IOException | ServletException e) { e.printStackTrace(); } finally { try { clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } private HttpServletRequest createRequest(InputStream input) { // 요청 객체 생성 코드 } private HttpServletResponse createResponse(OutputStream output) { // 응답 객체 생성 코드 } private HttpServlet getServlet(HttpServletRequest request) { // 서블릿 매핑 코드 } }
-
추상화 객체 Request,Reponse 인스턴스를 생성합니다.
JSP 처리 과정
WAS는 요청 URL을 확인하고 관련된 JSP 파일이나 서블릿 객체를 찾습니다.
JSP에 해당하는 서블릿이 존재하지 않는 경우
- JSP 페이지를 WAS는 자바 코드와 문자를 분리하여 서블릿 클래스
.java
파일로 생성합니다.import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class SampleServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } private void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 콘텐츠 타입 설정 response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); try { // 동적 콘텐츠 생성 out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Sample JSP Page</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Hello, JSP!</h1>"); // 기본 객체 사용 예시 String name = request.getParameter("name"); if (name == null) { name = "World"; } out.println("안녕하세요, " + name + "!"); // 세션 객체 사용 예시 HttpSession session = request.getSession(); session.setAttribute("user", name); out.println("<br>세션에 저장된 사용자 이름: " + session.getAttribute("user")); out.println("</body>"); out.println("</html>"); } finally { out.close(); } } }
SampleServlet.java
를JDK
로SampleServlet.class
컴파일 합니다.- 톰캣은 해당
.class
파일로 서블릿 객체를 생성합니다. - 톰캣은 리플렉션을 사용하여 서블릿 클래스를 동적으로 인스턴스화 합니다..
- 서블릿에게 파라미터로 기본 객체를 전달하고 서블릿은 결과를 respone에 저장합니다.
- 톰캣은
reponse
객체를 응답으로 웹브라우저에게 전송합니다
JSP에 해당하는 서블릿이 존재하는 경우
- 서블릿에 클라이언트 요청을 전달합니다.
- 서블릿이 요청을 처리한 결과를 응답으로 생성합니다.
- 응답을 웹 브라우저에 전달합니다.
정리
JVM 내에 메인 쓰레드에서 WAS
가 실행이 됩니다.
WAS
는 제한된 자원 내에 최대한 많은 웹 요청을 수용하기 위해 커넥션 풀
을 생성합니다.
웹 요청이 오면 생성된 쓰레드 풀에서 하나씩 실행하여 결과를 다시 전달하는 과정이 반복되는 구조입니다.
.jsp
파일을 .java
로 변환하는 역할은 was
이며, 이후 .class
파일로 컴파일 하는건 jdk
의 역할입니다.
was는 리플렉션으로 동적으로 클래스를 인스턴스화 하여 사용합니다.
결국
.jsp
파일은 하나의 클래스로 관리된다고 생각하면 되며,그 클래스를 서블릿이라고 합니다. 또 다른 말로는 웹 컴포넌트로 웹 요청에 대한 하나의 기능을 말합니다.
출력 버퍼와 응답
출력 버퍼가 왜 필요할까?
웹 브라우저와 웹 서버는 TCP/IP 통신으로 연결되면 소켓을 통해 데이터 통신이 가능합니다.
여기서 JSP 코드를 다시보면
PrintWriter out = response.getWriter();
try {
// 동적 콘텐츠 생성
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>Sample JSP Page</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello, JSP!</h1>");
// 기본 객체 사용 예시
String name = request.getParameter("name");
if (name == null) {
name = "World";
}
out.println("안녕하세요, " + name + "!");
// 세션 객체 사용 예시
HttpSession session = request.getSession();
session.setAttribute("user", name);
out.println("<br>세션에 저장된 사용자 이름: " + session.getAttribute("user"));
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
TCP/IP 웹 소켓 연결이 되어있는 상태입니다.
현재 상황
- 웹 소켓에
I/O
를 하면 웹 브라우저에 전송됩니다. HTTP
는 항상BODY
보다Header
가 도착해야합니다. 그래서write
하게되면header
는 먼저 전송됩니다.
TCP/IP
통신으로 정확한 전송과 수신을 보장하기 위해 여러가지 확인 절차가 필요합니다.
버퍼를 사용하는 이유
- 데이터 전송 성능 향상: 데이터 교환을 최대한 큰 단위로 묶어 불필요한 확인 절차를 최소화 하기 위해서
- JSP 실행동안 버퍼에 저장하다가 오류가 발생하거나 추가 파일이 생기면 새로운 내용으로 변경이 가능하다.
- 버퍼가 다 차기전까지 헤더 변경이 가능하다.
버퍼를 flush
하기 전까지 내용물을 전송하지 않았으므로 중간 수정이 가능하고, 최적화를 할 수 있다는 의미입니다.
폴더 구성과 URL 매핑
서블릿/JSP로 구성된 웹 어플리케이션의 기본 구조는 다음과 같습니다.
WEB-INF/
├── web.xml # 웹 애플리케이션 배포 설명자
├── classes/ # 컴파일된 자바 클래스 파일들이 위치하는 디렉터리
│ └── com/
│ └── example/
│ └── MyServlet.class
├── lib/ # 애플리케이션에 필요한 라이브러리(JAR 파일)들이 위치하는 디렉터리
│ ├── library1.jar
│ └── library2.jar
└── jsp/ # JSP 파일들이 위치하는 디렉터리 (옵션)
└── index.jsp
폴더와 URL 관계
tomcat/webapps/[웹경로]
> http://host:port/[웹경로]
로 매핑됩니다.
tomcat
이 조회하는 방식은 다음과 같습니다.
만약 http://localhost:8080/views/app.jsp
를 검색한다면 탐색 우선순위는 다음과 같습니다.
-
webapps/views
내에app.jsp
파일을 찾습니다 -
ROOT/views/app.jsp
파일을 찾습니다.
하위 폴더 사용
.jsp
파일을 모두 한 폴더 안에 관리하게 된다면 유지보수가 어렵습니다.
수십 수백개가 되는 .jsp
파일을 찾아서 수정하고, 관련된 .jsp
도 수정하기 힘들기 때문입니다.
기능 별로 분류하기
.jsp
파일은 웹 컴포넌트로 웹 요청과 1:1 매칭이 됩니다.
웹 브라우저의 요청을 웹 서버는 웹 컴포넌트를 수행 결과를 응답해주는 방식입니다.
이렇게 생각해보면 이해가 잘되는 거같습니다.
클래스
회원 클래스가 있다면 회원 클래스내 메서드는 이런게 있을 겁니다.
- 회원 생성 메서드
- 회원 등급 수정 메서드
- 회원 잔고 수정 메서드
- 기타..
메서드는 클래스의 기능을 나타내며 클래스의 상태를 사용하여 기능을 수행합니다.
웹 컴포넌트인 .jsp
파일도 하나의 기능을 하므로
public class Member {
public Member(..){
//..
}
public String incrementGrade(){
//..
}
}
이 구조를 다시 웹 컴포넌트로 표현하면 다음과 같이 표현할 수 있습니다.
Member/
├── IncrementGrade.jsp
└── changeBlance.jsp
웹 기능을 클래스의 메서드로 생각하여 추상화하여 폴더로 생성할 수 있습니다.
웹 어플리케이션 배포
- 대상 폴더에 파일을 직접 복사
- war 파일로 묶어서 배포
첫번째 방법은 실제 .jsp
파일을 \wabapps
구조 안에 직접 넣는 방식입니다.
두번째 방식은 war
(Web application archive) 구조로 구성을 묶는 것입니다.
JDK를 사용하여 .class
의 묶음을 jar
파일로 만들거나 웹 애플리케이션용인 war
로 압축이 가능합니다.
단 jar
,war
모두 Manifest
라는 파일에 실행할 main()
메서드가 있는 클래스를 입력해야합니다.
쿠키
쿠키를 사용하는 이유는 HTTP
의 특징인 Stateless
인 비상태을 극복하기 위해서 사용됩니다.
쿠키를 사용하여 서버와 클라이언트간 상태 유지하, 이를 통해 사용자 세션,사용자 인증,맞춤 설정을 저장할 수 있습니다.
키워드
- 상태 저장
- stateless 극복
쿠키를 구성하는 요소는 다음과 같습니다.
구성 요소 | 설명 | 예시 |
---|---|---|
이름 (Name) | 쿠키의 식별자. 각 쿠키는 고유한 이름을 가져야 합니다. | sessionId |
값 (Value) | 쿠키에 저장되는 데이터. 값은 문자열 형태로 저장됩니다. | abc123 |
도메인 (Domain) | 쿠키가 유효한 도메인을 지정. 지정된 도메인에서만 쿠키가 전송됩니다. | example.com |
경로 (Path) | 쿠키가 유효한 URL 경로를 지정. 지정된 경로에서만 쿠키가 전송됩니다. | /app |
유효기간 (Expires/Max-Age) | 쿠키의 만료 시간을 지정. Expires는 절대 시간을, Max-Age는 현재 시간부터의 상대 시간을 지정합니다. | Expires=Wed, 09 Jul 2024 10:18:14 GMT, Max-Age=3600 |
보안 (Secure) | Secure 속성이 설정된 쿠키는 HTTPS를 통해서만 전송됩니다. | Secure |
HTTP 전용 (HttpOnly) | HttpOnly 속성이 설정된 쿠키는 클라이언트 측 스크립트에서 접근할 수 없습니다. 이는 보안 목적으로 사용됩니다. | HttpOnly |
SameSite | 쿠키가 크로스 사이트 요청에 대해 전송되는 방식을 지정합니다. 값으로 Strict, Lax, None을 가질 수 있습니다. | SameSite=Strict |
웹 브라우저는 여러 개의 쿠키를 갖을 수 있습니다.
웹 서버는 웹 브라우저가 전송한 쿠키중 쿠키의 이름을 사용하여 원하는 작업을 수행합니다.
쿠키 사용하기
쿠키 클래스는 아스키 문자로 구성됩니다. UTF-8
로 작성하는 경우 인코딩이 필요합니다.
// 쿠키 값에 한국어가 포함된 경우 인코딩
String value = "홍길동";
String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
// URL 인코딩된 쿠키 설정
Cookie cookie = new Cookie("username", encodedValue);
쿠키 관리
- 쿠키 읽기
// 쿠키 배열을 가져옴
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())) {
String value = cookie.getValue();
// 쿠키 값 사용
}
}
}
- 쿠키 삭제
// 쿠키를 삭제하려면 동일한 이름과 경로로 만료 시간을 0으로 설정하여 추가
Cookie cookie = new Cookie("username", "");
cookie.setMaxAge(0); // 만료 시간 0으로 설정
cookie.setPath("/"); // 쿠키가 생성된 경로와 동일하게 설정
response.addCookie(cookie);
- 쿠키 변경
쿠키 변경시 주의사항
쿠키의 값을 변경한다는 것은 기존에 쿠키에 저장되어있다는 의미입니다.
변경하려는 쿠키 이름에 값이 있는지 확인하고 추가해야합니다.
// 쿠키를 변경하려면 동일한 이름으로 새 값을 설정하여 추가
Cookie cookie = new Cookie("username", "new_value");
cookie.setMaxAge(3600); // 새로운 유효 시간 설정
cookie.setPath("/"); // 쿠키가 생성된 경로와 동일하게 설정
response.addCookie(cookie);
쿠키 도메인
기본적으로 쿠키는 생성한 서버에만 전송됩니다.
www.naver.com/login.jsp
에서 저장된 쿠키는 같은 호스트(www.naver.com
)에만 전송됩니다.
만약 mail.naver.com
혹은 shop.naver.com
등 같은 도메인을 사용하지만 다른 서버에도 쿠키를 보낼때 사용합니다.
// 쿠키 생성
Cookie cookie = new Cookie("username", "user_value");
// 도메인을 .naver.com으로 설정
cookie.setDomain(".naver.com");
// 쿠키를 응답에 추가
response.addCookie(cookie);
이제 username
의 쿠키는 naver.com
도메인을 사용하는 모든 서버에 요청을 보낼때 같이 따라갑니다.
도메인 주의사항
setDomain
은 현재 서버의 도메인 및 상위 도메인만 전달 할 수 있습니다.
현재 연결 중인 서버의 호스트가 www.naver.com
이라면 setDomain
은 www.naver.com
과 .naver.com
만 가능합니다.
cookie.setDomain(`mail.naver.com`)
이런 경우는 다른 주소의 값을 주는 경우는 웹 브라우저에서 저장하지 않습니다
왜?
웹 브라우저가 타 도메인으로 지정한 쿠키를 받지 않는 이유는 보안 문제입니다.
임의의 다른 서버에서 타 도메인의 쿠키를 사용하여 값을 변경한다면 보안이 뚫릴 수 있습니다.
댓글남기기