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단계] 허브(방대의) 미션 제출합니다. #431

Merged
merged 42 commits into from
Sep 11, 2023

Conversation

greeng00se
Copy link
Member

안녕하세요 디노 🦖 주말 잘 보내고 계신가요?

3, 4단계 미션 리뷰 요청드립니다!

전체적인 의존성 방향은 다음과 같이 구성되어있습니다.

graph LR
  jwp --> catalina --> coyote
Loading

세부적인 부분은 아래와 같이 구성했습니다.

graph LR
	subgraph coyote
		M[Mapper]
		HP[Http11Processor] --> M
		HP --> HttpRequestParser
		HP --> HttpResponseGenerator
  end

  subgraph catalina
		RM[RequestMapper] -.-> M
    AC[AbstractController] -.-> C[Controller]  
		StaticController -.-> AC  
		SM[SessionManger] -.-> Manager
		TC[Tomcat] --> RM
		RM --> C
		RM --> SM
  end

  subgraph jwp
    LC[LoginController] -.-> AC
		Application --> TC
  end
Loading

조금 더 꼼꼼하게 코드를 작성하고 싶은 부분이 있었는데, 시간 관계상 그러지 못한 부분이 조금 많았던 것 같아요!
디노랑 이야기해보면서 조금 더 개선해 나가고 싶습니다! 호호..

@greeng00se greeng00se self-assigned this Sep 9, 2023
Copy link
Member

@jjongwa jjongwa left a comment

Choose a reason for hiding this comment

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

안녕하세요 허브~🌿 다이노입니다🦖

코드리뷰 재밌네요 ㅇㅡㅇ
이번에는 커밋 순서대로 따라가기 보단 전체적인 구조와 패키지 의존성 방향을 따라가 보며 이해해 봤어요.
어렵네요.
간단한 컨벤션 + 궁금한 부분 리뷰 남겼습니다!

제가 남긴 코멘트 중에 이해가 안되는 부분이 있으면 답글 남겨 주셔도 되고 직접 물어보셔도 됩니다!
그럼 이제 전 제 코드 리팩터링하러.. 이만 총총

connector.start();

try {
// make the application wait until we press any key.
System.in.read();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
} finally{
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
} finally{
} finally {

Copy link
Member Author

Choose a reason for hiding this comment

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

우테코 스타일 적용했는데 자동 저장하니까 공백이 없게 되네요 ㅠㅠ 리포맷 풀고 수정했습니다 👍


public class SessionManager implements Manager {

private static final Map<String, Session> SESSIONS = new ConcurrentHashMap<>();
Copy link
Member

Choose a reason for hiding this comment

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

구구의 리뷰에서 본건데요
#440 (comment)

상수 이름은 모두 대문자로 된 UPPER_SNAKE_CASE를 사용하며, 각 단어는 밑줄 하나로 다음 단어와 구분됩니다. 하지만 상수란 정확히 무엇일까요?

상수는 내용이 거의 불변이고 메서드에 감지할 수 있는 부작용이 없는 정적 최종 필드입니다. 예를 들어 원시값, 문자열, 불변 값 클래스, null로 설정된 모든 것이 포함됩니다. 인스턴스의 관찰 가능한 상태 중 하나라도 변경될 수 있다면 상수가 아닙니다. 객체를 절대 변경하지 않겠다는 의도만으로는 충분하지 않습니다.

이에 따르면 sessions로 사용하는게 코딩 스타일 가이드에 맞는 것 같습니다.!

Copy link
Member Author

Choose a reason for hiding this comment

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

호호 감사합니다! 상수명을 일관성있게 수정했습니다 👍

Comment on lines 11 to 15
new Tomcat()
.addController("/", new HomeController())
.addController("/login", new LoginController())
.addController("/register", new RegisterController())
.start();
Copy link
Member

Choose a reason for hiding this comment

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

각 컨트롤러를 RequestMapper에서 static으로 미리 넣어주지 않고 Tomcat의 addController 메서드를 사용하는 이유가 무엇인가요?

제가 허브의 코드를 보며 이해한 바로는 컨트롤러들(/, /login, /register)이 jwp단에 위치하기 때문에 catalina단에 존재하는 tomcat과 RequestMapper가 모르게 해야 하기 위해서 인것 같은데요.

제가 이해하지 못한 다른 이유가 있다면 코멘트 부탁드립니다.!

Copy link
Member Author

Choose a reason for hiding this comment

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

말씀해주신대로 RequestMapper에서 넣어주게 된다면 현재 코드에서 의존성 방향이 양방향이 됩니다.
또한 로그인과 회원가입과 같은 비즈니스 로직은 Tomcat을 사용하는 쪽에서 등록해줘야한다고 생각했습니다!

public class Tomcat {

private static final Logger log = LoggerFactory.getLogger(Tomcat.class);
private static final int DEFAULT_THREAD_COUNT = 250;

private final Mapper mapper = new RequestMapper(new StaticController());
Copy link
Member

Choose a reason for hiding this comment

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

다른 컨트롤러들과 StaticController의 패키지 위치가 다른 이유는 무엇인가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

staticController의 경우 기본적으로 resources에 있는 정적 파일에 대한 요청이 있을 때 사용됩니다.
실제로 웹서버를 사용할 때도 따로 정적 파일에 대한 부분을 지정해주지 않는 것을 생각하여 catalina 패키지에 두었습니당 😄

Copy link
Member

Choose a reason for hiding this comment

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

하나 배워갑니다..!ㅎㅎ

Comment on lines 58 to 73
final String generate = httpResponseGenerator.generate(httpResponse);
outputStream.write(generate.getBytes());
outputStream.flush();
} catch (final Exception e) {
LOG.error(e.getMessage(), e);
internalServerError(outputStream);
}
final String account = requestBody.get(ACCOUNT);
final String password = requestBody.get(PASSWORD);
return InMemoryUserRepository.findByAccount(account)
.filter(user -> user.checkPassword(password))
.map(this::loginSuccess)
.orElseGet(() -> new ResponseEntity(HttpStatus.UNAUTHORIZED, "/401.html"));
}

private ResponseEntity loginSuccess(final User user) {
final String uuid = UUID.randomUUID().toString();
final ResponseEntity responseEntity = new ResponseEntity(HttpStatus.FOUND, INDEX_PAGE);
responseEntity.setJSessionId(uuid);
final Session session = new Session(uuid);
session.setAttribute("user", user);
sessionManager.add(session);
return responseEntity;
}

private ResponseEntity register(final RequestLine requestLine, final RequestBody requestBody) {
if (requestLine.getHttpMethod() == HttpMethod.GET) {
return new ResponseEntity(HttpStatus.OK, REGISTER_PAGE);
}
final String account = requestBody.get(ACCOUNT);

if (InMemoryUserRepository.findByAccount(account).isPresent()) {
return new ResponseEntity(HttpStatus.CONFLICT, "/409.html");
}

final String password = requestBody.get(PASSWORD);
final String email = requestBody.get(EMAIL);
InMemoryUserRepository.save(new User(account, password, email));
return new ResponseEntity(HttpStatus.FOUND, INDEX_PAGE);
private void internalServerError(final OutputStream outputStream) throws IOException {
final HttpResponse httpResponse = new HttpResponse(HttpVersion.HTTP_1_1)
.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR)
.sendRedirect("/500.html");
final String response = httpResponseGenerator.generate(httpResponse);
outputStream.write(response.getBytes());
outputStream.flush();
Copy link
Member

Choose a reason for hiding this comment

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

final String generate = httpResponseGenerator.generate(httpResponse);
            outputStream.write(generate.getBytes());
            outputStream.flush();

요 부분 함수 추출하는거 어떠신가요? (그냥 의견임.)

Copy link
Member Author

Choose a reason for hiding this comment

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

너무 좋아요 👍 덕분에 중복 제거 깔끔하게 했습니다!

Comment on lines 35 to 50
private void setSessionToHttpRequest(final HttpRequest httpRequest) {
final String sessionId = httpRequest.parseSessionId();
if (sessionId == null) {
return;
}
final Session session = sessionManager.findSession(sessionId);
httpRequest.setSession(session);
}

private void addSession(final HttpResponse httpResponse) {
final Session session = httpResponse.getSession();
if (session == null) {
return;
}
sessionManager.add(session);
}
Copy link
Member

Choose a reason for hiding this comment

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

Request의 parseSessionId와 Response의 getSession 에서 null 체크를 하고 여기서 try-catch로 잡는 방식은 어떠신가요?

메서드에서 null을 반환한다면 추후에 다른 로직에 해당 메서드가 사용되는 상황에서 null 체크를 까먹을 수도 있고, 계속 null 반환 여부를 염두에 둔 채 개발을 진행해야 한다는 번거로움이 생긴다고 생각해서요.!

Copy link
Member Author

Choose a reason for hiding this comment

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

해당 부분은 try-catch 대신 Session을 관리하는 SessionManager에서 null 체크를 하도록 수정했습니다!

Comment on lines 30 to 32
return Arrays.stream(body.split(SEPARATOR))
.map(field -> field.split(DELIMITER))
.collect(collectingAndThen(
Copy link
Member

Choose a reason for hiding this comment

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

현재 로그인과 회원가입의 입력칸에 아무것도 넣지 않았을 때
해당 부분의 로직에서
index에 해당하는 value가 없으면 IndexOutOfBoundError 발생 -> Http11Processor 에서 catch -> 500 Response 반환
의 흐름을 통해 처리하고 있는데요.

Suggested change
return Arrays.stream(body.split(SEPARATOR))
.map(field -> field.split(DELIMITER))
.collect(collectingAndThen(
return Arrays.stream(body.split(SEPARATOR, 2))
.map(field -> field.split(DELIMITER, 2))
.collect(collectingAndThen(

의 형태로 바꾸어 빈 문자열을 받은 뒤 이에 따른 예외 처리를 진행해 주는 방식은 어떤가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

body.split에 2를 넣어주는건 account, email, password 3개를 받을 때 오류를 발생시킬 것 같습니다!
field의 경우 2개로 받아서 생성하는 부분 너무 좋은 것 같아요 👍

Copy link
Member

Choose a reason for hiding this comment

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

아 그러네요 ㅎㅎ 잘못 봤습니다.!

}

public void addHeader(String line) {
if (line == null || line.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

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

isBlank() 어떠신가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

좋아용 👍

Copy link
Member

Choose a reason for hiding this comment

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

지금 사용되고 있는 쪽은 request지만 HTTPMethod의 의미상 common 패키지에 둔 걸로 이해하면 될까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

넵 맞습니다!

@greeng00se
Copy link
Member Author

안녕하세요 다이노 🦖
꼼꼼히 리뷰를 남겨주셔서 리뷰 반영하면서 너무 재밌었습니다!
이전의 RequestMapper의 책임이 너무 큰 것 같아서 아래와 같이 Adapter 클래스를 추가하였습니다.
감사합니다 👍

graph LR
	subgraph coyote
		HP[Http11Processor] --> A
		HP --> HttpRequestParser
		HP --> HttpResponseGenerator
		A[Adapter]
  end

  subgraph catalina
		RA[RequestAdapter] -.-> A
		AC[AbstractController] -.-> C[Controller]
		StaticController -.-> AC
		SM[SessionManger] -.-> Manager
		TC[Tomcat] --> RA
		RA --> C
		RA --> Manager
		RA --> RM
		RM[RequestMapper] --> C
  end

  subgraph jwp
		LC[LoginController] -.-> AC
		Application --> TC
  end

Loading

@sonarcloud
Copy link

sonarcloud bot commented Sep 11, 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 0 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

Copy link
Member

@jjongwa jjongwa left a comment

Choose a reason for hiding this comment

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

안녕하세요 허브~🌿 다이노입니다🦖

리뷰 남긴 부분에 대한 답글 확인했습니다!

허브가 남겨 주신 그래프가 코드를 이해하는데 아주 큰 도움이 되었고,
전체적인 구성과 로직에 대한 근거와 사고 과정을 따라가는 과정에서
제가 오히려 많이 배웠다고 생각합니다ㅎㅎ 즐거웠어요~~

요구사항도 모두 만족했기에(이전에 이미 만족했지만) 이만 머지하도록 할께요ㅎㅎ 다음 미션도 파이팅입니다~~~~!!

@jjongwa jjongwa merged commit b0695b9 into woowacourse:greeng00se Sep 11, 2023
2 checks passed
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