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단계] 홍고(홍여진) 미션 제출합니다 #483

Merged
merged 17 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion study/src/test/java/thread/stage0/SynchronizationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private static final class SynchronizedMethods {

private int sum = 0;

public void calculate() {
synchronized public void calculate() {
setSum(getSum() + 1);
}

Expand Down
6 changes: 3 additions & 3 deletions study/src/test/java/thread/stage0/ThreadPoolsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ void testNewFixedThreadPool() {
executor.submit(logWithSleep("hello fixed thread pools"));

// 올바른 값으로 바꿔서 테스트를 통과시키자.
final int expectedPoolSize = 0;
final int expectedQueueSize = 0;
final int expectedPoolSize = 2;
final int expectedQueueSize = 1;

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
Expand All @@ -46,7 +46,7 @@ void testNewCachedThreadPool() {
executor.submit(logWithSleep("hello cached thread pools"));

// 올바른 값으로 바꿔서 테스트를 통과시키자.
final int expectedPoolSize = 0;
final int expectedPoolSize = 3;
final int expectedQueueSize = 0;

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
Expand Down
7 changes: 7 additions & 0 deletions tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package nextstep;

import org.apache.catalina.startup.Tomcat;
import nextstep.jwp.controller.LoginController;
import nextstep.jwp.controller.RegisterController;
import nextstep.jwp.controller.RootController;
import org.apache.catalina.controller.ControllerStatus;

public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();
tomcat.addController(new ControllerStatus("/"), new RootController());
tomcat.addController(new ControllerStatus("/login"), new LoginController());
tomcat.addController(new ControllerStatus("/register"), new RegisterController());
tomcat.start();
}
}
61 changes: 61 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package nextstep.jwp.controller;

import java.util.Map;
import java.util.Optional;
import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.catalina.controller.AbstractController;
import org.apache.coyote.http11.Session;
import org.apache.coyote.http11.SessionManager;
import org.apache.coyote.http11.message.HttpStatus;
import org.apache.coyote.http11.message.request.Request;
import org.apache.coyote.http11.message.response.Response;

public class LoginController extends AbstractController {

private static final String INDEX_TEMPLATE = "index.html";
private static final String USER = "user";
private static final String ACCOUNT = "account";
private static final String PASSWORD = "password";
private static final String LOGIN_TEMPLATE = "login.html";
private static final String JSESSIONID = "JSESSIONID";

@Override
protected void doPost(final Request request, final Response response) {
final Map<String, String> requestForms = request.getRequestForms().getFormData();
final Optional<User> optionalUser = login(requestForms.get(ACCOUNT), requestForms.get(PASSWORD));
optionalUser.ifPresentOrElse(user -> loginSuccess(request, response, user), () -> loginFail(response));
}

private void loginSuccess(final Request request, final Response response, final User user) {
response.setLocation(INDEX_TEMPLATE);
response.setStatus(HttpStatus.FOUND);

if (request.noSession()) {
final Session session = new Session();
session.setAttribute(USER, user);
SessionManager.add(session);
response.addCookie(JSESSIONID, session.getId());
}
}

private void loginFail(final Response response) {
final Response failedResponse = Response.createByTemplate(HttpStatus.UNAUTHORIZED, "401.html");
response.setBy(failedResponse);
}

private Optional<User> login(final String account, final String password) {
return InMemoryUserRepository.findByAccountAndPassword(account, password);
}

@Override
protected void doGet(final Request request, final Response response) throws Exception {
if (request.getSessionValue(USER) != Optional.empty()) {
Copy link

Choose a reason for hiding this comment

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

request.getSessionValue(USER).isEmpty() 처럼 옵셔널 내부 메서드로 검사해주는게 더 옵셔널스럽게 사용하는 방법 같네요! 그러면 null 비교에 비해 이점을 확실하게 가져갈 수 있을 것 같아요😎

Copy link
Author

Choose a reason for hiding this comment

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

request.getSessionValue(USER).isEmpty() 너무 좋아요~~~ 다른 Optional들도 동일하게 수정했습니다!

response.setLocation(INDEX_TEMPLATE);
response.setStatus(HttpStatus.FOUND);
return;
}
final Response createdResponse = Response.createByTemplate(HttpStatus.OK, LOGIN_TEMPLATE);
response.setBy(createdResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package nextstep.jwp.controller;

import java.util.Map;
import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.catalina.controller.AbstractController;
import org.apache.coyote.http11.message.HttpStatus;
import org.apache.coyote.http11.message.request.Request;
import org.apache.coyote.http11.message.response.Response;

public class RegisterController extends AbstractController {

private static final String ACCOUNT = "account";
private static final String EMAIL = "email";
private static final String PASSWORD = "password";
private static final String INDEX_TEMPLATE = "index.html";
private static final String REGISTER_TEMPLATE = "register.html";

@Override
protected void doPost(final Request request, final Response response) throws Exception {
final Map<String, String> requestForms = request.getRequestForms().getFormData();
final String account = requestForms.get(ACCOUNT);
final String email = requestForms.get(EMAIL);
final String password = requestForms.get(PASSWORD);
InMemoryUserRepository.save(new User(account, password, email));

response.setLocation(INDEX_TEMPLATE);
response.setStatus(HttpStatus.FOUND);
}

@Override
protected void doGet(final Request request, final Response response) throws Exception {
final Response createdResponse = Response.createByTemplate(HttpStatus.OK, REGISTER_TEMPLATE);
// response.setStatus(HttpStatus.OK);
// final ResponseBody responseBody = ResponseBody.from(REGISTER_TEMPLATE);
// response.setBody(responseBody);
response.setBy(createdResponse);
}
}
26 changes: 26 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/RootController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.jwp.controller;

import org.apache.catalina.controller.AbstractController;
import org.apache.catalina.exception.UnsupportedRequestException;
import org.apache.coyote.http11.message.ContentType;
import org.apache.coyote.http11.message.HttpStatus;
import org.apache.coyote.http11.message.request.Request;
import org.apache.coyote.http11.message.response.Response;
import org.apache.coyote.http11.message.response.ResponseBody;

public class RootController extends AbstractController {

private static final String ROOT_MESSAGE = "Hello world!";

@Override
protected void doPost(final Request request, final Response response) throws Exception {
throw new UnsupportedRequestException();
Copy link

Choose a reason for hiding this comment

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

커스텀 예외 멋지네요👍

}

@Override
protected void doGet(final Request request, final Response response) throws Exception {
final Response createdResponse = Response.createByResponseBody(HttpStatus.OK,
new ResponseBody(ROOT_MESSAGE, ContentType.HTML));
response.setBy(createdResponse);
Comment on lines +21 to +24
Copy link

Choose a reason for hiding this comment

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

setBody, setStatus 대신 response 객체 자체를 새로 만들어주신 이유를 공유해주실 수 있나요?

Copy link
Author

Choose a reason for hiding this comment

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

기존에 유효한 정보(status, body)들을 받아 Response를 생성해주는 로직이 존재했는데, 3단계 요구사항에서 doGet(final Request request, final Response response) 메소드가 생기면서 Response를 무조건 디폴트값으로 초기화하게 되었습니다.
기존에 생성한 유효한 정보(status, body)들을 받아 Response를 생성해주는 로직을 그대로 재활용하기 위해 setBy()를 생성했습니다...ㅎㅎ

물론 사용하지 않을 복사용 Response 객체가 생기긴 하지만...ㅎㅎ 이 정도는 괜찮지 않을까 했어요...ㅎㅎ
혹시 마음에 걸리는 부분이 있으신가요?

Copy link

Choose a reason for hiding this comment

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

저도 큰 차이는 없다고 생각하는데 혹시 제가 생각못한 장단점이 있을까해서 여쭤봤습니다! 🫡

일반적인 서블릿 프로그래밍과 약간의 차이가 있다면 공통 헤더를 처리해주는 filter들을 따로 두어 doFilter 메서드로 리스폰스에 필요한 것들을 계속해서 set해주는 과정으로 가져가는 것 정도 같네요!

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package nextstep.jwp.controller;

import org.apache.catalina.controller.AbstractController;
import org.apache.catalina.exception.UnsupportedRequestException;
import org.apache.coyote.http11.message.request.Request;
import org.apache.coyote.http11.message.response.Response;

public class StaticFileController extends AbstractController {

@Override
protected void doPost(final Request request, final Response response) throws Exception {
throw new UnsupportedRequestException();
}

@Override
protected void doGet(final Request request, final Response response) throws Exception {
final Response createdResponse = Response.createByTemplate(request.getRequestURI());
response.setBy(createdResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ public class InMemoryUserRepository {
database.put(user.getAccount(), user);
}

public static void save(User user) {
public static void save(final User user) {
database.put(user.getAccount(), user);
}

public static Optional<User> findByAccount(String account) {
public static Optional<User> findByAccount(final String account) {
return Optional.ofNullable(database.get(account));
}

Expand Down
6 changes: 3 additions & 3 deletions tomcat/src/main/java/nextstep/jwp/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ public class User {
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) {
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) {
public boolean checkPassword(final String password) {
return this.password.equals(password);
}

Expand Down
82 changes: 61 additions & 21 deletions tomcat/src/main/java/org/apache/catalina/connector/Connector.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,62 @@
package org.apache.catalina.connector;

import org.apache.coyote.http11.Http11Processor;
import org.apache.coyote.http11.handler.HandlerMapper;
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.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.coyote.http11.Http11Processor;
import org.apache.catalina.controller.ControllerMapper;
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 MIN_PORT = 1;
private static final int MAX_PORT = 65535;
private static final int DEFAULT_MAX_THREAD = 250;
private static final String SERVER_START_MESSAGE = "Web Application Server started {} port.";
public static final int TASK_AWAIT_TIME = 60;

private final ServerSocket serverSocket;
private final HandlerMapper handlerMapper;
private final ControllerMapper controllerMapper;
private boolean stopped;
private final ExecutorService executorService;

public Connector(final ControllerMapper controllerMapper) {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, controllerMapper, DEFAULT_MAX_THREAD);
}

public Connector(final HandlerMapper handlerMapper) {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, handlerMapper);
public Connector(final int port, final int acceptCount, final ControllerMapper controllerMapper) {
this(port, acceptCount, controllerMapper, DEFAULT_MAX_THREAD);
}

public Connector(final int port, final int acceptCount, final HandlerMapper handlerMapper) {
public Connector(
final int port,
final int acceptCount,
final ControllerMapper controllerMapper,
final int maxThreads
) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.handlerMapper = handlerMapper;
this.controllerMapper = controllerMapper;
this.executorService = new ThreadPoolExecutor(
getMaxThread(maxThreads),
getMaxThread(maxThreads),
0,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(acceptCount)
);
}

private int getMaxThread(final int maxThread) {
return Math.max(maxThread, DEFAULT_MAX_THREAD);
}

private ServerSocket createServerSocket(final int port, final int acceptCount) {
Expand All @@ -42,16 +70,15 @@ private ServerSocket createServerSocket(final int port, final int acceptCount) {
}

public void start() {
var thread = new Thread(this);
final var thread = new Thread(this);
thread.setDaemon(true);
thread.start();
stopped = false;
log.info("Web Application Server started {} port.", serverSocket.getLocalPort());
log.info(SERVER_START_MESSAGE, serverSocket.getLocalPort());
}

@Override
public void run() {
// 클라이언트가 연결될때까지 대기한다.
while (!stopped) {
connect();
}
Expand All @@ -60,7 +87,7 @@ public void run() {
private void connect() {
try {
process(serverSocket.accept());
} catch (IOException e) {
} catch (final IOException e) {
log.error(e.getMessage(), e);
}
}
Expand All @@ -69,23 +96,36 @@ private void process(final Socket connection) {
if (connection == null) {
return;
}
var processor = new Http11Processor(connection, handlerMapper);
new Thread(processor).start();
final var processor = new Http11Processor(connection, controllerMapper);
executorService.execute(processor);
}

public void stop() {
stopped = true;
try {
serverSocket.close();
} catch (IOException e) {
shutdown();
} catch (final IOException e) {
log.error(e.getMessage(), e);
}
}

private int checkPort(final int port) {
final var MIN_PORT = 1;
final var MAX_PORT = 65535;
private void shutdown () {
executorService.shutdown();
try {
if (!executorService.awaitTermination(TASK_AWAIT_TIME, TimeUnit.SECONDS)) {
executorService.shutdownNow();
if (!executorService.awaitTermination(TASK_AWAIT_TIME, TimeUnit.SECONDS)) {
log.error("Pool did not terminate");
}
}
} catch (final InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}

private int checkPort(final int port) {
if (port < MIN_PORT || MAX_PORT < port) {
return DEFAULT_PORT;
}
Expand Down
Loading