-
Notifications
You must be signed in to change notification settings - Fork 309
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단계] 이오(이지우) 미션 제출합니다. #343
Conversation
Kudos, SonarCloud Quality Gate passed! 0 Bugs 0.0% Coverage 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. |
1 similar comment
Kudos, SonarCloud Quality Gate passed! 0 Bugs 0.0% Coverage 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. |
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.
안녕하세요 이오!
미션 고생하셨습니다 👍
(테스트가 없다는 것만 빼면) 훌륭하게 코드를 작성하신 것 같습니다
일단 이오의 의견이 궁금한 부분이 있어서 Request changes로 설정하도록 하겠습니다
리뷰는 뭐 이것저것 제안 드리기는 했는데 별 내용 없어서...적용하고 싶으신 부분만 적용해주시면 좋을 것 같습니다
고려한 부분의 경우 제 생각이 궁금하신?것? 같아서, 그에 대한 제 생각은 밑에다가 추가적으로 적어두도록 하겠습니다!
-
쿠키
저도 헤더나 쿠키나 쿼리 스트링이나 아무튼 Map 구조라는 것에 동의합니다!
통일성을 유지하는 것도 중요한 관점이라고 생각하고요 ㅎㅎ
다만 저는 헤더나 쿠키나 쿼리 스트링을 모두 별도의 래핑 클래스로 감싸주기는 했습니다.
감쌀 경우 가독성은 좋아지더라고요! 시간이 없어서 테스트 코드 작성할 때 후회하기는 했지만요...
예를 들어 헤더같은 경우는 적어도 하나 이상의 데이터가 필요한데 쿠키는 아예 값이 없는 빈 쿠키도 허용하기는 해서 이런 차이점을 좀 명확하게 표현하기 위해서는 감싸는게 더 잘 보이겠다 싶더라고요 ㅎㅎ
꺼내오는 메서드도 이름을 조금 더 명확하게 할 수 있었고...
근데 이건 개인 차이라 아무튼 이오가 선택하신 근거가 충분히 납득이 되어서 좋았습니다? 네 -
Processor
확실히 담당하고 있는 일이 많기는 한 것 같습니다
근데 말씀해주신 대로 Step3에서 개선될 내용이라 굳이 신경쓸 필요가 있을까? 싶기는 하네요...
결론만 말씀드리면 Step2에서는 이오가 해주신 것처럼 메서드 분리만으로 충분하다고 생각합니다!
여기서 클래스로 분리하게 되면 Step3의 내용을 침범하는 것이라고 생각합니다
하면 좋지만 굳이? 느낌...
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class UserService { |
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.
UserService 👍
전 이 생각을 못하고 그냥 InMemoryUserRepository를 가져다 썼는데 좋은 접근 방법인 것 같습니다
final Optional<User> user = InMemoryUserRepository.findByAccount(account); | ||
|
||
if (user.isEmpty()) { | ||
throw new UnauthorizedException(account); | ||
} | ||
if (!user.get().checkPassword(password)) { | ||
throw new UnauthorizedException(account); | ||
} |
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.
해당 코드의 경우
Optional<User>
가 아니라 User
로 타입을 변경하고, null을 허용한다고 가정한다면
지금 로직과 유사하거나, Optional
보다 비용이 덜 들게 될 것 같습니다(Optional
을 생성하는 비용이 없음)
조금 더 Optional
을 활용할 수 있는 코드로 리펙토링 해 보시는건 어떨까요?
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.
refactor(UserService): Optional 사용 방식 변경
Optional을 잘 활용하지 못하고 있었던것 같네요, 감사합니다! 😀
final String password = requestBody.get("password"); | ||
final String email = requestBody.get("email"); | ||
|
||
if (InMemoryUserRepository.findByAccount(account).isPresent()) { |
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.
Optional.ifPresent()
를 활용해보시는건 어떠실까요?
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.
refactor(UserService): Optional 사용 방식 변경
동일하게 반영하였습니다~
if (!user.get().checkPassword(password)) { | ||
throw new UnauthorizedException(account); | ||
} | ||
log.info(user.toString()); |
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.
log.info(user.toString()); | |
log.info("{}", user); |
취향 차이기는 하지만 이런 방법도 있습니다
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.
refactor(all): 코드 리팩토링
code smell 없는 코드 좋네요..👍
return setSessionWithUUID(user.get()); | ||
} | ||
|
||
private String setSessionWithUUID(final User user) { |
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.
해당 메서드를 호출하는 클라이언트인 register()나 logIn() 입장에서 UUID를 기반으로 저장한다는 사실을 알 필요가 있을까요?
이오는 어떻게 생각하시나요?
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.
refactor(UserService): 메소드 네이밍 변경
그렇네요! 미처 놓쳤던 부분이었습니다. 수정했습니다 :)
private String formatHeader(final String h) { | ||
return h + ": " + headers.get(h); | ||
} |
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.
getResponse()
에서만 호출되는 private method
로 보이는데, 정렬을 통일해주시면 좋을 것 같습니다!
다른 코드를 보니 호출되는 메서드 밑에 위치해주시는 식으로 정렬해주신 것 같더라고요 ㅎㅎ
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.
추가적으로 h
가 아마도 header
의 key
인 것 같은데, 조금 더 명확한 네이밍을 사용해주시는 건 어떨까 건의드려봅니다!
input.append(s); | ||
input.append(LINE_SEPARATOR); |
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.
input.append(s); | |
input.append(LINE_SEPARATOR); | |
input.append(s) | |
.append(LINE_SEPARATOR); |
요런 식으로 체이닝도 가능합니다!
try { | ||
bufferedReader.read(buffer, 0, contentLength); | ||
} catch (final IOException e) { | ||
throw new RuntimeException(e); |
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.
Unchekced Exception
을 던져주는 것을 의도하신걸까요?
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.
refactor(Http11Processor): 예외 처리 방식 변경
이 부분도 위의 메소드와 동일하게 로그를 남기도록 변경하였습니다!
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 Http11Response response = new Http11Response(OK, resource); | ||
checkContentType(request, response); | ||
return response; | ||
|
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 void checkContentType(final Http11Request request, final Http11Response response) { | ||
final String accept = request.getHeader("Accept"); | ||
if (accept != null && accept.contains("css")) { | ||
response.addHeader("Content-Type", "text/css;charset=utf-8 "); |
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.
Content-Type
도 Status
나 Method
처럼 enum
으로 관리하는 방법은 어떠실까요?
Content-Type
을 찾는 로직까지 하나로 캡슐화할 수 있을 것 같습니다!
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.
Header를 enum으로 구현하였습니다. 구현 과정에서 ResponseHeader와 RequestHeader를 합쳐야하나 고민을 많이 했지만 각각 사용되는 헤더의 종류가 달라 구분을 주는 것이 명확하다고 생각하여 따로 두었습니다. 두 enum이 공통된 부분이 많아 상속으로 구현하고 싶었는데 enum은 상속을 적용할 수 없어 아쉽네요..🫠
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.
저도 헤더의 종류도 다르고, 그로 인해 동작도 변경되니(대표적으로 ResponseHeader의 Location) 이오의 판단에 동의합니다!
상수 클래스가 되었든 enum이 되었든 상속과 같은 공통 처리는 저도 딱히 떠오르지는 않네요...그나마 상수 클래스 하나 만들어서 모든 헤더를 명시하고 공통 헤더가 아닌 헤더는 접두사로 REQUEST나 RESPONSE로 구분하는 것 정도?
지저분해서 별로일 것 같기는 하네요..
Kudos, SonarCloud Quality Gate passed! 0 Bugs 0.0% Coverage 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. |
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.
고생하셨습니다!
테스트 코드도 추가되었고, 리펙토링한 내용도 충분히 좋게 작성해주신 것 같아서 소소한 리뷰 몇 가지 남기고 approve를 하도록 하겠습니다
이오가 진행하실 3단계가 기대되네요 ㅎㅎ
화이팅입니다!
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class SessionManager { |
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.
답변 감사합니다! stateless라고 판단하셔서 이렇게 선택하셨군요
다른 분들도 동일하게 작업하신 것 같더라고요 ㅎㅎ
assertThat(socket.output()).contains("HTTP/1.1 302 Found"); | ||
assertThat(socket.output()).contains("JSESSIONID"); | ||
assertThat(socket.output()).contains("Location: /index.html"); |
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.
import org.apache.coyote.http11.Http11Processor; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import support.StubSocket; | ||
|
||
class Http11ProcessorTest { |
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 void checkContentType(final Http11Request request, final Http11Response response) { | ||
final String accept = request.getHeader("Accept"); | ||
if (accept != null && accept.contains("css")) { | ||
response.addHeader("Content-Type", "text/css;charset=utf-8 "); |
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.
저도 헤더의 종류도 다르고, 그로 인해 동작도 변경되니(대표적으로 ResponseHeader의 Location) 이오의 판단에 동의합니다!
상수 클래스가 되었든 enum이 되었든 상속과 같은 공통 처리는 저도 딱히 떠오르지는 않네요...그나마 상수 클래스 하나 만들어서 모든 헤더를 명시하고 공통 헤더가 아닌 헤더는 접두사로 REQUEST나 RESPONSE로 구분하는 것 정도?
지저분해서 별로일 것 같기는 하네요..
try { | ||
bufferedReader.read(buffer, 0, contentLength); | ||
} catch (final IOException e) { | ||
throw new RuntimeException(e); |
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.
확인했습니다!
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class Http11Request { |
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.
아 그래서 http11
패키지에 두셨군요 제가 의도를 파악하지 못한 것 같습니다 ㅎㅎ;
그런 이유라면 근거로 충분할 것 같습니다! 다만 HTTP 버전
도 여러개고, 톰캣에서는 Http11NioProcessor
, Http11Nio2Processor
등등과 같이 다양하게 제공하는 만큼 Request
나 Response
도 그만큼 더 늘어날 것 같기는 하지만...거기까지는 필요 없을 것 같아서 이전의 방식도 좋은 것 같습니다!
물론 3단계에서 GET / HTTP/1.1
을 처리할 수 있는 객체인 RequestLine
을 만들라는 내용이 있어서 변경하신 내용도...괜찮은 것 같기도 하고 그렇습니다
안녕하세요 지토 🙂
이번 리뷰 잘부탁드립니다.
기능 구현은 완료하였지만 전체적으로 부족한 부분이 많네요.🥲
대부분의 메소드가 private라 통합 테스트만 작성하였는데, 부족한 테스트는 리팩토링 과정에서 구현하도록 하겠습니다.
고려한 부분
미션에서
Cookie
를 별도의 클래스를 분리해 구현하였지만, 저의 경우Header
와QueryString
을Map<String, String>
으로 두고 있었기 때문에 이와 비슷한 구조&역할인Cookie
또한Map
으로 구현하였습니다. 분리에서 오는 장점보다 관리해야할 클래스를 늘린다는 단점이 더 크게 느껴졌고, 통일성을 유지하는 것이 더 낫다고 생각해서요.현재
Processor
가 하는 일이 과도하게 많다는 생각이 들어 고민중에 있습니다. 여러 Handler 메소드를 사용하여 리팩토링 하였지만 아직 부족한 것 같아서요. (다음 단계의 미션으로 Controller를 구현하면 좀 나아질 것으로 예상되긴 합니다.) 지토는 현재 단계의Processor
가 충분하다고 느끼시나요? 혹은 더 간결해 질 수 있도록 역할을 분리해야한다고 생각하시나요?