Skip to content

Commit

Permalink
[톰캣 구현하기 3 & 4단계] 홍고(홍여진) 미션 제출합니다 (#483)
Browse files Browse the repository at this point in the history
* refactor: 정적 파일을 읽는 로직을 클래스로 분리

* refactor: ResponseBody를 클래스로 분리

* feat: Controller 생성

* refactor: HandlerMapper를 ControllerMapper로 대체

* test: thread 실습 테스트

* refactor: HttpMethod를 상수로 분리

* refactor: 매직넘버 상수로 분리

* refactor: 커스텀 예외 생성

* refactor: 패키지 변경

* refactor: final 추가

* test: RootController 할당

* style: 코드 스타일 적용

* refactor: URL이 null이면 바로 예외를 던지게 변경

* test: StaticFile 테스트 추가

* refactor: null객체 Optional로 변경 및 Optional 메소드 활용

* refactor: awaitTermination을 사용해 shutdown하게 변경

* refactor: 스레드풀의 큐 사이즈 지정
  • Loading branch information
hgo641 authored Sep 13, 2023
1 parent 03a7ba1 commit fed4fdc
Show file tree
Hide file tree
Showing 41 changed files with 1,128 additions and 729 deletions.
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()) {
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();
}

@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);
}
}
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

0 comments on commit fed4fdc

Please sign in to comment.