Java 언어 및 스프링 프레임워크를 사용하며 방대한 양에 어느 정도 공부를 진행했다고 생각하였지만 실제로는 충분히 이해를 하지 못한 채 사용을 하고 있다는 생각이 계속해서 들게 되었다.(예를 들어, 코드를 작성할 때는 그저 플로우에 맞춰서 작성을 하고 문제가 발생하고 나서야 부랴부랴 원인을 찾게 된다. SecurityConfig에서 무한재귀가 발생한 일도 이러한 이유때문이지 않을까..)
이러한 문제를 해결할 좋은 방법이 뭐가 있을까 고민하던 와중, 간단하게라도 스프링 프레임워크를 구현해보면 추후 더 좋은 방향으로 성장할 수 있을 것 같다는 생각이 들었다. 따라서, 개인적으로 빠르게 미니 스프링 프레임워크를 구현해보려 한다.
서버 실행
먼저, 스프링 프레임워크는 웹 서버를 개발하기 위한 여러 가지 서비스를 제공하는 프레임워크이다. 실제로 spring-boot-starter-web 라이브러리를 사용하면 내장 톰캣 서버를 이용하는 모습을 확인할 수 있다.
결국 Http 요청을 받아 처리하고 응답을 하는 구조이기 때문에 먼저 웹 서버부터 만들어보려 한다.
먼저 스프링 프레임워크의 기본 구조처럼 run이라는 static 메소드부터 만들어보자.
먼저 서버를 만들기 위해, Server 객체에 port 번호와 쓰레드풀 개수를 설정해준다. 기본값인 8080과 쓰레드풀의 수는 현재는 10개로 설정하였지만 추후 테스트를 통해 적절한 개수가 어느 정도인지 파악하는 것 까지 진행해보려 한다. 또한, 실제로는 포트번호라던지 다른 설정들을 application.yml에서 파악해서 진행하지만 현재는 일단 고정된 값으로 서버를 띄우고 테스트하는 것까지만 1차로 시도해보자.
서버는 당연히 강제로 종료하지 않는 이상 계속 유지되어야하기 때문에 while(true)문으로 유지시켜줄 수 있도록 했다.
그 다음 accept을 통하여 clientSocket을 받고, 이를 handleRequest 로직을 통해 요청과 응답을 처리하는 구문으로 넘어간다.
여기서 ExecutorService라는 클래스는, 병렬 작업 시 여러 개의 작업을 효율적으로 처리하기 위한 Java 라이브러리라고 한다.
간단하게는 쓰레드를 생성하여 작업처리를 진행하고, 완료되면 해당 쓰레드를 제거하는 작업을 도와주는 클래스라고 한다.
HTTP 요청 처리
기본적으로 서버로 요청이 들어오면, 서버는 이 요청에 대해서 path, method, version, headers, body 등을 확인하고 이를 통한 작업을 진행한 후 클라이언트로 다시 response를 보내야 한다.
InputStream은 쉽게는 데이터를 읽어들이는 통로, OutputStream은 데이터를 내보내는 통로라고 생각하면 편하다.
즉 InputStream을 통해서 Http 요청을 읽고, OutputStream을 통해서 응답을 내보내면 된다.
먼저 Http 요청을 처리할 수 있는 클래스를 생성한다.
체크할 수 있도록 변수들을 설정한다. Http 헤더의 경우에는 Content-Type: application/json;charset=UTF-8 과 같은 형태로 key: value 형태로 오기 때문에 Map을 이용해서 저장하면 편할 것이라 생각한다.
Http Request의 값은 대략 다음과 같은 형태로 전송된다.
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.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, zstd
Accept-Language: ko,en-US;q=0.9,en;q=0.8
Cookie: JSESSIONID=B0B1C7F4E9FBA274123A1DE1B75071CB
Body Data
이렇게 온다.
맨 첫 줄에는 method path version이 띄어쓰기 간격으로 전송 되며 그 다음줄부터 바로 헤더들이 전송된다.
그 후, 바디 값은 헤더로부터 줄바꿈을 한 이후에 나온다. 물론 이미지 데이터 같은 형태가 들어온다면 또 추가가 되지만 간단한 형태부터 구현해보려 한다.
일반적으로 POST, PUT 요청에만 Request Body값이 존재하며 그 길이는 헤더의 Content-Length에 적혀진 길이만큼 전송된다.
그런데, DELETE의 경우 Request Body를 보내는 것이 "금지된 것은 아니지만" 일반적으로 사용하지 않는 것이 권장된다고 한다.
하지만 금지된 것이 아니라 사용될 수도 있기 때문에 추가하여 POST, PUT, DELETE 메소드의 경우에는 request body값을 담을 수 있도록 하였다.
이제 요청을 파싱하였으면 응답을 생성하러 가보자.
HTTP 응답 처리
HTTP 응답에서 중요한 점은 상태코드, 상태 메세지, 헤더(쿠키 설정 등), 응답 본문이다.
기본적인 정보를 생성해주고 setter까지 만들어준다.
그 후에는 HTTP 요청이 왔던 것처럼 버전 상태코드 상태메시지를 적고 그 다음줄부터는 헤더, 그리고 한 줄 건너뛰고 응답 본문을 적어준다.
응답 본문이 없는 경우에도 명시적으로 Content-Length: 0 을 작성해주었다. 여기서 버전은 HTTP/1.1로 하였지만 추후에는 요청에서 온 버전을 작성할 수 있도록 변경해야 할 것 같다.
그 후에 response.send() 로직이 실행된다면
이런 식으로 빈화면에 200 OK 라는 응답이 오게 될 것이다.
이 다음 포스팅은 DispatcherServlet을 구현하여 실제로 컨트롤러를 찾고 매핑하는 과정을 진행해보려 한다.
'Back-End > Spring' 카테고리의 다른 글
스프링 프레임워크 따라해보기 - 멀티 모듈과 빈 생성 및 등록 (1) | 2024.10.15 |
---|---|
스프링 프레임워크 따라해보기 - 컨트롤러 매핑과 응답 전송 (0) | 2024.10.11 |
오류가 발생했을 때 Jwt 검증 필터로 계속해서 들어오는 건에 대하여.. (0) | 2024.09.20 |
[Spring] Spring Security를 이용한 우당탕탕 인증/인가 개발기(1) - 회원가입(일반/Oauth2.0) (0) | 2023.12.21 |
[Spring] Oauth2.0과 Spring Security 작동원리(+ 42Seoul Api를 이용한 Oauth2.0 로그인 구현) (0) | 2023.11.24 |
댓글