자바로 HTTP 요청은 어떻게 주고받아 지는걸까? Spring을 사용하면서 궁금한 점을 모두 공부해보자
0. 들어가며
오늘 알아볼 내용은 톰캣이 어떻게 HTTP 요청을 받고, 처리하고, 서블릿으로 전달하는지 알아볼 것이다.
지금까지 Spring boot를 사용하면서, 또는 Spring mvc를 사용하면서 톰캣을 사용했지만 어떤식으로 요청이 전달받아지고 있는지까지는 생각하고있지 않았다.
오늘은 톰캣이 어떻게 요청을 받고있는지 원리를 Chat GPT를 활용해가면서 공부할 것이다.
1. 소켓
- ip와 포트를 이용해서 네트워크 양방향 소통을 처리하는 구성요소
- TCP / IP 또는 UDP 등의 프로토콜을 사용해 데이터를 전송할 수 있음
가장 기본적인 구성요소로, 모든 HTTP 통신은 이 소켓을 통해서 구현된다
- 채팅, 웹서버 등 모든 네트워크 통신이 소켓을 통해서 이루어 진다고 보면된다.
- 아래는 간단한 HttpServer의 구현으로, 8080 서버에 요청을 보내면 HTTP응답이 오게된다.
import java.io.*;
import java.net.*;
public class SimpleHttpServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("HTTP 서버가 포트 8080에서 대기 중입니다.");
// 클라이언트 요청을 기다리고 연결되면 처리
while (true) {
Socket clientSocket = serverSocket.accept();
// 새로운 클라이언트 연결에 대해 처리
handleRequest(clientSocket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void handleRequest(Socket clientSocket) {
try (
InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
PrintWriter writer = new PrintWriter(outputStream, true)
) {
// HTTP 요청을 읽습니다
String requestLine = reader.readLine();
System.out.println("요청: " + requestLine);
// 간단한 HTTP 응답 생성
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"Hello, World!";
// HTTP 응답을 클라이언트에 전송
writer.println(response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 가장 단순한 구현으로, InputStream과 OutputStream BufferedReader는 익숙한 사람이 많을 것이다.
- 채팅 서버, 클라이언트 구현할 때 봤던 소켓도 여기서 볼 수 있다. 8080 포트로 서버를 여는 코드가 들어가있다.
2. HttpConnector
채팅 프로그램에서 위 부분을 사용했던 기억이 있어서 알 수 있는 부분이지만,
저 상태라면 한 번에 한 요청밖에 처리하지 못한다.
멀티쓰레드를 사용해서 여러개의 요청을 동시에 처리할 수 있는 처리를 해 주어야 한다.
톰캣 서버로 한 번에 여러개의 요청이 들어올 때, 멀티쓰레드를 사용해서 처리한다는 사실을 알 수 있다.
- 소켓을 기반으로 고도화하여, HTTP요청 / 응답을 처리할 수 있도록 한 컴포넌트
- HTTP 요청 파싱, 응답생성, 멀티스레딩 지원
- 아래는 간단하게 커넥터를 구현한 예시이다.
import java.io.*;
import java.net.*;
public class SimpleHttpConnector {
private static final int PORT = 8080;
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("서버가 포트 " + PORT + "에서 요청을 기다립니다.");
while (true) {
Socket clientSocket = serverSocket.accept(); // 클라이언트의 연결을 기다립니다.
new Thread(new RequestHandler(clientSocket)).start(); // 요청을 처리하는 새로운 쓰레드를 시작합니다.
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 클라이언트의 요청을 처리하는 핸들러
static class RequestHandler implements Runnable {
private Socket clientSocket;
public RequestHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
try (
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)
) {
// 클라이언트 요청을 읽습니다
String requestLine = in.readLine();
System.out.println("클라이언트 요청: " + requestLine);
// HTTP 응답 헤더 작성
out.println("HTTP/1.1 200 OK");
out.println("Content-Type: text/plain");
out.println(""); // 헤더와 본문을 구분하는 빈 줄
// HTTP 응답 본문 작성
out.println("Hello, World! This is a simple HTTP response.");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close(); // 연결 종료
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
위처럼 여러개의 요청을 한 번에 처리할 수 있도록 쓰레드 구조로 구현되었다.
하지만 이 경우, 서블릿을 통해 여러 url을 처리하는 구조를 사용할 수 없다.
그래서 HttpServletRequest 객체를 만들어서 서블릿 컨테이너로 전달하는 커넥터를 구현하여야 한다. 이 과정을 톰캣이 전부 해주는 것이다.
3. 서블릿 매핑
이제 URL이 전달이 되면 우선순위와 파싱, 문자열 패턴등을 분해하여
해당 요청을 처리할 서블릿 함수로 연결이 된다.
이 요청을 처리하기 위해서는 web.xml이나, 어노테이션을 사용하여
URL과 서블릿함수에 대한 설정을 추가해줘야한다.
어노테이션 방식은 톰캣 3.0 버전이후로 사용할 수 있다.
위와같이 작성하고 톰캣 설정을 완료한 뒤, 요청을 보내면 아래와 같은 화면이 뜬다.
오늘 하루 톰캣이 어떤 역할을 하는지, 서블릿으로 어떻게 연결을 시키는지 알 수 있었다.
IOC가 톰캣을 사용하고 서블릿을 사용하면서부터 사용되는 것일줄은 몰랐는데
굉장히 근본적인 이해가 시작된 것 같아서 기분이 좋다.
'JAVA > 웹 프로그래밍 딥하게 파보기' 카테고리의 다른 글
stmt와 pstmt의 차이 (2) | 2024.12.18 |
---|---|
왜 무상태여야하는가? (0) | 2024.12.09 |
스프링 대규모 요청이 왔을 때 (0) | 2024.12.09 |
스프링의 역할 (1) | 2024.12.06 |
웹 프로그래밍 딥하게 파보기 (2) | 2024.11.28 |