-
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단계] 켬미(김경미) 미션 제출합니다. #555
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.
1. [File, I/O Stream](study/src/test/java/study) | ||
2. [HTTP Cache](study/src/test/java/cache) | ||
3. [Thread](study/src/test/java/thread) | ||
|
||
## 기능 요구 사항 |
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.
레벨 2에서 놓쳤다가 리뷰어에게 잊지말라는 조언을 듣고 나서 기능요구사항을 작게라도 적는 편입니다 ~! 좋은 말 고마워용 !
WebContentInterceptor interceptor = new WebContentInterceptor(); | ||
interceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**"); | ||
registry.addInterceptor(interceptor); |
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.
제 리뷰이 폴라도 폭포와 동일한 방식으로 했더라고요 ! 해당 방식도 너무 좋은 것 같고 장점이 명확한 것 같아요 !
그래서 제가 간단하게 찾아본 두 방식의 장단점입니다. ! (ChatGPT 많이 이용한 정리)
1. Custom HandlerInterceptor 방식
장점:
세밀한 제어 가능: HandlerInterceptor를 직접 구현하기 때문에 요청과 응답에 대해 더 구체적인 로직을 구현할 수 있음. 예를 들어 특정 경로에 따라 다르게 처리하거나, 조건에 따라 다른 헤더를 추가할 수 있음.
응답 헤더가 이미 설정된 경우 중복 방지: response.containsHeader를 사용해, 이미 Cache-Control 헤더가 있는지 확인한 후 처리하기 때문에 불필요한 덮어쓰기를 방지할 수 있음.
단점:
복잡한 관리: 모든 인터셉터 로직을 직접 작성해야 하기 때문에 코드가 길어질 수 있으며, 다른 설정과 결합하려면 더 많은 코드 작성이 필요.
일관성 유지 어려움: 여러 요청 유형에 대해 동일한 캐시 로직을 유지하려면 코드가 더 복잡해질 수 있음.
2. WebContentInterceptor 방식
장점:
설정의 간결함: Spring에서 제공하는 WebContentInterceptor와 CacheControl API를 사용하여 캐시 전략을 간단하게 설정할 수 있음. 여러 경로에 대한 캐시 제어를 손쉽게 매핑 가능.
일관된 설정: Spring의 설정 기반이기 때문에, 전체 애플리케이션에 걸쳐 동일한 캐시 제어 설정을 쉽게 적용 가능.
유지보수 용이: Spring의 설정 방식을 따르므로 다른 개발자들도 직관적으로 이해할 수 있음. 또한 캐시 전략을 변경할 때 코드가 간결하게 유지됨.
단점:
세밀한 제어 부족: 경로를 기반으로 캐시 전략을 적용하지만, 개별 요청에 대해 세밀하게 제어하기는 어려움. 특정 조건이나 상황에 따라 캐시 설정을 다르게 적용하고 싶다면, 추가적인 커스텀 로직을 작성해야 함.
요약
따라서, 애플리케이션 요구사항에 따라 선택하면 되지만, 간단한 설정을 원하는 경우에는 WebContentInterceptor 방식이 더 적합, 특정 요구사항이 있다면 Custom HandlerInterceptor 방식이 적합.
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? | ||
*/ | ||
final String actual = ""; | ||
|
||
final String actual = new String(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를 사용한 것도 좋네요~! 공유해줘서 고마워요 ! 폭포 덕분에 read 메서드에 대해 더 잘 알게 되었어요 👍🏻
String s = bufferedReader.readLine(); | ||
while (s != null && !s.isBlank()) { | ||
actual.append(s).append("\r\n"); | ||
s = bufferedReader.readLine(); | ||
} |
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.
폭포가 한 번 더 정리해줘서 저도 다시 공부하네요 👍🏻
String s = bufferedReader.readLine(); | ||
while (s != null && !s.isBlank()) { | ||
actual.append(s).append("\r\n"); | ||
s = bufferedReader.readLine(); | ||
} |
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.
무조건적으로 한 줄에 넣는 것은 안좋을 수 있지만 해당 방법은 가독성이 나쁘지 않으니 좋운 것 같아요! 반영하겠습니다 👍🏻
if (request.getQueryParam().size() < 2) { | ||
return new ResponseContent(HttpStatus.BAD_REQUEST, accept, FileReader.loadFileContent(BAD_REQUEST_PAGE)); | ||
} | ||
if (queryParams.get(ACCOUNT) == null || queryParams.get(PASSWORD) == null) { | ||
return new ResponseContent(HttpStatus.BAD_REQUEST, accept, FileReader.loadFileContent(BAD_REQUEST_PAGE)); | ||
} |
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.
좋은 것 같아요~ 대신 if문이 너무 길어지니 따로 메서드 분리하였습니다 😁
private Optional<User> authenticateUser(String account, String password) { | ||
Optional<User> user = InMemoryUserRepository.findByAccount(account); | ||
if (user.isPresent() && user.get().checkPassword(password)) { | ||
log.info("인증된 사용자: {}", user.get()); | ||
} | ||
return 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.
폭포의 의견대로 존재하는 아이디에 잘못된 비밀번호를 입력하면 통과되네요 ..!
해당부분은 체크하지 못했습니다. 해당 메서드에서 비밀번호까지 체크된 경우 user을 넘기고 아닌 경우 empty를 넘기도록 변경하겠습니다 ~!
private static ResponseContent generateResponseForPostUrl(Request headers) { | ||
String url = headers.getUrl(); | ||
String accept = headers.getFileType(); | ||
if (REGISTER_PATH.equals(url)) { | ||
return handleRegistration(headers.getBody(), accept); | ||
} | ||
return new ResponseContent(HttpStatus.BAD_REQUEST, accept, FileReader.loadFileContent(NOT_FOUND_PAGE)); | ||
} | ||
|
||
private static ResponseContent handleRegistration(Map<String, String> bodyParams, String accept) { |
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 handleRequestMethod(Request request) { | ||
String httpMethod = request.getHttpMethod(); | ||
if (httpMethod.equals("GET")) { | ||
handleGetRequest(request); | ||
} else if (httpMethod.equals("POST")) { | ||
handlePostRequest(request); | ||
} else { | ||
log.warn("지원되지 않는 HTTP 메서드: {}", httpMethod); | ||
} | ||
} | ||
|
||
final var response = String.join("\r\n", | ||
"HTTP/1.1 200 OK ", | ||
"Content-Type: text/html;charset=utf-8 ", | ||
"Content-Length: " + responseBody.getBytes().length + " ", | ||
"", | ||
responseBody); | ||
private void handleGetRequest(Request headers) { | ||
try (final OutputStream outputStream = connection.getOutputStream()) { | ||
String response = generateResponseForUrl(headers).responseToString(); | ||
outputStream.write(response.getBytes()); | ||
outputStream.flush(); | ||
} catch (IOException | UncheckedServletException e) { | ||
log.error("GET 요청 처리 중 오류 발생: {}", e.getMessage(), 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.
너무 좋은 방식인 것 같아요 저도 두번째 방법이 마음에 듭니다 ~! 추후 3단계에서 더 리펙토링 하도록 하겠습니다 !
@@ -0,0 +1,49 @@ | |||
package org.apache.catalina.response; | |||
|
|||
public class ResponseContent { |
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.
제가 제안드린 사항들 빠르게 반영해주셨네요!
저도 켬미 코드를 리뷰하면서, 켬미가 정리해주신 내용들 보면서 많이 배워갈 수 있어서 좋았습니다. 😄
리뷰 방식은 알려주신 방식이 있는지 처음 알게 되었고, 다음부터 적용해보도록 하겠습니다. 감사합니다.
다른 리뷰들은 전부 해결되었고, 아래의 리뷰 하나만 생각해봐도 좋을 것 같습니다.
#555 (comment)
그리고 패스워드 문제는 해결되었지만, 앱을 실행해보니 아직 쿠키 및 세션 관련 문제가 완전히 해결되지 않은 것으로 보입니다. (해당 시나리오는 LMS에 있는 실행 결과 시나리오를 참고했습니다.)
localhost:8080/login
접속- 회원가입 하러 가기 클릭
- 아이디 이메일주소 비밀번호 입력 후 가입하기
- 화면은
index.html
화면인데, url은localhost:8080/register
로 표시됨 localhost:8080/login
으로 url 직접 입력으로 다시 접근- 만든 아이디 비밀번호 정확히 입력해도 401. 이때 url을 보면 쿼리파라미터로 접근한 모습이 보여짐
기능 정상 작동을 위해, 이 부분들 한번 더 확인부탁드립니다!
폭포 리뷰 읽고 리다이렉션이 html 만 바뀌고 url은 바뀌지 않았다는 걸 알았네요..! ㅎㅎ |
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.
오류가 난 부분이나 변경사항에 대한 수정 속도가 정말 빠르다고 느껴져서 대단하신 것 같아요.
이제 정말_정말_진짜_마지막 최종_최종 수정사항 하나 남았습니다.
- 로그인된 상태에서 /login 페이지에 HTTP GET method로 접근하면 이미 로그인한 상태니 index.html 페이지로 리다이렉트 처리한다.
이 요구사항이 아직 지켜지지 않은 것 같습니다.
추가로 기능이 잘 작동하는지를 확인하기 위해, 다음 3~4단계에서는 테스트 코드를 작성해서 실수를 줄이는 것도 좋을 것 같아요! 그리고 테스트 코드 뿐만 아니라 웹에서 실제로 기능들이 잘 작동하는지 확인하는 것도 한 스푼 추가되면 더더욱 좋을 것 같습니다. 👍
@@ -9,11 +9,11 @@ public class ResponseContent { | |||
|
|||
private final HttpStatus httpStatus; | |||
private final String body; | |||
private final Map<String, String> headers = new HashMap<>(); | |||
private final Map<String, String> headers = new LinkedHashMap<>(); |
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.
LinkedHashMap
을 사용하면 넣은 순서대로 순서를 유지할 수 있네요! 딱 제가 필요한 자료구조였습니다. 덕분에 알게 되었습니다. 감사합니다! 😄
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.
특징 | HashMap | LinkedHashMap |
---|---|---|
순서 보장 | 순서를 보장하지 않음 | 삽입된 순서 또는 접근 순서를 보장 |
내부 구현 | 해시 테이블 기반으로 구성 | 해시 테이블 + 이중 연결 리스트로 구성 |
성능 | O(1) 시간 복잡도 (평균적으로 빠름) |
O(1) 시간 복잡도 (LinkedList로 인해 약간 느림) |
메모리 사용 | 메모리 사용량이 적음 | 이중 연결 리스트로 인해 메모리 사용량이 더 많음 |
접근 순서 유지 | 지원하지 않음 | 옵션으로 접근 순서 유지 가능 (accessOrder=true 시) |
사용 예 | 순서가 중요하지 않을 때 주로 사용 | 삽입 순서나 접근 순서가 중요한 경우에 사용 |
순회 방식 | 무작위 순서로 키와 값을 순회 | 삽입된 순서대로 키와 값을 순회 |
정말_정말_진짜_마지막 최종_최종 수정하였습니다 😁 테스트 코드 리펙터링 후에 넣으려고 했으나 매번 오류 찾기 힘들어서 최소한의 테스트 코드만 구현해두었습니다. 폭포가 말씀해주신 오류의 이유는 request에서 cookie를 찾을 때 Key가 Set-Cookie인 값이 있는지 확인해서 문제가 되었습니다..! 해당 코드를 Cookie로 변경하니 해결되었어요 ! |
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.
모든 클래스의 테스트를 엄청 상세하게 구현하셨네요! 저는 Http11ProcessorTest
정도만 작성해서 httpRequest
에 대해 의도한 httpResponse
가 오는 정도로만 확인했는데, 제가 반성해야 할 것 같습니다. 😓
그래도 Http11ProcessTest
를 TDD 하는 것만으로도, 정확한 응답이 오는지 테스트로 체크가 가능해서 1~2단계를 구현할 때 좀더 원활했던 것 같습니다. 켬미가 지금 이렇게 테스트를 자세하게 만든 만큼, 3단계 리팩토링을 하면서 큰 도움이 될 거 같아요!!
모든 기능 정상 작동하는지 확인 완료했습니다. 테스트 코드는 3단계때 리팩토링 한다고 하셔서 따로 자세하게 확인하지 않았습니다. 정말 수고 많으셨습니다! 👍👍👍👍👍👍👍👍
안녕하세요 폭포! 😁 이렇게 리뷰이 리뷰어로 만나니 반갑네요 !
이번 PR에서는 1-2단계를 구현하였습니다.
기능은 어찌저찌 동작하지만 코드는 ... 만족스럽지 못하네요 🥲
그러니 마구마구 리뷰 남겨주세요~!