-
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 구현하기] 테드(김규태) 미션 제출합니다. #572
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.
안녕하세요 테드! 😁 오랜만이네요! 잘 지내고 있죠? ㅎㅎ
대부분 스프링의 구조를 따라서 확장가능하고 가독성 있게 코드를 너무 잘 작성하셨네요! 많이 배워갑니다! 😊
2단계까지 빠르게 구현하셨네요! 궁금한 부분은 질문)
, 수정이 필요하다고 생각하는 부분은 의견)
키워드로 남겼어요! 확인 부탁드릴게요!!
@@ -6,4 +6,5 @@ server: | |||
accept-count: 1 | |||
max-connections: 1 | |||
threads: | |||
min-spare: 2 |
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.
min-spare
를 설정한 이유와 2인 근거가 궁금해요!
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 HttpRequest(BufferedReader reader) throws IOException { | ||
this.requestLine = extractRequestLine(reader); | ||
this.headers = extractHeaders(reader); | ||
this.requestBody = extractRequestBody(reader); | ||
|
||
log.info("request header: {}", headers); | ||
} |
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 를 파라미터로 받은 이유가 궁금해요! 외부에서 최대한 처리하고 모든 값을 읽은 다음에 파라미터로 넘겨주는 방식 대신 이러한 방식을 선택하신 이유가 무엇인가요?😊
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.
우선은 Http11Processor 에서 분리를 위한 목적으로만 했었는데요, HttpRequest에서 값을 분리하고, 예외 처리하는 책임이 없는 것이 맞을 것 같아 HttpRequestParser를 둬서 요청을 분리하는 책임을 나누는 것으로 변경했습니다.
public String extractRequestBody(BufferedReader reader) throws IOException { | ||
try { | ||
int contentLength = Integer.parseInt(headers.get("Content-Length")); | ||
char[] buffer = new char[contentLength]; | ||
reader.read(buffer, 0, contentLength); | ||
return new String(buffer); | ||
|
||
} catch (NumberFormatException e) { | ||
return null; | ||
} | ||
} |
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.
의견) containsKey
를 활용하는 방법도 있겠네요! 물론 "Content-Length" 값의 대한 검증은 필요하겠지만요 😊
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.
이 부분은 어차피 body가 없으면 null을 넣기 위함이라 불필요한 조건이 추가되는 것 같아 NumberFormatException만 체크하면 될 것 같습니다!
public class RequestMapping { | ||
|
||
private final Map<String, Controller> controllerMapping = new HashMap<>(); | ||
|
||
public RequestMapping() { | ||
controllerMapping.put("/login", new LoginController()); | ||
controllerMapping.put("/register", new RegisterController()); | ||
} | ||
|
||
public Controller getController(HttpRequest request) { | ||
String path = request.getRequestLine().getPath(); | ||
Controller controller = controllerMapping.get(path); | ||
if (controller == null) { | ||
return new ViewController(); | ||
} | ||
|
||
return controller; | ||
} | ||
} |
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 responseDefaultPage(HttpResponse response) { | ||
response.setStatus200(); | ||
response.setContentTypeHtml(); | ||
response.setResponseBody("Hello world!"); | ||
} |
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-2 CSS 지원하기" 요구사항의 gif 와 같이 /
요청에 대해 "대시보드" 페이지가 나오도록 바꿔주는건 어떨까요?😊
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가 통과하지 못해서 그냥 두었었어요..
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.request.HttpRequest; | ||
import org.apache.coyote.http11.response.HttpResponse; | ||
|
||
public class LoginController extends AbstractController { |
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.
비즈니스 로직을 처리하는 클래스가 아파치에 있는 것이 이상하네요.. techcourse 패키지로 구조를 변경하였습니다!
if (requestBody == null) { | ||
throw new IllegalArgumentException("RequestBody is missing in the request"); | ||
} |
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.
의견) IllegalArgumentException
가 발생하면 해당 예외를 처리해주는 catch
구문이 없어서 확인하기 어려울 것 같아요!
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.
예외가 발생할 경우 400을 응답하도록 catch 구문을 추가했습니다~
// public HttpCookie(String cookie) { | ||
// setCookies(cookie); | ||
// } | ||
// | ||
// private void setCookies(String cookie) { | ||
// for (String cookiesPart : cookie.split(" ")) { | ||
// String[] keyValue = cookiesPart.split("="); | ||
// cookies.put(keyValue[0], keyValue[1]); | ||
// } | ||
// } |
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 cookie = request.getHeader("Cookie"); | ||
Map<String, String> cookies = new HashMap<>(); | ||
for (String cookieParts : cookie.split(" ")) { |
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.
의견) 만약 "Cookie" 헤더가 없다면 NPE가 발생하지 않을까요??🧐
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.
꼼꼼하네요 👍 쿠키가 없으면 null을 반환하도록(로그인 페이지를 응답할 수 있게) 변경했습니다~
} | ||
|
||
String jsessionId = cookies.get("JSESSIONID"); | ||
SessionManager sessionManager = new 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.
질문) SessionManager 의 메서드들을 정적 메서드로 만들어서 호출하는 방식 대신 매번 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.
세션 데이터가 하나로 관리되고, 매번 인스턴스를 생성할 필요가 없으므로 static 메서드로 활용하도록 변경했습니다!
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.
안녕하세요 테드! 타칸입니다!
전반적으로 많이 리팩터링 되었네요!
가독성 있게 코드를 잘 작성하시고 구조가 실제 스프링과 유사한 구조로 구현하신 것 같아 많이 배웠습니다!
몇가지 검증에 대한 커멘트 남겨두었는데 다음 PR때 반영해주시면 될 것 같아서 이만 merge 하겠습니다!
추가로 다음 PR에는 테스트 코드도 같이 추가해주시면 좋을 것 같습니다! 수고하셨습니다!😊
String[] requestLineParts = firstLine.split(" "); | ||
method = requestLineParts[0]; | ||
String uri = requestLineParts[1]; | ||
protocol = requestLineParts[2]; |
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.
의견) 배열에 인덱스로 접근하는 로직은 NPE가 발생하지 않도록 검증하는 로직이 필요할 것 같아요! requestLineParts 의 length를 검증하는 로직을 추가해주면 어떨까요??
String[] headerLineParts = readLine.split(": "); | ||
headers.put(headerLineParts[0], headerLineParts[1]); |
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 (controller == null) { | ||
return new ViewController(); | ||
} | ||
|
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.
제안) ViewController도 싱글톤으로 구현하는게 좋을 것 같아요!😊
|
||
public class SessionManager { | ||
|
||
private static final Map<String, Session> SESSIONS = new HashMap<>(); |
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.
의견) 지금은 동시성을 처리할 필요는 없지만 추후 동시성을 고려한다면 ConcurrentHashMap으로 바꿔주면 좋을 것 같네요!
private Session extractSession(HttpRequest request) { | ||
String cookie = request.getCookie(); | ||
if (cookie == null) { | ||
Map<String, String> cookies = new HashMap<>(); | ||
for (String cookieParts : cookie.split(" ")) { | ||
String[] keyAndValue = cookieParts.split("="); | ||
cookies.put(keyAndValue[0], keyAndValue[1]); | ||
} | ||
|
||
String jsessionId = cookies.get("JSESSIONID"); | ||
return SessionManager.findSession(jsessionId); | ||
} | ||
|
||
return null; | ||
} |
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.
의견) 해당 메서드는 LoginController 보다는 SesseionManager의 책임이라고 느껴지는 것 같은데 테드의 생각이 궁금해요!🧐
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.
쿠키에서 JSessionId를 추출하는 책임을 쿠키가 가지고 있는 것이 맞는 것 같아서 조정했습니다
Map<String, String> requestFormData = request.getFormData(); | ||
String account = requestFormData.get("account"); | ||
String password = requestFormData.get("password"); | ||
String email = requestFormData.get("email"); | ||
|
||
User user = new User(account, password, email); |
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.
의견) reqeustForm 에 account 가 없거나 email 이 없거나 등에 대한 검증은 추가해주면 좋을 것 같아요!
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.
반영 완료 👍
response.setStatus400(); | ||
response.setResponseBody(e.getMessage()); | ||
log.info("Bad Request: {}", e.getMessage()); | ||
|
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.
3단계 하면서 공백도 다시 확인했습니다 👍
private final String id; | ||
private final Map<String, Object> values = new HashMap<>(); |
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-2 단계 구현 완료해서 리뷰 요청드립니다.
두 번째 학습(cache)은 시간이 부족하여 진행하지 못했습니다.
구현하면서 여러 책임이 합쳐지게 되어 나름대로 클래스를 분리하며 진행해봤습니다.
그래도 아직 개선할 부분이 많이 보이긴 하는데요. 편하게 리뷰 부탁드립니다!