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단계] 코코닥(이승용) 미션 제출합니다. #373

Merged
merged 15 commits into from
Sep 7, 2023

Conversation

kokodak
Copy link
Member

@kokodak kokodak commented Sep 4, 2023

존경하는 로이햄 안녕하신지요..
테스트 따위 없는 못난 코드 보여드려서 죄송할 따름입니다.
제가 내일이면 포천 어딘가의 부대에서 열심히 훈련을 하게 됩니다. 그래서 코드리뷰 반영이 느릴 것 같아요.
하지만 열심히 해보겠습니다!!!!!!!!!

@sonarcloud
Copy link

sonarcloud bot commented Sep 4, 2023

SonarCloud Quality Gate failed.    Quality Gate failed

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
6.7% 6.7% 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

idea Catch issues before they fail your Quality Gate with our IDE extension sonarlint SonarLint

Copy link

@the9kim the9kim left a comment

Choose a reason for hiding this comment

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

안녕하세요 코코닥!

국방의 의무를 다하느라 고생이 많으십니다.

핸들러까지 멋지게 구현해주셔서 깜짝 놀랐습니다.

개인적으로 톰캣의 작동 원리를 잘 모르는 터라, 통찰력 있는 리뷰를 남기지 못해서 죄송합니다.

최대한 꼼꼼히 보고 도움이 될만한 내용을 코멘트로 남겼습니다.

어디까지나 하나의 의견으로 봐주시고, 코드의 주인은 코코닥인 만큼 취사 선택하여 반영해주시면 좋을 것 같습니다.

코코닥 흥해라 흥!

Comment on lines +60 to +64
private static void validateStartLine(final String startLine) {
if (startLine.split(" ").length != 3) {
throw new IllegalArgumentException("Invalid HTTP Request Message");
}
}
Copy link

Choose a reason for hiding this comment

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

매직넘버 상수화에 관해서는 어떻게 생각하시나요?

필요성에 공감하신다면 코드 전반에 거쳐 매직넘버 & delimiter를 상수화 하는 것도 좋을 듯 합니다!

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 +80 to +88
private static String getBody(final BufferedReader bufferedReader, final Map<String, String> header) throws IOException {
if (header.containsKey("Content-Length")) {
int contentLength = Integer.parseInt(header.get("Content-Length"));
char[] buffer = new char[contentLength];
bufferedReader.read(buffer, 0, contentLength);
return new String(buffer);
}
return "";
}
Copy link

Choose a reason for hiding this comment

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

"Content-Length"와 같은 키값은 반복해도 사용되는 만큼 상수화로 표현해도 좋을 듯합니다.

이것 이외에도 문자열 파싱 과정에서 발생하는 매직 넘버를 상수화 하는 방안도 한번 고민해보시면 좋을 듯 해요!

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 +66 to +78
private static Map<String, String> getHeader(final List<String> primitiveRequest) {
final Map<String, String> header = new HashMap<>();
final int size = primitiveRequest.size();
for (int i = 1; i < size; i++) {
final String request = primitiveRequest.get(i);
if (request.equals(CRLF.getValue())) {
break;
}
final String[] headerKeyValue = request.split(COLON.getValue() + BLANK.getValue());
header.put(headerKeyValue[0], headerKeyValue[1]);
}
return header;
}
Copy link

Choose a reason for hiding this comment

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

  1. ":"를 기준으로 헤더를 파싱할 때, 헤더의 값에 ":"가 포함되지 않을 경우 예외처리가 필요할 듯합니다. 또 indexOf()와 subString()을 활용한다면 안전하게 파싱할 수 있을 듯 합니다.

  2. 사소한 내용이지만 강화 for문을 적용해서 가독성을 높일 수 있을 것 같아요.

  3. 가독성을 높이기 위해 getHeader와 getBody를 파싱하는 로직을 별도의 클래스로 분리하는 것도 고려해봄직 합니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

예외 처리에 대해 크게 공감하여 헤더에 대한 예외 처리 기능을 추가했습니다!
다만, 현재 반복문은 반드시 (size - 1)번 돌아야하기 때문에 강화 for문을 적용하기가 어려웠습니다 ㅠㅠ
클래스를 분리하는 것과 관련해서는 조금 더 고민해보겠습니다!!

Comment on lines +34 to +38
public String cookie(final String key) {
if (cookie.containsKey(key)) {
return cookie.get(key);
}
return "";
Copy link

Choose a reason for hiding this comment

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

보다 간결한 코드를 위해 getOrDefault() 메서드를 활용해보는 것도 좋을 듯 합니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

맞네요.. 적용하겠습니다!

private String id;
private Map<String, Object> values;

public Session(final String id) {
Copy link

Choose a reason for hiding this comment

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

Session Manager(필드 선언 초기화)와 달리 Session class에서는 values를 생성자에서 초기화한 이유가 있으실까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

차이점이라고 한다면, static 변수에 대한 초기화인지 인스턴스 변수에 대한 초기화인지로 나뉜 것 같습니다. 인스턴스 변수는 최대한 생성자에서 초기화하려했고, static 변수는 static 블럭을 활용하여 초기화에 섬세한 조정이 필요하지 않은 이상, 선언과 동시에 초기화를 해주려고 한 것 같습니다!

return new HttpResponseMessageBuilder();
}

public static class HttpResponseMessageBuilder {
Copy link

Choose a reason for hiding this comment

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

빌더 패턴 굿입니다👍

Response message 생성 로직이 해당 클래스에 모두 포함되어 있는데,

추후 여력이 된다면 Response 메시지 생성 관련 복잡성을 별도의 클래스에 위임해보는 방식도 고민해볼 수 있을듯 해요!

public Object resolve(final HttpRequest httpRequest) {
final HttpCookie httpCookie = httpRequest.getHttpCookie();
final String jSessionId = httpCookie.cookie("JSESSIONID");
if ("".equals(jSessionId)) {
Copy link

Choose a reason for hiding this comment

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

isEmpty() 메서드를 활용하면 명확히 표현할 수 있다는 장점이 있을 듯 해요!

Copy link
Member Author

Choose a reason for hiding this comment

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

맞습니다! 다만 jSessionId 가 null 인 경우에는 isEmpty() 를 호출할 때 NPE 에러가 발생할 여지가 있어서, 이런 식의 처리를 하게 되었습니다.

final String responseBody = "Hello world!";
return HttpResponse.builder()
.header("Content-Type", "text/html;charset=utf-8")
.header("Content-Length", String.valueOf(responseBody.getBytes().length))
Copy link

Choose a reason for hiding this comment

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

.autoSetContentLength()를 활용하면 HttpReponse 빌더가 본문의 길이를 자동으로 설정한다고 합니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

엇 그런 기능이 있나요?! 어떤 객체의 메서드인지 궁금합니다.


private List<Argument> arguments = new ArrayList<>();

@Override
Copy link

@the9kim the9kim Sep 5, 2023

Choose a reason for hiding this comment

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

메서드가 길어서 가독성이 좀 떨어지는 측면이 있는 듯 해요!

POST와 GET 메서드 처리 로직 정도만 분리한다고 해도 훨씬 가독성이 좋아질 듯 합니다!

(코드 라인을 잘못 지정했네요 handle() 메서드에 관한 내용입니다)

Copy link
Member Author

Choose a reason for hiding this comment

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

맞습니다.. 가독성이 많이 떨어져서 이번에 POST와 GET의 내용을 분리해보았습니다.

Comment on lines +22 to +52
@Override
public HttpResponse handle(final HttpRequest httpRequest) throws IOException {
if (httpRequest.getHttpMethod() == HttpMethod.POST) {
final Map<String, String> formData = FormDataParser.parse(httpRequest.getBody());
final User user = new User(formData.get("account"), formData.get("password"), formData.get("email"));
InMemoryUserRepository.save(user);
return HttpResponse.builder()
.redirect("http://localhost:8080/index.html")
.build();
} else {
final Session argument = arguments.stream()
.filter(ag -> ag.getImlClass() == Session.class)
.map(ag -> (Session) ag)
.findFirst()
.orElseThrow(IllegalArgumentException::new);
if (argument.getId() != null) {
return HttpResponse.builder()
.redirect("http://localhost:8080/index.html")
.build();
}
final String fileName = "static/register.html";
final URL resourceUrl = getClass().getClassLoader().getResource(fileName);
final Path path = new File(resourceUrl.getPath()).toPath();
final String responseBody = new String(Files.readAllBytes(path));
return HttpResponse.builder()
.header("Content-Type", "text/html;charset=utf-8")
.header("Content-Length", String.valueOf(responseBody.getBytes().length))
.body(responseBody)
.build();
}
}
Copy link

Choose a reason for hiding this comment

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

'파일 읽기 - 응답 생성' 로직을 메서드 단위로 각기 분리하여 가독성을 높이는 것도 좋을 듯 합니다.

@the9kim the9kim merged commit 4845f92 into woowacourse:kokodak Sep 7, 2023
1 of 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