diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 8b74bdfd88..9e7edae7af 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -4,7 +4,7 @@ handlebars: server: tomcat: accept-count: 1 - max-connections: 1 + max-connections: 2 threads: min-spare: 2 max: 2 diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java index 59afce5646..6297f3dfdf 100644 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ b/study/src/test/java/thread/stage0/SynchronizationTest.java @@ -8,21 +8,17 @@ import org.junit.jupiter.api.Test; /** - * 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다. - * 자바는 공유 데이터에 대한 스레드 접근을 동기화(synchronization)하여 경쟁 조건을 방지한다. - * 동기화된 블록은 하나의 스레드만 접근하여 실행할 수 있다. - * - * Synchronization - * https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html + * 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다. 자바는 공유 데이터에 대한 스레드 접근을 + * 동기화(synchronization)하여 경쟁 조건을 방지한다. 동기화된 블록은 하나의 스레드만 접근하여 실행할 수 있다. + *

+ * Synchronization https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html */ class SynchronizationTest { /** - * 테스트가 성공하도록 SynchronizedMethods 클래스에 동기화를 적용해보자. - * synchronized 키워드에 대하여 찾아보고 적용하면 된다. - * - * Guide to the Synchronized Keyword in Java - * https://www.baeldung.com/java-synchronized + * 테스트가 성공하도록 SynchronizedMethods 클래스에 동기화를 적용해보자. synchronized 키워드에 대하여 찾아보고 적용하면 된다. + *

+ * Guide to the Synchronized Keyword in Java https://www.baeldung.com/java-synchronized */ @Test void testSynchronized() throws InterruptedException { diff --git a/study/src/test/java/thread/stage0/ThreadPoolsTest.java b/study/src/test/java/thread/stage0/ThreadPoolsTest.java index c3ecd5c80c..afe5ff6db2 100644 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ b/study/src/test/java/thread/stage0/ThreadPoolsTest.java @@ -9,14 +9,11 @@ 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 { diff --git a/tomcat/src/main/java/com/techcourse/Application.java b/tomcat/src/main/java/com/techcourse/Application.java index 6dc0e04e1d..dd81f6f6a0 100644 --- a/tomcat/src/main/java/com/techcourse/Application.java +++ b/tomcat/src/main/java/com/techcourse/Application.java @@ -1,6 +1,6 @@ package com.techcourse; -import org.apache.catalina.startup.Tomcat; +import org.apache.coyote.startup.Tomcat; public class Application { diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index d3fa57feeb..f1c4a0567a 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -10,10 +10,7 @@ public class InMemoryUserRepository { private static final Map database = new ConcurrentHashMap<>(); - static { - final User user = new User(1L, "gugu", "password", "hkkang@woowahan.com"); - database.put(user.getAccount(), user); - } + private InMemoryUserRepository() {} public static void save(User user) { database.put(user.getAccount(), user); @@ -23,5 +20,7 @@ public static Optional findByAccount(String account) { return Optional.ofNullable(database.get(account)); } - private InMemoryUserRepository() {} + public static void reset() { + database.clear(); + } } diff --git a/tomcat/src/main/java/org/apache/catalina/ServletContainer.java b/tomcat/src/main/java/org/apache/catalina/ServletContainer.java new file mode 100644 index 0000000000..ae74f93aaa --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/ServletContainer.java @@ -0,0 +1,20 @@ +package org.apache.catalina; + +import org.apache.catalina.controller.Controller; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; +import org.apache.coyote.processor.ControllerMapper; + +public class ServletContainer { + + private final ControllerMapper controllerMapper; + + public ServletContainer(ControllerMapper controllerMapper) { + this.controllerMapper = controllerMapper; + } + + public void run(HttpRequest httpRequest, HttpResponse httpResponse) { + Controller controller = controllerMapper.getController(httpRequest.getPath()); + controller.service(httpRequest, httpResponse); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/AbstractController.java b/tomcat/src/main/java/org/apache/catalina/controller/AbstractController.java new file mode 100644 index 0000000000..fc99718cf4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/AbstractController.java @@ -0,0 +1,37 @@ +package org.apache.catalina.controller; + +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.http.HttpMethod; +import org.apache.catalina.http.StatusCode; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; + +public abstract class AbstractController implements Controller { + + @Override + public void service(HttpRequest request, HttpResponse response) { + try { + if (request.isMethod(HttpMethod.GET)) { + doGet(request, response); + return; + } + if (request.isMethod(HttpMethod.POST)) { + doPost(request, response); + return; + } + throw new IllegalArgumentException("HttpMethod not found"); + } catch (IllegalArgumentException e) { + response.setStatusCode(StatusCode.FOUND); + response.addHeader(HeaderName.LOCATION, "/500.html"); + } + } + + protected void doPost(HttpRequest request, HttpResponse response) { + throw new IllegalArgumentException("HttpMethod unsupported"); + } + + protected void doGet(HttpRequest request, HttpResponse response) { + throw new IllegalArgumentException("HttpMethod unsupported"); + } +} + diff --git a/tomcat/src/main/java/org/apache/catalina/controller/Controller.java b/tomcat/src/main/java/org/apache/catalina/controller/Controller.java new file mode 100644 index 0000000000..7ca917421c --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/Controller.java @@ -0,0 +1,9 @@ +package org.apache.catalina.controller; + +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; + +public interface Controller { + + void service(HttpRequest request, HttpResponse response); +} diff --git a/tomcat/src/main/java/org/apache/coyote/controller/HomeController.java b/tomcat/src/main/java/org/apache/catalina/controller/HomeController.java similarity index 56% rename from tomcat/src/main/java/org/apache/coyote/controller/HomeController.java rename to tomcat/src/main/java/org/apache/catalina/controller/HomeController.java index 7b47ba6fdd..51ccda8134 100644 --- a/tomcat/src/main/java/org/apache/coyote/controller/HomeController.java +++ b/tomcat/src/main/java/org/apache/catalina/controller/HomeController.java @@ -1,8 +1,8 @@ -package org.apache.coyote.controller; +package org.apache.catalina.controller; -import org.apache.coyote.http.StatusCode; -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.response.HttpResponse; +import org.apache.catalina.http.StatusCode; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; public class HomeController extends AbstractController { diff --git a/tomcat/src/main/java/org/apache/catalina/controller/LoginController.java b/tomcat/src/main/java/org/apache/catalina/controller/LoginController.java new file mode 100644 index 0000000000..6ee925abcd --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/LoginController.java @@ -0,0 +1,61 @@ +package org.apache.catalina.controller; + +import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.model.User; +import java.util.Optional; +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.http.StatusCode; +import org.apache.catalina.manager.SessionManager; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; + +public class LoginController extends AbstractController { + + private final SessionManager sessionManager; + + public LoginController() { + this.sessionManager = SessionManager.getInstance(); + } + + @Override + protected void doPost(HttpRequest request, HttpResponse response) { + String account = request.getBodyParam("account"); + String password = request.getBodyParam("password"); + Optional user = InMemoryUserRepository.findByAccount(account); + if (!isMember(user, password)) { + response.setStatusCode(StatusCode.FOUND); + response.addHeader(HeaderName.LOCATION, "/401.html"); + + } + if (isMember(user, password)) { + response.setStatusCode(StatusCode.FOUND); + response.addHeader(HeaderName.LOCATION, "/index.html"); + login(response, user.get()); + } + } + + private boolean isMember(Optional user, String password) { + return user.isPresent() && user.get().checkPassword(password); + } + + private void login(HttpResponse response, User user) { + String sessionId = sessionManager.generateSession(user); + response.addSession(sessionId); + } + + @Override + protected void doGet(HttpRequest request, HttpResponse response) { + if (hasLogined(request)) { + response.setStatusCode(StatusCode.FOUND); + response.addHeader(HeaderName.LOCATION, "/index.html"); + } + if (!hasLogined(request)) { + response.setStatusCode(StatusCode.OK); + response.setBody("/login.html"); + } + } + + private boolean hasLogined(HttpRequest request) { + return request.hasSession() && sessionManager.isSessionExist(request.getSessionId()); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/controller/RegisterController.java b/tomcat/src/main/java/org/apache/catalina/controller/RegisterController.java similarity index 56% rename from tomcat/src/main/java/org/apache/coyote/controller/RegisterController.java rename to tomcat/src/main/java/org/apache/catalina/controller/RegisterController.java index 79a0b1be5b..eb5fd26f23 100644 --- a/tomcat/src/main/java/org/apache/coyote/controller/RegisterController.java +++ b/tomcat/src/main/java/org/apache/catalina/controller/RegisterController.java @@ -1,21 +1,19 @@ -package org.apache.coyote.controller; +package org.apache.catalina.controller; import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; -import org.apache.coyote.http.HeaderName; -import org.apache.coyote.http.StatusCode; -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.request.RequestBody; -import org.apache.coyote.response.HttpResponse; +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.http.StatusCode; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; public class RegisterController extends AbstractController { @Override protected void doPost(HttpRequest request, HttpResponse response) { - RequestBody requestBody = request.getBody(); - String account = requestBody.get("account"); - String password = requestBody.get("password"); - String email = requestBody.get("email"); + String account = request.getBodyParam("account"); + String password = request.getBodyParam("password"); + String email = request.getBodyParam("email"); InMemoryUserRepository.save(new User(account, password, email)); response.setStatusCode(StatusCode.FOUND); diff --git a/tomcat/src/main/java/org/apache/coyote/coockie/HttpCookie.java b/tomcat/src/main/java/org/apache/catalina/coockie/HttpCookie.java similarity index 55% rename from tomcat/src/main/java/org/apache/coyote/coockie/HttpCookie.java rename to tomcat/src/main/java/org/apache/catalina/coockie/HttpCookie.java index c8f5ccd3b1..1532757c62 100644 --- a/tomcat/src/main/java/org/apache/coyote/coockie/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/catalina/coockie/HttpCookie.java @@ -1,11 +1,18 @@ -package org.apache.coyote.coockie; +package org.apache.catalina.coockie; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; public class HttpCookie { + public static final String COOKIE_DELIMITER = "; "; + public static final String COOKIE_VALUE_DELIMITER = "="; + public static final int KEY_INDEX = 0; + public static final int VALUE_INDEX = 1; + public static final int KEY_VALUE_COUNT = 2; private static final String JSESSIONID_VALUE = "JSESSIONID"; private final Map cookies; @@ -19,18 +26,14 @@ public HttpCookie(String rawCookies) { } private static Map mapCookies(String rawCookies) { - Map cookieGroup = new HashMap<>(); - - if (rawCookies != null && !rawCookies.isBlank()) { - String[] cookiesElements = rawCookies.split("; "); - for (int i = 0; i < cookiesElements.length; i++) { - String[] cookiePair = cookiesElements[i].split("="); - if (cookiePair.length > 1) { - cookieGroup.put(cookiePair[0], cookiePair[1]); - } - } + if (rawCookies == null || rawCookies.isBlank()) { + return Map.of(); } - return cookieGroup; + + return Arrays.stream(rawCookies.split(COOKIE_DELIMITER)) + .map(cookie -> cookie.split(COOKIE_VALUE_DELIMITER)) + .filter(cookiePair -> cookiePair.length == KEY_VALUE_COUNT) + .collect(Collectors.toMap(cookiePair -> cookiePair[KEY_INDEX], cookiePair -> cookiePair[VALUE_INDEX])); } public boolean hasSessionId() { @@ -41,6 +44,10 @@ public String getSessionId() { return cookies.get(JSESSIONID_VALUE); } + public boolean hasCookie() { + return !cookies.isEmpty(); + } + public String getResponse() { if (cookies.isEmpty()) { return ""; @@ -49,7 +56,7 @@ public String getResponse() { StringBuilder response = new StringBuilder(); for (Entry stringStringEntry : cookies.entrySet()) { response.append(stringStringEntry.getKey()) - .append("=") + .append(COOKIE_VALUE_DELIMITER) .append(stringStringEntry.getValue()) .append(";"); } diff --git a/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java b/tomcat/src/main/java/org/apache/catalina/exception/UncheckedServletException.java similarity index 57% rename from tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java rename to tomcat/src/main/java/org/apache/catalina/exception/UncheckedServletException.java index 64466b42de..549a48bcb3 100644 --- a/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java +++ b/tomcat/src/main/java/org/apache/catalina/exception/UncheckedServletException.java @@ -1,8 +1,8 @@ -package com.techcourse.exception; +package org.apache.catalina.exception; public class UncheckedServletException extends RuntimeException { public UncheckedServletException(Exception e) { - super(e); + super("UncheckedServletException occured", e); } } diff --git a/tomcat/src/main/java/org/apache/catalina/http/HeaderName.java b/tomcat/src/main/java/org/apache/catalina/http/HeaderName.java new file mode 100644 index 0000000000..fec3ad723c --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/http/HeaderName.java @@ -0,0 +1,21 @@ +package org.apache.catalina.http; + +public enum HeaderName { + CONTENT_LENGTH("Content-Length"), + COOKIE("Cookie"), + CONTENT_TYPE("Content-Type"), + LOCATION("Location"), + SET_COOKIE("Set-Cookie"), + + ; + + private final String value; + + HeaderName(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http/HttpMethod.java b/tomcat/src/main/java/org/apache/catalina/http/HttpMethod.java similarity index 58% rename from tomcat/src/main/java/org/apache/coyote/http/HttpMethod.java rename to tomcat/src/main/java/org/apache/catalina/http/HttpMethod.java index ecc69cf8d8..487c55cde3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http/HttpMethod.java +++ b/tomcat/src/main/java/org/apache/catalina/http/HttpMethod.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http; +package org.apache.catalina.http; public enum HttpMethod { GET, diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java b/tomcat/src/main/java/org/apache/catalina/http/ProtocolVersion.java similarity index 87% rename from tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java rename to tomcat/src/main/java/org/apache/catalina/http/ProtocolVersion.java index 1955a3942c..238be35385 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java +++ b/tomcat/src/main/java/org/apache/catalina/http/ProtocolVersion.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.catalina.http; public enum ProtocolVersion { HTTP1_1("HTTP/1.1"), diff --git a/tomcat/src/main/java/org/apache/coyote/http/StatusCode.java b/tomcat/src/main/java/org/apache/catalina/http/StatusCode.java similarity index 92% rename from tomcat/src/main/java/org/apache/coyote/http/StatusCode.java rename to tomcat/src/main/java/org/apache/catalina/http/StatusCode.java index d521fda212..77cc8eaa8f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http/StatusCode.java +++ b/tomcat/src/main/java/org/apache/catalina/http/StatusCode.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http; +package org.apache.catalina.http; public enum StatusCode { OK(200, "OK"), diff --git a/tomcat/src/main/java/org/apache/coyote/manager/Manager.java b/tomcat/src/main/java/org/apache/catalina/manager/Manager.java similarity index 95% rename from tomcat/src/main/java/org/apache/coyote/manager/Manager.java rename to tomcat/src/main/java/org/apache/catalina/manager/Manager.java index 9d87f2970a..71459eb82b 100644 --- a/tomcat/src/main/java/org/apache/coyote/manager/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/manager/Manager.java @@ -1,7 +1,7 @@ -package org.apache.coyote.manager; +package org.apache.catalina.manager; import java.io.IOException; -import org.apache.coyote.session.Session; +import org.apache.catalina.session.Session; /** * A Manager manages the pool of Sessions that are associated with a diff --git a/tomcat/src/main/java/org/apache/coyote/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java similarity index 77% rename from tomcat/src/main/java/org/apache/coyote/manager/SessionManager.java rename to tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java index fe0c706c63..c0e8ab79c8 100644 --- a/tomcat/src/main/java/org/apache/coyote/manager/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -1,12 +1,12 @@ -package org.apache.coyote.manager; +package org.apache.catalina.manager; import com.techcourse.model.User; -import java.util.HashMap; import java.util.Map; -import org.apache.coyote.session.Session; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.catalina.session.Session; public class SessionManager implements Manager { - private static final Map SESSIONS = new HashMap<>(); + private static final Map SESSIONS = new ConcurrentHashMap<>(); private static SessionManager instance; private SessionManager() { @@ -14,7 +14,7 @@ private SessionManager() { public static SessionManager getInstance() { if (instance == null) { - return new SessionManager(); + instance = new SessionManager(); } return instance; } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/catalina/request/HttpRequest.java similarity index 87% rename from tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java rename to tomcat/src/main/java/org/apache/catalina/request/HttpRequest.java index e0c25a843f..f5f0ecb5e5 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/catalina/request/HttpRequest.java @@ -1,7 +1,7 @@ -package org.apache.coyote.request; +package org.apache.catalina.request; import java.util.List; -import org.apache.coyote.http.HttpMethod; +import org.apache.catalina.http.HttpMethod; public class HttpRequest { @@ -42,6 +42,10 @@ public String getHttpCookie() { return header.getHttpCookie(); } + public boolean hasCookie() { + return header.hasCookie(); + } + public boolean isMethod(HttpMethod httpMethod) { return requestLine.isMethod(httpMethod); } @@ -50,19 +54,15 @@ public boolean isStaticRequest() { return requestLine.isStaticRequest(); } - public boolean hasQueryParam() { - return requestLine.hasQueryParam(); - } - public boolean hasSession() { return header.hasSession(); } - public RequestBody getBody() { - return body; + public String getBodyParam(String parameter) { + return body.get(parameter); } - public String getBody(String parameter) { - return body.get(parameter); + public RequestBody getBody() { + return body; } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/QueuryParam.java b/tomcat/src/main/java/org/apache/catalina/request/QueuyParam.java similarity index 62% rename from tomcat/src/main/java/org/apache/coyote/request/QueuryParam.java rename to tomcat/src/main/java/org/apache/catalina/request/QueuyParam.java index 063ee3b15c..4b27665b13 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/QueuryParam.java +++ b/tomcat/src/main/java/org/apache/catalina/request/QueuyParam.java @@ -1,17 +1,19 @@ -package org.apache.coyote.request; +package org.apache.catalina.request; import java.util.HashMap; import java.util.Map; -public class QueuryParam { +public class QueuyParam { public static final String QUERY_INDICATOR = "?"; - public static final String QUERY_COMPONENT_DELIMITER = "&"; - public static final String QUERY_COMPONENT_VALUE_DELIMITER = "="; + public static final String COMPONENT_DELIMITER = "&"; + public static final String COMPONENT_VALUE_DELIMITER = "="; + public static final int KEY_INDEX = 0; + public static final int VALUE_INDEX = 1; private final Map params; - public QueuryParam(String requestLineEntry) { + public QueuyParam(String requestLineEntry) { this.params = mapQueryParam(requestLineEntry); } @@ -23,12 +25,12 @@ private Map mapQueryParam(String requestLineEntry) { int queryParamIndex = requestLineEntry.indexOf(QUERY_INDICATOR); String queryString = requestLineEntry.substring(queryParamIndex + 1); - String[] splittedQueryString = queryString.split(QUERY_COMPONENT_DELIMITER); + String[] splittedQueryString = queryString.split(COMPONENT_DELIMITER); for (String queryParamEntry : splittedQueryString) { mappedQueryParams.put( - queryParamEntry.split(QUERY_COMPONENT_VALUE_DELIMITER)[0], - queryParamEntry.split(QUERY_COMPONENT_VALUE_DELIMITER)[1] + queryParamEntry.split(COMPONENT_VALUE_DELIMITER)[KEY_INDEX], + queryParamEntry.split(COMPONENT_VALUE_DELIMITER)[VALUE_INDEX] ); } return mappedQueryParams; diff --git a/tomcat/src/main/java/org/apache/catalina/request/RequestBody.java b/tomcat/src/main/java/org/apache/catalina/request/RequestBody.java new file mode 100644 index 0000000000..59d5312027 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/request/RequestBody.java @@ -0,0 +1,43 @@ +package org.apache.catalina.request; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class RequestBody { + + public static final String COMPONENT_DELIMITER = "&"; + public static final String COMPONENT_VALUE_DELIMITER = "="; + public static final int KEY_INDEX = 0; + public static final int VALUE_INDEX = 1; + + private final Map body; + + public RequestBody() { + this.body = new HashMap<>(); + } + + public RequestBody(String bodyLine) { + this.body = mapBody(bodyLine); + } + + private Map mapBody(String bodyLine) { + if (bodyLine == null || bodyLine.isEmpty()) { + return Map.of(); + } + + return Arrays.stream(bodyLine.split(COMPONENT_DELIMITER)) + .map(bodyElement -> bodyElement.split(COMPONENT_VALUE_DELIMITER)) + .filter(bodyElements -> bodyElements.length == 2) + .collect(Collectors.toMap(bodyElements -> bodyElements[KEY_INDEX], + bodyElements -> bodyElements[VALUE_INDEX])); + } + + public String get(String key) { + if (!body.containsKey(key)) { + throw new IllegalArgumentException("Body parameter not found"); + } + return body.get(key); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestHeader.java b/tomcat/src/main/java/org/apache/catalina/request/RequestHeader.java similarity index 65% rename from tomcat/src/main/java/org/apache/coyote/request/RequestHeader.java rename to tomcat/src/main/java/org/apache/catalina/request/RequestHeader.java index 1b478cd98b..dc49643d8d 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestHeader.java +++ b/tomcat/src/main/java/org/apache/catalina/request/RequestHeader.java @@ -1,13 +1,18 @@ -package org.apache.coyote.request; +package org.apache.catalina.request; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.coyote.coockie.HttpCookie; -import org.apache.coyote.http.HeaderName; +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.coockie.HttpCookie; public class RequestHeader { + public static final String NAME_VALUE_DELIMITER = ": "; + public static final int NAME_VALUE_COUNT = 2; + public static final int NAME_INDEX = 0; + public static final int VALUE_INDEX = 1; + private final Map header; private final HttpCookie httpCookie; @@ -27,8 +32,8 @@ private Map mapHeader(List headerLines) { Map rawHeader = new HashMap<>(); for (String line : headerLines) { - String[] headerEntry = line.split(": ", 2); - rawHeader.put(headerEntry[0], headerEntry[1]); + String[] headerEntry = line.split(NAME_VALUE_DELIMITER, NAME_VALUE_COUNT); + rawHeader.put(headerEntry[NAME_INDEX], headerEntry[VALUE_INDEX]); } return rawHeader; } @@ -44,4 +49,8 @@ public String getSessionId() { public String getHttpCookie() { return httpCookie.getResponse(); } + + public boolean hasCookie() { + return httpCookie.hasCookie(); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java b/tomcat/src/main/java/org/apache/catalina/request/RequestLine.java similarity index 63% rename from tomcat/src/main/java/org/apache/coyote/request/RequestLine.java rename to tomcat/src/main/java/org/apache/catalina/request/RequestLine.java index 350fa4d465..913136674c 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java +++ b/tomcat/src/main/java/org/apache/catalina/request/RequestLine.java @@ -1,31 +1,37 @@ -package org.apache.coyote.request; +package org.apache.catalina.request; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import org.apache.coyote.http.HttpMethod; +import org.apache.catalina.http.HttpMethod; public class RequestLine { + public static final String COMPONENT_DELIMITER = " "; + public static final String PATH_INDICATOR = "?"; + public static final int METHOD_INDEX = 0; + public static final int PATH_INDEX = 1; + public static final int VERSION_OF_PROTOCOL_INDEX = 2; + private final HttpMethod httpMethod; private final String path; private final String versionOfProtocol; - private final QueuryParam queryParams; + private final QueuyParam queryParams; public RequestLine(String headerLines) { - String[] requestLineEntries = headerLines.split(" "); + String[] requestLineEntries = headerLines.split(COMPONENT_DELIMITER); - this.httpMethod = HttpMethod.valueOf(requestLineEntries[0]); - this.queryParams = new QueuryParam(requestLineEntries[1]); - this.path = mapPath(requestLineEntries[1]); - this.versionOfProtocol = requestLineEntries[2]; + this.httpMethod = HttpMethod.valueOf(requestLineEntries[METHOD_INDEX]); + this.queryParams = new QueuyParam(requestLineEntries[PATH_INDEX]); + this.path = mapPath(requestLineEntries[PATH_INDEX]); + this.versionOfProtocol = requestLineEntries[VERSION_OF_PROTOCOL_INDEX]; } private String mapPath(String path) { if (queryParams.hasQueryParam()) { - int queryStringIndex = path.indexOf("?"); + int queryStringIndex = path.indexOf(PATH_INDICATOR); return path.substring(0, queryStringIndex); } return path; diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/catalina/response/HttpResponse.java similarity index 55% rename from tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java rename to tomcat/src/main/java/org/apache/catalina/response/HttpResponse.java index 05c460a468..6dbd62aba2 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/catalina/response/HttpResponse.java @@ -1,7 +1,10 @@ -package org.apache.coyote.response; +package org.apache.catalina.response; -import org.apache.coyote.http.HeaderName; -import org.apache.coyote.http.StatusCode; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.http.StatusCode; public class HttpResponse { @@ -34,8 +37,25 @@ public void addHeader(HeaderName headerName, String value) { header.addHeader(headerName, value); } + public void setBody(String path, String contentType) { + body.setBody(path); + header.addHeader(HeaderName.CONTENT_LENGTH, String.valueOf(body.getLength())); + header.addHeader(HeaderName.CONTENT_TYPE, contentType); + } + public void setBody(String resource) { body.setBody(resource); header.addHeader(HeaderName.CONTENT_LENGTH, String.valueOf(body.getLength())); + String contentType; + try { + contentType = Files.probeContentType(new File(resource).toPath()); + } catch (IOException e) { + throw new IllegalArgumentException("Not found path"); + } + header.addHeader(HeaderName.CONTENT_TYPE, contentType); + } + + public void addSession(String sessionId) { + header.addSession(sessionId); } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/ResponseBody.java b/tomcat/src/main/java/org/apache/catalina/response/ResponseBody.java similarity index 86% rename from tomcat/src/main/java/org/apache/coyote/response/ResponseBody.java rename to tomcat/src/main/java/org/apache/catalina/response/ResponseBody.java index 105921ce7b..9834397160 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/ResponseBody.java +++ b/tomcat/src/main/java/org/apache/catalina/response/ResponseBody.java @@ -1,6 +1,7 @@ -package org.apache.coyote.response; +package org.apache.catalina.response; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -32,7 +33,7 @@ public void setBody(String resource) { } public int getLength() { - return content.length(); + return content.getBytes(StandardCharsets.UTF_8).length; } public String getContent() { diff --git a/tomcat/src/main/java/org/apache/coyote/response/ResponseHeader.java b/tomcat/src/main/java/org/apache/catalina/response/ResponseHeader.java similarity index 68% rename from tomcat/src/main/java/org/apache/coyote/response/ResponseHeader.java rename to tomcat/src/main/java/org/apache/catalina/response/ResponseHeader.java index 9079199079..05ffde41a7 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/ResponseHeader.java +++ b/tomcat/src/main/java/org/apache/catalina/response/ResponseHeader.java @@ -1,12 +1,14 @@ -package org.apache.coyote.response; +package org.apache.catalina.response; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import org.apache.coyote.http.HeaderName; +import org.apache.catalina.http.HeaderName; public class ResponseHeader { + public static final String SESSION_KEY = "JSESSIONID"; + public static final String SESSION_DELIMITER = "="; private final Map header; public ResponseHeader() { @@ -28,4 +30,8 @@ public String getResponse() { } return String.valueOf(response); } + + public void addSession(String sessionId) { + addHeader(HeaderName.SET_COOKIE, SESSION_KEY + SESSION_DELIMITER + sessionId); + } } diff --git a/tomcat/src/main/java/org/apache/catalina/response/StatusLine.java b/tomcat/src/main/java/org/apache/catalina/response/StatusLine.java new file mode 100644 index 0000000000..029ccd60d0 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/response/StatusLine.java @@ -0,0 +1,23 @@ +package org.apache.catalina.response; + +import org.apache.catalina.http.ProtocolVersion; +import org.apache.catalina.http.StatusCode; + +public class StatusLine { + + private final ProtocolVersion versionOfProtocol; + private StatusCode statusCode; + + public StatusLine() { + this.versionOfProtocol = ProtocolVersion.HTTP1_1; + this.statusCode = StatusCode.OK; + } + + public String getResponse() { + return "%s %s %s".formatted(versionOfProtocol.getValue(), statusCode.getCode(), statusCode.getMessage()); + } + + public void setStatusCode(StatusCode statusCode) { + this.statusCode = statusCode; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/session/Session.java b/tomcat/src/main/java/org/apache/catalina/session/Session.java similarity index 94% rename from tomcat/src/main/java/org/apache/coyote/session/Session.java rename to tomcat/src/main/java/org/apache/catalina/session/Session.java index 71b179e577..ecc62c36a4 100644 --- a/tomcat/src/main/java/org/apache/coyote/session/Session.java +++ b/tomcat/src/main/java/org/apache/catalina/session/Session.java @@ -1,4 +1,4 @@ -package org.apache.coyote.session; +package org.apache.catalina.session; import com.techcourse.model.User; import java.util.HashMap; diff --git a/tomcat/src/main/java/org/apache/coyote/connector/Connector.java b/tomcat/src/main/java/org/apache/coyote/connector/Connector.java index 4a132ed9f9..9d715716a3 100644 --- a/tomcat/src/main/java/org/apache/coyote/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/coyote/connector/Connector.java @@ -1,13 +1,14 @@ package org.apache.coyote.connector; -import org.apache.coyote.processor.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.processor.Http11Processor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Connector implements Runnable { @@ -15,15 +16,18 @@ 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_THREAD_COUNT = 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_THREAD_COUNT); } - public Connector(final int port, final int acceptCount) { + public Connector(final int port, final int acceptCount, final int maxThreads) { + this.executorService = Executors.newFixedThreadPool(maxThreads); this.serverSocket = createServerSocket(port, acceptCount); this.stopped = false; } @@ -67,7 +71,7 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + executorService.submit(() -> processor.run()); } public void stop() { diff --git a/tomcat/src/main/java/org/apache/coyote/connector/RequestReader.java b/tomcat/src/main/java/org/apache/coyote/connector/RequestReader.java new file mode 100644 index 0000000000..7ade67b6a5 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/connector/RequestReader.java @@ -0,0 +1,47 @@ +package org.apache.coyote.connector; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import org.apache.catalina.request.HttpRequest; + +public class RequestReader { + + private RequestReader() { + } + + public static HttpRequest readRequest(InputStream inputStream) throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + List headerLines = new ArrayList<>(); + + String contentLengthHeader = readHeader(bufferedReader, headerLines); + String bodyLine = readBody(bufferedReader, contentLengthHeader); + return new HttpRequest(headerLines, bodyLine); + } + + private static String readHeader(BufferedReader bufferedReader, List headerLines) throws IOException { + String rawLine; + String contentLengthHeader = null; + while ((rawLine = bufferedReader.readLine()) != null && !rawLine.isEmpty()) { + headerLines.add(rawLine); + if (rawLine.startsWith("Content-Length")) { + contentLengthHeader = rawLine.split(": ")[1]; + } + } + return contentLengthHeader; + } + + private static String readBody(BufferedReader bufferedReader, String contentLengthHeader) throws IOException { + if (contentLengthHeader == null) { + return ""; + } + int contentLength = Integer.parseInt(contentLengthHeader); + char[] buffer = new char[contentLength]; + bufferedReader.read(buffer, 0, contentLength); + return new String(buffer); + } +} + diff --git a/tomcat/src/main/java/org/apache/coyote/controller/AbstractController.java b/tomcat/src/main/java/org/apache/coyote/controller/AbstractController.java deleted file mode 100644 index 996689650a..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/controller/AbstractController.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.apache.coyote.controller; - -import org.apache.coyote.http.HttpMethod; -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.response.HttpResponse; - -public abstract class AbstractController implements Controller { - - @Override - public void service(HttpRequest request, HttpResponse response) { - if (request.isMethod(HttpMethod.GET)) { - doGet(request, response); - return; - } - if (request.isMethod(HttpMethod.POST)) { - doPost(request, response); - return; - } - throw new IllegalArgumentException("HttpMethod not found"); - } - - protected void doPost(HttpRequest request, HttpResponse response) { /* NOOP */ } - - protected void doGet(HttpRequest request, HttpResponse response) { /* NOOP */ } -} - diff --git a/tomcat/src/main/java/org/apache/coyote/controller/Controller.java b/tomcat/src/main/java/org/apache/coyote/controller/Controller.java deleted file mode 100644 index d97ddb99bb..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/controller/Controller.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.apache.coyote.controller; - -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.response.HttpResponse; - -public interface Controller { - - void service(HttpRequest request, HttpResponse response); -} diff --git a/tomcat/src/main/java/org/apache/coyote/controller/LoginController.java b/tomcat/src/main/java/org/apache/coyote/controller/LoginController.java deleted file mode 100644 index 7e84cca85d..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/controller/LoginController.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.apache.coyote.controller; - -import com.techcourse.db.InMemoryUserRepository; -import com.techcourse.model.User; -import java.util.Optional; -import org.apache.coyote.http.HeaderName; -import org.apache.coyote.http.StatusCode; -import org.apache.coyote.manager.SessionManager; -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.response.HttpResponse; - -public class LoginController extends AbstractController { - - private final SessionManager sessionManager; - - public LoginController() { - this.sessionManager = SessionManager.getInstance(); - } - - @Override - protected void doPost(HttpRequest request, HttpResponse response) { - String account = request.getBody("account"); - String password = request.getBody("password"); - Optional user = InMemoryUserRepository.findByAccount(account); - - if (user.isEmpty() || !user.get().checkPassword(password)) { - response.setStatusCode(StatusCode.UNAUTHORIZED); - response.setBody("/401.html"); - } - if (user.isPresent() && user.get().checkPassword(password)) { - response.setStatusCode(StatusCode.FOUND); - response.setBody("/index.html"); - - String sessionId = sessionManager.generateSession(user.get()); - response.addHeader(HeaderName.SET_COOKIE, "JSESSIONID=" + sessionId); - } - } - - @Override - protected void doGet(HttpRequest request, HttpResponse response) { - if (request.hasSession() && sessionManager.isSessionExist(request.getSessionId())) { - response.setStatusCode(StatusCode.FOUND); // - response.addHeader(HeaderName.LOCATION, "/index.html"); - } - if (!request.hasSession() || !sessionManager.isSessionExist(request.getSessionId())) { - response.setStatusCode(StatusCode.OK); - response.setBody("/login.html"); - } - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/controller/StaticController.java b/tomcat/src/main/java/org/apache/coyote/controller/StaticController.java deleted file mode 100644 index 8611504b89..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/controller/StaticController.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.apache.coyote.controller; - -import org.apache.coyote.http.StatusCode; -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.response.HttpResponse; - -public class StaticController extends AbstractController { - - @Override - protected void doGet(HttpRequest request, HttpResponse response) { - response.setStatusCode(StatusCode.OK); - response.setBody(request.getPath()); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http/HeaderName.java b/tomcat/src/main/java/org/apache/coyote/http/HeaderName.java deleted file mode 100644 index b29f1f2c8c..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http/HeaderName.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.apache.coyote.http; - -import java.util.Arrays; - -public enum HeaderName { - CONTENT_LENGTH("Content-Length"), - COOKIE("Cookie"), - CONTENT_TYPE("Content-Type"), - LOCATION("Location"), - SET_COOKIE("Set-Cookie"), - - ; - - private final String value; - - HeaderName(String value) { - this.value = value; - } - - public static HeaderName findByName(String name) { - return Arrays.stream(values()) - .filter(headerName -> headerName.value.equals(name)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("HeaderName not found")); - } - - public String getValue() { - return value; - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Dispatcher.java b/tomcat/src/main/java/org/apache/coyote/http11/Dispatcher.java deleted file mode 100644 index ee0807aef8..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/Dispatcher.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.apache.coyote.http11; - -import java.util.Map; -import org.apache.coyote.controller.Controller; -import org.apache.coyote.controller.HomeController; -import org.apache.coyote.controller.LoginController; -import org.apache.coyote.controller.RegisterController; -import org.apache.coyote.controller.StaticController; -import org.apache.coyote.http.HeaderName; -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.response.HttpResponse; - -public class Dispatcher { - - private final Map controllers; - private final Controller staticController; - - public Dispatcher() { - this.controllers = Map.of( - "/login", new LoginController(), - "/register", new RegisterController(), - "/", new HomeController() - ); - this.staticController = new StaticController(); - } - - public void run(HttpRequest httpRequest, HttpResponse httpResponse) { - httpResponse.addHeader(HeaderName.CONTENT_TYPE, httpRequest.getContentType()); - httpResponse.addHeader(HeaderName.SET_COOKIE, httpRequest.getHttpCookie()); - - if (httpRequest.isStaticRequest()) { - staticController.service(httpRequest, httpResponse); - } - if (!httpRequest.isStaticRequest()) { - Controller controller = controllers.get(httpRequest.getPath()); - controller.service(httpRequest, httpResponse); - } - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/processor/ControllerMapper.java b/tomcat/src/main/java/org/apache/coyote/processor/ControllerMapper.java new file mode 100644 index 0000000000..5312674743 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/processor/ControllerMapper.java @@ -0,0 +1,24 @@ +package org.apache.coyote.processor; + +import java.util.Map; +import org.apache.catalina.controller.Controller; +import org.apache.catalina.controller.HomeController; +import org.apache.catalina.controller.LoginController; +import org.apache.catalina.controller.RegisterController; + +public class ControllerMapper { + + private final Map controllers; + + public ControllerMapper() { + this.controllers = Map.of( + "/login", new LoginController(), + "/register", new RegisterController(), + "/", new HomeController() + ); + } + + public Controller getController(String path) { + return controllers.get(path); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/processor/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/processor/Http11Processor.java index c7260d3a59..8b22a4463d 100644 --- a/tomcat/src/main/java/org/apache/coyote/processor/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/processor/Http11Processor.java @@ -1,13 +1,14 @@ package org.apache.coyote.processor; -import com.techcourse.exception.UncheckedServletException; import java.io.IOException; import java.net.Socket; -import org.apache.coyote.Processor; -import org.apache.coyote.http11.Dispatcher; -import org.apache.coyote.request.HttpRequest; -import org.apache.coyote.request.RequestReader; -import org.apache.coyote.response.HttpResponse; +import org.apache.catalina.ServletContainer; +import org.apache.catalina.exception.UncheckedServletException; +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.http.StatusCode; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; +import org.apache.coyote.connector.RequestReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,9 +17,11 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); private final Socket connection; + private final ServletContainer servletContainer; public Http11Processor(final Socket connection) { this.connection = connection; + this.servletContainer = new ServletContainer(new ControllerMapper()); } @Override @@ -35,8 +38,14 @@ public void process(final Socket connection) { HttpRequest httpRequest = RequestReader.readRequest(inputStream); HttpResponse httpResponse = new HttpResponse(); - Dispatcher dispatcher = new Dispatcher(); - dispatcher.run(httpRequest, httpResponse); + if (httpRequest.isStaticRequest()) { + httpResponse.addHeader(HeaderName.CONTENT_TYPE, httpRequest.getContentType()); + httpResponse.setStatusCode(StatusCode.OK); + httpResponse.setBody(httpRequest.getPath(), httpRequest.getContentType()); + } + if (!httpRequest.isStaticRequest()) { + servletContainer.run(httpRequest, httpResponse); + } outputStream.write(httpResponse.getReponse().getBytes()); outputStream.flush(); diff --git a/tomcat/src/main/java/org/apache/coyote/Processor.java b/tomcat/src/main/java/org/apache/coyote/processor/Processor.java similarity index 96% rename from tomcat/src/main/java/org/apache/coyote/Processor.java rename to tomcat/src/main/java/org/apache/coyote/processor/Processor.java index 6604ab83de..6d8113ee09 100644 --- a/tomcat/src/main/java/org/apache/coyote/Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/processor/Processor.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.coyote; +package org.apache.coyote.processor; import java.net.Socket; diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java deleted file mode 100644 index 75827379bd..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.apache.coyote.request; - -import java.util.HashMap; -import java.util.Map; - -public class RequestBody { - - private final Map body; - - public RequestBody() { - this.body = new HashMap<>(); - } - - public RequestBody(String bodyLine) { - this.body = mapBody(bodyLine); - } - - private Map mapBody(String bodyLine) { - Map rawBody = new HashMap<>(); - - String[] bodyElements = bodyLine.split("&"); - for (int i = 0; i < bodyElements.length; i++) { - String[] info = bodyElements[i].split("="); - rawBody.put(info[0], info[1]); - } - return rawBody; - } - - public String get(String key) { - return body.get(key); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestReader.java b/tomcat/src/main/java/org/apache/coyote/request/RequestReader.java deleted file mode 100644 index 6925b67851..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestReader.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.apache.coyote.request; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; - -public class RequestReader { - - public static HttpRequest readRequest(InputStream inputStream) { - InputStreamReader inputStreamReader = new InputStreamReader(inputStream); - BufferedReader bufferedReader = new BufferedReader(inputStreamReader); - - List headerLines = new ArrayList<>(); - String rawLine; - String contentLengthHeader = ""; - try { - while ((rawLine = bufferedReader.readLine()) != null && !rawLine.isEmpty()) { - headerLines.add(rawLine); - - if (rawLine.startsWith("Content-Length")) { - contentLengthHeader = rawLine.split(": ")[1]; - } - } - - String bodyLine = ""; - if (!contentLengthHeader.isEmpty()) { - bodyLine = readBody(bufferedReader, contentLengthHeader); - } - - return new HttpRequest(headerLines, bodyLine); - - } catch (IOException e) { - throw new IllegalStateException("Error reading input stream", e); - } - } - - private static String readBody(BufferedReader bufferedReader, String contentLengthHeader) throws IOException { - int contentLength = Integer.parseInt(contentLengthHeader); - char[] buffer = new char[contentLength]; - bufferedReader.read(buffer, 0, contentLength); // 본문을 버퍼로 읽어들임 - return new String(buffer); // 읽은 버퍼를 문자열로 변환 - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java b/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java deleted file mode 100644 index 189b283e71..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.apache.coyote.response; - -import org.apache.coyote.http.StatusCode; -import org.apache.coyote.http11.ProtocolVersion; - -public class StatusLine { - - private final ProtocolVersion versionOfProtocol; - private StatusCode statusCode; - - public StatusLine() { - this.versionOfProtocol = ProtocolVersion.HTTP1_1; - this.statusCode = StatusCode.OK; - } - - public String getResponse() { - StringBuilder response = new StringBuilder(); - response.append(versionOfProtocol.getValue()) - .append(" ") - .append(statusCode.getCode()) - .append(" ") - .append(statusCode.getMessage()); - return String.valueOf(response); - } - - public void setStatusCode(StatusCode statusCode) { - this.statusCode = statusCode; - } -} diff --git a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java b/tomcat/src/main/java/org/apache/coyote/startup/Tomcat.java similarity index 94% rename from tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java rename to tomcat/src/main/java/org/apache/coyote/startup/Tomcat.java index 3027877bc6..508e3cc910 100644 --- a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java +++ b/tomcat/src/main/java/org/apache/coyote/startup/Tomcat.java @@ -1,11 +1,10 @@ -package org.apache.catalina.startup; +package org.apache.coyote.startup; +import java.io.IOException; import org.apache.coyote.connector.Connector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; - public class Tomcat { private static final Logger log = LoggerFactory.getLogger(Tomcat.class); diff --git a/tomcat/src/test/java/org/apache/catalina/controller/AbstractControllerTest.java b/tomcat/src/test/java/org/apache/catalina/controller/AbstractControllerTest.java new file mode 100644 index 0000000000..712280fd27 --- /dev/null +++ b/tomcat/src/test/java/org/apache/catalina/controller/AbstractControllerTest.java @@ -0,0 +1,39 @@ +package org.apache.catalina.controller; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; +import org.junit.jupiter.api.Test; + +class AbstractControllerTest { + + @Test + void doGet() { + // given + AbstractController abstractController = new TestController(); + HttpRequest request = new HttpRequest(List.of("GET / HTTP/1.1"), ""); + HttpResponse response = new HttpResponse(); + + // when + abstractController.service(request, response); + + // then + assertThat(response.getReponse()).contains("Content-Length: 1"); + } + + @Test + void doPost() { + // given + AbstractController abstractController = new TestController(); + HttpRequest request = new HttpRequest(List.of("POST / HTTP/1.1"), ""); + HttpResponse response = new HttpResponse(); + + // when + abstractController.service(request, response); + + // then + assertThat(response.getReponse()).contains("Content-Length: 0"); + } +} diff --git a/tomcat/src/test/java/org/apache/catalina/controller/HomeControllerTest.java b/tomcat/src/test/java/org/apache/catalina/controller/HomeControllerTest.java new file mode 100644 index 0000000000..9d6426c191 --- /dev/null +++ b/tomcat/src/test/java/org/apache/catalina/controller/HomeControllerTest.java @@ -0,0 +1,29 @@ +package org.apache.catalina.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; +import org.junit.jupiter.api.Test; + +class HomeControllerTest { + + @Test + void doGet() { + // given + HomeController homeController = new HomeController(); + HttpRequest request = new HttpRequest(List.of("GET / HTTP/1.1"), ""); + HttpResponse response = new HttpResponse(); + + // when + homeController.doGet(request, response); + + // then + assertAll( + () -> assertThat(response.getReponse()).contains("200 OK"), + () -> assertThat(response.getReponse()).contains("대시보드") + ); + } +} diff --git a/tomcat/src/test/java/org/apache/catalina/controller/LoginControllerTest.java b/tomcat/src/test/java/org/apache/catalina/controller/LoginControllerTest.java new file mode 100644 index 0000000000..950cb015a0 --- /dev/null +++ b/tomcat/src/test/java/org/apache/catalina/controller/LoginControllerTest.java @@ -0,0 +1,115 @@ +package org.apache.catalina.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.model.User; +import java.util.List; +import org.apache.catalina.manager.SessionManager; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; +import org.apache.catalina.session.Session; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class LoginControllerTest { + + private final SessionManager sessionManager; + + public LoginControllerTest() { + this.sessionManager = SessionManager.getInstance(); + } + + @BeforeEach + void initializeDatabase() { + InMemoryUserRepository.reset(); + } + + @Test + void doPost_로그인성공() { + // given + LoginController loginController = new LoginController(); + String account = "account"; + String password = "password"; + String email = "email"; + InMemoryUserRepository.save(new User(account, password, email)); + + HttpRequest request = new HttpRequest( + List.of("POST / HTTP/1.1"), + "account=" + account + "&password=" + password + "&email=" + email + ); + HttpResponse response = new HttpResponse(); + + // when + loginController.doPost(request, response); + + // then + assertAll( + () -> assertThat(response.getReponse()).contains("302 FOUND"), + () -> assertThat(response.getReponse()).contains("Location: /index.html") + ); + } + + @Test + void doPost_로그인실패() { + // given + LoginController loginController = new LoginController(); + String account = "account"; + String password = "password"; + String email = "email"; + HttpRequest request = new HttpRequest( + List.of("POST / HTTP/1.1"), + "account=" + account + "&password=" + password + "&email=" + email + ); + HttpResponse response = new HttpResponse(); + + // when + loginController.doPost(request, response); + + // then + assertAll( + () -> assertThat(response.getReponse()).contains("302 FOUND"), + () -> assertThat(response.getReponse()).contains("Location: /401.html") + ); + } + + @Test + void doGet_로그인_안한_상태() { + // given + LoginController loginController = new LoginController(); + HttpRequest request = new HttpRequest(List.of("GET / HTTP/1.1"), ""); + HttpResponse response = new HttpResponse(); + + // when + loginController.doGet(request, response); + + // then + assertAll( + () -> assertThat(response.getReponse()).contains("200 OK"), + () -> assertThat(response.getReponse()).contains("로그인") + ); + } + + @Test + void doGet_로그인_한_상태() { + // given + LoginController loginController = new LoginController(); + Session session = new Session(new User("account", "password", "email")); + sessionManager.add(session); + HttpRequest request = new HttpRequest(List.of( + "GET / HTTP/1.1", + "Cookie: JSESSIONID=" + session.getId() + "; " + ), ""); + HttpResponse response = new HttpResponse(); + + // when + loginController.doGet(request, response); + + // then + assertAll( + () -> assertThat(response.getReponse()).contains("302 FOUND"), + () -> assertThat(response.getReponse()).contains("Location: /index.html") + ); + } +} diff --git a/tomcat/src/test/java/org/apache/catalina/controller/RegisterControllerTest.java b/tomcat/src/test/java/org/apache/catalina/controller/RegisterControllerTest.java new file mode 100644 index 0000000000..86e72076aa --- /dev/null +++ b/tomcat/src/test/java/org/apache/catalina/controller/RegisterControllerTest.java @@ -0,0 +1,52 @@ +package org.apache.catalina.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; +import org.junit.jupiter.api.Test; + +class RegisterControllerTest { + @Test + void doPost() { + // given + RegisterController registerController = new RegisterController(); + String account = "account"; + String password = "password"; + String email = "email"; + + HttpRequest request = new HttpRequest( + List.of("POST / HTTP/1.1"), + "account=" + account + "&password=" + password + "&email=" + email + ); + HttpResponse response = new HttpResponse(); + + // when + registerController.doPost(request, response); + + // then + assertAll( + () -> assertThat(response.getReponse()).contains("302 FOUND"), + () -> assertThat(response.getReponse()).contains("Location: /index.html") + ); + } + + @Test + void doGet() { + // given + RegisterController registerController = new RegisterController(); + HttpRequest request = new HttpRequest(List.of("GET / HTTP/1.1"), ""); + HttpResponse response = new HttpResponse(); + + // when + registerController.doGet(request, response); + + // then + assertAll( + () -> assertThat(response.getReponse()).contains("200 OK"), + () -> assertThat(response.getReponse()).contains("회원가입") + ); + } +} diff --git a/tomcat/src/test/java/org/apache/catalina/controller/TestController.java b/tomcat/src/test/java/org/apache/catalina/controller/TestController.java new file mode 100644 index 0000000000..dea486a4d2 --- /dev/null +++ b/tomcat/src/test/java/org/apache/catalina/controller/TestController.java @@ -0,0 +1,17 @@ +package org.apache.catalina.controller; + +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.request.HttpRequest; +import org.apache.catalina.response.HttpResponse; + +public class TestController extends AbstractController { + @Override + protected void doPost(HttpRequest request, HttpResponse response) { + response.addHeader(HeaderName.CONTENT_LENGTH, "0"); + } + + @Override + protected void doGet(HttpRequest request, HttpResponse response) { + response.addHeader(HeaderName.CONTENT_LENGTH, "1"); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/coockie/HttpCookieTest.java b/tomcat/src/test/java/org/apache/coyote/coockie/HttpCookieTest.java index 101a9a1f94..9ad599f31b 100644 --- a/tomcat/src/test/java/org/apache/coyote/coockie/HttpCookieTest.java +++ b/tomcat/src/test/java/org/apache/coyote/coockie/HttpCookieTest.java @@ -1,6 +1,8 @@ package org.apache.coyote.coockie; import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.catalina.coockie.HttpCookie; import org.junit.jupiter.api.Test; class HttpCookieTest { diff --git a/tomcat/src/test/java/org/apache/coyote/processor/ControllerMapperTest.java b/tomcat/src/test/java/org/apache/coyote/processor/ControllerMapperTest.java new file mode 100644 index 0000000000..50dad28f14 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/processor/ControllerMapperTest.java @@ -0,0 +1,22 @@ +package org.apache.coyote.processor; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.catalina.controller.Controller; +import org.apache.catalina.controller.HomeController; +import org.junit.jupiter.api.Test; + +class ControllerMapperTest { + + @Test + void getController() { + // given + ControllerMapper controllerMapper = new ControllerMapper(); + + // when + Controller controller = controllerMapper.getController("/"); + + // then + assertThat(controller.getClass()).isEqualTo(HomeController.class); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/request/QueuryParamTest.java b/tomcat/src/test/java/org/apache/coyote/request/QueuryParamTest.java deleted file mode 100644 index 835a948993..0000000000 --- a/tomcat/src/test/java/org/apache/coyote/request/QueuryParamTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.apache.coyote.request; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -class QueuryParamTest { - - @Test - void hasQueryParam() { - QueuryParam queuryParam = new QueuryParam("GET /index.html?name=kirby&part=backend HTTP/1.1"); - assertThat(queuryParam.hasQueryParam()).isTrue(); - } - - @Test - void getQueryParam() { - QueuryParam queuryParam = new QueuryParam("GET /index.html?name=kirby&part=backend HTTP/1.1"); - assertThat(queuryParam.getQueryParam("name")).isEqualTo("kirby"); - } -} diff --git a/tomcat/src/test/java/org/apache/coyote/request/QueuyParamTest.java b/tomcat/src/test/java/org/apache/coyote/request/QueuyParamTest.java new file mode 100644 index 0000000000..3b0447e52e --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/request/QueuyParamTest.java @@ -0,0 +1,21 @@ +package org.apache.coyote.request; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.catalina.request.QueuyParam; +import org.junit.jupiter.api.Test; + +class QueuyParamTest { + + @Test + void hasQueryParam() { + QueuyParam queuyParam = new QueuyParam("GET /index.html?name=kirby&part=backend HTTP/1.1"); + assertThat(queuyParam.hasQueryParam()).isTrue(); + } + + @Test + void getQueryParam() { + QueuyParam queuyParam = new QueuyParam("GET /index.html?name=kirby&part=backend HTTP/1.1"); + assertThat(queuyParam.getQueryParam("name")).isEqualTo("kirby"); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java index 756c4c534e..5446928d66 100644 --- a/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java @@ -1,8 +1,8 @@ package org.apache.coyote.request; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import org.apache.catalina.request.RequestBody; import org.junit.jupiter.api.Test; class RequestBodyTest { diff --git a/tomcat/src/test/java/org/apache/coyote/request/RequestHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestHeaderTest.java index 1f982c12bf..d725afb904 100644 --- a/tomcat/src/test/java/org/apache/coyote/request/RequestHeaderTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestHeaderTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; +import org.apache.catalina.request.RequestHeader; import org.junit.jupiter.api.Test; class RequestHeaderTest { diff --git a/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java index fea726836e..819c2f4e58 100644 --- a/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java @@ -2,7 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.apache.coyote.http.HttpMethod; +import org.apache.catalina.http.HttpMethod; +import org.apache.catalina.request.RequestLine; import org.junit.jupiter.api.Test; class RequestLineTest { diff --git a/tomcat/src/test/java/org/apache/coyote/response/HttpResponseTest.java b/tomcat/src/test/java/org/apache/coyote/response/HttpResponseTest.java index 32a627ed7e..b768f3c6d2 100644 --- a/tomcat/src/test/java/org/apache/coyote/response/HttpResponseTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/HttpResponseTest.java @@ -2,8 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.apache.coyote.http.HeaderName; -import org.apache.coyote.http.StatusCode; +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.http.StatusCode; +import org.apache.catalina.response.HttpResponse; import org.junit.jupiter.api.Test; class HttpResponseTest { @@ -43,5 +44,6 @@ class HttpResponseTest { // then assertThat(response.getReponse()).contains("대시보드"); + assertThat(response.getReponse()).contains("Content-Length: 5670"); } } diff --git a/tomcat/src/test/java/org/apache/coyote/response/ResponseBodyTest.java b/tomcat/src/test/java/org/apache/coyote/response/ResponseBodyTest.java index 9f16c17bd0..5eef1c223b 100644 --- a/tomcat/src/test/java/org/apache/coyote/response/ResponseBodyTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/ResponseBodyTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import org.apache.catalina.response.ResponseBody; import org.junit.jupiter.api.Test; class ResponseBodyTest { diff --git a/tomcat/src/test/java/org/apache/coyote/response/ResponseHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/response/ResponseHeaderTest.java index 3f575452d4..d1688ac1f7 100644 --- a/tomcat/src/test/java/org/apache/coyote/response/ResponseHeaderTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/ResponseHeaderTest.java @@ -2,7 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.apache.coyote.http.HeaderName; +import org.apache.catalina.http.HeaderName; +import org.apache.catalina.response.ResponseHeader; import org.junit.jupiter.api.Test; class ResponseHeaderTest { diff --git a/tomcat/src/test/java/org/apache/coyote/response/StatusLineTest.java b/tomcat/src/test/java/org/apache/coyote/response/StatusLineTest.java index cd06411390..cd55a078de 100644 --- a/tomcat/src/test/java/org/apache/coyote/response/StatusLineTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/StatusLineTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import org.apache.catalina.response.StatusLine; import org.junit.jupiter.api.Test; class StatusLineTest {