-
Notifications
You must be signed in to change notification settings - Fork 311
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단계 - Tomcat 구현하기] 로키(정해성) 미션 제출합니다. #564
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 로키!! 초롱입니다.
리뷰를 하러 왔는데 의도치 않게 공부만 잔뜩 하고 가버리게 됐네요..
각 객체별로 역할에 맞게 구분이 잘 되어 있는 것 같고, 전반적으로 코드가 너무 깔끔해서 실제 사용되는 코드인 줄 알았습니다.
따끔한 리뷰를 해드리고 싶었지만 역량 부족으로 간단한 리뷰밖에 남기지 못했네요.
한 번 읽어보시고 반영할지는 로키가 정해주시면 될 것 같아요!!
한 가지 궁금한 점이 있는데, 코드가 너무 잘 짜여있는 것 같아서 어떤 식으로 공부를 하신 건지 너무 궁금합니다.
LMS에 있는 정보만으로는 이정도로 세세하게 각 도메인들의 역할을 적절하게 나누기 힘들었을 것 같은데 이전에 해본 적이 있는 건지, 아니라면 어떤 방식으로 공부하신 건지 너무나도 궁금합니다
@@ -96,6 +109,7 @@ class OutputStream_학습_테스트 { | |||
* try-with-resources를 사용한다. | |||
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. | |||
*/ | |||
outputStream.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석에 안내된대로 try-with-resources를 사용하는 방법도 생각해보시면 좋을 것 같습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇네요 주석에 애초에 try-with-resources 를 사용하라고 명시되어 있었군요! 수정하겠습니다!
byte[] readBytes = new byte[4]; | ||
|
||
int i = 0; | ||
while (true) { | ||
int read = inputStream.read(); | ||
if (read == -1) { | ||
break; | ||
} | ||
readBytes[i] = (byte) read; | ||
i++; | ||
} | ||
final String actual = new String(readBytes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 inputStream.readAllBytes()
를 사용했는데 코드가 굉장히 단순해졌습니다!! 강추드립니다ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 코드는 read
메서드가 -1
을 리턴하는걸 눈으로 확인해보고 싶어서 의도적으로 반복문으로 작성하게 되었습니다!
처음에 "왜 read
가 byte가 아닌 int를 리턴하는거지?" 라는 궁금증에서 코드의 구현을 찾아보게 되었습니다.
The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned.
기본적으로 read는 스트림에서 바이트를 읽어 0 ~ 255의 값을 리턴해주고 더이상 읽을게 없을때는 -1을 리턴해주도록 구현되었습니다. 이때 -1 ~ 255를 담기에는 byte는 부족하기 때문에 read가 byte가 아닌 int를 리턴하고 불편하게 형변환을 해줘야 하는것을 알게되었습니다!
그래서 이 코드는 유지하고 싶습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 정보 감사합니다!! 로키의 학구열이 정말 대단하네요 👍
학습용 코드인만큼 로키가 원하는 대로 두어도 좋을 것 같습니다!
final var responseBody = "Hello world!"; | ||
try (final var inputStreamReader = new InputStreamReader(connection.getInputStream()); | ||
final var outputStreamWriter = new OutputStreamWriter(connection.getOutputStream())) { | ||
InputView inputView = new InputView(new BufferedReader(inputStreamReader)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BufferedReader
도 close를 시켜줘야 하는 클래스인데, 자원이 낭비되지 않도록 try-with-resource 안에 포함시켜보는 것은 어떤가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BufferedReader와 InputStreamReader 모두 Closeable 인터페이스를 구현하고 있기 때문에, InputStreamReader가 닫히면 그와 연결된 BufferedReader도 자동으로 닫히게 된다고 들어 명시적으로 try-with-resource에 추가하지는 않았습니다. 😀
초롱이 보기에는 명시적으로 추가해주는게 좋아보이실까요!!?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와 연결된 것까지 자동으로 닫히는 것은 처음 알았네요!!
흠.. 어려운 문제네요. 저라면 명시적으로 추가해줄 것 같기는 합니다. 저처럼 이런 사실을 모르는 사람들도 있을 것 같고, try-with-resource에 추가하지 못 할 만한 코드로는 보이지 않아서요!!
하지만 반대로 성능적인 문제가 있는 것도 아니고, 반드시 닫아야 하는 자원들만 try-with-resource에 추가한다는 관점으로 본다면 오히려 추가하는 것이 어색하게 느껴질 수도 있을 것 같네요.
이 부분은 로키의 선택에 맡기겠습니다!!
@Test | ||
@DisplayName("로그인을 한다.") | ||
void login() { | ||
UserService userService = new UserService(); | ||
|
||
UserResponse userResponse = userService.login("gugu", "password"); | ||
|
||
assertThat(userResponse.email()).isEqualTo("[email protected]"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UserServiceTest
에서 컴파일 오류가 발생합니다!! UserResponse
가 없어진 클래스인 것 같네요.
확인 부탁드립니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이부분은 제가 잘 확인하고 제출했어야하는데 죄송해요!!
🙇♂️🙇♂️🙇♂️🙇♂️
private Map<String, String> parseHeaders(List<String> headerLines) { | ||
return headerLines.stream() | ||
.filter(header -> header.contains(":")) | ||
.collect(Collectors.toMap( | ||
header -> header.substring(0, header.indexOf(":")).trim(), | ||
header -> header.substring(header.indexOf(":") + 1).trim()) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 코드는 :
를 포함하는 것들만 필터링하여 파싱을 진행하는 것으로 보이는데 만약 :
가 없는 문자열이 포함된다면 어떻게 되는지 궁금합니다.
이외에도 거의 모든 파싱 과정에서 필터링을 진행하셨던데 예외처리 대신 이렇게 구현하신 이유가 있을까요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 제가 고민했었던 부분인데 정확히 질문주셔서 기쁘네요!
처음 구현을 할때는 실제 톰캣의 동작과 최대한 비슷하게 구현하고 싶은 생각이 들어 실제 톰캣에 여러가지 이상한 요청을 보내보며 어떤식으로 구현할지 고민해봤습니다.
그러나 그렇게 구현하기는 쉽지 않다는것을 느꼈고 그럴 필요도 없다고 생각되었습니다. 왜냐하면 우리 과제의 목표는 http header를 파싱하는 연습을 하는게 아니라 톰켓의 동작 방식을 학습하는것이니까요!
당연히 Header가 형식에 맞춰서 :
으로 잘 구분되어서 오는 경우가 당연히 베스트라고 생각합니다! 만약 클라이언트에서 Http 규약에 맞지 않게 보냈을때 예상한 결과가 나오지 않는것은 당연한일입니다.
그러나 경험상 제가 이용한 웹서비스에서는 제가 형식을 잘못준 요청에 대해서 예외를 던지기 보다는 예상하지 않은 결과로 자연스럽게 처리되는 것 같아 가장 쉬우면서 자연스럽게 처리되는식으로 구현했습니다.
아래는 톰켓이 내장된 스프링 서버를 띄우고 curl로 요청을 보낸 경우입니다.
curl -H "Header-Name" http://localhost:8080
헤더에 :
없이 요청을 날려도 예외를 던지기 보다 정상적인 요청을 보냅니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>방탈출 예약 페이지</title>
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만약 클라이언트에서 Http 규약에 맞지 않게 보냈을때 예상한 결과가 나오지 않는것은 당연한일입니다.
이 부분이 굉장히 공감이 많이 가는 말이네요. 로키의 말대로 오히려 예외처리 되는 것이 어색한 것 같아요. 저도 잘못된 요청에 대한 처리를 어떻게 해야 할 지 고민이 많았는데 많은 도움이 됐습니다!!
로키의 고민이 많이 담긴 코드인 것 같아서 좋네요 😁
} | ||
|
||
private List<String> readHttpHeaders(InputView inputView) throws IOException { | ||
ArrayList<String> headerLines = new ArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소한 거긴 한데 List
가 아닌 ArrayList
로 선언하신 이유가 있나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 그렇네요! List로 선언해도 될것 같아요! 👍👍
public AbstractController() { | ||
methods.put(HttpMethod.GET, this::doGet); | ||
methods.put(HttpMethod.POST, this::doPost); | ||
methods.put(HttpMethod.HEAD, this::doHead); | ||
methods.put(HttpMethod.OPTIONS, this::doOptions); | ||
methods.put(HttpMethod.PUT, this::doPut); | ||
methods.put(HttpMethod.DELETE, this::doDelete); | ||
methods.put(HttpMethod.TRACE, this::doTrace); | ||
methods.put(HttpMethod.CONNECT, this::doConnect); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍👍👍👍👍👍
public boolean canHandle(HttpRequest request) { | ||
return controllers.containsKey(request.getPath()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 메서드는 사용되지 않는 것으로 보이는데 추후에 활용될 가능성을 염두에 두고 일부러 남겨두신 건가요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇네요! 당장 불필요한 메서드인데 삭제하겠습니다!
|
||
public class RequestURI { | ||
|
||
private static final String QUESTION_MARK = "?"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다른 상수들은 해당 문자가 쓰인 의미로 네이밍을 진행했는데 해당 상수만 문자의 이름으로 네이밍을 한 것 같네요. 그렇게 하신 이유가 있을까요?
없으시다면 QUERY_DELIMITER
와 같이 쓰인 의미로 이름을 변경해보면 어떤가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QUERY_DELIMITER 이름 좋네요!
반영하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다 로키!! 피드백 반영도 굉장히 빠르고 좋네요.
그리고 코드 하나하나에 많은 고민이 담겨있는 것 같아서 너무 좋았어요.
특히 톰캣에 이런저런 요청을 직접 보내보면서 어떻게 구현되어있는지 확인하는 점도 정말 놀라웠는데, 그 결과를 그대로 따르지 않고 여기에 자신의 생각을 입혀서 재해석하는 과정이 굉장히 인상적이었습니다.
피드백 반영 및 테스트 코드 정상 동작 모두 확인했으니 이만 merge 하도록 하겠습니다!!
다음 단계도 화이팅입니다.
초롱 안녕하세요! 🙇♂️
어쩌다 보니 조금씩 리펙토링 하며 코드를 짜서 3단계를 진행해버린것 같아요. 🔨
코드량이 좀 많습니다. 😂
리뷰 잘 부탁드립니다! 🤗