From 1ae577058cf3a5102cb15b0c825f682d2b6afcde Mon Sep 17 00:00:00 2001 From: YoungHoon Choi <90854702+younghoondoodoom@users.noreply.github.com> Date: Thu, 14 Sep 2023 17:51:26 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B9=A0=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EB=B0=98=EC=98=81=ED=95=B4=EC=A3=BC=EC=85=A8?= =?UTF-8?q?=EB=84=A4=EC=9A=94!=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EA=B0=80=20?= =?UTF-8?q?=EC=97=86=EC=96=B4=EC=84=9C=20=EC=95=84=EC=89=BD=EB=84=A4?= =?UTF-8?q?=EC=9A=94,=20=EC=B6=94=ED=9B=84=EC=97=90=20=EA=BC=AD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=B4=EC=A3=BC=EC=84=B8=EC=9A=94!=20?= =?UTF-8?q?=EA=B3=A0=EC=83=9D=ED=95=98=EC=85=A8=EC=8A=B5=EB=8B=88=EB=8B=A4?= =?UTF-8?q?~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리뷰 빠르게 반영해주셨네요! 테스트가 없어서 아쉽네요, 추후에 꼭 추가해주세요! 고생하셨습니다~ --- .../example/cachecontrol/CacheWebConfig.java | 9 ++ .../example/etag/EtagFilterConfiguration.java | 14 ++- .../version/CacheBustingWebConfig.java | 7 +- study/src/main/resources/application.yml | 10 +- .../thread/stage0/SynchronizationTest.java | 17 ++- .../java/thread/stage0/ThreadPoolsTest.java | 17 ++- .../java/thread/stage1/ConcurrencyTest.java | 8 +- .../src/main/java/nextstep/Application.java | 20 +--- .../java/nextstep/jwp/DispatcherServlet.java | 29 +++++ .../java/nextstep/jwp/HandlerMapping.java | 19 ++++ .../java/nextstep/jwp/HandlerResolver.java | 26 ----- .../java/nextstep/jwp/JwpHttpDispatcher.java | 46 -------- .../jwp/controller/AbstractController.java | 29 +++++ .../nextstep/jwp/controller/Controller.java | 9 ++ .../jwp/controller/LoginController.java | 102 ++++++++++++++++++ .../jwp/controller/RegisterController.java | 49 +++++++++ .../jwp/controller/RootGetController.java | 16 +++ .../jwp/handler/get/LoginGetHandler.java | 52 --------- .../jwp/handler/get/RegisterGetHandler.java | 30 ------ .../jwp/handler/get/RootGetHandler.java | 15 --- .../jwp/handler/post/LoginPostHandler.java | 100 ----------------- .../jwp/handler/post/RegisterPostHandler.java | 56 ---------- .../apache/catalina/connector/Connector.java | 22 ++-- .../org/apache/catalina/startup/Tomcat.java | 8 +- .../java/org/apache/coyote/http11/Cookie.java | 4 + .../org/apache/coyote/http11/Handler.java | 10 -- .../apache/coyote/http11/Http11Processor.java | 59 ++++------ .../apache/coyote/http11/HttpDispatcher.java | 9 -- .../org/apache/coyote/http11/HttpHeader.java | 13 ++- .../org/apache/coyote/http11/HttpServlet.java | 9 ++ .../coyote/http11/ResponseGenerator.java | 82 ++++++++++++++ .../apache/coyote/http11}/SessionManager.java | 3 +- .../coyote/http11/request/Http11Request.java | 41 ------- .../coyote/http11/request/HttpRequest.java | 67 ++++++++++++ .../http11/request/HttpRequestParser.java | 35 +++--- .../coyote/http11/request/RequestBody.java | 17 ++- .../http11/response/Http11Response.java | 51 --------- .../coyote/http11/response/HttpResponse.java | 91 ++++++++++++++++ .../coyote/http11/Http11ProcessorTest.java | 23 +--- 39 files changed, 643 insertions(+), 581 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/jwp/DispatcherServlet.java create mode 100644 tomcat/src/main/java/nextstep/jwp/HandlerMapping.java delete mode 100644 tomcat/src/main/java/nextstep/jwp/HandlerResolver.java delete mode 100644 tomcat/src/main/java/nextstep/jwp/JwpHttpDispatcher.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/Controller.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/LoginController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/RootGetController.java delete mode 100644 tomcat/src/main/java/nextstep/jwp/handler/get/LoginGetHandler.java delete mode 100644 tomcat/src/main/java/nextstep/jwp/handler/get/RegisterGetHandler.java delete mode 100644 tomcat/src/main/java/nextstep/jwp/handler/get/RootGetHandler.java delete mode 100644 tomcat/src/main/java/nextstep/jwp/handler/post/LoginPostHandler.java delete mode 100644 tomcat/src/main/java/nextstep/jwp/handler/post/RegisterPostHandler.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Handler.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpDispatcher.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpServlet.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/ResponseGenerator.java rename tomcat/src/main/java/{nextstep/jwp => org/apache/coyote/http11}/SessionManager.java (90%) delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/Http11Request.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/Http11Response.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java index 305b1f1e1e..6916969e2a 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -1,13 +1,22 @@ package cache.com.example.cachecontrol; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.WebContentInterceptor; @Configuration public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + final CacheControl cacheControl = CacheControl.noCache() + .cachePrivate(); + + final WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); + webContentInterceptor.addCacheMapping(cacheControl, "/**"); + + registry.addInterceptor(webContentInterceptor); } } diff --git a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java index 41ef7a3d9a..c460db2d73 100644 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java @@ -1,12 +1,18 @@ package cache.com.example.etag; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ShallowEtagHeaderFilter; @Configuration public class EtagFilterConfiguration { -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + final FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); + filterRegistrationBean.addUrlPatterns("/etag"); + filterRegistrationBean.addUrlPatterns("/resources/*"); + return filterRegistrationBean; + } } diff --git a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java index 6da6d2c795..4d518a2ccc 100644 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java @@ -2,8 +2,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.concurrent.TimeUnit; @Configuration public class CacheBustingWebConfig implements WebMvcConfigurer { @@ -13,13 +15,14 @@ public class CacheBustingWebConfig implements WebMvcConfigurer { private final ResourceVersion version; @Autowired - public CacheBustingWebConfig(ResourceVersion version) { + public CacheBustingWebConfig(final ResourceVersion version) { this.version = version; } @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .addResourceLocations("classpath:/static/") + .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic()); } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..9989de98e3 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -3,7 +3,11 @@ handlebars: server: tomcat: - accept-count: 1 - max-connections: 1 + accept-count: 10 + max-connections: 2 threads: - max: 2 + max: 10 + + compression: + enabled: true + min-response-size: 10 diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java index 0333c18e3b..09c9059955 100644 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ b/study/src/test/java/thread/stage0/SynchronizationTest.java @@ -1,18 +1,17 @@ package thread.stage0; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; -import static org.assertj.core.api.Assertions.assertThat; - /** * 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다. * 자바는 공유 데이터에 대한 스레드 접근을 동기화(synchronization)하여 경쟁 조건을 방지한다. * 동기화된 블록은 하나의 스레드만 접근하여 실행할 수 있다. - * + *

* Synchronization * https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html */ @@ -21,14 +20,14 @@ class SynchronizationTest { /** * 테스트가 성공하도록 SynchronizedMethods 클래스에 동기화를 적용해보자. * synchronized 키워드에 대하여 찾아보고 적용하면 된다. - * + *

* Guide to the Synchronized Keyword in Java * https://www.baeldung.com/java-synchronized */ @Test void testSynchronized() throws InterruptedException { - var executorService = Executors.newFixedThreadPool(3); - var synchronizedMethods = new SynchronizedMethods(); + final var executorService = Executors.newFixedThreadPool(3); + final var synchronizedMethods = new SynchronizedMethods(); IntStream.range(0, 1000) .forEach(count -> executorService.submit(synchronizedMethods::calculate)); @@ -41,7 +40,7 @@ private static final class SynchronizedMethods { private int sum = 0; - public void calculate() { + public synchronized void calculate() { setSum(getSum() + 1); } @@ -49,7 +48,7 @@ public int getSum() { return sum; } - public void setSum(int sum) { + public void setSum(final int sum) { this.sum = sum; } } diff --git a/study/src/test/java/thread/stage0/ThreadPoolsTest.java b/study/src/test/java/thread/stage0/ThreadPoolsTest.java index 238611ebfe..f451da5f31 100644 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ b/study/src/test/java/thread/stage0/ThreadPoolsTest.java @@ -1,21 +1,20 @@ package thread.stage0; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; -import static org.assertj.core.api.Assertions.assertThat; - /** * 스레드 풀은 무엇이고 어떻게 동작할까? * 테스트를 통과시키고 왜 해당 결과가 나왔는지 생각해보자. - * + *

* 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 */ @@ -31,8 +30,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 +45,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()); @@ -57,7 +56,7 @@ private Runnable logWithSleep(final String message) { return () -> { try { Thread.sleep(1000); - } catch (InterruptedException e) { + } catch (final InterruptedException e) { throw new RuntimeException(e); } log.info(message); diff --git a/study/src/test/java/thread/stage1/ConcurrencyTest.java b/study/src/test/java/thread/stage1/ConcurrencyTest.java index f5e8ee070a..532eb3bcec 100644 --- a/study/src/test/java/thread/stage1/ConcurrencyTest.java +++ b/study/src/test/java/thread/stage1/ConcurrencyTest.java @@ -1,17 +1,17 @@ package thread.stage1; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + /** * 스레드를 다룰 때 어떤 상황을 조심해야 할까? * - 상태를 가진 한 객체를 여러 스레드에서 동시에 접근할 경우 * - static 변수를 가진 객체를 여러 스레드에서 동시에 접근할 경우 - * + *

* 위 경우는 동기화(synchronization)를 적용시키거나 객체가 상태를 갖지 않도록 한다. * 객체를 불변 객체로 만드는 방법도 있다. - * + *

* 웹서버는 여러 사용자가 동시에 접속을 시도하기 때문에 동시성 이슈가 생길 수 있다. * 어떤 사례가 있는지 아래 테스트 코드를 통해 알아보자. */ diff --git a/tomcat/src/main/java/nextstep/Application.java b/tomcat/src/main/java/nextstep/Application.java index 2de38178bd..9d412f4032 100644 --- a/tomcat/src/main/java/nextstep/Application.java +++ b/tomcat/src/main/java/nextstep/Application.java @@ -1,29 +1,11 @@ package nextstep; -import nextstep.jwp.HandlerResolver; -import nextstep.jwp.JwpHttpDispatcher; -import nextstep.jwp.SessionManager; -import nextstep.jwp.handler.get.LoginGetHandler; -import nextstep.jwp.handler.get.RegisterGetHandler; -import nextstep.jwp.handler.get.RootGetHandler; -import nextstep.jwp.handler.post.LoginPostHandler; -import nextstep.jwp.handler.post.RegisterPostHandler; import org.apache.catalina.startup.Tomcat; -import org.apache.coyote.http11.Handler; -import java.util.Map; public class Application { - private static final Map httpGetHandlers = - Map.of("/", new RootGetHandler(), - "/login", new LoginGetHandler(new SessionManager()), - "/register", new RegisterGetHandler()); - private static final Map httpPostHandlers = - Map.of("/login", new LoginPostHandler(new SessionManager()), - "/register", new RegisterPostHandler()); - public static void main(final String[] args) { - final var tomcat = new Tomcat(new JwpHttpDispatcher(new HandlerResolver(httpGetHandlers, httpPostHandlers))); + final var tomcat = new Tomcat(); tomcat.start(); } } diff --git a/tomcat/src/main/java/nextstep/jwp/DispatcherServlet.java b/tomcat/src/main/java/nextstep/jwp/DispatcherServlet.java new file mode 100644 index 0000000000..349720950b --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/DispatcherServlet.java @@ -0,0 +1,29 @@ +package nextstep.jwp; + +import nextstep.jwp.controller.Controller; +import org.apache.coyote.http11.ContentType; +import org.apache.coyote.http11.HttpServlet; +import org.apache.coyote.http11.StatusCode; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public class DispatcherServlet implements HttpServlet { + + private static final HandlerMapping handlerMapping = new HandlerMapping(); + + @Override + public void service(final HttpRequest httpRequest, final HttpResponse httpResponse) { + final Controller controller = handlerMapping.findController(httpRequest.getPath()); + if (controller == null) { + generateNotFoundController(httpResponse); + return; + } + controller.service(httpRequest, httpResponse); + } + + private void generateNotFoundController(final HttpResponse response) { + response.setStatusCode(StatusCode.NOT_FOUND) + .setContentType(ContentType.HTML) + .setRedirect("/404.html"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/HandlerMapping.java b/tomcat/src/main/java/nextstep/jwp/HandlerMapping.java new file mode 100644 index 0000000000..3bbb018d08 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/HandlerMapping.java @@ -0,0 +1,19 @@ +package nextstep.jwp; + +import nextstep.jwp.controller.Controller; +import nextstep.jwp.controller.LoginController; +import nextstep.jwp.controller.RegisterController; +import nextstep.jwp.controller.RootGetController; +import java.util.Map; + +public class HandlerMapping { + + private static final Map controllers = + Map.of("/", new RootGetController(), + "/login", new LoginController(), + "/register", new RegisterController()); + + public Controller findController(final String path) { + return controllers.get(path); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/HandlerResolver.java b/tomcat/src/main/java/nextstep/jwp/HandlerResolver.java deleted file mode 100644 index e983e7f5d8..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/HandlerResolver.java +++ /dev/null @@ -1,26 +0,0 @@ -package nextstep.jwp; - -import org.apache.coyote.http11.Handler; -import org.apache.coyote.http11.HttpMethod; -import java.util.Map; - -public class HandlerResolver { - - private final Map httpGetHandlers; - private final Map httpPostHandlers; - - public HandlerResolver(final Map httpGetHandlers, final Map httpPostHandlers) { - this.httpGetHandlers = httpGetHandlers; - this.httpPostHandlers = httpPostHandlers; - } - - public Handler resolve(final HttpMethod httpMethod, final String path) { - if (httpMethod.equals(HttpMethod.GET)) { - return httpGetHandlers.get(path); - } - if (httpMethod.equals(HttpMethod.POST)) { - return httpPostHandlers.get(path); - } - return null; - } -} diff --git a/tomcat/src/main/java/nextstep/jwp/JwpHttpDispatcher.java b/tomcat/src/main/java/nextstep/jwp/JwpHttpDispatcher.java deleted file mode 100644 index c2f14e8f12..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/JwpHttpDispatcher.java +++ /dev/null @@ -1,46 +0,0 @@ -package nextstep.jwp; - -import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Handler; -import org.apache.coyote.http11.HttpDispatcher; -import org.apache.coyote.http11.StatusCode; -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.response.Http11Response; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Objects; - -public class JwpHttpDispatcher implements HttpDispatcher { - - private static final String STATIC = "static"; - - private final HandlerResolver handlerResolver; - - public JwpHttpDispatcher(final HandlerResolver handlerResolver) { - this.handlerResolver = handlerResolver; - } - - @Override - public Http11Response handle(final Http11Request request) throws IOException { - final Handler handler = handlerResolver.resolve(request.getHttpMethod(), request.getPath()); - if (handler != null) { - return handler.resolve(request); - } - final var resource = getClass().getClassLoader().getResource(STATIC + request.getPath()); - if (resource == null) { - final var notFoundResource = getClass().getClassLoader().getResource(STATIC + "/404.html"); - return makeHttp11Response(Objects.requireNonNull(notFoundResource), StatusCode.NOT_FOUND); - } - return makeHttp11Response(resource, StatusCode.OK); - } - - private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException { - final var actualFilePath = new File(resource.getPath()).toPath(); - final var fileBytes = Files.readAllBytes(actualFilePath); - final String responseBody = new String(fileBytes, StandardCharsets.UTF_8); - return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody); - } -} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java new file mode 100644 index 0000000000..484a2789cb --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -0,0 +1,29 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.HttpMethod; +import org.apache.coyote.http11.StatusCode; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public class AbstractController implements Controller { + + @Override + public void service(final HttpRequest request, final HttpResponse response) { + if (request.methodEquals(HttpMethod.GET)) { + doGet(request, response); + return; + } + if (request.methodEquals(HttpMethod.POST)) { + doPost(request, response); + return; + } + response.setStatusCode(StatusCode.NOT_FOUND) + .setRedirect("/404.html"); + } + + protected void doGet(final HttpRequest request, final HttpResponse response) { + } + + protected void doPost(final HttpRequest request, final HttpResponse response) { + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/Controller.java b/tomcat/src/main/java/nextstep/jwp/controller/Controller.java new file mode 100644 index 0000000000..6299c321fd --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/Controller.java @@ -0,0 +1,9 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public interface Controller { + + void service(HttpRequest request, HttpResponse response); +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java new file mode 100644 index 0000000000..e1053a65e3 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -0,0 +1,102 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.coyote.http11.ContentType; +import org.apache.coyote.http11.Cookies; +import org.apache.coyote.http11.Session; +import org.apache.coyote.http11.SessionManager; +import org.apache.coyote.http11.StatusCode; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import java.util.Optional; +import java.util.UUID; + +public class LoginController extends AbstractController { + + private static final SessionManager sessionManager = new SessionManager(); + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) { + if (request.notContainJsessionId()) { + redirectToLoginPage(response); + return; + } + final String jsessionId = request.findJsessionId(); + final Session session = sessionManager.findSession(jsessionId); + if (session == null) { + redirectToLoginPage(response); + return; + } + response.setStatusCode(StatusCode.FOUND) + .setContentType(ContentType.HTML) + .setRedirect("/index.html"); + } + + private void redirectToLoginPage(final HttpResponse response) { + response.setStatusCode(StatusCode.OK) + .setContentType(ContentType.HTML) + .setRedirect("/login.html"); + } + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + if (request.containJsessionId()) { + generateAlreadyAuthorized(request.findJsessionId(), response); + return; + } + final User user = findUser(request); + if (user == null) { + generateUnauthorized(response); + return; + } + final Session session = createSession(user); + generateLogin(response, session); + } + + private void generateAlreadyAuthorized(final String jsessionId, final HttpResponse response) { + final Session session = sessionManager.findSession(jsessionId); + if (session == null) { + response.setStatusCode(StatusCode.OK) + .setContentType(ContentType.HTML) + .setRedirect("/register.html"); + return; + } + generateLogin(response, session); + } + + private void generateLogin(final HttpResponse response, final Session session) { + response.addCookie(Cookies.ofJSessionId(session.getId())); + response.setStatusCode(StatusCode.OK) + .setContentType(ContentType.HTML) + .setRedirect("/index.html"); + } + + private User findUser(final HttpRequest request) { + final String account = request.findBodyParameter("account"); + final String password = request.findBodyParameter("password"); + return checkUser(account, password); + } + + private void generateUnauthorized(final HttpResponse response) { + response.setStatusCode(StatusCode.UNAUTHORIZED) + .setContentType(ContentType.HTML) + .setRedirect("/401.html"); + } + + private User checkUser(final String account, final String password) { + final Optional optionalUser = InMemoryUserRepository.findByAccount(account); + if (optionalUser.isPresent() && optionalUser.get().checkPassword(password)) { + return optionalUser.get(); + } + return null; + } + + + private Session createSession(final User user) { + final Session session = new Session(String.valueOf(UUID.randomUUID())); + session.addAttribute("user", user); + sessionManager.add(session); + return session; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java new file mode 100644 index 0000000000..feb4aa0475 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -0,0 +1,49 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.coyote.http11.ContentType; +import org.apache.coyote.http11.Cookies; +import org.apache.coyote.http11.Session; +import org.apache.coyote.http11.SessionManager; +import org.apache.coyote.http11.StatusCode; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import java.util.UUID; + +public class RegisterController extends AbstractController { + + private static final SessionManager sessionManager = new SessionManager(); + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) { + response.setStatusCode(StatusCode.OK) + .setContentType(ContentType.HTML) + .setRedirect("/register.html"); + } + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + final User user = makeUser(request); + InMemoryUserRepository.save(user); + final Session session = createSession(user); + response.addCookie(Cookies.ofJSessionId(session.getId())); + response.setStatusCode(StatusCode.CREATED) + .setContentType(ContentType.HTML) + .setRedirect("/index.html"); + } + + private User makeUser(final HttpRequest request) { + final String account = request.findBodyParameter("account"); + final String email = request.findBodyParameter("email"); + final String password = request.findBodyParameter("password"); + return new User(account, password, email); + } + + private Session createSession(final User user) { + final Session session = new Session(String.valueOf(UUID.randomUUID())); + session.addAttribute("user", user); + sessionManager.add(session); + return session; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RootGetController.java b/tomcat/src/main/java/nextstep/jwp/controller/RootGetController.java new file mode 100644 index 0000000000..13057426c0 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/RootGetController.java @@ -0,0 +1,16 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.ContentType; +import org.apache.coyote.http11.StatusCode; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public class RootGetController extends AbstractController { + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) { + response.setStatusCode(StatusCode.OK) + .setContentType(ContentType.HTML) + .setBody("Hello world!"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/handler/get/LoginGetHandler.java b/tomcat/src/main/java/nextstep/jwp/handler/get/LoginGetHandler.java deleted file mode 100644 index 74744989b1..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/handler/get/LoginGetHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -package nextstep.jwp.handler.get; - -import nextstep.jwp.SessionManager; -import nextstep.jwp.exception.BusinessException; -import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Handler; -import org.apache.coyote.http11.Session; -import org.apache.coyote.http11.StatusCode; -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.response.Http11Response; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; - -public class LoginGetHandler implements Handler { - - private static final String STATIC = "static"; - - private final SessionManager sessionManager; - - public LoginGetHandler(final SessionManager sessionManager) { - this.sessionManager = sessionManager; - } - - @Override - public Http11Response resolve(final Http11Request request) throws IOException { - if (request.notContainJsessionId()) { - final var resource = getClass().getClassLoader().getResource(STATIC + "/login.html"); - return makeHttp11Response(resource, StatusCode.OK); - } - final String jsessionId = request.findJsessionId(); - final Session session = sessionManager.findSession(jsessionId); - validateSession(session); - final var resource = getClass().getClassLoader().getResource(STATIC + "/index.html"); - return makeHttp11Response(resource, StatusCode.FOUND); - } - - private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException { - final var actualFilePath = new File(resource.getPath()).toPath(); - final var fileBytes = Files.readAllBytes(actualFilePath); - final String responseBody = new String(fileBytes, StandardCharsets.UTF_8); - return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody); - } - - private void validateSession(final Session session) { - if (session == null) { - throw new BusinessException("세션이 적절하지 않습니다."); - } - } -} diff --git a/tomcat/src/main/java/nextstep/jwp/handler/get/RegisterGetHandler.java b/tomcat/src/main/java/nextstep/jwp/handler/get/RegisterGetHandler.java deleted file mode 100644 index f9cb39260b..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/handler/get/RegisterGetHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package nextstep.jwp.handler.get; - -import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Handler; -import org.apache.coyote.http11.StatusCode; -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.response.Http11Response; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; - -public class RegisterGetHandler implements Handler { - - private static final String STATIC = "static"; - - @Override - public Http11Response resolve(final Http11Request request) throws IOException { - final var resource = getClass().getClassLoader().getResource(STATIC + "/register.html"); - return makeHttp11Response(resource, StatusCode.OK); - } - - private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException { - final var actualFilePath = new File(resource.getPath()).toPath(); - final var fileBytes = Files.readAllBytes(actualFilePath); - final String responseBody = new String(fileBytes, StandardCharsets.UTF_8); - return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody); - } -} diff --git a/tomcat/src/main/java/nextstep/jwp/handler/get/RootGetHandler.java b/tomcat/src/main/java/nextstep/jwp/handler/get/RootGetHandler.java deleted file mode 100644 index 38e5a63d2d..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/handler/get/RootGetHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -package nextstep.jwp.handler.get; - -import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Handler; -import org.apache.coyote.http11.StatusCode; -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.response.Http11Response; - -public class RootGetHandler implements Handler { - - @Override - public Http11Response resolve(final Http11Request request) { - return new Http11Response(StatusCode.OK, ContentType.HTML, "Hello world!"); - } -} diff --git a/tomcat/src/main/java/nextstep/jwp/handler/post/LoginPostHandler.java b/tomcat/src/main/java/nextstep/jwp/handler/post/LoginPostHandler.java deleted file mode 100644 index b94b9d59a3..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/handler/post/LoginPostHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -package nextstep.jwp.handler.post; - -import nextstep.jwp.SessionManager; -import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.exception.BusinessException; -import nextstep.jwp.model.User; -import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Cookies; -import org.apache.coyote.http11.Handler; -import org.apache.coyote.http11.Session; -import org.apache.coyote.http11.StatusCode; -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.request.RequestBody; -import org.apache.coyote.http11.response.Http11Response; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Arrays; -import java.util.Optional; -import java.util.UUID; - -public class LoginPostHandler implements Handler { - - private static final String STATIC = "static"; - - private final SessionManager sessionManager; - - public LoginPostHandler(final SessionManager sessionManager) { - this.sessionManager = sessionManager; - } - - @Override - public Http11Response resolve(final Http11Request request) throws IOException { - final RequestBody requestBody = request.getRequestBody(); - final User user = findUser(requestBody); - if (user == null) { - final var resource = getClass().getClassLoader().getResource(STATIC + "/401.html"); - return makeHttp11Response(resource, StatusCode.UNAUTHORIZED); - } - final var resource = getClass().getClassLoader().getResource(STATIC + "/index.html"); - final Http11Response response = makeHttp11Response(resource, StatusCode.FOUND); - if (request.notContainJsessionId()) { - final Session session = new Session(String.valueOf(UUID.randomUUID())); - session.addAttribute("user", user); - sessionManager.add(session); - response.addCookie(Cookies.ofJSessionId(session.getId())); - return response; - } - final String jsessionId = request.findJsessionId(); - final Session session = sessionManager.findSession(jsessionId); - validateSession(user, session); - response.addCookie(Cookies.ofJSessionId(session.getId())); - return response; - } - - private User findUser(final RequestBody requestBody) { - final String[] tokens = requestBody.getValue().split("&"); - - final String account = findValueByKey(tokens, "account"); - final String password = findValueByKey(tokens, "password"); - - return checkUser(account, password); - } - - private User checkUser(final String account, final String password) { - final Optional optionalUser = InMemoryUserRepository.findByAccount(account); - if (optionalUser.isPresent() && optionalUser.get().checkPassword(password)) { - return optionalUser.get(); - } - return null; - } - - private String findValueByKey(final String[] tokens, final String key) { - return Arrays.stream(tokens) - .filter(it -> it.split("=")[0] - .equals(key)) - .map(it -> it.split("=")[1]) - .findFirst() - .orElseThrow(() -> new BusinessException(key + "에 대한 정보가 존재하지 않습니다.")); - } - - private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException { - final var actualFilePath = new File(resource.getPath()).toPath(); - final var fileBytes = Files.readAllBytes(actualFilePath); - final String responseBody = new String(fileBytes, StandardCharsets.UTF_8); - return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody); - } - - private void validateSession(final User user, final Session session) { - if (session == null) { - throw new BusinessException("세션이 적절하지 않습니다."); - } - final User sessionUser = (User) session.getAttribute("user"); - if (!user.equals(sessionUser)) { - throw new BusinessException("세션이 적절하지 않습니다."); - } - } -} diff --git a/tomcat/src/main/java/nextstep/jwp/handler/post/RegisterPostHandler.java b/tomcat/src/main/java/nextstep/jwp/handler/post/RegisterPostHandler.java deleted file mode 100644 index 070db1ae8b..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/handler/post/RegisterPostHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -package nextstep.jwp.handler.post; - -import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.exception.BusinessException; -import nextstep.jwp.model.User; -import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Handler; -import org.apache.coyote.http11.StatusCode; -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.request.RequestBody; -import org.apache.coyote.http11.response.Http11Response; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Arrays; - -public class RegisterPostHandler implements Handler { - - private static final String STATIC = "static"; - - @Override - public Http11Response resolve(final Http11Request request) throws IOException { - final User user = makeUser(request.getRequestBody()); - InMemoryUserRepository.save(user); - final var resource = getClass().getClassLoader().getResource(STATIC + "/index.html"); - return makeHttp11Response(resource, StatusCode.CREATED); - } - - private User makeUser(final RequestBody requestBody) { - final String[] tokens = requestBody.getValue().split("&"); - - final String account = findValueByKey(tokens, "account"); - final String email = findValueByKey(tokens, "email"); - final String password = findValueByKey(tokens, "password"); - - return new User(account, password, email); - } - - private String findValueByKey(final String[] tokens, final String key) { - return Arrays.stream(tokens) - .filter(it -> it.split("=")[0] - .equals(key)) - .map(it -> it.split("=")[1]) - .findFirst() - .orElseThrow(() -> new BusinessException(key + "에 대한 정보가 존재하지 않습니다.")); - } - - private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException { - final var actualFilePath = new File(resource.getPath()).toPath(); - final var fileBytes = Files.readAllBytes(actualFilePath); - final String responseBody = new String(fileBytes, StandardCharsets.UTF_8); - return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody); - } -} 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 06406ba996..44f415734e 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -1,35 +1,35 @@ package org.apache.catalina.connector; import org.apache.coyote.http11.Http11Processor; -import org.apache.coyote.http11.HttpDispatcher; -import org.apache.coyote.http11.request.HttpRequestParser; 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; public class Connector implements Runnable { private static final Logger log = LoggerFactory.getLogger(Connector.class); - private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_ACCEPT_COUNT = 100; + private static final int DEFAULT_THREAD_COUNT = 250; - private final HttpDispatcher httpDispatcher; private final ServerSocket serverSocket; - + private final ExecutorService executorService; + private boolean stopped; - public Connector(final HttpDispatcher httpDispatcher) { - this(httpDispatcher, DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); + public Connector() { + this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_THREAD_COUNT); } - public Connector(final HttpDispatcher httpDispatcher, final int port, final int acceptCount) { - this.httpDispatcher = httpDispatcher; + private Connector(final int port, final int acceptCount, final int threadCount) { this.serverSocket = createServerSocket(port, acceptCount); this.stopped = false; + this.executorService = Executors.newFixedThreadPool(threadCount); } private ServerSocket createServerSocket(final int port, final int acceptCount) { @@ -70,8 +70,8 @@ private void process(final Socket connection) { if (connection == null) { return; } - final var processor = new Http11Processor(connection, new HttpRequestParser(), httpDispatcher); - new Thread(processor).start(); + final var processor = new Http11Processor(connection); + executorService.execute(processor); } public void stop() { diff --git a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java index 0e7e5df407..debc1b9df6 100644 --- a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java +++ b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java @@ -1,7 +1,6 @@ package org.apache.catalina.startup; import org.apache.catalina.connector.Connector; -import org.apache.coyote.http11.HttpDispatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; @@ -10,14 +9,9 @@ public class Tomcat { private static final Logger log = LoggerFactory.getLogger(Tomcat.class); - private final HttpDispatcher httpDispatcher; - - public Tomcat(final HttpDispatcher httpDispatcher) { - this.httpDispatcher = httpDispatcher; - } public void start() { - final var connector = new Connector(httpDispatcher); + final var connector = new Connector(); connector.start(); try { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java b/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java index b3862f53eb..e356cbc5f6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java @@ -36,6 +36,10 @@ public boolean containsJsessionId() { return value.containsKey(JSESSIONID); } + public String findByKey(final String key) { + return value.get(key); + } + public Map getValue() { return value; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Handler.java b/tomcat/src/main/java/org/apache/coyote/http11/Handler.java deleted file mode 100644 index f21feb1160..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/Handler.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.apache.coyote.http11; - -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.response.Http11Response; -import java.io.IOException; - -public interface Handler { - - Http11Response resolve(Http11Request request) throws IOException; -} 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 2f0f6c695e..ec2e5fc4bf 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,8 +1,10 @@ package org.apache.coyote.http11; +import nextstep.jwp.DispatcherServlet; import nextstep.jwp.exception.UncheckedServletException; +import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.request.HttpRequestParser; -import org.apache.coyote.http11.response.Http11Response; +import org.apache.coyote.http11.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; @@ -10,21 +12,16 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; -import java.util.Map; -import java.util.stream.Collectors; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + private static final HttpServlet httpServlet = new DispatcherServlet(); private final Socket connection; - private final HttpRequestParser httpRequestParser; - private final HttpDispatcher httpDispatcher; - public Http11Processor(final Socket connection, final HttpRequestParser httpRequestParser, final HttpDispatcher httpDispatcher) { + public Http11Processor(final Socket connection) { this.connection = connection; - this.httpRequestParser = httpRequestParser; - this.httpDispatcher = httpDispatcher; } @Override @@ -39,43 +36,31 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream(); final var reader = new BufferedReader(new InputStreamReader(inputStream))) { - final var request = httpRequestParser.parse(reader); - final var response = httpDispatcher.handle(request); + final var request = HttpRequestParser.parse(reader); + final var response = new HttpResponse(request); + if (request.isFile()) { + generateStaticFile(request, response); + sendResponse(outputStream, response); + return; + } + + httpServlet.service(request, response); sendResponse(outputStream, response); } catch (final IOException | UncheckedServletException exception) { log.error(exception.getMessage(), exception); } } - private void sendResponse(final OutputStream outputStream, final Http11Response response) throws IOException { - final String httpResponse = makeHttpResponse(response); - outputStream.write(httpResponse.getBytes()); - outputStream.flush(); + private void generateStaticFile(final HttpRequest request, final HttpResponse response) { + response.setStatusCode(StatusCode.OK) + .setContentType(ContentType.findByPath(request.getPath())) + .setRedirect(request.getPath()); } - private String makeHttpResponse(final Http11Response response) { - final StringBuilder httpResponse = new StringBuilder(); - - httpResponse.append("HTTP/1.1 ").append(response.getStatusCode().getResponse()).append("\r\n"); - httpResponse.append("Content-Type: ").append(response.getContentType().getType()).append(";charset=utf-8\r\n"); - httpResponse.append("Content-Length: ").append(response.getResponseBody().getBytes().length).append("\r\n"); - - if (response.containJsessionId()) { - final Map value = response.getCookie().getValue(); - httpResponse.append("Set-Cookie: ").append("JSESSIONID" + "=" + value.get("JSESSIONID")).append("\r\n"); - } - - if (!response.getOtherHeader().isEmpty()) { - final String additionalHeaders = response.getOtherHeader().entrySet().stream() - .map(entry -> entry.getKey() + ": " + entry.getValue()) - .collect(Collectors.joining("\r\n")); - httpResponse.append(additionalHeaders).append("\r\n"); - } - - httpResponse.append("\r\n"); - httpResponse.append(response.getResponseBody()); - - return httpResponse.toString(); + private void sendResponse(final OutputStream outputStream, final HttpResponse response) throws IOException { + final String httpResponse = ResponseGenerator.makeResponse(response); + outputStream.write(httpResponse.getBytes()); + outputStream.flush(); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpDispatcher.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpDispatcher.java deleted file mode 100644 index 5cf6098ac5..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpDispatcher.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.apache.coyote.http11; - -import org.apache.coyote.http11.request.Http11Request; -import org.apache.coyote.http11.response.Http11Response; -import java.io.IOException; - -public interface HttpDispatcher { - Http11Response handle(final Http11Request request) throws IOException; -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java index a93ba7c23d..3f0f212755 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java @@ -11,6 +11,7 @@ public class HttpHeader { private static final String CONTENT_LENGTH = "Content-Length"; private static final String COOKIE = "Cookie"; + private static final String JSESSIONID = "JSESSIONID"; private final Map values = new HashMap<>(); @@ -26,17 +27,25 @@ public boolean containContentLength() { return values.containsKey(CONTENT_LENGTH); } + public boolean containJsessionId() { + final String cookie = values.get(COOKIE); + if (cookie == null) { + return false; + } + return cookie.contains(JSESSIONID); + } + public boolean notContainJsessionId() { final String cookie = values.get(COOKIE); if (cookie == null) { return true; } - return !cookie.contains("JSESSIONID"); + return !cookie.contains(JSESSIONID); } public String findJsessionId() { final String cookie = values.get(COOKIE); - final Pattern pattern = Pattern.compile("JSESSIONID=([a-zA-Z0-9-]+)"); + final Pattern pattern = Pattern.compile(JSESSIONID + "=([a-zA-Z0-9-]+)"); final Matcher matcher = pattern.matcher(cookie); if (matcher.find()) { return matcher.group(1); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpServlet.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpServlet.java new file mode 100644 index 0000000000..45d90e5fab --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpServlet.java @@ -0,0 +1,9 @@ +package org.apache.coyote.http11; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public interface HttpServlet { + + void service(HttpRequest httpRequest, HttpResponse httpResponse); +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResponseGenerator.java b/tomcat/src/main/java/org/apache/coyote/http11/ResponseGenerator.java new file mode 100644 index 0000000000..57e78ec894 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/ResponseGenerator.java @@ -0,0 +1,82 @@ +package org.apache.coyote.http11; + +import org.apache.coyote.http11.response.HttpResponse; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.stream.Collectors; + +public class ResponseGenerator { + + private static final String STATIC = "static"; + private static final String HTTP_VERSION = "HTTP/1.1 "; + private static final String CONTENT_TYPE_HEADER_KEY = "Content-Type"; + private static final String CONTENT_LENGTH_HEADER_KEY = "Content-Length"; + private static final String SET_COOKIE_HEADER_KEY = "Set-Cookie"; + private static final String JSESSIONID = "JSESSIONID"; + + public static String makeResponse(final HttpResponse response) throws IOException { + if (response.containBody()) { + return makeHttpResponse(response, response.getBody()); + } + final var resource = ResponseGenerator.class.getClassLoader().getResource(STATIC + response.getRedirect()); + final String responseBody = makeResponseBody(resource); + return makeHttpResponse(response, responseBody); + } + + private static String makeHttpResponse(final HttpResponse response, final String responseBody) { + final var stringBuilder = new StringBuilder(); + makeBasicResponse(response.getStatusCode(), response.getContentType(), + responseBody.getBytes().length, stringBuilder); + addJsessionId(response, stringBuilder); + addOtherHeader(response, stringBuilder); + addResponseBody(responseBody, stringBuilder); + return stringBuilder.toString(); + } + + private static void makeBasicResponse(final StatusCode statusCode, final ContentType contentType, + final int contentLength, final StringBuilder stringBuilder) { + stringBuilder.append(HTTP_VERSION) + .append(statusCode.getResponse()) + .append("\r\n"); + stringBuilder.append(CONTENT_TYPE_HEADER_KEY + ": ") + .append(contentType.getType()) + .append(";charset=utf-8\r\n"); + stringBuilder.append(CONTENT_LENGTH_HEADER_KEY + ": ") + .append(contentLength) + .append("\r\n"); + } + + private static void addJsessionId(final HttpResponse response, final StringBuilder stringBuilder) { + if (response.containJsessionId()) { + stringBuilder.append(SET_COOKIE_HEADER_KEY + ": ") + .append(JSESSIONID + "=") + .append(response.findCookie(JSESSIONID)) + .append("\r\n"); + } + } + + private static void addOtherHeader(final HttpResponse response, final StringBuilder stringBuilder) { + if (response.isOtherHeaderNotEmpty()) { + final String additionalHeaders = response.getOtherHeader() + .entrySet() + .stream() + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.joining("\r\n")); + stringBuilder.append(additionalHeaders).append("\r\n"); + } + } + + private static void addResponseBody(final String responseBody, final StringBuilder stringBuilder) { + stringBuilder.append("\r\n"); + stringBuilder.append(responseBody); + } + + private static String makeResponseBody(final URL resource) throws IOException { + final var actualFilePath = new File(resource.getPath()).toPath(); + final var fileBytes = Files.readAllBytes(actualFilePath); + return new String(fileBytes, StandardCharsets.UTF_8); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java similarity index 90% rename from tomcat/src/main/java/nextstep/jwp/SessionManager.java rename to tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java index 35e88e937d..f45e0b5386 100644 --- a/tomcat/src/main/java/nextstep/jwp/SessionManager.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java @@ -1,7 +1,6 @@ -package nextstep.jwp; +package org.apache.coyote.http11; import org.apache.catalina.Manager; -import org.apache.coyote.http11.Session; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Http11Request.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Http11Request.java deleted file mode 100644 index 934481a5c8..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Http11Request.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.apache.coyote.http11.request; - -import org.apache.coyote.http11.HttpHeader; -import org.apache.coyote.http11.HttpMethod; - -public class Http11Request { - - private final HttpMethod httpMethod; - private final String path; - private final String query; - private final HttpHeader httpHeader; - private final RequestBody requestBody; - - public Http11Request(final HttpMethod httpMethod, final String path, final String query, final HttpHeader httpHeader, final RequestBody requestBody) { - this.httpMethod = httpMethod; - this.path = path; - this.query = query; - this.httpHeader = httpHeader; - this.requestBody = requestBody; - } - - public boolean notContainJsessionId() { - return httpHeader.notContainJsessionId(); - } - - public String findJsessionId() { - return httpHeader.findJsessionId(); - } - - public HttpMethod getHttpMethod() { - return httpMethod; - } - - public String getPath() { - return path; - } - - public RequestBody getRequestBody() { - return requestBody; - } -} 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 new file mode 100644 index 0000000000..74547c6b9d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -0,0 +1,67 @@ +package org.apache.coyote.http11.request; + +import org.apache.coyote.http11.ContentType; +import org.apache.coyote.http11.HttpException; +import org.apache.coyote.http11.HttpHeader; +import org.apache.coyote.http11.HttpMethod; + +public class HttpRequest { + + private final HttpMethod httpMethod; + private final String path; + private final String version; + private final String query; + private final HttpHeader httpHeader; + private final RequestBody requestBody; + + public HttpRequest(final HttpMethod httpMethod, final String path, final String version, final String query, + final HttpHeader httpHeader, final RequestBody requestBody) { + this.httpMethod = httpMethod; + this.path = path; + this.version = version; + this.query = query; + this.httpHeader = httpHeader; + this.requestBody = requestBody; + } + + public boolean containJsessionId() { + return httpHeader.containJsessionId(); + } + + public boolean notContainJsessionId() { + return httpHeader.notContainJsessionId(); + } + + public String findJsessionId() { + return httpHeader.findJsessionId(); + } + + public boolean methodEquals(final HttpMethod httpMethod) { + return this.httpMethod.equals(httpMethod); + } + + public boolean isFile() { + try { + ContentType.findByPath(this.path); + } catch (final HttpException exception) { + return false; + } + return true; + } + + public String findBodyParameter(final String key) { + return requestBody.getParameter(key); + } + + public String getPath() { + return path; + } + + public RequestBody getRequestBody() { + return requestBody; + } + + public String getVersion() { + return version; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java index 8b7637618a..1e0fc834e5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java @@ -7,35 +7,44 @@ public class HttpRequestParser { - public Http11Request parse(final BufferedReader reader) throws IOException { + public static HttpRequest parse(final BufferedReader reader) throws IOException { final String startLine = reader.readLine(); - if (startLine == null) { - throw new IOException("Start Line is Empty"); - } + validateEmpty(startLine); final String[] startLineTokens = startLine.split(" "); final HttpMethod httpMethod = HttpMethod.valueOf(startLineTokens[0]); final String pathWithQuery = startLineTokens[1]; + final String version = startLineTokens[2]; final String[] pathWithQueryTokens = pathWithQuery.split("\\?"); final String path = pathWithQueryTokens[0]; final String query = findQuery(pathWithQuery, pathWithQueryTokens); final HttpHeader httpHeader = new HttpHeader(reader); + final RequestBody requestBody = makeRequestBody(reader, httpHeader); - RequestBody requestBody = null; - if (httpHeader.containContentLength()) { - final int contentLength = httpHeader.getContentLength(); - final char[] body = new char[contentLength]; - reader.read(body, 0, contentLength); - requestBody = new RequestBody(new String(body)); - } + return new HttpRequest(httpMethod, path, version, query, httpHeader, requestBody); + } - return new Http11Request(httpMethod, path, query, httpHeader, requestBody); + private static void validateEmpty(final String startLine) throws IOException { + if (startLine == null) { + throw new IOException("Start Line is Empty"); + } } - private String findQuery(final String pathWithQuery, final String[] pathWithQueryTokens) { + private static String findQuery(final String pathWithQuery, final String[] pathWithQueryTokens) { if (pathWithQuery.contains("?")) { return pathWithQueryTokens[1]; } return null; } + + private static RequestBody makeRequestBody(final BufferedReader reader, final HttpHeader httpHeader) throws IOException { + RequestBody requestBody = null; + if (httpHeader.containContentLength()) { + final int contentLength = httpHeader.getContentLength(); + final char[] body = new char[contentLength]; + reader.read(body, 0, contentLength); + requestBody = new RequestBody(new String(body)); + } + return requestBody; + } } 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 568ffbd07e..6e5acaff29 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 @@ -1,14 +1,27 @@ package org.apache.coyote.http11.request; +import java.util.Arrays; + public class RequestBody { + private static final String PARAMETER_DELIMITER = "&"; + private static final String KEY_VALUE_DELIMITER = "="; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + private final String value; public RequestBody(final String value) { this.value = value; } - public String getValue() { - return value; + public String getParameter(final String key) { + final String[] tokens = value.split(PARAMETER_DELIMITER); + return Arrays.stream(tokens) + .filter(it -> it.split(KEY_VALUE_DELIMITER)[KEY_INDEX] + .equals(key)) + .map(it -> it.split(KEY_VALUE_DELIMITER)[VALUE_INDEX]) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(key + "에 대한 정보가 존재하지 않습니다.")); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/Http11Response.java b/tomcat/src/main/java/org/apache/coyote/http11/response/Http11Response.java deleted file mode 100644 index 089eef5f4c..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/Http11Response.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.apache.coyote.http11.response; - -import org.apache.coyote.http11.ContentType; -import org.apache.coyote.http11.Cookie; -import org.apache.coyote.http11.StatusCode; -import java.util.HashMap; -import java.util.Map; - -public class Http11Response { - - private final StatusCode statusCode; - private final ContentType contentType; - private final String responseBody; - private final Map otherHeader = new HashMap<>(); - - private Cookie cookie; - - public Http11Response(final StatusCode statusCode, final ContentType contentType, final String responseBody) { - this.statusCode = statusCode; - this.contentType = contentType; - this.responseBody = responseBody; - } - - public void addCookie(final String cookie) { - this.cookie = new Cookie(cookie); - } - - public boolean containJsessionId() { - return cookie != null && cookie.containsJsessionId(); - } - - public StatusCode getStatusCode() { - return statusCode; - } - - public ContentType getContentType() { - return contentType; - } - - public String getResponseBody() { - return responseBody; - } - - public Map getOtherHeader() { - return otherHeader; - } - - public Cookie getCookie() { - return cookie; - } -} 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 new file mode 100644 index 0000000000..7b875474f4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -0,0 +1,91 @@ +package org.apache.coyote.http11.response; + +import org.apache.coyote.http11.ContentType; +import org.apache.coyote.http11.Cookie; +import org.apache.coyote.http11.StatusCode; +import org.apache.coyote.http11.request.HttpRequest; +import java.util.HashMap; +import java.util.Map; + +public class HttpResponse { + + private final String version; + private final Map otherHeader = new HashMap<>(); + + private StatusCode statusCode; + private ContentType contentType; + private String redirect; + private String body; + private Cookie cookie; + + public HttpResponse(final HttpRequest request) { + this(request.getVersion(), null, null, null); + } + + public HttpResponse(final String version, final StatusCode statusCode, final ContentType contentType, final String redirect) { + this.version = version; + this.statusCode = statusCode; + this.contentType = contentType; + this.redirect = redirect; + } + + public void addCookie(final String cookie) { + this.cookie = new Cookie(cookie); + } + + public boolean containJsessionId() { + return cookie != null && cookie.containsJsessionId(); + } + + public boolean containBody() { + return this.body != null; + } + + public String findCookie(final String key) { + return cookie.findByKey(key); + } + + public boolean isOtherHeaderNotEmpty() { + return !otherHeader.isEmpty(); + } + + public StatusCode getStatusCode() { + return statusCode; + } + + public ContentType getContentType() { + return contentType; + } + + public Map getOtherHeader() { + return otherHeader; + } + + public String getRedirect() { + return redirect; + } + + public String getBody() { + return body; + } + + public HttpResponse setStatusCode(final StatusCode statusCode) { + this.statusCode = statusCode; + return this; + } + + public HttpResponse setContentType(final ContentType contentType) { + this.contentType = contentType; + return this; + } + + public HttpResponse setRedirect(final String redirect) { + this.redirect = redirect; + return this; + } + + public HttpResponse setBody(final String body) { + this.body = body; + return this; + } +} diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java index 749adbf888..35ca0fbc17 100644 --- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java @@ -2,39 +2,21 @@ import static org.assertj.core.api.Assertions.assertThat; -import nextstep.jwp.HandlerResolver; -import nextstep.jwp.JwpHttpDispatcher; -import nextstep.jwp.SessionManager; -import nextstep.jwp.handler.get.LoginGetHandler; -import nextstep.jwp.handler.get.RegisterGetHandler; -import nextstep.jwp.handler.get.RootGetHandler; -import nextstep.jwp.handler.post.LoginPostHandler; -import org.apache.coyote.http11.Handler; import org.apache.coyote.http11.Http11Processor; -import org.apache.coyote.http11.request.HttpRequestParser; import org.junit.jupiter.api.Test; import support.StubSocket; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; -import java.util.Map; class Http11ProcessorTest { - private final Map httpGetHandlers = - Map.of("/", new RootGetHandler(), - "/login", new LoginGetHandler(new SessionManager()), - "/register", new RegisterGetHandler()); - private final Map httpPostHandlers = - Map.of("/login", new LoginPostHandler(new SessionManager())); - @Test void process() { // given final var socket = new StubSocket(); - final JwpHttpDispatcher httpDispatcher = new JwpHttpDispatcher(new HandlerResolver(httpGetHandlers, httpPostHandlers)); - final var processor = new Http11Processor(socket, new HttpRequestParser(), httpDispatcher); + final var processor = new Http11Processor(socket); // when processor.process(socket); @@ -61,8 +43,7 @@ void index() throws IOException { ""); final var socket = new StubSocket(httpRequest); - final JwpHttpDispatcher httpDispatcher = new JwpHttpDispatcher(new HandlerResolver(httpGetHandlers, httpPostHandlers)); - final var processor = new Http11Processor(socket, new HttpRequestParser(), httpDispatcher); + final var processor = new Http11Processor(socket); // when processor.process(socket);