Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[톰캣 구현하기 - 3,4단계] 도이(유도영) 미션 제출합니다. #453

Merged
merged 36 commits into from
Sep 12, 2023

Conversation

yoondgu
Copy link

@yoondgu yoondgu commented Sep 10, 2023

안녕하세요 홍실 ,, 🧶
지난 단계 잘 확인해주셔서 일찍이 이번 단계를 시작할 수 있었음에도 리뷰 요청이 많이 늦었습니다 🥲
욕심을 부렸는지 삽질..+ 전체 구조에 변화가 여러 번 있어서, 시간이 오래 걸렸어요.
인터페이스, 클래스명, 패키지 구조 모두 전체적으로 바뀌었고 커밋 당 변경 사항도 많아서 ㅜㅜ
커밋 단위로 코드를 보시기에 좀 힘들 것 같아 죄송스럽네요.

변경된 흐름으로 다시 설명 드리자면, 아래와 같습니다.

  1. 톰캣을 사용하는 어플리케이션(jwp 패키지)에서 필요한 객체를 담은 Container를 전달해 Connector를 생성
    • 어플리케이션 개발자가 요청에 대해 매핑한 내용을 가진 HttpServlet 객체들
    • SessionManager 객체
  2. 요청마다 스레드 생성 -> Http11Processor에서 Container를 통해 응답 반환
  3. ContainerRequestHandlerAdaptor를 통해 적절한 HttpServlet을 찾아 요청 처리
    • 이 때 ContainerHttpServlet이 사용할 HttpServletRequest, HttpServletResponse 객체를 service() 메서드의 인자로 전달
    • 두 객체는 http11 패키지의 Request, Response와는 별도로 관리됨 (어플리케이션 코드와의 의존성 제거를 위함)

참고로 3단계 요구사항인 Controller 인터페이스와 추상클래스의 경우,

  • 톰캣 Controller 인터페이스 -> RequestHandler
  • 톰캣 AbstractController -> HttpServlet
    이라는 네이밍으로 바꾸어 구현했습니다.
    저는 어플리케이션 코드 개발자가 작성하는 Controller와 톰캣의 Controller 역할을 분리해서 구현하는 데 신경썼는데, 그러다보니 서로 다른 클래스가 같은 네이밍을 쓰면 헷갈릴 것 같아서요!

설계 삽질에 빠져버려서 스레드 관련 고민은 깊게 하지 못한 것 같네요..
동시성 문제 관련 테스트를 짜보려고 했는데 어떻게 해야 할지, 혹시 좋은 의견 있으시면 부탁드립니다 ㅎㅎㅎ

너무 늦게.. 너무 바뀐 코드로 요청드리지만 🥹🥹 잘 부탁드려요!
질문 있으시면 편하게 말씀해주세용

yoondgu and others added 30 commits September 7, 2023 23:08
- isNew 메서드로 인해 Session SessionManager 양방향 의존 해결이 필요했음
- Session이 항상 SessionManager에서 만들어지도록 보장하면 로그인 여부 확인 시
isNew 메서드가 필요없는 상황
- 따라서 SessionManager에서 항상 Session을 만들도록 함
- 외부의 Session 생성 방지를 위해 내부 클래스로 변경
- ContentType 관련, 응답 출력 관련 리팩터링 필요
- 하나의 요청 프로세스에 대응하는 객체라는 점에서 기존 RequestHandler -> FrontController 네이밍 변경에서 DispatcherServlet 으로 변경
- 요구사항의 Controller 클래스는 jwp 패키지와의 구분을 위해 Handler로 명명
- 현재는 static하게 사용하지만, 추후 동시성 처리하며 인스턴스를 서블릿 컨테이너에서 관리하도록 하기
- DispatcherServlet -> RequestHandlerAdaptor
- RequestHandlerMapping -> RequestMapping
- 정적 파일 핸들러는 RequestHandler만 구현하도록 함
- AbstractRequestHandler는 동적 파일 핸들러만 상속하도록 하고 HttpServlet으로 변경
- StaticResourceRequestHandler 는 주입받는 대신 톰캣에서 관리
- 중간 계층 역할로서 ServletResponse 를 Response 클래스에서 분리
- 프로덕션 코드는 톰캣에 의존할 수 있게 톰캣 패키지로 이동
- StaticResourceRequestHandler -> DefaultServlet
- 프로덕션 코드의 XXXHandler -> XXXServlet
- Response와 마찬가지로 서블릿에서 사용하는 Request의 추상화 계층 정의
- 프로덕션 코드에서 http11 패키지에 의존하지 않도록 하기 위함
@yoondgu yoondgu self-assigned this Sep 10, 2023
@sonarcloud
Copy link

sonarcloud bot commented Sep 10, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 5 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

warning The version of Java (11.0.20.1) you have used to run this analysis is deprecated and we will stop accepting it soon. Please update to at least Java 17.
Read more here

@yoondgu yoondgu marked this pull request as ready for review September 10, 2023 14:53
Copy link
Member

@hong-sile hong-sile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 많이 배울 수 있는 코드였습니다.

이번 미션에서 학습해야 할 것과 본인이 구현하려고 노력한 방식이 신선해서 재밌었어요.

동시성 문제 관련한 테스트는 저도 코드로 구현해보려다가 실패했어요.. ㅠ
학습테스트에 있는 내용을 응용하는게 제가 지식이 모자라서 그런지 어렵더라고요.
그래서 테스트코드 만으론 동시성을 확인하기 어려워서, postMan을 이용해서 동시성에 대해서 학습을 해봤어요.
Postman에 동시성 테스트를 위한 기능들이 많으니, 한 번 이용해보시는 것도 추천드립니다.

궁금한 부분에 대해서 질문 남겼는데 확인해주시면 감사하겠습니다.
이만 Approve Merge 할게요!! 이번 미션 수고하셨습니다.

import org.apache.catalina.core.RequestHandler;

public abstract class HttpServlet implements RequestHandler {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTP Servlet(abstract class)은 catalina에 두고, 실 구현체는 jwp에 둔게 인상적이네요. 패키지를 잘 고려해서 분리하신 것 같아요.

*/
HttpSession findSession(String id) throws IOException;
Optional<Session> findSession(String id) throws IOException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떻게 이걸 활용했나 했는데, 기존에 주어진 코드를 수정하셨군요!
좋은 방법인 것 같아요


public interface RequestHandler {

void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쭉 보다 보니 HttpServlet abstract class가 구현되고 나서,
RequestHandler를 사용하는 곳은 없는 것? 같아요.
HttpServlet을 제외한 다른 클래스에서 이 RequestHandler를 구현받진 않더라고요.
실제로 쓰는 곳도 반환하는 용도? 였고요.(이 부분도 반환타입을 HttpServlet으로 둘 수 있을 것 같아요.)

RequetHandler를 남겨두신 이유가 있으신가요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공감합니다 !! 요구사항에 주어진 구조라 그대로 유지한 게 큰 이유이긴 했어요.
요구사항에서는 왜 추상 클래스 상위에 인터페이스를 두는 방식이 주어졌을까요?
HttpServlet이 아니라, 다른 종류의 구현체가 올 수 있도록 하기 위함일까요...? 홍실의 생각도 궁금하네용

final var tomcat = new Tomcat();
tomcat.start();
tomcat.start(Set.of(new LoginRequestServlet(), new RegisterRequestServlet()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Application에서 Servlet을 주입받는군요.

진짜 코드가 맛있네요. 패키지에 신경을 쓰셨다는게 눈에 보여요.

정말 단순한 궁금증인데, Servlet들을 start로 넘기신 이유가 있을까요?

Tomcat 생성자에 넣을 수도 있을 것 같아서요.
(별 의미 없을 수도 있을 것 같은데, 혹시 도이라면 이유가 있을까? 궁금해서 코멘트 남깁니다.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자로 받게 된다면, 서블릿들을 Tomcat의 멤버변수로 가지게 될텐데 (서블릿의 상태를 관리하는 다른 기능이 없기 때문에) 지금으로서는 그럴 이유가 없다고 생각했어요! 그래서 start 메서드를 통해 Http11Processor까지 전달해주기만 했습니다.

@@ -0,0 +1,5 @@
package nextstep.jwp.controller;

public interface Controller {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 Controller를 별도의 인터페이스로 분리하신 이유가 있으신가요?

각각의 Controller에 Implement를 하고 별도로 사용하진 않는 것 같아서요.
(따로 추상 메서드가 있는 것도 아니고요.)

Copy link
Author

@yoondgu yoondgu Sep 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 catalina에서 도메인 컨트롤러의 static 메서드를 호출하는 방식을 사용하고 있는데,
이 컨트롤러들도 인스턴스화해서 주입하는 방식으로 바꿀 필요가 있다고 생각하면서 일단 인터페이스만 만들어놨는데요. 해당 부분까지 진행하면 너무 커질 것 같아서 내버려뒀습니다... ㅎㅎㅎ

여기에 쪼금 더 합리화하자면 Spring에서 @Service 어노테이션이 지금은 특별한 기능이 없어도 사용되는 것처럼, 기능이 없는 인터페이스도 추후 변경 시 같이 변경되어야 할 레이어를 지정하는 역할을 할 수 있다고 생각했어요.

}

public static HttpServletResponse internalSeverError() {
return new HttpServletResponse(INTERNAL_SERVER_ERROR);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InternalServeError나 notFound의 경우에는 사용하지 않으니,
삭제해도 될것 같아요!

private final Method method;
private final String uri;
private final Headers headers;
private final RequestLine requestLine;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coyote 패키지엔 Request를 두고,
catalina 패키지엔 HttpServletRequest로 두셨더라고요.

혹시 그렇게 분리하신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

패키지 별 Request를 분리한 이유를 물어보시는 게 맞을까요?
아니면 네이밍에 대한 질문이실까요?

전자의 경우,
jwp 패키지에서는 통신 방법을 몰라야 한다고 생각해 http 패키지를 참조하지 않게 하기 위해서 분리했습니다. 이를 통해 SessionManager와 Request 의 의존도 끊을 수 있었어요.

Hoxy 후자의 질문이라면,
coyote 패키지는 이미 http11 패키지에 속해있으므로 이름에 Http~ 를 붙일 필요 없다고 생각했고
catalina 패키지에서는 HttpServlet에서 사용하는 Request라는 의미로 구체화했습니다.

/**
* Entity header
*/
CONTENT_ENCODING("Content-Encoding"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헤더도 역할별로 분리한것도 야무지네요. 이를 이용해서 클래스를 분리한 것도 인상적이었습니다.


public class ResponseHeaders extends Headers {

public ResponseHeaders() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얘는 별도로 안쓰면 private으로 빼도 될 것 같아요.

}

private Map<String, String> filterByHeaderType(final Map<String, String> headers) {
return headers.entrySet().stream()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이게 어떤식으로 동작하나 했는데, 각각의 헤더를 분리하기 위한 용도군요.
재밌는 구현방식인 것 같아요.

@hong-sile hong-sile merged commit d638346 into woowacourse:yoondgu Sep 12, 2023
2 checks passed
@yoondgu yoondgu changed the title [톰캣 구현하기 - 3, 4단계] 도이(유도영) 미션 제출합니다. [톰캣 구현하기 - 3,4단계] 도이(유도영) 미션 제출합니다. Sep 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants