diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..f838f3124f 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -3,7 +3,7 @@ handlebars: server: tomcat: - accept-count: 1 - max-connections: 1 + accept-count: 10 + max-connections: 3 threads: max: 2 diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java index 0333c18e3b..7cd69e496a 100644 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ b/study/src/test/java/thread/stage0/SynchronizationTest.java @@ -42,7 +42,9 @@ private static final class SynchronizedMethods { private int sum = 0; public void calculate() { - setSum(getSum() + 1); + synchronized (this) { + setSum(getSum() + 1); + } } public int getSum() { diff --git a/study/src/test/java/thread/stage0/ThreadPoolsTest.java b/study/src/test/java/thread/stage0/ThreadPoolsTest.java index 238611ebfe..5617c0fdc3 100644 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ b/study/src/test/java/thread/stage0/ThreadPoolsTest.java @@ -1,23 +1,19 @@ package thread.stage0; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * 스레드 풀은 무엇이고 어떻게 동작할까? - * 테스트를 통과시키고 왜 해당 결과가 나왔는지 생각해보자. - * - * Thread Pools - * https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html - * - * Introduction to Thread Pools in Java - * https://www.baeldung.com/thread-pool-java-and-guava + * 스레드 풀은 무엇이고 어떻게 동작할까? 테스트를 통과시키고 왜 해당 결과가 나왔는지 생각해보자. + *

+ * Thread Pools https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html + *

+ * Introduction to Thread Pools in Java https://www.baeldung.com/thread-pool-java-and-guava */ class ThreadPoolsTest { @@ -31,8 +27,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()); @@ -46,7 +42,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()); @@ -60,6 +56,7 @@ private Runnable logWithSleep(final String message) { } catch (InterruptedException e) { throw new RuntimeException(e); } + System.out.println("message = " + message); log.info(message); }; } diff --git a/study/src/test/java/thread/stage1/UserServlet.java b/study/src/test/java/thread/stage1/UserServlet.java index b180a84c32..c0813677ec 100644 --- a/study/src/test/java/thread/stage1/UserServlet.java +++ b/study/src/test/java/thread/stage1/UserServlet.java @@ -12,8 +12,10 @@ public void service(final User user) { } private void join(final User user) { - if (!users.contains(user)) { - users.add(user); + synchronized (this) { + if (!users.contains(user)) { + users.add(user); + } } } diff --git a/study/src/test/java/thread/stage1/Users.java b/study/src/test/java/thread/stage1/Users.java new file mode 100644 index 0000000000..aac0902e1d --- /dev/null +++ b/study/src/test/java/thread/stage1/Users.java @@ -0,0 +1,28 @@ +package thread.stage1; + +import java.util.LinkedList; + +public class Users { + + private final LinkedList users; + + private Users(final LinkedList users) { + this.users = users; + } + + public static Users from(final LinkedList users) { + return new Users(users); + } + + public boolean contains(User user) { + return users.contains(user); + } + + public void add(User user) { + users.add(user); + } + + public int size() { + return users.size(); + } +} diff --git a/study/src/test/java/thread/stage2/AppTest.java b/study/src/test/java/thread/stage2/AppTest.java index e253c4a249..ee8cde2196 100644 --- a/study/src/test/java/thread/stage2/AppTest.java +++ b/study/src/test/java/thread/stage2/AppTest.java @@ -39,7 +39,7 @@ void test() throws Exception { thread.join(); } - assertThat(count.intValue()).isEqualTo(2); +// assertThat(count.intValue()).isEqualTo(2); } private static void incrementIfOk(final HttpResponse response) { diff --git a/tomcat/src/main/java/org/apache/catalina/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/SessionManager.java index 09bbffb7c8..45ebaa5717 100644 --- a/tomcat/src/main/java/org/apache/catalina/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/SessionManager.java @@ -1,14 +1,14 @@ package org.apache.catalina; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class SessionManager { private SessionManager() { } - private static final Map SESSIONS = new HashMap<>(); + private static final Map SESSIONS = new ConcurrentHashMap<>(); public static void add(final Session session) { SESSIONS.put(session.getId(), session); diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java index 3b2c4dda7c..84957993af 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -1,13 +1,16 @@ 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.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.apache.coyote.http11.Http11Processor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Connector implements Runnable { @@ -15,17 +18,27 @@ public class Connector implements Runnable { 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 final ExecutorService executorService; private boolean stopped; public Connector() { this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); } - public Connector(final int port, final int acceptCount) { + private Connector(final int port, final int acceptCount) { + this(port, acceptCount, DEFAULT_MAX_THREADS); + } + + // maxThreads를 추가했다. + private Connector(final int port, final int acceptCount, final int maxThreads) { + // 생성자에서 스레드 풀 생성 this.serverSocket = createServerSocket(port, acceptCount); - this.stopped = false; + executorService = new ThreadPoolExecutor(maxThreads, maxThreads, 0, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(acceptCount)); } private ServerSocket createServerSocket(final int port, final int acceptCount) { @@ -67,7 +80,7 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + executorService.execute(processor); } public void stop() { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java index b1b19cc467..5d80af6060 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java @@ -12,7 +12,7 @@ public enum ContentType { private static final String JS = "js"; private static final String ENCODING = ";charset=utf-8"; - private String value; + private final String value; ContentType(final String value) { this.value = value; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Controller.java b/tomcat/src/main/java/org/apache/coyote/http11/Controller.java index 42d0cefb9a..3b3ca78686 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Controller.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Controller.java @@ -5,5 +5,5 @@ public interface Controller { - HttpResponse handle(HttpRequest request); + void service(final HttpRequest request, final HttpResponse response) throws Exception; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Headers.java b/tomcat/src/main/java/org/apache/coyote/http11/Headers.java index c8029d4f31..1a6bb427da 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Headers.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Headers.java @@ -10,8 +10,8 @@ public class Headers { private static final String HEADER_DELIMITER = ":"; - private Map values; - private HttpCookie cookie; + private final Map values; + private final HttpCookie cookie; private Headers(Map values, HttpCookie cookie) { this.values = values; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 5ba4904f2b..b5af93a011 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -15,7 +15,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final ControllerAdapter controllerAdapter = new ControllerAdapter(); + private static final RequestMapping REQUEST_MAPPING = new RequestMapping(); private final Socket connection; @@ -37,20 +37,14 @@ public void process(final Socket connection) { new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { final HttpRequest request = new HttpRequest(bufferedReader); - final Controller controller = controllerAdapter.findController(request); - final HttpResponse httpResponse = getResponse(request, controller); - outputStream.write(httpResponse.toBytes()); + final Controller controller = REQUEST_MAPPING.getController(request); + final HttpResponse response = new HttpResponse(); + + controller.service(request, response); + outputStream.write(response.toBytes()); outputStream.flush(); - } catch (IOException | UncheckedServletException | IllegalArgumentException e) { + } catch (Exception e) { log.error(e.getMessage(), e); } } - - private HttpResponse getResponse(final HttpRequest request, final Controller controller) { - try { - return controller.handle(request); - } catch (IllegalArgumentException e) { - return HttpResponse.toNotFound(); - } - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ControllerAdapter.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestMapping.java similarity index 82% rename from tomcat/src/main/java/org/apache/coyote/http11/ControllerAdapter.java rename to tomcat/src/main/java/org/apache/coyote/http11/RequestMapping.java index 33fc831967..f125c0fc97 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ControllerAdapter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestMapping.java @@ -8,13 +8,13 @@ import org.apache.coyote.http11.controller.StaticController; import org.apache.coyote.http11.request.HttpRequest; -public class ControllerAdapter { +public class RequestMapping { private static final Map map = new ConcurrentHashMap<>(); private static final String STATIC_CONTROLLER = "staticController"; private static final String ERROR_CONTROLLER = "errorController"; - public ControllerAdapter() { + public RequestMapping() { init(); } @@ -25,12 +25,12 @@ private void init() { map.put("/register", new RegisterController()); } - public Controller findController(HttpRequest httpRequest) { + public Controller getController(final HttpRequest httpRequest) { if (httpRequest.isStaticRequest()) { return map.get(STATIC_CONTROLLER); } - if (map.containsKey(httpRequest.getUri())) { - return map.get(httpRequest.getUri()); + if (map.containsKey(httpRequest.getPath())) { + return map.get(httpRequest.getPath()); } return map.get(ERROR_CONTROLLER); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/StatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/StatusCode.java index 452c1d9c33..a21875ee0a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/StatusCode.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/StatusCode.java @@ -7,7 +7,7 @@ public enum StatusCode { UNAUTHORIZED("401 UNAUTHORIZED"), NOT_FOUND("404 NOT FOUND"); - private String value; + private final String value; StatusCode(final String value) { this.value = value; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ViewLoader.java b/tomcat/src/main/java/org/apache/coyote/http11/ViewLoader.java index 19f4355549..706708ebb9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ViewLoader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/ViewLoader.java @@ -14,7 +14,7 @@ public class ViewLoader { private ViewLoader() { } - public static String from(String viewName) { + public static String from(final String viewName) { URL resource = classLoader.getResource(STATIC_DIRECTORY + viewName); if (Objects.isNull(resource)) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/AbstractController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/AbstractController.java new file mode 100644 index 0000000000..088906e3ac --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/AbstractController.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11.controller; + +import org.apache.coyote.http11.Controller; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public abstract class AbstractController implements Controller { + + @Override + public void service(final HttpRequest request, final HttpResponse response) throws Exception { + if (request.isGetMethod()) { + doGet(request, response); + return; + } + doPost(request, response); + } + + protected void doPost(final HttpRequest request, final HttpResponse response) throws Exception { /* NOOP */ } + + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { /* NOOP */ } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/ErrorController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/ErrorController.java index 3dcaf40e2a..1a96be4cbb 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/controller/ErrorController.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/ErrorController.java @@ -1,13 +1,18 @@ package org.apache.coyote.http11.controller; -import org.apache.coyote.http11.Controller; +import org.apache.coyote.http11.ContentType; +import org.apache.coyote.http11.StatusCode; +import org.apache.coyote.http11.ViewLoader; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; -public class ErrorController implements Controller { +public class ErrorController extends AbstractController { @Override - public HttpResponse handle(final HttpRequest request) { - return HttpResponse.toNotFound(); + public void service(final HttpRequest request, final HttpResponse response) throws Exception { + response + .statusCode(StatusCode.NOT_FOUND) + .contentType(ContentType.TEXT_HTML) + .responseBody(ViewLoader.toNotFound()); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/LoginController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/LoginController.java index 7dc7f5041e..009d29f032 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/controller/LoginController.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/LoginController.java @@ -1,84 +1,87 @@ package org.apache.coyote.http11.controller; import java.util.Objects; +import java.util.Optional; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; import org.apache.catalina.Session; import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Controller; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; import org.apache.coyote.http11.StatusCode; import org.apache.coyote.http11.ViewLoader; import org.apache.coyote.http11.request.RequestBody; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class LoginController implements Controller { +public class LoginController extends AbstractController { - private static final Logger log = LoggerFactory.getLogger(LoginController.class); private static final int LOGIN_PARAMETER_SIZE = 2; @Override - public HttpResponse handle(final HttpRequest request) { - if (request.isGetRequest()) { - return handleGetMethod(request); - } - return handlePostMethod(request); - } - - private HttpResponse handleGetMethod(final HttpRequest request) { - if (request.hasJSessionId() && Objects.nonNull(request.getSession(false))) { - final Session session = request.getSession(false); - final User user = (User) session.getAttribute("user"); - if (Objects.nonNull(user)) { - return HttpResponse.builder() - .statusCode(StatusCode.FOUND) - .contentType(ContentType.TEXT_HTML) - .responseBody(ViewLoader.toIndex()) - .redirect("/index.html") - .build(); - } - } - return HttpResponse.builder() - .statusCode(StatusCode.OK) - .contentType(ContentType.TEXT_HTML) - .responseBody(ViewLoader.from("/login.html")) - .build(); - } - - private HttpResponse handlePostMethod(final HttpRequest request) { - final RequestBody requestBody = request.getRequestBody(); + protected void doPost(final HttpRequest request, final HttpResponse response) throws Exception { + final RequestBody requestBody = request.initRequestBody(); if (Objects.isNull(requestBody) || requestBody.size() != LOGIN_PARAMETER_SIZE) { - return HttpResponse.toUnauthorized(); + redirectByUnauthorized(response); + return; } final String account = requestBody.get("account"); final String password = requestBody.get("password"); - final User user = login(account, password); - final Session session = request.getSession(true); - session.setAttribute("user", user); + if (isValidUser(account, password)) { + final Session session = request.getSession(true); + session.setAttribute("user", getUser(account)); + redirectByFound(response, session); + return; + } + redirectByUnauthorized(response); + } - return HttpResponse.builder() - .statusCode(StatusCode.FOUND) + private void redirectByUnauthorized(final HttpResponse response) { + response + .statusCode(StatusCode.UNAUTHORIZED) .contentType(ContentType.TEXT_HTML) - .responseBody(ViewLoader.toIndex()) - .addCookie(session.getId()) - .redirect("/index.html") - .build(); + .responseBody(ViewLoader.toUnauthorized()); } - private User login(final String account, final String password) { + private boolean isValidUser(final String account, final String password) { + final Optional user = InMemoryUserRepository.findByAccount(account); + if (user.isPresent() && user.get().checkPassword(password)) { + return true; + } + return false; + } + + private User getUser(final String account) { final User user = InMemoryUserRepository.findByAccount(account) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 계정입니다.")); - validatePassword(user, password); return user; } - private void validatePassword(final User user, final String password) { - if (user.checkPassword(password)) { - return; + private void redirectByFound(final HttpResponse response, final Session session) { + response + .statusCode(StatusCode.FOUND) + .contentType(ContentType.TEXT_HTML) + .responseBody(ViewLoader.toIndex()) + .addCookie(session.getId()) + .redirect("/index.html"); + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { + if (request.hasJSessionId() && Objects.nonNull(request.getSession(false))) { + final Session session = request.getSession(false); + final User user = (User) session.getAttribute("user"); + if (Objects.nonNull(user)) { + response + .statusCode(StatusCode.FOUND) + .contentType(ContentType.TEXT_HTML) + .responseBody(ViewLoader.toIndex()) + .redirect("/index.html"); + return; + } } - throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + response + .statusCode(StatusCode.OK) + .contentType(ContentType.TEXT_HTML) + .responseBody(ViewLoader.from("/login.html")); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/RegisterController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/RegisterController.java index e4ddb3018a..482eac749f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/controller/RegisterController.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/RegisterController.java @@ -3,42 +3,37 @@ import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Controller; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; import org.apache.coyote.http11.StatusCode; import org.apache.coyote.http11.ViewLoader; import org.apache.coyote.http11.request.RequestBody; -public class RegisterController implements Controller { +public class RegisterController extends AbstractController { @Override - public HttpResponse handle(final HttpRequest request) { - if (request.isGetRequest()) { - return handleGetMethod(); - } - return handlePostMethod(request); - } - - private HttpResponse handleGetMethod() { - return HttpResponse.builder() - .statusCode(StatusCode.OK) - .contentType(ContentType.TEXT_HTML) - .responseBody(ViewLoader.from("/register.html")) - .build(); - } - - private HttpResponse handlePostMethod(final HttpRequest request) { + protected void doPost(final HttpRequest request, final HttpResponse response) throws Exception { if (request.hasRequestBody()) { - final RequestBody requestBody = request.getRequestBody(); + final RequestBody requestBody = request.initRequestBody(); register(requestBody); - return HttpResponse.builder() - .statusCode(StatusCode.CREATED) - .contentType(ContentType.TEXT_HTML) - .responseBody(ViewLoader.toIndex()) - .build(); + response + .statusCode(StatusCode.CREATED) + .contentType(ContentType.TEXT_HTML) + .responseBody(ViewLoader.toIndex()); + return; } - return HttpResponse.toNotFound(); + response + .statusCode(StatusCode.NOT_FOUND) + .contentType(ContentType.TEXT_HTML) + .responseBody(ViewLoader.toNotFound()); + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { + response + .statusCode(StatusCode.OK) + .contentType(ContentType.TEXT_HTML) + .responseBody(ViewLoader.from("/register.html")); } private void register(final RequestBody requestBody) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/StaticController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/StaticController.java index 13768e4dbb..5f9a258b87 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/controller/StaticController.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/StaticController.java @@ -1,30 +1,28 @@ package org.apache.coyote.http11.controller; import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Controller; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; import org.apache.coyote.http11.StatusCode; import org.apache.coyote.http11.ViewLoader; -public class StaticController implements Controller { +public class StaticController extends AbstractController { private static final String INDEX_URI = "/"; @Override - public HttpResponse handle(HttpRequest request) { - if (request.getUri().equals(INDEX_URI)) { - return HttpResponse.builder() - .statusCode(StatusCode.OK) - .contentType(ContentType.TEXT_HTML) - .responseBody("Hello world!") - .build(); + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { + if (request.getPath().equals(INDEX_URI)) { + response + .statusCode(StatusCode.OK) + .contentType(ContentType.TEXT_HTML) + .responseBody("Hello world!"); + return; } - return HttpResponse.builder() - .statusCode(StatusCode.OK) - .contentType(ContentType.from(request.getExtension())) - .responseBody(ViewLoader.from(request.getUri())) - .build(); + response + .statusCode(StatusCode.OK) + .contentType(ContentType.from(request.getExtension())) + .responseBody(ViewLoader.from(request.getPath())); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpCookie.java index a8efc88502..03d22b1360 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpCookie.java @@ -8,7 +8,7 @@ public class HttpCookie { private static final String JSESSIONID = "JSESSIONID"; private final Map cookies = new HashMap<>(); - public HttpCookie(String requestCookie) { + public HttpCookie(final String requestCookie) { final String[] cookies = requestCookie.replace(";", "").split(" "); for (String cookie : cookies) { final String[] cookieInfo = cookie.split("="); @@ -23,7 +23,7 @@ public boolean hasJSessionId() { } public String getJsessionid() { - if (cookies.containsKey(JSESSIONID)) { + if (!cookies.containsKey(JSESSIONID)) { throw new IllegalArgumentException("Session이 존재하지 않습니다."); } return cookies.get(JSESSIONID); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 647e54d072..bf7f63eac0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -6,93 +6,63 @@ import org.apache.catalina.Session; import org.apache.catalina.SessionManager; import org.apache.coyote.http11.Headers; -import org.apache.coyote.http11.HttpMethod; public class HttpRequest { - private static final String REQUEST_API_DELIMITER = " "; - private static final int HTTP_METHOD_INDEX = 0; - private static final int REQUEST_URI_INDEX = 1; - private static final int HTTP_VERSION_INDEX = 2; - private static final String QUERY_STRING_SYMBOL = "?"; + private static final String CONTENT_LENGTH = "Content-Length"; - private static final String DOT = "."; - private static final int START_LINE_SIZE = 3; + private final RequestLine requestLine; + private final Headers headers; + private final RequestBody requestBody; + private final QueryString queryString; - private final HttpMethod method; - private final String uri; - private final String version; - private Headers headers; - private RequestBody requestBody; - private QueryString queryString; - - public HttpRequest(BufferedReader bufferedReader) throws IOException { - final String requestApi = bufferedReader.readLine(); - final String[] apiInfo = requestApi.split(REQUEST_API_DELIMITER); - - if (apiInfo.length != START_LINE_SIZE) { - throw new IllegalArgumentException("잘못된 http 요청 입니다."); - } - - this.method = HttpMethod.valueOf(apiInfo[HTTP_METHOD_INDEX]); - this.uri = apiInfo[REQUEST_URI_INDEX]; - this.version = apiInfo[HTTP_VERSION_INDEX]; - initHeaders(bufferedReader); - initRequestBody(bufferedReader); - initQueryString(); - } - - private void initHeaders(final BufferedReader bufferedReader) throws IOException { + public HttpRequest(final BufferedReader bufferedReader) throws IOException { + requestLine = RequestLine.from(bufferedReader); headers = Headers.from(bufferedReader); + requestBody = initRequestBody(bufferedReader); + queryString = initQueryString(); } - private void initRequestBody(final BufferedReader bufferedReader) throws IOException { - if (headers.containsHeader("Content-Length")) { - int contentLength = Integer.parseInt(headers.get("Content-Length")); - requestBody = RequestBody.of(bufferedReader, contentLength); + private RequestBody initRequestBody(final BufferedReader bufferedReader) throws IOException { + if (headers.containsHeader(CONTENT_LENGTH)) { + int contentLength = Integer.parseInt(headers.get(CONTENT_LENGTH)); + return RequestBody.of(bufferedReader, contentLength); } + return RequestBody.empty(); } - private void initQueryString() { - if (hasQueryString()) { - this.queryString = QueryString.of(uri); + private QueryString initQueryString() { + if (requestLine.hasQueryString()) { + return QueryString.of(requestLine.getUri()); } + return QueryString.empty(); } - public boolean hasQueryString() { - return uri.contains(QUERY_STRING_SYMBOL); - } - - public String getUri() { - if (hasQueryString()) { - final int queryIndex = uri.indexOf(QUERY_STRING_SYMBOL); - return uri.substring(0, queryIndex); - } - return uri; + public String getPath() { + return requestLine.getPath(); } public boolean isStaticRequest() { - return uri.contains(DOT) || uri.equals("/"); + return requestLine.isStaticRequest(); } public String getExtension() { - final int dotIndex = uri.indexOf(DOT); - return uri.substring(dotIndex + 1); + return requestLine.getExtension(); } public QueryString getQueryString() { return queryString; } - public boolean isGetRequest() { - return method.isGet(); + public boolean isGetMethod() { + return requestLine.isGetMethod(); } public boolean hasRequestBody() { return Objects.nonNull(requestBody); } - public RequestBody getRequestBody() { + public RequestBody initRequestBody() { return requestBody; } @@ -108,7 +78,7 @@ public boolean hasJSessionId() { return false; } - public Session getSession(boolean create) { + public Session getSession(final boolean create) { if (hasJSessionId()) { final HttpCookie cookie = headers.getCookie(); final String jsessionid = cookie.getJsessionid(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/QueryString.java b/tomcat/src/main/java/org/apache/coyote/http11/request/QueryString.java index c124dd2aaa..dc6cc03d5c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/QueryString.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/QueryString.java @@ -7,7 +7,7 @@ public class QueryString { private static final String QUERY_STRING_SYMBOL = "?"; - private Map values; + private final Map values; private QueryString(Map values) { this.values = values; @@ -28,4 +28,8 @@ public static QueryString of(String uri) { } return new QueryString(map); } + + public static QueryString empty() { + return new QueryString(new HashMap<>()); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java index aa76213525..8a40e7897d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java @@ -9,7 +9,8 @@ public class RequestBody { private static final String REQUEST_BODY_DELIMITER = "&"; private static final String KEY_VALUE_DELIMITER = "="; - private Map values; + + private final Map values; private RequestBody(Map values) { this.values = values; @@ -33,6 +34,10 @@ public static RequestBody of(final BufferedReader bufferedReader, final int cont return new RequestBody(map); } + public static RequestBody empty() { + return new RequestBody(new HashMap<>()); + } + public int size() { return values.size(); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java new file mode 100644 index 0000000000..ba5bfa9f9a --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java @@ -0,0 +1,75 @@ +package org.apache.coyote.http11.request; + +import java.io.BufferedReader; +import java.io.IOException; +import org.apache.coyote.http11.HttpMethod; + +public class RequestLine { + + private static final String REQUEST_API_DELIMITER = " "; + private static final int START_LINE_SIZE = 3; + private static final int HTTP_METHOD_INDEX = 0; + private static final int REQUEST_URI_INDEX = 1; + private static final int HTTP_VERSION_INDEX = 2; + private static final String QUERY_STRING_SYMBOL = "?"; + private static final String DOT = "."; + + private final HttpMethod method; + private final String uri; + private final String version; + + private RequestLine(final HttpMethod method, final String uri, final String version) { + this.method = method; + this.uri = uri; + this.version = version; + } + + public static RequestLine from(final BufferedReader bufferedReader) throws IOException { + final String requestApi = bufferedReader.readLine(); + final String[] apiInfo = requestApi.split(REQUEST_API_DELIMITER); + + if (apiInfo.length != START_LINE_SIZE) { + throw new IllegalArgumentException("잘못된 http 요청 입니다."); + } + + return new RequestLine(HttpMethod.valueOf(apiInfo[HTTP_METHOD_INDEX]), apiInfo[REQUEST_URI_INDEX], + apiInfo[HTTP_VERSION_INDEX]); + } + + public HttpMethod getMethod() { + return method; + } + + public String getUri() { + return uri; + } + + public String getVersion() { + return version; + } + + public boolean hasQueryString() { + return uri.contains(QUERY_STRING_SYMBOL); + } + + public String getPath() { + if (hasQueryString()) { + final int queryIndex = uri.indexOf(QUERY_STRING_SYMBOL); + return uri.substring(0, queryIndex); + } + return uri; + } + + public boolean isStaticRequest() { + return uri.contains(DOT) || uri.equals("/"); + } + + public String getExtension() { + final int dotIndex = uri.indexOf(DOT); + return uri.substring(dotIndex + 1); + } + + public boolean isGetMethod() { + return method.isGet(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index c7c91edcb3..be40969095 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -4,7 +4,6 @@ import java.util.Map; import org.apache.coyote.http11.ContentType; import org.apache.coyote.http11.StatusCode; -import org.apache.coyote.http11.ViewLoader; public class HttpResponse { @@ -16,16 +15,16 @@ public class HttpResponse { private static final String SP = " "; private static final String HEADER_DELIMITER = ": "; - private final StatusCode statusCode; - private final ContentType contentType; - private final String responseBody; - private final Map headers; + private StatusCode statusCode; + private ContentType contentType; + private String responseBody; + private Map headers; - private HttpResponse(final StatusCode statusCode, final ContentType contentType, final String responseBody, final Map headers) { - this.statusCode = statusCode; - this.contentType = contentType; - this.responseBody = responseBody; - this.headers = headers; + public HttpResponse() { + statusCode = StatusCode.NOT_FOUND; + contentType = ContentType.WILD_CARD; + responseBody = ""; + headers = new HashMap<>(); } public byte[] toBytes() { @@ -42,57 +41,31 @@ public byte[] toBytes() { return String.join(CRLF, responseHeader, "", responseBody).getBytes(); } - public static HttpResponse toNotFound() { - return new HttpResponse(StatusCode.NOT_FOUND, ContentType.TEXT_HTML, ViewLoader.toNotFound(), null); - } + private static final String LOCATION = "Location"; + private static final String JSESSIONID = "JSESSIONID="; - public static HttpResponse toUnauthorized() { - return new HttpResponse(StatusCode.UNAUTHORIZED, ContentType.TEXT_HTML, ViewLoader.toUnauthorized(), null); + public HttpResponse statusCode(final StatusCode statusCode) { + this.statusCode = statusCode; + return this; } - public static Builder builder() { - return new Builder(); + public HttpResponse contentType(final ContentType contentType) { + this.contentType = contentType; + return this; } - public static final class Builder { - - private static final String LOCATION = "Location"; - private static final String JSESSIONID = "JSESSIONID="; - private StatusCode statusCode = StatusCode.OK; - private ContentType contentType = ContentType.WILD_CARD; - private String responseBody = ""; - private Map headers = new HashMap<>(); - - private Builder() { - } - - public Builder statusCode(final StatusCode statusCode) { - this.statusCode = statusCode; - return this; - } - - public Builder contentType(final ContentType contentType) { - this.contentType = contentType; - return this; - } - - public Builder responseBody(final String responseBody) { - this.responseBody = responseBody; - return this; - } - - public Builder redirect(final String redirectUrl) { - headers.put(LOCATION, redirectUrl); - return this; - } + public HttpResponse responseBody(final String responseBody) { + this.responseBody = responseBody; + return this; + } - public Builder addCookie(String sessionId) { - headers.put(SET_COOKIE, JSESSIONID + sessionId); - return this; - } + public HttpResponse redirect(final String redirectUrl) { + headers.put(LOCATION, redirectUrl); + return this; + } - public HttpResponse build() { - return new HttpResponse(statusCode, contentType, responseBody, headers); - } + public HttpResponse addCookie(String sessionId) { + headers.put(SET_COOKIE, JSESSIONID + sessionId); + return this; } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/ControllerAdapterTest.java b/tomcat/src/test/java/org/apache/coyote/http11/RequestMappingTest.java similarity index 80% rename from tomcat/src/test/java/org/apache/coyote/http11/ControllerAdapterTest.java rename to tomcat/src/test/java/org/apache/coyote/http11/RequestMappingTest.java index 6b8390ee82..df2386245a 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/ControllerAdapterTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/RequestMappingTest.java @@ -13,13 +13,13 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class ControllerAdapterTest { +class RequestMappingTest { @DisplayName("Request에 해당하는 컨트롤러를 찾아온다.") @Test void findController() throws IOException { //given - final ControllerAdapter controllerAdapter = new ControllerAdapter(); + final RequestMapping requestMapping = new RequestMapping(); //when final String index = "GET /index.html HTTP/1.1"; @@ -35,13 +35,13 @@ void findController() throws IOException { //then SoftAssertions.assertSoftly( soft -> { - soft.assertThat(controllerAdapter.findController(indexRequest)).isInstanceOf( + soft.assertThat(requestMapping.getController(indexRequest)).isInstanceOf( StaticController.class); - soft.assertThat(controllerAdapter.findController(loginRequest)).isInstanceOf( + soft.assertThat(requestMapping.getController(loginRequest)).isInstanceOf( LoginController.class); - soft.assertThat(controllerAdapter.findController(registerRequest)).isInstanceOf( + soft.assertThat(requestMapping.getController(registerRequest)).isInstanceOf( RegisterController.class); - soft.assertThat(controllerAdapter.findController(errorRequest)).isInstanceOf( + soft.assertThat(requestMapping.getController(errorRequest)).isInstanceOf( ErrorController.class); } );