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

[톰캣 구현하기 - 3, 4단계] 조이(김성연) 미션 제출합니다. #473

Merged
merged 19 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
aa3dd74
refactor: HttpRequestUri가 path와 queryString을 가지도록 수정
yeonkkk Sep 6, 2023
1074eca
refactor: 개행 및 final 컨벤션 통일
yeonkkk Sep 6, 2023
85c96fa
refactor: toString 메서드명 변경
yeonkkk Sep 6, 2023
4ffb6f7
feat: Cookie & Session 구현
yeonkkk Sep 8, 2023
0bb79b4
refactor: 역할 분리를 위한 Controller 및 RequestMapping 추가
yeonkkk Sep 10, 2023
59e023f
chore: 패키지 변경
yeonkkk Sep 10, 2023
3bb9396
refactor: Thread Pool 적용
yeonkkk Sep 11, 2023
9674e73
refactor: 동시성 컬렉션 적용
yeonkkk Sep 11, 2023
0922f5d
refactor: 매직 리터럴 상수화
yeonkkk Sep 11, 2023
c1870e8
refactor: 메서드 위치 수정, 불필요한 코드 제거
yeonkkk Sep 11, 2023
7d6ae10
refactor: User 생성자 추가, password 비교 로직 변경
yeonkkk Sep 11, 2023
838dd4a
refactor: static import, 메서드 분리
yeonkkk Sep 11, 2023
aa653ca
refactor: 상수 접근자 변경, 로그인 로그 추가
yeonkkk Sep 11, 2023
7bd3ef3
refactor: 허용되지 않은 메서드로 요청할 경우 상태코드 405를 반환하도록 수정
yeonkkk Sep 12, 2023
a814c60
refactor: Authorizer를 통해 session 존재 여부를 확인하도록 변경
yeonkkk Sep 12, 2023
c5ff7bf
chore: RequestMapping 패키지 변경
yeonkkk Sep 12, 2023
c85a3e9
refactor: 404 페이지 반환 방식 변경
yeonkkk Sep 12, 2023
42235a3
refactor: containsKey() 검증 제거
yeonkkk Sep 12, 2023
ecbff03
refactor: 쓰레드 작업 처리 메서드 변경
yeonkkk Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions tomcat/src/main/java/nextstep/jwp/model/User.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
package nextstep.jwp.model;

import java.util.Map;

public class User {

public static final String ACCOUNT_KEY = "account";
public static final String PASSWORD_KEY = "password";
public static final String EMAIL_KEY = "email";

private final Long id;
private final String account;
private final String password;
private final String email;

public User(Long id, String account, String password, String email) {
public User(final Long id, final String account, final String password, final String email) {

Choose a reason for hiding this comment

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

발빠르게 적용하셨군요! ㅎㅎ

둘다 급하게 작성하느라 놓친 부분이네요.. 감사합니다 ㅎㅎ 반영하겠습니다!! 🙂

굿굿~~

this.id = id;
this.account = account;
this.password = password;
this.email = email;
}

public User(String account, String password, String email) {
public User(final String account, final String password, final String email) {
this(null, account, password, email);
}

public boolean checkPassword(String password) {
return this.password.equals(password);
public User(final Map<String, String> userInfo) {
this(null, userInfo.get(ACCOUNT_KEY), userInfo.get(PASSWORD_KEY), userInfo.get(EMAIL_KEY));
}

public boolean checkPassword(final User user) {
return this.password.equals(user.password);
}

public String getAccount() {
Expand Down
11 changes: 3 additions & 8 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;

import java.io.IOException;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
Expand All @@ -29,7 +26,7 @@ public interface Manager {
*
* @param session Session to be added
*/
void add(HttpSession session);
void add(Session session);

/**
* Return the active Session, associated with this Manager, with the
Expand All @@ -39,18 +36,16 @@ public interface Manager {
*
* @exception IllegalStateException if a new session cannot be
* instantiated for any reason
* @exception IOException if an input/output error occurs while
* processing this request
*
* @return the request session or {@code null} if a session with the
* requested ID could not be found
*/
HttpSession findSession(String id) throws IOException;

Choose a reason for hiding this comment

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

throws IOException;

조이는 throws IOException이 붙어져있는 이유에 대해 생각해보신 적이 있으신가요!?
저도 조이와 같이 throws IOException를 없앴는데요.

이번에 구구 코치가 Controller interface의 시그니처를 유지하라는 이야기를 해주셨잖아요.
거기에도 Throws Exception이 포함되어있었거든요!
그래서 혹시 Throws Exception을 없애면 안되는 이유가 있나? 하는 고민이 있었어요.

이 부분도 마찬가지로 throws IOException이 기본적으로 붙어져있는 이유에 대해 생각해보신 적이 있으신지 궁금합니다!
저도 잘 모르겠어서 같이 이야기 나눠보고 싶네요!

Copy link
Member Author

Choose a reason for hiding this comment

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

저도 궁금해서 찾아봤는데, 정확한 이유는 잘 모르겠네요 ㅠ
그래서 맞는지는 모르겠지만 추측을 좀 해봤어요!

실제 톰캣에서 적용되는 findSession() 메서드는 세션을 찾는 작업을 수행할 때, 세션 데이터를 디스크에서 읽어오는 경우에 입출력 작업이 발생할 수 있다고 하네요. 그 과정에서 IOException이 발생할 수 있을 것 같아요!
(찾아보니 네트워크 클러스터에서 세션을 찾는 경우에와 같은 입출력 작업도 발생할 수 있다네요)

콩하나는 어떻게 생각하시나요?

Choose a reason for hiding this comment

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

오 그런 경우도 있겠군요!
말씀해주신 내용이 맞는 것 같아요!!

저도 단순히 Manager를 구현한 클래스 중 어딘가에서 I/O 작업을 할 수 있으니
해당 인터페이스에 명시적으로 throws IOException을 던진 것이겠거니~ 했어요!

조이 덕분에 하나 더 알아갑니다~

Session findSession(String id);

/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
*/
void remove(HttpSession session);
void remove(Session session);
}
38 changes: 38 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/RequestMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.apache.catalina;

import static org.apache.catalina.controller.StaticResourceUri.DEFAULT_PAGE;

import java.util.HashMap;
import java.util.Map;
import org.apache.catalina.controller.Controller;
import org.apache.catalina.controller.DefaultController;
import org.apache.catalina.controller.LoginController;
import org.apache.catalina.controller.RegisterController;
import org.apache.catalina.controller.RootController;
import org.apache.catalina.controller.StaticResourceController;
import org.apache.coyote.http11.request.HttpRequest;

public class RequestMapping {

private static final String ROOT_REQUEST_URL = "/";
private static final String LOGIN_REQUEST_URL = "/login";
private static final String REGISTER_REQUEST_URL = "/register";
private static final Map<String, Controller> controllers = new HashMap<>();

static {
controllers.put(ROOT_REQUEST_URL, new RootController());
controllers.put(DEFAULT_PAGE.getUri(), new DefaultController());
controllers.put(LOGIN_REQUEST_URL, new LoginController());
controllers.put(REGISTER_REQUEST_URL, new RegisterController());
}

public Controller getController(final HttpRequest request) {
final String path = request.getUri().getPath();

if (controllers.containsKey(path)) {
return controllers.get(path);
}

return new StaticResourceController();
}
}
39 changes: 39 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.apache.catalina;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class Session {

private final String id;
private final Map<String, Object> values = new ConcurrentHashMap<>();

Choose a reason for hiding this comment

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

new ConcurrentHashMap<>();

good~~


public Session() {
this(UUID.randomUUID().toString());
}

public Session(final String id) {
this.id = id;
}

public String getId() {
return this.id;
}

public Object getAttribute(final String name) {
return this.values.get(name);
}

public void setAttribute(final String name, final Object value) {
values.put(name, value);
}

public void removeAttribute(final String name) {
values.remove(name);
}

public void invalidate() {
values.clear();
}
}
24 changes: 24 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/SessionManger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.apache.catalina;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SessionManger implements Manager {

private static final Map<String, Session> SESSIONS = new ConcurrentHashMap<>();

@Override
public void add(final Session session) {
SESSIONS.put(session.getId(), session);
}

@Override
public Session findSession(final String id) {
return SESSIONS.get(id);
}

@Override
public void remove(final Session session) {
SESSIONS.remove(session.getId());
}
}
18 changes: 11 additions & 7 deletions tomcat/src/main/java/org/apache/catalina/connector/Connector.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
package org.apache.catalina.connector;

import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Connector implements Runnable {

private static final Logger log = LoggerFactory.getLogger(Connector.class);

private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int DEFAULT_MAX_THREADS = 250;

private final ServerSocket serverSocket;
private boolean stopped;
private final ExecutorService executorService;

public Connector() {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_MAX_THREADS);
}

public Connector(final int port, final int acceptCount) {
public Connector(final int port, final int acceptCount, final int maxThreads) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.executorService = Executors.newFixedThreadPool(maxThreads);
}

private ServerSocket createServerSocket(final int port, final int acceptCount) {
Expand Down Expand Up @@ -67,7 +71,7 @@ private void process(final Socket connection) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
executorService.submit(processor);

Choose a reason for hiding this comment

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

submit할게요~

}

public void stop() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.apache.catalina.controller;

import static org.apache.coyote.http11.response.ResponseHeaderType.ALLOW;

import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.HttpRequestMethod;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpStatusCode;

public abstract class AbstractController implements Controller {

static final String RESOURCE_DIRECTORY = "static";
public static final String ALLOW_HEADER_DELIMITER = ", ";

@Override
public void service(HttpRequest request, HttpResponse response) throws Exception {
HttpRequestMethod requestMethod = request.getMethod();

if (requestMethod == HttpRequestMethod.POST) {
doPost(request, response);
return;
}

if (requestMethod == HttpRequestMethod.GET) {
doGet(request, response);
return;
}
getMethodNotAllowedResponse(response);
}

protected void doPost(HttpRequest request, HttpResponse response) throws Exception {
getMethodNotAllowedResponse(response);
}

protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
getMethodNotAllowedResponse(response);
}

private void getMethodNotAllowedResponse(final HttpResponse response) {
final String allowedHeaders = String.join(ALLOW_HEADER_DELIMITER,
HttpRequestMethod.POST.name(),
HttpRequestMethod.GET.name());

response.setStatusCode(HttpStatusCode.METHOD_NOT_ALLOWED)
.addHeader(ALLOW, allowedHeaders);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.apache.catalina.controller;

import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public interface Controller {

void service(HttpRequest request, HttpResponse response) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.apache.catalina.controller;

import static java.util.Objects.requireNonNull;
import static org.apache.catalina.controller.StaticResourceUri.*;
import static org.apache.coyote.http11.response.ResponseContentType.TEXT_HTML;

import org.apache.catalina.util.FileLoader;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpResponseBody;
import org.apache.coyote.http11.response.HttpStatusCode;

public class DefaultController extends AbstractController {

@Override
protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception {
final String resource = FileLoader.load(RESOURCE_DIRECTORY + DEFAULT_PAGE.getUri());

response.setStatusCode(HttpStatusCode.OK)
.addContentTypeHeader(TEXT_HTML.getType())
.addContentLengthHeader(requireNonNull(resource).getBytes().length)
.setResponseBody(new HttpResponseBody(resource));
Comment on lines +19 to +22

Choose a reason for hiding this comment

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

오... 저는 왜 빌더 패턴을 적용할 생각을 못했을까요??
훨씬 깔끔해지네요!!

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.apache.catalina.controller;

import static java.util.Objects.requireNonNull;
import static org.apache.catalina.controller.StaticResourceUri.DEFAULT_PAGE;
import static org.apache.catalina.controller.StaticResourceUri.LOGIN_PAGE;
import static org.apache.coyote.http11.response.ResponseContentType.TEXT_HTML;

import java.util.NoSuchElementException;
import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.catalina.Session;
import org.apache.catalina.util.Authorizer;
import org.apache.catalina.util.FileLoader;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.HttpRequestBody;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpResponseBody;
import org.apache.coyote.http11.response.HttpStatusCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoginController extends AbstractController {

private static final String USER_ATTRIBUTE_KEY = "user";
private static final Logger log = LoggerFactory.getLogger(LoginController.class);

@Override
protected void doPost(final HttpRequest request, final HttpResponse response) {
if (Authorizer.hasValidSession(request)) {
redirectToDefaultPage(response);
return;
}

final HttpRequestBody requestBody = request.getBody();
final User reqeustUser = new User(requestBody.parse());

final User user = InMemoryUserRepository.findByAccount(reqeustUser.getAccount())
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 사용자입니다."));

if (user.checkPassword(reqeustUser)) {
final Session newSession = createSession(user);

response.setStatusCode(HttpStatusCode.FOUND)
.addContentTypeHeader(TEXT_HTML.getType())
.addLocationHeader(DEFAULT_PAGE.getUri())
.addSetCookieHeader("JSESSIONID=" + newSession.getId());
}
}

@Override
protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception {
if (Authorizer.hasValidSession(request)) {
redirectToDefaultPage(response);
return;
}

final String resource = FileLoader.load(RESOURCE_DIRECTORY + LOGIN_PAGE.getUri());

response.setStatusCode(HttpStatusCode.OK)
.addContentTypeHeader(TEXT_HTML.getType())
.addContentLengthHeader(requireNonNull(resource).getBytes().length)
.setResponseBody(new HttpResponseBody(resource));
}

private Session createSession(final User user) {
Session newSession = new Session();
newSession.setAttribute(USER_ATTRIBUTE_KEY, user);
Authorizer.addSession(newSession);
log.info("로그인 성공! 아이디: {}", user.getAccount());
return newSession;
}

private void redirectToDefaultPage(final HttpResponse response) {
response.setStatusCode(HttpStatusCode.FOUND)
.addContentTypeHeader(TEXT_HTML.getType())
.addLocationHeader(DEFAULT_PAGE.getUri());
}
}
Loading