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

[톰캣 구현하기 - 1,2단계] 엔델(김민준) 미션 제출합니다. #331

Merged
merged 11 commits into from
Sep 5, 2023

Conversation

SproutMJ
Copy link

@SproutMJ SproutMJ commented Sep 4, 2023

안녕하세요 가비, 엔델입니다.

HTTP에 대한 이해도가 부족하다보니 미션 구현에 집중했던 것 같아요. 마침 3단계가 리팩토링이기도 하니 메서드 분리를 최대한 하는 방향으로 진행했습니다.

흐름은 아래와 같습니다.

  1. Http11Processor클래스의 process 메서드에서 시작합니다.
  2. BufferedReader를 통해 requestLine, header, requestBody를 파싱합니다.
  3. requestLine에서 다시 method, path, queryString을 파싱합니다.
  4. controller에서 요청을 처리하고 response를 생성합니다.
4-1. if 분기를 통해 method와 path에 따라 다르게 처리합니다.
4-2. redirect인 경우, result를 만드는 경우(현재의 경우 파일을 전송하는 경우) generateResult, 단순히 string만 보내는 경우( 현재의 경우 "Hello world!" 전송하는 경우) generateResponseBody에 따라 다른 결과를 만듭니다. 이때 쿠키가 들어가는 경우에 따라 메서드가 오버로딩 되어있습니다.
  1. outputStream.write를 합니다.

현재 가장 큰 문제는 구현에 집중하기 위해 Cookie, Session 등을 제외한 클래스 분리가 되어있지 않은 것입니다.
특히 Header나 Cookie가 Optional하게 들어가는 경우가 많은데 이를 위하다보니 코드가 많이 복잡해진 것 같습니다.
이점 참고해주시면 감사하겠습니다.

1. GET /index.html 응답하기
2. CSS 지원하기
3. Query String 파싱
1. HTTP Status Code 302
2. POST 방식으로 회원가입
3. Cookie에 JSESSIONID 값 저장하기
4. Session 구현하기
@SproutMJ SproutMJ self-assigned this Sep 4, 2023
Copy link
Member

@iamjooon2 iamjooon2 left a comment

Choose a reason for hiding this comment

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

안녕하세요 엔델🙇🏻‍♂️

코드가 일관성있고, 작성해주신 코멘트 덕에 수월하게 읽을 수 있었습니다👍
구조는 이미 언급해주시기도 했고, step3가 있으니 찬찬히 고민하셔도 될 것 같습니다!
덕분에 사소한 부분들에 코멘트를 남기게 된 것 같네요!
여유가 되신다면 테스트를 작성하시면 더욱 좋을 것 같습니다~

미션 고생 많으셨습니다!

@@ -28,20 +70,185 @@ public void run() {
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
Copy link
Member

Choose a reason for hiding this comment

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

BufferedReader도 try-with-resource 구문 안으로 넣어서 사용 후 자원종료를 해줄 수 있을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

생각하고 있었는데 미처 적용하지 못했네요 감사합니다~

Comment on lines 140 to 143
return String.join("\r\n",
"HTTP/1.1 " + HTTP_STATUS.get(statusCode) + " ",
"Set-Cookie: " + cookie.generateCookieHeaderValue() + " ",
"Location: " + location + " ");
Copy link
Member

Choose a reason for hiding this comment

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

공백, 개행문자, 헤더 key가 다양한 곳에서 쓰일 수 있을 것 같아서 상수화 할 수 있을 것 같습니다

Copy link
Author

Choose a reason for hiding this comment

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

일단은 헤더의 Key 부분은 HttpHeaders 클래스를 만들어 상수화했습니다.
추가로 캡슐화를 하고 싶은데 이 부분은 ResponseBody를 리팩토링 하면서 같이 하면 좋을 것 같아서 남겨두었습니다.
지금 당장 이부분만 하려니 엄두가 안나네요

Comment on lines 44 to 56
final Map<String, String> contentType = new HashMap<>();
contentType.put("html", "text/html;charset=utf-8");
contentType.put("css", "text/css");
contentType.put("js", "application/javascript");
contentType.put("ico", "image/x-icon");
CONTENT_TYPE = Collections.unmodifiableMap(contentType);
final Map<String, String> httpStatus = new HashMap<>();
httpStatus.put("OK", "200 OK");
httpStatus.put("CREATED", "201 CREATED");
httpStatus.put("FOUND", "302 FOUND");
httpStatus.put("NOT_FOUND", "404 NOT FOUND");
httpStatus.put("UNAUTHORIZED", "401 UNAUTHORIZED");
HTTP_STATUS = Collections.unmodifiableMap(httpStatus);
Copy link
Member

Choose a reason for hiding this comment

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

enum이라는 선택지도 있었을 텐데, Map을 사용하신 이유를 여쭙고 싶습니다!

Copy link
Author

Choose a reason for hiding this comment

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

일단 직관적인 구현을 위해 이렇게한 것 같습니다.
현재는 두 ContentType, HttpStatus를 enum화 했습니다.

} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
}

private String controller(final Map<String, String> httpRequestHeader, final String method, final String path, final Map<String, String> queryString, Map<String, String> requestBody) throws IOException {
Copy link
Member

@iamjooon2 iamjooon2 Sep 4, 2023

Choose a reason for hiding this comment

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

마지막 requestBody만 final이 빠져있네요! ( 달게 없을 때 적는 비겁한 리뷰입니다 )

Copy link
Author

Choose a reason for hiding this comment

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

붙였습니다!

Comment on lines 146 to 150
private String generateResult(final String path, final String statusCode) throws IOException {
return generateResult(path, statusCode, Cookie.newInstance());
}

private String generateResult(final String path, final String statusCode, final Cookie cookie) 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.

쿠키를 담아 보낼 경우 generateResult가 아닌 generateRedirect를 사용중이군요!
두 메서드를 합칠 수 있을 것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

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

현재 구현에 어려움이 있어서
쿠키를 보내면서 보내는 경우 / 쿠키를 보내지 않으면서 보내는 경우 / 쿠키를 보내면서 리다이렉트하는 경우 / 쿠키를 보내지 않으면서 리다이렉트하는 경우
총 네가지의 메서드로 response 결과를 만듭니다.
이 과정이 복잡해서 역시 리펙토링 대상인데요, 현재는 Header를 최종적으로 만들어서 response에 전달하는 방식으로 해볼까 생각중입니다. 다만, 3단계때 한꺼번에 하는 것은 어떤지 여쭤봐도 될까요?

Copy link
Member

Choose a reason for hiding this comment

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

넵 좋습니다!!

return "";
}
final Path staticFilePath = new File(resource.getPath()).toPath();
final int lastDotIndex = staticFilePath.toString().lastIndexOf(".");
Copy link
Member

Choose a reason for hiding this comment

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

lastIndexOf()...! 배워갑니다👀

@@ -20,7 +20,7 @@
<div class="card shadow-lg border-0 rounded-lg mt-5">
<div class="card-header"><h3 class="text-center font-weight-light my-4">회원 가입</h3></div>
<div class="card-body">
<form method="post" action="register">
<form method="post" action="/register">
Copy link
Member

Choose a reason for hiding this comment

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

남길 곳이 마땅치 않아서 이곳에 남깁니다

2단계 요구사항 중 로그인 페이지도 버튼을 눌렀을 때 GET 방식에서 POST 방식으로 전송하도록 변경하자. 가 있었는데요
요 부분을 깜빡 하신 것 같습니다. 한번 확인부탁드립니다!

Copy link
Author

Choose a reason for hiding this comment

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

어쩐지 뭔가 이상하다고 생각했었는데 미처 적용하지 못했네요
페이지를 불러오는 경우에는 GET, 정보를 입력하고 버튼을 누르면 POST를 사용해 요청하도록 구현했습니다.

final String requestBody = new String(buffer);
Arrays.stream(requestBody.split("&")).forEach(query -> {
final String[] keyValue = query.split("=");
final String decodedValue = URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8);
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 23 to 25
public static Cookie newInstance() {
return new Cookie(new HashMap<>());
}
Copy link
Member

@iamjooon2 iamjooon2 Sep 4, 2023

Choose a reason for hiding this comment

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

미리 생성해둔 객체를 사용하면 좀 더 효율적이지 않을까라는 생각이 들었습니다! (사실 저도 놓친 부분입니다😂)
엔델은 어떻게 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

제가 미처 이해를 정확히 하지 못했는데요, 개별적인 빈 캐시를 만들기 위해 만든 메서드입니다.
일단 직관적인 네이밍이 아니어서 empty()로 변경해보았습니다.

Copy link
Member

@iamjooon2 iamjooon2 Sep 5, 2023

Choose a reason for hiding this comment

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

이 부분은 제가 잘못봤네요!
해당 메서드를 통해 빈 쿠키를 반환하는 데만 사용하는 것으로 보고 단 코멘트입니다
혼란을 드려서 죄송합니다🙇🏻‍♂️

@sonarcloud
Copy link

sonarcloud bot commented Sep 5, 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 6 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

@iamjooon2 iamjooon2 left a comment

Choose a reason for hiding this comment

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

엔델 고생 많으셨습니다!
다음 단계에서 만나시죠🚀

@iamjooon2 iamjooon2 merged commit 5b5ee3f into woowacourse:sproutmj Sep 5, 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