From 90a7fab901aab8bbf14f3b054ab331d85763705d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=80=EB=B9=84?= <67318165+eunbii0213@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:08:51 +0900 Subject: [PATCH] =?UTF-8?q?[=ED=86=B0=EC=BA=A3=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=98=EA=B8=B0=203,=204=EB=8B=A8=EA=B3=84]=20=EC=A3=BC?= =?UTF-8?q?=EB=94=94(=EC=98=A4=EC=9D=80=EB=B9=84)=20=EB=AF=B8=EC=85=98=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C=ED=95=A9=EB=8B=88=EB=8B=A4.=20(#497)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/build.gradle | 35 -- .../src/main/java/cache/com/example/App.java | 12 - .../main/java/cache/com/example/Greeting.java | 20 -- .../cache/com/example/GreetingController.java | 30 -- .../example/cachecontrol/CacheWebConfig.java | 13 - .../com/example/dto/RegisterRequest.java | 20 -- .../example/etag/EtagFilterConfiguration.java | 12 - .../version/CacheBustingWebConfig.java | 25 -- .../com/example/version/ResourceVersion.java | 29 -- .../version/VersionHandlebarsHelper.java | 25 -- study/src/main/java/thread/stage2/App.java | 12 - .../java/thread/stage2/HelloWorldService.java | 11 - .../java/thread/stage2/SampleController.java | 33 -- study/src/main/resources/application.yml | 9 - study/src/main/resources/static/js/index.js | 1 - study/src/main/resources/templates/index.html | 10 - .../templates/resource-versioning.html | 11 - .../com/example/GreetingControllerTest.java | 102 ------ study/src/test/java/study/FileTest.java | 54 --- study/src/test/java/study/IOStreamTest.java | 213 ------------ .../thread/stage0/SynchronizationTest.java | 56 --- .../java/thread/stage0/ThreadPoolsTest.java | 66 ---- .../test/java/thread/stage0/ThreadTest.java | 82 ----- .../java/thread/stage1/ConcurrencyTest.java | 40 --- .../java/thread/stage1/HttpProcessor.java | 17 - study/src/test/java/thread/stage1/User.java | 36 -- .../test/java/thread/stage1/UserServlet.java | 27 -- .../src/test/java/thread/stage2/AppTest.java | 50 --- .../java/thread/stage2/TestHttpUtils.java | 29 -- study/src/test/resources/nextstep.txt | 1 - .../jwp/exception/UserNotFoundException.java | 8 + .../nextstep/jwp/presentation/Controller.java | 10 + .../jwp/presentation/GetLoginController.java | 42 +++ .../presentation/GetRegisterController.java | 15 + .../jwp/presentation/NotFoundController.java | 15 + .../jwp/presentation/PostLoginController.java | 67 ++++ .../presentation/PostRegisterController.java | 25 ++ .../jwp/presentation/RootController.java | 11 + .../jwp/presentation/StaticController.java | 14 + .../presentation/handler/FrontController.java | 58 +++ .../apache/catalina/connector/Connector.java | 20 +- .../org/apache/coyote/http11/ContentType.java | 31 ++ .../apache/coyote/http11/Http11Processor.java | 329 +----------------- .../coyote/http11/HttpRequestParser.java | 110 ++++++ .../coyote/http11/HttpResponseBuilder.java | 110 ++++++ .../org/apache/coyote/http11/HttpStatus.java | 23 ++ .../org/apache/coyote/http11/Response.java | 44 --- .../apache/coyote/http11/SessionManager.java | 19 + .../coyote/http11/cookie/HttpCookie.java | 24 -- .../coyote/http11/cookie/SessionManager.java | 27 -- .../coyote/http11/enums/ContentType.java | 34 -- .../org/apache/coyote/http11/enums/Path.java | 32 -- tomcat/src/main/resources/static/favicon.ico | Bin 0 -> 6373 bytes tomcat/src/main/resources/static/login.html | 2 +- .../coyote/http11/Http11ProcessorTest.java | 63 ++++ .../presentation/GetLoginControllerTest.java | 67 ++++ .../GetRegisterControllerTest.java | 37 ++ .../presentation/NotFoundControllerTest.java | 37 ++ .../presentation/PostLoginControllerTest.java | 61 ++++ .../PostRegisterControllerTest.java | 39 +++ .../jwp/presentation/RootControllerTest.java | 39 +++ .../presentation/StaticControllerTest.java | 59 ++++ .../handler/FrontControllerTest.java | 98 ++++++ .../coyote/http11/Http11ProcessorTest.java | 244 ------------- .../apache/coyote/http/ContentTypeTest.java | 27 ++ .../coyote/http/HttpRequestParserTest.java | 118 +++++++ .../coyote/http/HttpResponseBuilderTest.java | 79 +++++ .../apache/coyote/http/RequestFixture.java | 81 +++++ .../coyote/http/SessionManagerTest.java | 24 ++ 69 files changed, 1415 insertions(+), 1809 deletions(-) delete mode 100644 study/build.gradle delete mode 100644 study/src/main/java/cache/com/example/App.java delete mode 100644 study/src/main/java/cache/com/example/Greeting.java delete mode 100644 study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java delete mode 100644 study/src/main/java/cache/com/example/dto/RegisterRequest.java delete mode 100644 study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java delete mode 100644 study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java delete mode 100644 study/src/main/java/cache/com/example/version/ResourceVersion.java delete mode 100644 study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java delete mode 100644 study/src/main/java/thread/stage2/App.java delete mode 100644 study/src/main/java/thread/stage2/HelloWorldService.java delete mode 100644 study/src/main/java/thread/stage2/SampleController.java delete mode 100644 study/src/main/resources/application.yml delete mode 100644 study/src/main/resources/static/js/index.js delete mode 100644 study/src/main/resources/templates/index.html delete mode 100644 study/src/main/resources/templates/resource-versioning.html delete mode 100644 study/src/test/java/cache/com/example/GreetingControllerTest.java delete mode 100644 study/src/test/java/study/FileTest.java delete mode 100644 study/src/test/java/study/IOStreamTest.java delete mode 100644 study/src/test/java/thread/stage0/SynchronizationTest.java delete mode 100644 study/src/test/java/thread/stage0/ThreadPoolsTest.java delete mode 100644 study/src/test/java/thread/stage0/ThreadTest.java delete mode 100644 study/src/test/java/thread/stage1/ConcurrencyTest.java delete mode 100644 study/src/test/java/thread/stage1/HttpProcessor.java delete mode 100644 study/src/test/java/thread/stage1/User.java delete mode 100644 study/src/test/java/thread/stage1/UserServlet.java delete mode 100644 study/src/test/java/thread/stage2/AppTest.java delete mode 100644 study/src/test/java/thread/stage2/TestHttpUtils.java delete mode 100644 study/src/test/resources/nextstep.txt create mode 100644 tomcat/src/main/java/nextstep/jwp/exception/UserNotFoundException.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/Controller.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/GetLoginController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/GetRegisterController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/NotFoundController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/PostLoginController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/PostRegisterController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/RootController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/StaticController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/presentation/handler/FrontController.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/ContentType.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpResponseBuilder.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Response.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/cookie/HttpCookie.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/cookie/SessionManager.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/enums/ContentType.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/enums/Path.java create mode 100644 tomcat/src/main/resources/static/favicon.ico create mode 100644 tomcat/src/test/java/coyote/http11/Http11ProcessorTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/GetLoginControllerTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/GetRegisterControllerTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/NotFoundControllerTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/PostLoginControllerTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/PostRegisterControllerTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/RootControllerTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/StaticControllerTest.java create mode 100644 tomcat/src/test/java/nextstep/jwp/presentation/handler/FrontControllerTest.java delete mode 100644 tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http/ContentTypeTest.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http/HttpRequestParserTest.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http/HttpResponseBuilderTest.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http/RequestFixture.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http/SessionManagerTest.java diff --git a/study/build.gradle b/study/build.gradle deleted file mode 100644 index 56714a5f76..0000000000 --- a/study/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -plugins { - id "java" - id "org.springframework.boot" version "2.7.14" - id "io.spring.dependency-management" version "1.0.11.RELEASE" -} - -group "nextstep" -version "1.0-SNAPSHOT" - -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 - -repositories { - mavenCentral() -} - -dependencies { - implementation "ch.qos.logback:logback-classic:1.2.12" - implementation "org.apache.commons:commons-lang3:3.13.0" - implementation "com.fasterxml.jackson.core:jackson-databind:2.15.2" - implementation "pl.allegro.tech.boot:handlebars-spring-boot-starter:0.3.4" - implementation "org.springframework.boot:spring-boot-starter-web:2.7.14" - implementation "org.springframework.boot:spring-boot-starter-webflux:2.7.14" - - testImplementation "org.assertj:assertj-core:3.24.2" - testImplementation "org.mockito:mockito-core:5.4.0" - testImplementation "org.junit.jupiter:junit-jupiter-engine:5.7.2" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.2" - testImplementation "org.springframework.boot:spring-boot-starter-test:2.7.2" -} - -test { - maxParallelForks 3 - useJUnitPlatform() -} diff --git a/study/src/main/java/cache/com/example/App.java b/study/src/main/java/cache/com/example/App.java deleted file mode 100644 index b83b139cc0..0000000000 --- a/study/src/main/java/cache/com/example/App.java +++ /dev/null @@ -1,12 +0,0 @@ -package cache.com.example; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class App { - - public static void main(String[] args) { - SpringApplication.run(App.class, args); - } -} diff --git a/study/src/main/java/cache/com/example/Greeting.java b/study/src/main/java/cache/com/example/Greeting.java deleted file mode 100644 index cde79d9c79..0000000000 --- a/study/src/main/java/cache/com/example/Greeting.java +++ /dev/null @@ -1,20 +0,0 @@ -package cache.com.example; - -public class Greeting { - - private final long id; - private final String content; - - public Greeting(String content) { - this.id = 1; - this.content = content; - } - - public long getId() { - return id; - } - - public String getContent() { - return content; - } -} diff --git a/study/src/main/java/cache/com/example/GreetingController.java b/study/src/main/java/cache/com/example/GreetingController.java index 8f04768530..978eefdc34 100644 --- a/study/src/main/java/cache/com/example/GreetingController.java +++ b/study/src/main/java/cache/com/example/GreetingController.java @@ -1,14 +1,9 @@ package cache.com.example; -import cache.com.example.dto.RegisterRequest; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpServletResponse; @@ -20,31 +15,6 @@ public String index() { return "index"; } - @GetMapping("/index.html") - public String indexHtml() { - return "index"; - } - - @GetMapping("/login") - public String login(@RequestParam String account, @RequestParam String password) { - return "login"; - } - - @GetMapping("/login") - public String loginWithNoParams() { - return "login"; - } - - @GetMapping("/register") - public String register() { - return "register"; - } - - @PostMapping("/register") - public String registerUser(@RequestBody String requestBody) { - return "index"; - } - /** * 인터셉터를 쓰지 않고 response에 직접 헤더값을 지정할 수도 있다. */ diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java deleted file mode 100644 index 305b1f1e1e..0000000000 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package cache.com.example.cachecontrol; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class CacheWebConfig implements WebMvcConfigurer { - - @Override - public void addInterceptors(final InterceptorRegistry registry) { - } -} diff --git a/study/src/main/java/cache/com/example/dto/RegisterRequest.java b/study/src/main/java/cache/com/example/dto/RegisterRequest.java deleted file mode 100644 index 3ea6aa79ea..0000000000 --- a/study/src/main/java/cache/com/example/dto/RegisterRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package cache.com.example.dto; - -public class RegisterRequest { - - private String account; - private String email; - private String password; - - public String getAccount() { - return account; - } - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } -} diff --git a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java deleted file mode 100644 index 41ef7a3d9a..0000000000 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -package cache.com.example.etag; - -import org.springframework.context.annotation.Configuration; - -@Configuration -public class EtagFilterConfiguration { - -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } -} diff --git a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java deleted file mode 100644 index 6da6d2c795..0000000000 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package cache.com.example.version; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class CacheBustingWebConfig implements WebMvcConfigurer { - - public static final String PREFIX_STATIC_RESOURCES = "/resources"; - - private final ResourceVersion version; - - @Autowired - public CacheBustingWebConfig(ResourceVersion version) { - this.version = version; - } - - @Override - public void addResourceHandlers(final ResourceHandlerRegistry registry) { - registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); - } -} diff --git a/study/src/main/java/cache/com/example/version/ResourceVersion.java b/study/src/main/java/cache/com/example/version/ResourceVersion.java deleted file mode 100644 index 7049b3d82a..0000000000 --- a/study/src/main/java/cache/com/example/version/ResourceVersion.java +++ /dev/null @@ -1,29 +0,0 @@ -package cache.com.example.version; - -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -@Component -public class ResourceVersion { - - private static final String DEFAULT_DATE_TIME_FORMAT = "yyyyMMddHHmmSS"; - - private String version; - - @PostConstruct - public void init() { - this.version = now(); - } - - public String getVersion() { - return version; - } - - private static String now() { - final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT); - return LocalDateTime.now().format(formatter); - } -} diff --git a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java b/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java deleted file mode 100644 index a8e004466a..0000000000 --- a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java +++ /dev/null @@ -1,25 +0,0 @@ -package cache.com.example.version; - -import com.github.jknack.handlebars.Options; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper; - -@HandlebarsHelper -public class VersionHandlebarsHelper { - - private static final Logger log = LoggerFactory.getLogger(VersionHandlebarsHelper.class); - - private final ResourceVersion version; - - @Autowired - public VersionHandlebarsHelper(ResourceVersion version) { - this.version = version; - } - - public String staticUrls(String path, Options options) { - log.debug("static url : {}", path); - return String.format("/resources/%s%s", version.getVersion(), path); - } -} diff --git a/study/src/main/java/thread/stage2/App.java b/study/src/main/java/thread/stage2/App.java deleted file mode 100644 index 34873c6169..0000000000 --- a/study/src/main/java/thread/stage2/App.java +++ /dev/null @@ -1,12 +0,0 @@ -package thread.stage2; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class App { - - public static void main(String[] args) { - SpringApplication.run(App.class, args); - } -} diff --git a/study/src/main/java/thread/stage2/HelloWorldService.java b/study/src/main/java/thread/stage2/HelloWorldService.java deleted file mode 100644 index e888c5e504..0000000000 --- a/study/src/main/java/thread/stage2/HelloWorldService.java +++ /dev/null @@ -1,11 +0,0 @@ -package thread.stage2; - -import org.springframework.stereotype.Component; - -@Component -public class HelloWorldService { - - public String helloWorld() { - return "Hello World"; - } -} diff --git a/study/src/main/java/thread/stage2/SampleController.java b/study/src/main/java/thread/stage2/SampleController.java deleted file mode 100644 index a9636dd934..0000000000 --- a/study/src/main/java/thread/stage2/SampleController.java +++ /dev/null @@ -1,33 +0,0 @@ -package thread.stage2; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.util.concurrent.atomic.AtomicInteger; - -@Controller -public class SampleController { - - private static final Logger log = LoggerFactory.getLogger(SampleController.class); - - private static final AtomicInteger count = new AtomicInteger(0); - - private final HelloWorldService helloWorldService; - - @Autowired - public SampleController(final HelloWorldService helloWorldService) { - this.helloWorldService = helloWorldService; - } - - @GetMapping("/test") - @ResponseBody - public String helloWorld() throws InterruptedException { - Thread.sleep(500); - log.info("http call count : {}", count.incrementAndGet()); - return helloWorldService.helloWorld(); - } -} diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml deleted file mode 100644 index 4e8655a962..0000000000 --- a/study/src/main/resources/application.yml +++ /dev/null @@ -1,9 +0,0 @@ -handlebars: - suffix: .html - -server: - tomcat: - accept-count: 1 - max-connections: 1 - threads: - max: 2 diff --git a/study/src/main/resources/static/js/index.js b/study/src/main/resources/static/js/index.js deleted file mode 100644 index 5893f9d0d5..0000000000 --- a/study/src/main/resources/static/js/index.js +++ /dev/null @@ -1 +0,0 @@ -console.log('hello world'); \ No newline at end of file diff --git a/study/src/main/resources/templates/index.html b/study/src/main/resources/templates/index.html deleted file mode 100644 index 46cbef0f24..0000000000 --- a/study/src/main/resources/templates/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Getting Started: Serving Web Content - - - -Hello, World! - - diff --git a/study/src/main/resources/templates/resource-versioning.html b/study/src/main/resources/templates/resource-versioning.html deleted file mode 100644 index cb8323ebf9..0000000000 --- a/study/src/main/resources/templates/resource-versioning.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - Document - - -html > head > script 태그의 src에 version 디렉터리가 생겼다. - - diff --git a/study/src/test/java/cache/com/example/GreetingControllerTest.java b/study/src/test/java/cache/com/example/GreetingControllerTest.java deleted file mode 100644 index 9ce2a394f7..0000000000 --- a/study/src/test/java/cache/com/example/GreetingControllerTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package cache.com.example; - -import cache.com.example.version.ResourceVersion; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; -import org.springframework.test.web.reactive.server.WebTestClient; - -import java.time.Duration; - -import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class GreetingControllerTest { - - private static final Logger log = LoggerFactory.getLogger(GreetingControllerTest.class); - - @Autowired - private ResourceVersion version; - - @Autowired - private WebTestClient webTestClient; - - @Test - void testNoCachePrivate() { - final var response = webTestClient - .get() - .uri("/") - .exchange() - .expectStatus().isOk() - .expectHeader().cacheControl(CacheControl.noCache().cachePrivate()) - .expectBody(String.class).returnResult(); - - log.info("response body\n{}", response.getResponseBody()); - } - - @Test - void testCompression() { - final var response = webTestClient - .get() - .uri("/") - .exchange() - .expectStatus().isOk() - - // gzip으로 요청 보내도 어떤 방식으로 압축할지 서버에서 결정한다. - // 웹브라우저에서 localhost:8080으로 접근하면 응답 헤더에 "Content-Encoding: gzip"이 있다. - .expectHeader().valueEquals(HttpHeaders.TRANSFER_ENCODING, "chunked") - .expectBody(String.class).returnResult(); - - log.info("response body\n{}", response.getResponseBody()); - } - - @Test - void testETag() { - final var response = webTestClient - .get() - .uri("/etag") - .exchange() - .expectStatus().isOk() - .expectHeader().exists(HttpHeaders.ETAG) - .expectBody(String.class).returnResult(); - - log.info("response body\n{}", response.getResponseBody()); - } - - /** - * http://localhost:8080/resource-versioning - * 위 url의 html 파일에서 사용하는 js, css와 같은 정적 파일에 캐싱을 적용한다. - * 보통 정적 파일을 캐싱 무효화하기 위해 캐싱과 함께 버전을 적용시킨다. - * 정적 파일에 변경 사항이 생기면 배포할 때 버전을 바꿔주면 적용된 캐싱을 무효화(Caching Busting)할 수 있다. - */ - @Test - void testCacheBustingOfStaticResources() { - final var uri = String.format("%s/%s/js/index.js", PREFIX_STATIC_RESOURCES, version.getVersion()); - - // "/resource-versioning/js/index.js" 경로의 정적 파일에 ETag를 사용한 캐싱이 적용되었는지 확인한다. - final var response = webTestClient - .get() - .uri(uri) - .exchange() - .expectStatus().isOk() - .expectHeader().exists(HttpHeaders.ETAG) - .expectHeader().cacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic()) - .expectBody(String.class).returnResult(); - - log.info("response body\n{}", response.getResponseBody()); - - final var etag = response.getResponseHeaders().getETag(); - - // 캐싱되었다면 "/resource-versioning/js/index.js"로 다시 호출했을때 HTTP status는 304를 반환한다. - webTestClient.get() - .uri(uri) - .header(HttpHeaders.IF_NONE_MATCH, etag) - .exchange() - .expectStatus() - .isNotModified(); - } -} diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java deleted file mode 100644 index e1b6cca042..0000000000 --- a/study/src/test/java/study/FileTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package study; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. - * File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. - */ -@DisplayName("File 클래스 학습 테스트") -class FileTest { - - /** - * resource 디렉터리 경로 찾기 - * - * File 객체를 생성하려면 파일의 경로를 알아야 한다. - * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. - * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? - */ - @Test - void resource_디렉터리에_있는_파일의_경로를_찾는다() { - final String fileName = "nextstep.txt"; - - // todo - final String actual = ""; - - assertThat(actual).endsWith(fileName); - } - - /** - * 파일 내용 읽기 - * - * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. - * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. - */ - @Test - void 파일의_내용을_읽는다() { - final String fileName = "nextstep.txt"; - - // todo - final Path path = null; - - // todo - final List actual = Collections.emptyList(); - - assertThat(actual).containsOnly("nextstep"); - } -} diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java deleted file mode 100644 index 47a79356b6..0000000000 --- a/study/src/test/java/study/IOStreamTest.java +++ /dev/null @@ -1,213 +0,0 @@ -package study; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -/** - * 자바는 스트림(Stream)으로부터 I/O를 사용한다. - * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. - * - * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. - * FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. - * FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환) - * - * Stream은 데이터를 바이트로 읽고 쓴다. - * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. - * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다. - */ -@DisplayName("Java I/O Stream 클래스 학습 테스트") -class IOStreamTest { - - /** - * OutputStream 학습하기 - * - * 자바의 기본 출력 클래스는 java.io.OutputStream이다. - * OutputStream의 write(int b) 메서드는 기반 메서드이다. - * public abstract void write(int b) throws IOException; - */ - @Nested - class OutputStream_학습_테스트 { - - /** - * OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다. - * OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다. - * 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때, - * 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다. - * - * write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다. - * write(byte[] data)write(byte b[], int off, int len) 메서드는 - * 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다. - */ - @Test - void OutputStream은_데이터를_바이트로_처리한다() throws IOException { - final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112}; - final OutputStream outputStream = new ByteArrayOutputStream(bytes.length); - - /** - * todo - * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 - */ - - final String actual = outputStream.toString(); - - assertThat(actual).isEqualTo("nextstep"); - outputStream.close(); - } - - /** - * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. - * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. - * - * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. - * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. - * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 - * 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다. - */ - @Test - void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException { - final OutputStream outputStream = mock(BufferedOutputStream.class); - - /** - * todo - * flush를 사용해서 테스트를 통과시킨다. - * ByteArrayOutputStream과 어떤 차이가 있을까? - */ - - verify(outputStream, atLeastOnce()).flush(); - outputStream.close(); - } - - /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. - */ - @Test - void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException { - final OutputStream outputStream = mock(OutputStream.class); - - /** - * todo - * try-with-resources를 사용한다. - * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. - */ - - verify(outputStream, atLeastOnce()).close(); - } - } - - /** - * InputStream 학습하기 - * - * 자바의 기본 입력 클래스는 java.io.InputStream이다. - * InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다. - * InputStream의 read() 메서드는 기반 메서드이다. - * public abstract int read() throws IOException; - * - * InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다. - */ - @Nested - class InputStream_학습_테스트 { - - /** - * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. - * int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다. - * 그리고 Stream 끝에 도달하면 -1을 반환한다. - */ - @Test - void InputStream은_데이터를_바이트로_읽는다() throws IOException { - byte[] bytes = {-16, -97, -92, -87}; - final InputStream inputStream = new ByteArrayInputStream(bytes); - - /** - * todo - * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? - */ - final String actual = ""; - - assertThat(actual).isEqualTo("🤩"); - assertThat(inputStream.read()).isEqualTo(-1); - inputStream.close(); - } - - /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. - */ - @Test - void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException { - final InputStream inputStream = mock(InputStream.class); - - /** - * todo - * try-with-resources를 사용한다. - * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. - */ - - verify(inputStream, atLeastOnce()).close(); - } - } - - /** - * FilterStream 학습하기 - * - * 필터는 필터 스트림, reader, writer로 나뉜다. - * 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. - * reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다. - */ - @Nested - class FilterStream_학습_테스트 { - - /** - * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다. - * InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다. - * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? - */ - @Test - void 필터인_BufferedInputStream를_사용해보자() { - final String text = "필터에 연결해보자."; - final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; - - final byte[] actual = new byte[0]; - - assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); - assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); - } - } - - /** - * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. - * 문자열이 아닌 바이트 단위로 처리하려니 불편하다. - * 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. - * reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. - * 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다. - */ - @Nested - class InputStreamReader_학습_테스트 { - - /** - * InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다. - * 읽어온 문자(char)를 문자열(String)로 처리하자. - * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. - */ - @Test - void BufferedReader를_사용하여_문자열을_읽어온다() { - final String emoji = String.join("\r\n", - "😀😃😄😁😆😅😂🤣🥲☺️😊", - "😇🙂🙃😉😌😍🥰😘😗😙😚", - "😋😛😝😜🤪🤨🧐🤓😎🥸🤩", - ""); - final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); - - final StringBuilder actual = new StringBuilder(); - - assertThat(actual).hasToString(emoji); - } - } -} diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java deleted file mode 100644 index 0333c18e3b..0000000000 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package thread.stage0; - -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 - */ -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(); - - IntStream.range(0, 1000) - .forEach(count -> executorService.submit(synchronizedMethods::calculate)); - executorService.awaitTermination(500, TimeUnit.MILLISECONDS); - - assertThat(synchronizedMethods.getSum()).isEqualTo(1000); - } - - private static final class SynchronizedMethods { - - private int sum = 0; - - public void calculate() { - setSum(getSum() + 1); - } - - public int getSum() { - return sum; - } - - public void setSum(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 deleted file mode 100644 index 238611ebfe..0000000000 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package thread.stage0; - -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 - */ -class ThreadPoolsTest { - - private static final Logger log = LoggerFactory.getLogger(ThreadPoolsTest.class); - - @Test - void testNewFixedThreadPool() { - final var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2); - executor.submit(logWithSleep("hello fixed thread pools")); - executor.submit(logWithSleep("hello fixed thread pools")); - executor.submit(logWithSleep("hello fixed thread pools")); - - // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; - final int expectedQueueSize = 0; - - assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); - assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size()); - } - - @Test - void testNewCachedThreadPool() { - final var executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); - executor.submit(logWithSleep("hello cached thread pools")); - executor.submit(logWithSleep("hello cached thread pools")); - executor.submit(logWithSleep("hello cached thread pools")); - - // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; - final int expectedQueueSize = 0; - - assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); - assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size()); - } - - private Runnable logWithSleep(final String message) { - return () -> { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - log.info(message); - }; - } -} diff --git a/study/src/test/java/thread/stage0/ThreadTest.java b/study/src/test/java/thread/stage0/ThreadTest.java deleted file mode 100644 index 3ffef18869..0000000000 --- a/study/src/test/java/thread/stage0/ThreadTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package thread.stage0; - -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 자바로 동시에 여러 작업을 처리할 때 스레드를 사용한다. - * 스레드 객체를 직접 생성하는 방법부터 알아보자. - * 진행하면서 막히는 부분은 아래 링크를 참고해서 해결한다. - * - * Thread Objects - * https://docs.oracle.com/javase/tutorial/essential/concurrency/threads.html - * - * Defining and Starting a Thread - * https://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html - */ -class ThreadTest { - - private static final Logger log = LoggerFactory.getLogger(ThreadTest.class); - - /** - * 자바에서 직접 스레드를 만드는 방법은 2가지가 있다. - * 먼저 Thread 클래스를 상속해서 스레드로 만드는 방법을 살펴보자. - * 주석을 참고하여 테스트 코드를 작성하고, 테스트를 실행시켜서 메시지가 잘 나오는지 확인한다. - */ - @Test - void testExtendedThread() throws InterruptedException { - // 하단의 ExtendedThread 클래스를 Thread 클래스로 상속하고 스레드 객체를 생성한다. - Thread thread = new ExtendedThread("hello thread"); - - // 생성한 thread 객체를 시작한다. - thread.start(); - - // thread의 작업이 완료될 때까지 기다린다. - thread.join(); - } - - /** - * Runnable 인터페이스를 사용하는 방법도 있다. - * 주석을 참고하여 테스트 코드를 작성하고, 테스트를 실행시켜서 메시지가 잘 나오는지 확인한다. - */ - @Test - void testRunnableThread() throws InterruptedException { - // 하단의 RunnableThread 클래스를 Runnable 인터페이스의 구현체로 만들고 Thread 클래스를 활용하여 스레드 객체를 생성한다. - Thread thread = new Thread(new RunnableThread("hello thread")); - - // 생성한 thread 객체를 시작한다. - thread.start(); - - // thread의 작업이 완료될 때까지 기다린다. - thread.join(); - } - - private static final class ExtendedThread extends Thread { - - private String message; - - public ExtendedThread(final String message) { - this.message = message; - } - - @Override - public void run() { - log.info(message); - } - } - - private static final class RunnableThread implements Runnable { - - private String message; - - public RunnableThread(final String message) { - this.message = message; - } - - @Override - public void run() { - log.info(message); - } - } -} diff --git a/study/src/test/java/thread/stage1/ConcurrencyTest.java b/study/src/test/java/thread/stage1/ConcurrencyTest.java deleted file mode 100644 index f5e8ee070a..0000000000 --- a/study/src/test/java/thread/stage1/ConcurrencyTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package thread.stage1; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 스레드를 다룰 때 어떤 상황을 조심해야 할까? - * - 상태를 가진 한 객체를 여러 스레드에서 동시에 접근할 경우 - * - static 변수를 가진 객체를 여러 스레드에서 동시에 접근할 경우 - * - * 위 경우는 동기화(synchronization)를 적용시키거나 객체가 상태를 갖지 않도록 한다. - * 객체를 불변 객체로 만드는 방법도 있다. - * - * 웹서버는 여러 사용자가 동시에 접속을 시도하기 때문에 동시성 이슈가 생길 수 있다. - * 어떤 사례가 있는지 아래 테스트 코드를 통해 알아보자. - */ -class ConcurrencyTest { - - @Test - void test() throws InterruptedException { - final var userServlet = new UserServlet(); - - // 웹서버로 동시에 2명의 유저가 gugu라는 이름으로 가입을 시도했다. - // UserServlet의 users에 이미 가입된 회원이 있으면 중복 가입할 수 없도록 코드를 작성했다. - final var firstThread = new Thread(new HttpProcessor(new User("gugu"), userServlet)); - final var secondThread = new Thread(new HttpProcessor(new User("gugu"), userServlet)); - - // 스레드는 실행 순서가 정해져 있지 않다. - // firstThread보다 늦게 시작한 secondThread가 먼저 실행될 수도 있다. - firstThread.start(); - secondThread.start(); - secondThread.join(); // secondThread가 먼저 gugu로 가입했다. - firstThread.join(); - - // 이미 gugu로 가입한 사용자가 있어서 UserServlet.join() 메서드의 if절 조건은 false가 되고 크기는 1이다. - // 하지만 디버거로 개별 스레드를 일시 중지하면 if절 조건이 true가 되고 크기가 2가 된다. 왜 그럴까? - assertThat(userServlet.getUsers()).hasSize(1); - } -} diff --git a/study/src/test/java/thread/stage1/HttpProcessor.java b/study/src/test/java/thread/stage1/HttpProcessor.java deleted file mode 100644 index 8186e8ad13..0000000000 --- a/study/src/test/java/thread/stage1/HttpProcessor.java +++ /dev/null @@ -1,17 +0,0 @@ -package thread.stage1; - -public class HttpProcessor implements Runnable { - - private final User user; - private final UserServlet userServlet; - - public HttpProcessor(final User user, final UserServlet userServlet) { - this.user = user; - this.userServlet = userServlet; - } - - @Override - public void run() { - userServlet.service(user); - } -} diff --git a/study/src/test/java/thread/stage1/User.java b/study/src/test/java/thread/stage1/User.java deleted file mode 100644 index 62e1bf959c..0000000000 --- a/study/src/test/java/thread/stage1/User.java +++ /dev/null @@ -1,36 +0,0 @@ -package thread.stage1; - -import java.util.Objects; - -public class User { - - private final String name; - - public User(final String name) { - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof User)) return false; - final User user = (User) o; - return Objects.equals(name, user.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - - @Override - public String toString() { - return "User{" + - "name='" + name + '\'' + - '}'; - } -} diff --git a/study/src/test/java/thread/stage1/UserServlet.java b/study/src/test/java/thread/stage1/UserServlet.java deleted file mode 100644 index b180a84c32..0000000000 --- a/study/src/test/java/thread/stage1/UserServlet.java +++ /dev/null @@ -1,27 +0,0 @@ -package thread.stage1; - -import java.util.ArrayList; -import java.util.List; - -public class UserServlet { - - private final List users = new ArrayList<>(); - - public void service(final User user) { - join(user); - } - - private void join(final User user) { - if (!users.contains(user)) { - users.add(user); - } - } - - public int size() { - return users.size(); - } - - public List getUsers() { - return users; - } -} diff --git a/study/src/test/java/thread/stage2/AppTest.java b/study/src/test/java/thread/stage2/AppTest.java deleted file mode 100644 index e253c4a249..0000000000 --- a/study/src/test/java/thread/stage2/AppTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package thread.stage2; - -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; - -class AppTest { - - private static final AtomicInteger count = new AtomicInteger(0); - - /** - * 1. App 클래스의 애플리케이션을 실행시켜 서버를 띄운다. - * 2. 아래 테스트를 실행시킨다. - * 3. AppTest가 아닌 App의 콘솔에서 SampleController가 생성한 http call count 로그를 확인한다. - * 4. application.yml에서 설정값을 변경해보면서 어떤 차이점이 있는지 분석해본다. - * - 로그가 찍힌 시간 - * - 스레드명(nio-8080-exec-x)으로 생성된 스레드 갯수를 파악 - * - http call count - * - 테스트 결과값 - */ - @Test - void test() throws Exception { - final var NUMBER_OF_THREAD = 10; - var threads = new Thread[NUMBER_OF_THREAD]; - - for (int i = 0; i < NUMBER_OF_THREAD; i++) { - threads[i] = new Thread(() -> incrementIfOk(TestHttpUtils.send("/test"))); - } - - for (final var thread : threads) { - thread.start(); - Thread.sleep(50); - } - - for (final var thread : threads) { - thread.join(); - } - - assertThat(count.intValue()).isEqualTo(2); - } - - private static void incrementIfOk(final HttpResponse response) { - if (response.statusCode() == 200) { - count.incrementAndGet(); - } - } -} diff --git a/study/src/test/java/thread/stage2/TestHttpUtils.java b/study/src/test/java/thread/stage2/TestHttpUtils.java deleted file mode 100644 index ca37f2f8bb..0000000000 --- a/study/src/test/java/thread/stage2/TestHttpUtils.java +++ /dev/null @@ -1,29 +0,0 @@ -package thread.stage2; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; - -public class TestHttpUtils { - - private static final HttpClient httpClient = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .connectTimeout(Duration.ofSeconds(1)) - .build(); - - public static HttpResponse send(final String path) { - final var request = HttpRequest.newBuilder() - .uri(URI.create("http://localhost:8080" + path)) - .timeout(Duration.ofSeconds(1)) - .build(); - - try { - return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } -} diff --git a/study/src/test/resources/nextstep.txt b/study/src/test/resources/nextstep.txt deleted file mode 100644 index 3ba24f830d..0000000000 --- a/study/src/test/resources/nextstep.txt +++ /dev/null @@ -1 +0,0 @@ -nextstep \ No newline at end of file diff --git a/tomcat/src/main/java/nextstep/jwp/exception/UserNotFoundException.java b/tomcat/src/main/java/nextstep/jwp/exception/UserNotFoundException.java new file mode 100644 index 0000000000..20ac4c160e --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/exception/UserNotFoundException.java @@ -0,0 +1,8 @@ +package nextstep.jwp.exception; + +public class UserNotFoundException extends RuntimeException { + + public UserNotFoundException() { + super("유저를 찾을 수 없습니다."); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/Controller.java b/tomcat/src/main/java/nextstep/jwp/presentation/Controller.java new file mode 100644 index 0000000000..6820fc2c8d --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/Controller.java @@ -0,0 +1,10 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; + +import java.io.IOException; + +public interface Controller { + String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException; +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/GetLoginController.java b/tomcat/src/main/java/nextstep/jwp/presentation/GetLoginController.java new file mode 100644 index 0000000000..df85febedf --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/GetLoginController.java @@ -0,0 +1,42 @@ +package nextstep.jwp.presentation; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.exception.UserNotFoundException; +import nextstep.jwp.model.User; +import org.apache.catalina.connector.Connector; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.apache.coyote.http11.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Map; + +public class GetLoginController implements Controller { + + private static final Logger log = LoggerFactory.getLogger(Connector.class); + private static final String SESSION_ID = "JSESSIONID"; + + @Override + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + Map cookies = httpRequestParser.findCookies(); + if (cookies.containsKey(SESSION_ID) && SessionManager.isAlreadyLogin(cookies.get(SESSION_ID))) { + return httpResponseBuilder.buildStaticFileRedirectResponse(httpRequestParser, "/index.html"); + } + processQueryString(httpRequestParser); + return httpResponseBuilder.buildStaticFileOkResponse(httpRequestParser, "/login.html"); + } + + private void processQueryString(HttpRequestParser httpRequestParser) { + Map queryStrings = httpRequestParser.findQueryStrings(); + if (queryStrings.containsKey("account") && queryStrings.containsKey("password")) { + String account = queryStrings.get("account"); + String password = queryStrings.get("password"); + User user = InMemoryUserRepository.findByAccount(account).orElseThrow(UserNotFoundException::new); + if (user.checkPassword(password)) { + log.info(user.toString()); + } + } + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/GetRegisterController.java b/tomcat/src/main/java/nextstep/jwp/presentation/GetRegisterController.java new file mode 100644 index 0000000000..6a9778213c --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/GetRegisterController.java @@ -0,0 +1,15 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; + +import java.io.IOException; + +public class GetRegisterController implements Controller { + + @Override + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + return httpResponseBuilder.buildStaticFileOkResponse(httpRequestParser, + httpRequestParser.findPathWithoutQueryString() + ".html"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/NotFoundController.java b/tomcat/src/main/java/nextstep/jwp/presentation/NotFoundController.java new file mode 100644 index 0000000000..9e61f6828f --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/NotFoundController.java @@ -0,0 +1,15 @@ +package nextstep.jwp.presentation; + + +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; + +import java.io.IOException; + +public class NotFoundController implements Controller { + + @Override + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + return httpResponseBuilder.buildStaticFileNotFoundResponse(httpRequestParser); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/PostLoginController.java b/tomcat/src/main/java/nextstep/jwp/presentation/PostLoginController.java new file mode 100644 index 0000000000..8763494d96 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/PostLoginController.java @@ -0,0 +1,67 @@ +package nextstep.jwp.presentation; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.exception.UserNotFoundException; +import nextstep.jwp.model.User; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.apache.coyote.http11.SessionManager; + +import java.io.IOException; +import java.util.Map; +import java.util.UUID; + +public class PostLoginController implements Controller { + + private static final String SESSION_ID = "JSESSIONID"; + private static final String KEY_VALUE_SEPARATOR = "="; + + @Override + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + String[] splitRequestBody = httpRequestParser.getMessageBody().split("&"); + String account = splitRequestBody[0].split(KEY_VALUE_SEPARATOR)[1]; + String password = splitRequestBody[1].split(KEY_VALUE_SEPARATOR)[1]; + try { + User user = InMemoryUserRepository.findByAccount(account).orElseThrow(UserNotFoundException::new); + addSession(user, httpRequestParser); + return redirect(password, user, httpRequestParser, httpResponseBuilder); + } catch (UserNotFoundException e) { + return httpResponseBuilder.buildStaticFileRedirectResponse(httpRequestParser, "/401.html"); + } + } + + private void addSession(User user, HttpRequestParser httpRequestParser) { + Map cookies = httpRequestParser.findCookies(); + if (!cookies.containsKey(SESSION_ID)) { + String uuid = UUID.randomUUID().toString(); + addCookie(httpRequestParser, cookies, uuid); + cookies.put(SESSION_ID, uuid); + } + String jsessionid = cookies.get(SESSION_ID); + SessionManager.add(jsessionid, user); + } + + private void addCookie(HttpRequestParser httpRequestParser, Map cookies, String uuid) { + if (cookies.isEmpty()) { + httpRequestParser.addHeader("Cookie", SESSION_ID + "=" + uuid); + return; + } + //cookies에 잇는 cookie들로 Cookie header 만들어줘 + String existedCookie = joinExistedCookie(cookies); + httpRequestParser.addHeader("Cookie", existedCookie + "; " + SESSION_ID + "=" + uuid); + } + + private static String joinExistedCookie(Map cookies) { + return cookies.entrySet().stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .reduce((cookie1, cookie2) -> cookie1 + "; " + cookie2) + .orElse(""); + } + + private String redirect(String password, User user, HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + if (user.checkPassword(password)) { + return httpResponseBuilder.buildStaticFileRedirectResponse(httpRequestParser, "/index.html"); + } + return httpResponseBuilder.buildStaticFileRedirectResponse(httpRequestParser, "/401.html"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/PostRegisterController.java b/tomcat/src/main/java/nextstep/jwp/presentation/PostRegisterController.java new file mode 100644 index 0000000000..cd9907523a --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/PostRegisterController.java @@ -0,0 +1,25 @@ +package nextstep.jwp.presentation; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; + +import java.io.IOException; + +public class PostRegisterController implements Controller { + + private static final String KEY_VALUE_SEPARATOR = "="; + + @Override + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + String[] splitRequestBody = httpRequestParser.getMessageBody().split("&"); + String account = splitRequestBody[0].split(KEY_VALUE_SEPARATOR)[1]; + String email = splitRequestBody[1].split(KEY_VALUE_SEPARATOR)[1]; + email = email.replace("%40", "@"); + String password = splitRequestBody[2].split(KEY_VALUE_SEPARATOR)[1]; + + InMemoryUserRepository.save(new User(account, password, email)); + return httpResponseBuilder.buildStaticFileRedirectResponse(httpRequestParser, "/index.html"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/RootController.java b/tomcat/src/main/java/nextstep/jwp/presentation/RootController.java new file mode 100644 index 0000000000..e0dacb1832 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/RootController.java @@ -0,0 +1,11 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; + +public class RootController implements Controller { + @Override + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) { + return httpResponseBuilder.buildCustomResponse(httpRequestParser, "Hello world!"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/StaticController.java b/tomcat/src/main/java/nextstep/jwp/presentation/StaticController.java new file mode 100644 index 0000000000..ffc4c55d02 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/StaticController.java @@ -0,0 +1,14 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; + +import java.io.IOException; + +public class StaticController implements Controller { + + @Override + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + return httpResponseBuilder.buildStaticFileOkResponse(httpRequestParser, httpRequestParser.findPathWithoutQueryString()); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/presentation/handler/FrontController.java b/tomcat/src/main/java/nextstep/jwp/presentation/handler/FrontController.java new file mode 100644 index 0000000000..88af2a5258 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/presentation/handler/FrontController.java @@ -0,0 +1,58 @@ +package nextstep.jwp.presentation.handler; + +import nextstep.jwp.presentation.Controller; +import nextstep.jwp.presentation.GetLoginController; +import nextstep.jwp.presentation.GetRegisterController; +import nextstep.jwp.presentation.NotFoundController; +import nextstep.jwp.presentation.PostLoginController; +import nextstep.jwp.presentation.PostRegisterController; +import nextstep.jwp.presentation.RootController; +import nextstep.jwp.presentation.StaticController; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FrontController { + + private static final List STATIC_PATH = List.of(".css", ".js", ".ico", ".html", ".svg"); + + private final Map getMappingControllers = new HashMap<>(); + private final Map postMappingControllers = new HashMap<>(); + + public FrontController() { + getMappingControllers.put("/", new RootController()); + getMappingControllers.put("/login", new GetLoginController()); + getMappingControllers.put("/register", new GetRegisterController()); + postMappingControllers.put("/login", new PostLoginController()); + postMappingControllers.put("/register", new PostRegisterController()); + } + + public String process(HttpRequestParser httpRequestParser, HttpResponseBuilder httpResponseBuilder) throws IOException { + String method = httpRequestParser.findMethod(); + String path = httpRequestParser.findPathWithoutQueryString(); + Controller controller = findController(method, path); + + return controller.process(httpRequestParser, httpResponseBuilder); + } + + public Controller findController(String method, String path) { + if (isStaticPath(path)) { + return new StaticController(); + } + if (method.equals("GET") && getMappingControllers.containsKey(path)) { + return getMappingControllers.get(path); + } + if (method.equals("POST") && postMappingControllers.containsKey(path)) { + return postMappingControllers.get(path); + } + return new NotFoundController(); + } + + private boolean isStaticPath(String path) { + return STATIC_PATH.stream().anyMatch(path::endsWith); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java index 3b2c4dda7c..537eb1968b 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -8,6 +8,9 @@ import java.io.UncheckedIOException; import java.net.ServerSocket; import java.net.Socket; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class Connector implements Runnable { @@ -15,16 +18,26 @@ public class Connector implements Runnable { private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_ACCEPT_COUNT = 100; + private static final int MAX_THREAD = 250; + private static final int CORE_POOL_SIZE = 10; + private static final long KEEP_ALIVE_TIME = 60L; private final ServerSocket serverSocket; + private final ThreadPoolExecutor threadPoolExecutor; private boolean stopped; public Connector() { - this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); + this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, MAX_THREAD); } - public Connector(final int port, final int acceptCount) { + public Connector(final int port, final int acceptCount, final int maxThreads) { this.serverSocket = createServerSocket(port, acceptCount); + this.threadPoolExecutor = new ThreadPoolExecutor( + CORE_POOL_SIZE, + maxThreads, + KEEP_ALIVE_TIME, + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(DEFAULT_ACCEPT_COUNT)); this.stopped = false; } @@ -67,13 +80,14 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + threadPoolExecutor.execute(processor); } public void stop() { stopped = true; try { serverSocket.close(); + threadPoolExecutor.shutdown(); } catch (IOException e) { log.error(e.getMessage(), e); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java new file mode 100644 index 0000000000..be0b73e83d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java @@ -0,0 +1,31 @@ +package org.apache.coyote.http11; + +import java.util.Arrays; + +public enum ContentType { + CSS(".css", "text/css"), + ICO(".ico", "text/css"), + JS(".js", "text/javascript"), + SVG(".svg", "image/svg+xml"), + HTML(".html", "text/html"); + + private final String extension; + private final String type; + + ContentType(String extension, String type) { + this.extension = extension; + this.type = type; + } + + public static String findType(String path) { + return Arrays.stream(values()) + .filter(contentType -> path.endsWith(contentType.extension)) + .map(contentType -> contentType.type) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("지원하지 않는 Content Type 입니다.")); + } + + public String getType() { + return type; + } +} 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 cb70b76beb..6f42586f0b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,61 +1,28 @@ package org.apache.coyote.http11; -import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; -import nextstep.jwp.model.User; +import nextstep.jwp.presentation.handler.FrontController; import org.apache.coyote.Processor; -import org.apache.coyote.http11.cookie.HttpCookie; -import org.apache.coyote.http11.cookie.SessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.net.Socket; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.StringTokenizer; - -import static org.apache.coyote.http11.enums.ContentType.getContentType; -import static org.apache.coyote.http11.enums.Path.INDEX_URL; -import static org.apache.coyote.http11.enums.Path.LOGIN_HTML; -import static org.apache.coyote.http11.enums.Path.LOGIN_URL; -import static org.apache.coyote.http11.enums.Path.LOGIN_WITH_PARAM_URL; -import static org.apache.coyote.http11.enums.Path.REDIRECT; -import static org.apache.coyote.http11.enums.Path.REDIRECT_INDEX_HTML; -import static org.apache.coyote.http11.enums.Path.REGISTER_HTML; -import static org.apache.coyote.http11.enums.Path.REGISTER_URL; -import static org.apache.coyote.http11.enums.Path.STATIC; -import static org.apache.coyote.http11.enums.Path.UNAUTHORIZED_HTML; -import static org.apache.coyote.http11.enums.Path.isContainParam; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final int NOT_FOUND_BY_INDEX = -1; - private final HttpCookie httpCookie = new HttpCookie(); - private static Map headers; - private final Socket connection; - - private static final String EMPTY = ""; - private static final String POST_HTTP_METHOD = "POST"; - private static final char EQUALS = '='; - private static final char COLON = ':'; - - private static final String USER_SAVE_SUCCESS_MESSAGE = "유저 저장 성공!"; - private static final String LOGIN_SUCCESS_MESSAGE = "로그인 성공! 아이디 : "; - private static final String INVALID_HTTP_REQUEST_MESSAGE = "Invalid HTTP request"; - private static final String PARAMETER_DELIM = "&"; - private static final String JS_ICO_CSS_REGEX = ".*\\.(js|ico|css)$"; + private final Socket connection; + private final HttpRequestParser httpRequestParser; + private final HttpResponseBuilder httpResponseBuilder; + private final FrontController frontController; public Http11Processor(final Socket connection) { this.connection = connection; + this.httpRequestParser = new HttpRequestParser(); + this.httpResponseBuilder = new HttpResponseBuilder(); + this.frontController = new FrontController(); } @Override @@ -68,25 +35,8 @@ public void run() { public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); - String requestLine = br.readLine(); // HTTP 요청 라인을 읽음 (예: "GET /index.html HTTP/1.1") - final String httpMethod = parseHttpMethod(requestLine);// HTTP method를 읽음 (예: GET) - headers = parseRequestHeaders(br); // header를 읽음 - - if (POST_HTTP_METHOD.equals(httpMethod)) { //post method일 때만 requestBody 읽어오고 user를 등록 - final String requestBody = readRequestBody(br); - registerUser(requestBody); - } - - final String path = parseHttpRequest(requestLine); // 파싱된 HTTP 요청에서 경로 추출 - storeJsessionId(); - final String parsedPathBeforeSubString = parsePath(path, httpMethod); // 경로를 기반으로 정적 파일을 읽고 응답 생성 - final String parsedPath = subStringIfRedirectPath(parsedPathBeforeSubString); - - final String responseBody = readStaticFile(parsedPath); - final String contentType = getContentType(parsedPath); //content type을 다르게 준다 - final String response = createResponse(contentType, responseBody, parsedPathBeforeSubString); // JSESSIONID가 있는 경우, 없는 경우 다르게 response를 준다 + httpRequestParser.accept(inputStream); + String response = frontController.process(httpRequestParser, httpResponseBuilder); outputStream.write(response.getBytes()); outputStream.flush(); @@ -94,263 +44,4 @@ public void process(final Socket connection) { log.error(e.getMessage(), e); } } - - private String subStringIfRedirectPath(final String parsedPathBeforeSubString) { - if (parsedPathBeforeSubString.contains(REDIRECT.getValue() + COLON)) { - return parsedPathBeforeSubString.substring((REDIRECT.getValue() + COLON).length(), parsedPathBeforeSubString.length()); - - } - return parsedPathBeforeSubString; - } - - private String parseHttpMethod(final String requestLine) throws IOException { - // 요청 라인을 공백으로 분리하여 경로를 추출 - String[] requestParts = requestLine.split(" "); - return requestParts[0]; - } - - private Map parseRequestHeaders(final BufferedReader br) throws IOException { - final List lines = readAllLinesBeforeBody(br); - final Map headers = parseHeaderLines(lines); - - return headers; - } - - private List readAllLinesBeforeBody(final BufferedReader br) throws IOException { - final List lines = new ArrayList<>(); - String line; - while (!(line = br.readLine()).equals(EMPTY)) { - lines.add(line); - } - return lines; - } - - private Map parseHeaderLines(final List lines) { - Map headers = new HashMap<>(); - - String lineForParse; - for (int i = 0; i < getEmptyLineIndex(lines); i++) { - lineForParse = lines.get(i); - int colonIndex = lineForParse.indexOf(COLON); - if (isExistByIndex(colonIndex)) { - String key = lineForParse.substring(0, colonIndex).trim(); - String value = lineForParse.substring(colonIndex + 1).trim(); - headers.put(key, value); - } - } - - return headers; - } - - private boolean isExistByIndex(final int colonIndex) { - return colonIndex != NOT_FOUND_BY_INDEX; - } - - private int getEmptyLineIndex(final List lines) { - int emptyLineIndex = lines.indexOf(EMPTY); - if (emptyLineIndex == NOT_FOUND_BY_INDEX) { - emptyLineIndex = lines.size(); - } - return emptyLineIndex; - } - - private String storeJsessionId() { - if (isContainJsessionId()) { - return httpCookie.changeJSessionId(parseJsessionId()); - } - - return httpCookie.createJSession(); - } - - private String parseJsessionId() { - if (headers.containsKey("Cookie")) { - final String cookieValue = headers.get("Cookie"); - String[] cookiePairs = cookieValue.split("; "); - for (String cookiePair : cookiePairs) { - String[] parts = cookiePair.split("="); - if (parts[0].equals("JSESSIONID")) { - return parts[1]; - } - } - } - - return EMPTY; - } - - private String readRequestBody(final BufferedReader br) throws IOException { - final int contentLength = Integer.parseInt(headers.get("Content-Length")); - final char[] buffer = new char[contentLength]; - br.read(buffer, 0, contentLength); - - return new String(buffer); - } - - private void registerUser(final String requestBody) throws IOException { - Map userData = parseUserDataFromRequestBody(requestBody); - String parsedAccount = userData.get("account"); - String parsedEmail = userData.get("email"); - String parsedPassword = userData.get("password"); - - final User user = new User(parsedAccount, parsedPassword, parsedEmail); - InMemoryUserRepository.save(user); - log.info(USER_SAVE_SUCCESS_MESSAGE); - } - - private Map parseUserDataFromRequestBody(final String requestBody) { - Map userData = new HashMap<>(); - StringTokenizer st = new StringTokenizer(requestBody, PARAMETER_DELIM); - - while (st.hasMoreTokens()) { - String token = st.nextToken(); - int equalsIndex = token.indexOf(EQUALS); - - if (isExistByIndex(equalsIndex)) { - String key = token.substring(0, equalsIndex); - String value = token.substring(equalsIndex + 1); - userData.put(key, value); - } - } - - return userData; - } - - private String parseHttpRequest(final String requestLine) throws IOException { - // 요청 라인을 공백으로 분리하여 경로를 추출 - String[] requestParts = requestLine.split(" "); - - if (requestParts.length >= 2) { - return requestParts[1]; // 두 번째 요소가 경로 (예: "/index.html") - } - throw new IOException(INVALID_HTTP_REQUEST_MESSAGE); // 유효하지 않은 요청 처리 - } - - private String parsePath(final String path, final String httpMethod) { - String url = INDEX_URL.getValue(); - - if (path.matches(JS_ICO_CSS_REGEX)) { - url = STATIC.getValue() + path; - } - - if (path.contains(LOGIN_URL.getValue())) { - url = getPathForLogin(path); - } - - if (path.equals(REGISTER_URL.getValue())) { - url = getPathForRegister(httpMethod); - } - - return url; - } - - private String readStaticFile(final String parsedPath) throws IOException { - ClassLoader classLoader = getClass().getClassLoader(); - InputStream resourceInputStream = classLoader.getResourceAsStream(parsedPath); - - StringBuilder content = new StringBuilder(); - if (resourceInputStream != null) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceInputStream))) { - String line; - while ((line = reader.readLine()) != null) { - content.append(line).append("\n"); - } - } - } - - return content.toString(); - } - - private String createResponse(final String contentType, final String responseBody, final String path) { - if (path.equals(UNAUTHORIZED_HTML.getValue())) { - return Response.of(contentType, responseBody, "401 Unauthorized"); - } - - if (path.contains(REDIRECT.getValue() + COLON)) { - return Response.ofRedirect(contentType, responseBody, "302 Redirected", httpCookie); - } - - if (!path.matches(JS_ICO_CSS_REGEX)) { - return Response.of(contentType, responseBody, httpCookie); - } - - return Response.of(contentType, responseBody); - } - - private boolean isContainJsessionId() { - if (headers.containsKey("Cookie")) { - final String cookieValue = headers.get("Cookie"); - String[] cookiePairs = cookieValue.split("; "); - for (String cookiePair : cookiePairs) { - String[] parts = cookiePair.split("="); - if (parts[0].equals("JSESSIONID")) { - return true; - } - } - } - - return false; - } - - //요청 보낼 때 마다 쿠키 header읽고 쿠키 읽어와서 저장하는 것이 필요. >> 유저는 어떻게 할 것인가?? - private String getPathForLogin(final String path) { - if (isContainParam(path)) { - if (!isValidUser(path)) { - return UNAUTHORIZED_HTML.getValue(); - } - return REDIRECT_INDEX_HTML.getValue(); - } - - if (!SessionManager.isValidHttpCookie(httpCookie)) { - return LOGIN_HTML.getValue(); - } - - return REDIRECT.getValue() + COLON + INDEX_URL.getValue(); - } - - private boolean isValidUser(final String path) { - String noUrlPath = path.replace(LOGIN_WITH_PARAM_URL.getValue(), EMPTY); - Map loginData = parseLoginData(noUrlPath); - String parsedAccount = loginData.get("account"); - String parsedPassword = loginData.get("password"); - - final Optional maybeUser = InMemoryUserRepository.findByAccount(parsedAccount); - - if (maybeUser.isPresent()) { - final User foundUser = maybeUser.get(); - - if (foundUser.checkPassword(parsedPassword)) { - if (!SessionManager.isValidUser(foundUser, httpCookie.getJSessionId())) { - SessionManager.add(foundUser, httpCookie); - } - log.info(LOGIN_SUCCESS_MESSAGE + parsedAccount); - return true; - } - } - - return false; - } - - private Map parseLoginData(final String path) { - Map loginData = new HashMap<>(); - final StringTokenizer st = new StringTokenizer(path, PARAMETER_DELIM); - - while (st.hasMoreTokens()) { - String token = st.nextToken(); - int equalsIndex = token.indexOf(EQUALS); - - if (isExistByIndex(equalsIndex)) { - String key = token.substring(0, equalsIndex); - String value = token.substring(equalsIndex + 1); - loginData.put(key, value); - } - } - - return loginData; - } - - private String getPathForRegister(final String httpMethod) { - if (httpMethod.equals(POST_HTTP_METHOD)) { - return REDIRECT.getValue() + COLON + INDEX_URL.getValue(); - } - return REGISTER_HTML.getValue(); - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java new file mode 100644 index 0000000000..70e7ce0c77 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java @@ -0,0 +1,110 @@ +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class HttpRequestParser { + + private static final String KEY_VALUE_DELIMITER = "="; + private static final String START_LINE_DELIMITER = " "; + private static final String QUERY_PARAMETER_DELIMITER_REGEX = "\\?"; + private static final String QUERY_PARAMETER_DELIMITER = "?"; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + + private String startLine; + private Map header = new HashMap<>(); + private String messageBody; + + public void accept(InputStream inputStream) throws IOException { + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + + startLine = bufferedReader.readLine(); + readHeader(bufferedReader); + readMessageBody(bufferedReader); + } + + private void readHeader(BufferedReader bufferedReader) throws IOException { + String line = bufferedReader.readLine(); + while (line != null && !line.isBlank()) { + String[] split = line.split(":"); + header.put(split[KEY_INDEX], split[VALUE_INDEX].trim()); + line = bufferedReader.readLine(); + } + } + + private void readMessageBody(BufferedReader bufferedReader) throws IOException { + String key = "Content-Length"; + if (header.containsKey(key)) { + int contentLength = Integer.parseInt(header.get(key)); + char[] body = new char[contentLength]; + bufferedReader.read(body, 0, contentLength); + messageBody = new String(body); + } + } + + public String findMethod() { + return startLine.split(START_LINE_DELIMITER)[0]; + } + + public String findPath() { + return startLine.split(START_LINE_DELIMITER)[1]; + } + + public String findProtocol() { + return startLine.split(START_LINE_DELIMITER)[2]; + } + + public Map findCookies() { + Map cookie = header.entrySet().stream() + .filter(entry -> entry.getKey().equals("Cookie")) + .map(entry -> entry.getValue().split("; ")) + .flatMap(Arrays::stream) + .map(line -> line.split(KEY_VALUE_DELIMITER)) + .collect(Collectors.toMap(line -> line[0], line -> line[1])); + return cookie; + } + + public Map findQueryStrings() { + String path = findPath(); + if (!path.contains(QUERY_PARAMETER_DELIMITER)) { + return new HashMap<>(); + } + String queryString = path.split(QUERY_PARAMETER_DELIMITER_REGEX)[1]; + return Arrays.stream(queryString.split("&")) + .map(line -> line.split(KEY_VALUE_DELIMITER)) + .collect(Collectors.toMap(line -> line[0], line -> line[1])); + } + + public String findPathWithoutQueryString() { + String path = findPath(); + if (!path.contains(QUERY_PARAMETER_DELIMITER)) { + return path; + } + return path.split(QUERY_PARAMETER_DELIMITER_REGEX)[0]; + } + + public String getStartLine() { + return startLine; + } + + public Map getHeader() { + return header; + } + + public String getMessageBody() { + return messageBody; + } + + public void addHeader(String key, String value) { + header.put(key, value); + } + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponseBuilder.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponseBuilder.java new file mode 100644 index 0000000000..1b4d027f79 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponseBuilder.java @@ -0,0 +1,110 @@ +package org.apache.coyote.http11; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class HttpResponseBuilder { + + private static final String STATIC_DIRECTORY = "static"; + private static final String LINE_FEED = "\r\n"; + private static final String SPACE = " "; + private static final List STATIC_PATH = List.of(".css", ".js", ".ico", ".html", ".svg"); + + public String buildStaticFileOkResponse(HttpRequestParser httpRequestParser, String path) throws IOException { + String status = HttpStatus.OK.getHttpStatusCode() + SPACE + HttpStatus.OK.getHttpStatusMessage(); + String protocol = httpRequestParser.findProtocol(); + String contentType = joinContentType(ContentType.findType(path)); + String content = getContent(path); + String contentLength = joinContentLength(content); + + return protocol + SPACE + status + SPACE + LINE_FEED + + findCookie(httpRequestParser) + + contentType + SPACE + LINE_FEED + + contentLength + SPACE + LINE_FEED + + LINE_FEED + + content; + } + + private String joinContentLength(String content) { + return "Content-Length: " + content.getBytes().length; + } + + private String joinContentType(String path) { + return "Content-Type: " + path + ";charset=utf-8"; + } + + public String buildStaticFileRedirectResponse(HttpRequestParser httpRequestParser, String redirectPath) throws IOException { + String status = HttpStatus.REDIRECT.getHttpStatusCode() + SPACE + HttpStatus.REDIRECT.getHttpStatusMessage(); + String protocol = httpRequestParser.findProtocol(); + String contentType = joinContentType(ContentType.HTML.getType()); + String content = getContent(redirectPath); + String contentLength = joinContentLength(content); + + return protocol + SPACE + status + SPACE + LINE_FEED + + findCookie(httpRequestParser) + + contentType + SPACE + LINE_FEED + + contentLength + SPACE + LINE_FEED + + "Location: " + redirectPath + SPACE + LINE_FEED + + content; + } + + public String buildStaticFileNotFoundResponse(HttpRequestParser httpRequestParser) throws IOException { + String status = HttpStatus.NOT_FOUND.getHttpStatusCode() + SPACE + HttpStatus.NOT_FOUND.getHttpStatusMessage(); + String protocol = httpRequestParser.findProtocol(); + String contentType = joinContentType(ContentType.HTML.getType()); + String content = getContent("/404.html"); + String contentLength = joinContentLength(content); + + return protocol + SPACE + status + SPACE + LINE_FEED + + findCookie(httpRequestParser) + + contentType + SPACE + LINE_FEED + + contentLength + SPACE + LINE_FEED + + LINE_FEED + + content; + } + + public String buildCustomResponse(HttpRequestParser httpRequestParser, String content) { + String status = HttpStatus.OK.getHttpStatusCode() + SPACE + HttpStatus.OK.getHttpStatusMessage(); + String protocol = httpRequestParser.findProtocol(); + String contentType = joinContentType(ContentType.HTML.getType()); + String contentLength = joinContentLength(content); + + return protocol + SPACE + status + SPACE + LINE_FEED + + findCookie(httpRequestParser) + + contentType + SPACE + LINE_FEED + + contentLength + SPACE + LINE_FEED + + LINE_FEED + + content; + } + + private String getContent(String path) throws IOException { + URL resource = getClass().getClassLoader().getResource(STATIC_DIRECTORY + path); + return new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } + + private String findCookie(HttpRequestParser httpRequestParser) { + Map cookies = httpRequestParser.findCookies(); + if (!isStaticPath(httpRequestParser.findPath()) && !cookies.isEmpty()) { + StringBuilder cookieHeader = new StringBuilder("Set-Cookie: "); + Set> entries = cookies.entrySet(); + String collect = entries.stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .reduce((cookie1, cookie2) -> cookie1 + "; " + cookie2) + .orElse(""); + cookieHeader.append(collect); + + return cookieHeader.toString() + SPACE + LINE_FEED; + } + return ""; + } + + private boolean isStaticPath(String path) { + return STATIC_PATH.stream().anyMatch(path::endsWith); + } + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java new file mode 100644 index 0000000000..750f6b425e --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java @@ -0,0 +1,23 @@ +package org.apache.coyote.http11; + +public enum HttpStatus { + OK("200", "OK"), + REDIRECT("302", "Moved Temporarily"), + NOT_FOUND("404", "Not Found"); + + private final String httpStatusCode; + private final String httpStatusMessage; + + HttpStatus(String httpStatusCode, String httpStatusMessage) { + this.httpStatusCode = httpStatusCode; + this.httpStatusMessage = httpStatusMessage; + } + + public String getHttpStatusCode() { + return httpStatusCode; + } + + public String getHttpStatusMessage() { + return httpStatusMessage; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/Response.java deleted file mode 100644 index 5629f82b07..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/Response.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.apache.coyote.http11; - -import org.apache.coyote.http11.cookie.HttpCookie; - -public class Response { - - public static String of(final String contentType, final String responseBody) { - return String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + contentType + "charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - } - - public static String of(final String contentType, final String responseBody, final String status) { - return String.join("\r\n", - "HTTP/1.1 " + status + " ", - "Content-Type: " + contentType + "charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - } - - public static String of(final String contentType, final String responseBody, final HttpCookie httpCookie) { - return String.join("\r\n", - "HTTP/1.1 200 OK ", - "Set-Cookie: " + "JSESSIONID=" + httpCookie.getJSessionId() + " ", - "Content-Type: " + contentType + "charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - } - - public static String ofRedirect(final String contentType, final String responseBody, final String status, final HttpCookie httpCookie) { - return String.join("\r\n", - "HTTP/1.1 " + status + " ", - "Set-Cookie: " + "JSESSIONID=" + httpCookie.getJSessionId() + " ", - "Content-Type: " + contentType + "charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java new file mode 100644 index 0000000000..e6cdcf703c --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java @@ -0,0 +1,19 @@ +package org.apache.coyote.http11; + +import nextstep.jwp.model.User; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionManager { + + private static final Map sessions = new ConcurrentHashMap<>(); + + public static void add(String sessionId, User user) { + sessions.put(sessionId, user); + } + + public static boolean isAlreadyLogin(String sessionId) { + return sessions.containsKey(sessionId); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/cookie/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/cookie/HttpCookie.java deleted file mode 100644 index 2e4eaaf635..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/cookie/HttpCookie.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.apache.coyote.http11.cookie; - -import java.util.UUID; - -public class HttpCookie { - private String JSESSIONID = ""; - - public HttpCookie() { - } - - public String changeJSessionId(String jsessionId) { - JSESSIONID = jsessionId; - return JSESSIONID; - } - - public String createJSession() { - JSESSIONID = UUID.randomUUID().toString(); - return JSESSIONID; - } - - public String getJSessionId() { - return JSESSIONID; - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/cookie/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/cookie/SessionManager.java deleted file mode 100644 index baed6130fc..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/cookie/SessionManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.apache.coyote.http11.cookie; - -import nextstep.jwp.model.User; - -import java.util.HashMap; -import java.util.Map; - -public class SessionManager { - - private static final Map sessions = new HashMap<>(); - - public static void add(final User user, HttpCookie httpCookie) { - sessions.put(user, httpCookie.getJSessionId()); - } - - public static boolean isValidUser(final User user, final String jsessionId) { - return sessions.entrySet().stream() - .anyMatch(userStringEntry -> - userStringEntry.getKey().equals(user) - && userStringEntry.getValue().equals(jsessionId) - ); - } - - public static boolean isValidHttpCookie(final HttpCookie httpCookie) { - return sessions.containsValue(httpCookie.getJSessionId()); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/enums/ContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/enums/ContentType.java deleted file mode 100644 index 625d5f71bf..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/enums/ContentType.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.apache.coyote.http11.enums; - -import java.util.Arrays; - -public enum ContentType { - - JS(".js","Application/javascript;"), - CSS(".css","text/css;"), - HTML(".html","text/html;"); - - private final String fileType; - private final String path; - - ContentType(final String fileType, final String path) { - this.fileType = fileType; - this.path = path; - } - - public static String getContentType(final String path) { - return Arrays.stream(ContentType.values()) - .filter(value -> path.endsWith(value.getFileType())) - .findFirst() - .orElse(HTML) - .getPath(); - } - - public String getFileType() { - return fileType; - } - - public String getPath() { - return path; - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/enums/Path.java b/tomcat/src/main/java/org/apache/coyote/http11/enums/Path.java deleted file mode 100644 index 7436055e14..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/enums/Path.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.apache.coyote.http11.enums; - -public enum Path { - - INDEX_URL("static/index.html"), - LOGIN_URL("/login"), - LOGIN_WITH_PARAM_URL("/login?"), - REGISTER_URL("/register"), - - LOGIN_HTML("static/login.html"), - REGISTER_HTML("static/register.html"), - - REDIRECT_INDEX_HTML("redirect:static/index.html"), - UNAUTHORIZED_HTML("redirect:static/401.html"), - - STATIC("static"), - REDIRECT("redirect"); - - private final String value; - - Path(final String value) { - this.value = value; - } - - public static boolean isContainParam(String path) { - return path.contains("?"); - } - - public String getValue() { - return value; - } -} diff --git a/tomcat/src/main/resources/static/favicon.ico b/tomcat/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e6fa230e0113a5c217e80b4bef63d37b64ecefe8 GIT binary patch literal 6373 zcmb7mcQhQ(x9%_oi8e+b5i@%4U9@2k(R;5UVf0Q!5JXM1(R&@EMkjg=(G6h`BuEG% zL`x*D-@5nR_s{$1edjxCoprvm_TJ~Lb@tvj%Qu?fj0{W#W`RIh#9%O9vHx$}^aH3#2}l8bKms-ZAvFPzn&4&_aPKxo5W#;!{a+9e z0*OGxB&4@05)2?DAS42Uh)9S?L7>|s1cU$}H4y|Z3Q{p3reSmN5ko|#(i%GYMwPrJ z5lGZ`ZTGN~qiPlVDLxM?_htp`0)DCc%GBN_3HWDweFt{8rYF#M?OgkSNq>{R{nun=-n3#?rD z?V?0Vrj%szyVLEz6{uQ@^FHmuwpvCfxHk4;?RK*`=;Z~zeLv2SY8{|~R0!0n(4XZR z5BuukWGDkux_a;#gvQ$UP$Upb#WScQl(pm z#N)P5b!uidCK+OdB@wWHzCHJOAI6zHAB$d&2;eWHcJ!=zxU0cgBM&*_PH0OSV?q>r zyED?s+Avzo_109czCQ?JNZT%;y1!Z3iZ!%H&)%zjp2fP31L9RrNepDKB~=XkK#$=$ zHmrCJJ6Gu1)yua;)ZK@lzn+|L?QG^={328p$mbB7I(rS{Wz{1;Y`Otty}8D{Ks z(?d+1$3J}Tk-5$gVcLtjyB5NPMXNwWqGVwCZP_i~>(M`>lTO&p#~24W9pN7E8nzmJ zJb#{6D<6uR26C#)uv)7_le$m)q05l~TOfgL3%K7Iec)~LuA?VR9D4%@dF55kU6(<^ z%DWxhZKGA8qm`3z=xnkvYpa1MGjhi*mjxJk6|`SY^WoJ018+^M?I_v)i*}JypA(ztzXwj zl6W)QlRP)&ef1nq%L}OY?!f(c#>h((rpl zRf(>73u>mFA*p1jE}Plir7DDfYbyHQgk7g?fxyL_X>Fypryu`jU)t#+q4H(CO?(m; zTlosVad%Yp>tu)WHh!_=q^=E>Z3)&|^R81OuqNz?R7!+q+8-}ZD5FNL#-GTjtx{pY zR}HVmX%Kk@1a$m@rD(|GpXK$%w_GIm2$%mn`d}}Ats?=;s2prXNZrwn1r)Umn68sq zxqc@BQ;f^69fVBx(uWlw`h(}4s2hsK3^b=-mt^@S#Z92;JaYdD(iz)iCl6v4lLFaA z_~JVEB~LgH1O$TuFZAta#;GB~{(4q4+gH5|2%LByW(T=l4c zEc8rZhud7-;Guhh`-D@kpiTQzs)w+(h5hb!sH&`+48Kg^KLW z#SCm~_r2z|&ZGx61&6VSy`nQvy*FtwC+>=R#;!N7+u=?kU~uWU7LKSWy#Z+COS-*T z%SX!9Tlb*%=UwC~@=Qym^(*%u(zAPLO9>_0Iqbdt-3uOiUyWmBl3OKscX`8pwr2#It_1hjb>=g|5O z`wQ6#2-7W}l0ldnQOBejSEHWl{3g2r6sHY>yvnWcrUtm>H}CTvQImF8$1*X$`zTmn zRZQQtG4c76#sp{?0p|PC7;gd1OROQgukrrn`gd7 z$1>sqiP0~{Cv?M@rEGqE3ViBfk5&=*mx6PRyW!cggwa{@jN$_%!zRh?;b5Bagks#=b@O35_AW{Pw}d2E!hXN}X+jVZ2b?RjLF-I!j7ZH?xtZXU{j0W#uB97?B*d4Z{iRWj*^y<8*GseGeo|zwFJTJ5XrYX)tP8c1 zPq13~VQlTnNsO9Seet`LjE_Q{p2NPg*7${rtzfu%nh{YxmcZ$qboba)^7yHEh9%3c z_}y0qk9vmNmbx$%x_>Fw-)t6`DnVO}X==pz*pk;*omq3Z?FqP*UHlmFnR$p`9RA~t zlK`ouRlB8!d>zqnz<)ED?dNRd^rrDb&`^45@ZJsJO0?0g|DzqE>D+?hDQ{D8ePYfx z$d&p*v}Ng4T6=po#7(;v`(BkFFfo*y>K$&RrKjCs%67k>T2cN6mtZmh7F^NR+n~Z2gtqj4&+7_j;MPrQ4s`aJAUAO<=KR(c01ULUSeOyp z&@D!A(Q7Hk83qKHsRr0HY3`DChzWS|^1?BPXbtrE2{0#XDcBlmq9$~J#R^T14rmxY zp}h!2QIU|no{7vSrE)dQUBJfo!+YcAx(siAW@0BT8fNWOzRBr+ZMRm~-?G@V>nRC+ zPtu3h4Sj;~7ijzLnLbY8y(zQOtvY)HkPTI@5%DnVtu(Fw{m`bIyuG`7(Du`Z=Ot-c ziB}Ijpfj}i{OIyoUN$%10LJ=u$P}A-Bn~ekP&VwUpm%`FoeM92$NQ7MwYr)eFy%n4d{nSsY}MKh-C&^so~D#b1)T-bkHwZ?WU<=8&4mH4YWZ1bpp zzdKsTITtf1#`z4F2qj<8cB|6z7R&m{w`utgyv1xo7~Y#hrj9V&8GQpXuBElpE8mUb z*gm$UMw9!8l2E>1AAKKA9<*}ThUXYG$C7uTiV-d5QEp&`{b=GpB~nZNCv9!1plCSX z|GobmE5@UiwFUiYTC|}%V`OokptAsDD5kz67Uw2b3`ia@?2IHq2*FT@C-uB&G(Uh0 zLbr6Kr|fmmo>{yaZiFBZzbrsda5DEevx3OKo~TN2i5b)RxDzisPlN`cGo+_ncWwZA zKo_KGw!KLNEQ(YviZYsd{t@YYf?*k-!NuxWR64RLDrCVPn*QR>hy7S1wot0WM=>#O zGOe=^kuwjJv!jlb;+w5&znRtYd2R>F(F!+*KV!lMzkc?`_cF{)Cn z2TDpi!pGR23vlJb`AldTc2#?kkIn8dlWkyw5UowDRsysghoVI?LmV)>|`@F|8z~Nd-j7M1cn(aq%ts)B23Ddn@4U}bO zOJjP&NldJS66K94wh&s*YSL#A_+iq`Z|^U+@824k7sgKPYIJv4{H?G-?36*PFA4lP zU>iU~6LoxQ`nG<%$;c@+M_CdZVes9`8Ofk!Z-qGUv(et)1V_W10~2GCOrdqeH-I&D z{qP#kqz6NQaGJq6bBvI{;Qp5~_QyWgs;5P2diM{DC?rG|#S#U5HmWu{EI;hjt6!^Y z>uS2e4Nx01SFT)0ZX~;8OGi)l&b1f2IZ#q12yRS_;`wR&8Gm?^ zw?>gF`W`eI#vbutZ&6+DpC}vc%vYv_%Dwf^)Q=RI61N$CE;x@{c>K7RWiQ(#P(E7m zO*C?#Cr)biO7}&glVa+c!#5nv(aWGP&-^bFVV1K4W~MNgP(a;Rr2|4K_KM8x&M&u6 z9M)A%F!u4$+ImBWA&~x4|Bq)-zWox}PcT0HAq9AuiHkwYJiUnzRarHfCUa?s_U4hS^R#J5PD&G!s1MT)r*FOv^`H zEjf@8tPCe_@jDSK7te&!FTmV5tb~B??u=^GmslFG5M_EYS{}QJsrsWD~3(tCqS;d@#HWf~Kjt zN#+b3ua8ly5#XVvQ<8_=!z)G^ z@{+#5n)_;ODagOOB#7dvR(()JBNQO3Dbj(gD&kR;euDZQ=y3x`xm2F~B3MKnQ{Fp| zlU)95=|7i}evw;>`QD%^?ZWybODQDY!?sS{;M{dSP)%i9U^0B6J!35b0pV*k0j)$u zPQ?Lt?H`E*7pUklMaxkhFI9$EXZ#>(I2zB&ihCkep7i^f8lB!}w!=%6PfkB4*^5i! z1E+ua(BjW0;&sb{Oh~Oxw30kM{UdSzlRMpQDJ!_$mhG9cUmB*$npV`3@vgX86;9bx zv@?rDJ_5bgDn@s}R?v~As&g5Kzweu6Ig>x3s61db!AN0V@xNhvzVnCc%Y-PuX;oQO zaM?zcy~kJWf`5C8DH0`o`eQ~foJX!|Y|KMz%->|R1E-I(jS&Zv(?3!z3YTbScaIj; z6Ux^L%fAcb#=41w&fjZq@s2x z)7pggeluq@98L&MHN{X{WH0k@6h@F`GqMy7DFv>t2Tt&R_ID-%ZuCXlS(oW^8?LZOlCB;u{%^`#sg$H+)EZDrUu-&7K z_9fKYtJcXrQlgq+kwid{32bmpk11tWE-`0;yR#iSXvf8`l*1$8zMCzJ~3ejV6TCv1LjPNY9LS1viOV_a0b zqT-_PNR*?n(hUKjyDbGY%*nDCBl35+rK*+E@}Qy-@0UMlCcu&PH4Y9e|Ht$67e>l| zIr=O2z_?HS8JMmbva4?zBAQ>B2V~y7(9X0*Qzy$+KikH%aoFXisPgXCR&v1H{zmA{{@Y3g)IO8 literal 0 HcmV?d00001 diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html index f4ed9de875..bc933357f2 100644 --- a/tomcat/src/main/resources/static/login.html +++ b/tomcat/src/main/resources/static/login.html @@ -20,7 +20,7 @@

로그인

-
+
diff --git a/tomcat/src/test/java/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/coyote/http11/Http11ProcessorTest.java new file mode 100644 index 0000000000..430f346a3f --- /dev/null +++ b/tomcat/src/test/java/coyote/http11/Http11ProcessorTest.java @@ -0,0 +1,63 @@ +package coyote.http11; + +import org.apache.coyote.http11.Http11Processor; +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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class Http11ProcessorTest { + + @Test + void process() { + // given + final var socket = new StubSocket(); + final var processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + var expected = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: 12 ", + "", + "Hello world!"); + + assertThat(socket.output()).isEqualTo(expected); + } + + @Test + void index() throws IOException { + // given + final String httpRequest= String.join("\r\n", + "GET /index.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/index.html"); + var expected = "HTTP/1.1 200 OK \r\n" + + "Content-Type: text/html;charset=utf-8 \r\n" + + "Content-Length: 5564 \r\n" + + "\r\n"+ + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/GetLoginControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/GetLoginControllerTest.java new file mode 100644 index 0000000000..c91215c5d1 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/GetLoginControllerTest.java @@ -0,0 +1,67 @@ +package nextstep.jwp.presentation; + +import nextstep.jwp.model.User; +import org.apache.coyote.http.RequestFixture; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.apache.coyote.http11.SessionManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class GetLoginControllerTest { + + @Test + void process() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.GET_LOGIN_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + GetLoginController getLoginController = new GetLoginController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = getLoginController.process(httpRequestParser, httpResponseBuilder); + + //then + assertAll( + () -> assertThat(response).contains("HTTP/1.1 200 OK"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Content-Length: 3797") + ); + } + + @Test + void processIfAlreadyLogin() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.GET_LOGIN_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + GetLoginController getLoginController = new GetLoginController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + String sessionId = UUID.randomUUID().toString(); + httpRequestParser.addHeader("Cookie", "JSESSIONID=" + sessionId); + SessionManager.add(sessionId, new User("test", "test", "test")); + + //when + String response = getLoginController.process(httpRequestParser, httpResponseBuilder); + + //then + Assertions.assertAll( + () -> assertThat(response).contains("HTTP/1.1 302 Moved Temporarily"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Location: /index.html"), + () -> assertThat(response).contains("Content-Length: 5564") + ); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/GetRegisterControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/GetRegisterControllerTest.java new file mode 100644 index 0000000000..e4b192cb7e --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/GetRegisterControllerTest.java @@ -0,0 +1,37 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http.RequestFixture; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class GetRegisterControllerTest { + + @Test + void process() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.GET_REGISTER_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + GetRegisterController getRegisterController = new GetRegisterController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = getRegisterController.process(httpRequestParser, httpResponseBuilder); + + //then + assertAll( + () -> assertThat(response).contains("HTTP/1.1 200 OK"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Content-Length: 4319") + ); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/NotFoundControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/NotFoundControllerTest.java new file mode 100644 index 0000000000..31cd1c7647 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/NotFoundControllerTest.java @@ -0,0 +1,37 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http.RequestFixture; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class NotFoundControllerTest { + + @Test + void process() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.NOT_FOUND_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + NotFoundController notFoundController = new NotFoundController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = notFoundController.process(httpRequestParser, httpResponseBuilder); + + //then + assertAll( + () -> assertThat(response).contains("HTTP/1.1 404 Not Found"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Content-Length: 2426") + ); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/PostLoginControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/PostLoginControllerTest.java new file mode 100644 index 0000000000..392ea24de2 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/PostLoginControllerTest.java @@ -0,0 +1,61 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http.RequestFixture; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +class PostLoginControllerTest { + + @Test + void processIfUserExist() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.POST_SUCCESS_LOGIN_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + PostLoginController postLoginController = new PostLoginController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = postLoginController.process(httpRequestParser, httpResponseBuilder); + + //then + Assertions.assertAll( + () -> assertThat(response).contains("HTTP/1.1 302 Moved Temporarily"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Location: /index.html"), + () -> assertThat(response).contains("Content-Length: 5564") + ); + } + + @Test + void processIfNonExistUser() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.POST_FAIL_LOGIN_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + PostLoginController postLoginController = new PostLoginController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = postLoginController.process(httpRequestParser, httpResponseBuilder); + + //then + Assertions.assertAll( + () -> assertThat(response).contains("HTTP/1.1 302 Moved Temporarily"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Location: /401.html"), + () -> assertThat(response).contains("Content-Length: 2426") + ); + } + +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/PostRegisterControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/PostRegisterControllerTest.java new file mode 100644 index 0000000000..7bf3a13008 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/PostRegisterControllerTest.java @@ -0,0 +1,39 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http.RequestFixture; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +class PostRegisterControllerTest { + + @Test + void process() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.POST_REGISTER_LOGIN_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + PostRegisterController postRegisterController = new PostRegisterController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = postRegisterController.process(httpRequestParser, httpResponseBuilder); + + //then + Assertions.assertAll( + () -> assertThat(response).contains("HTTP/1.1 302 Moved Temporarily"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Location: /index.html"), + () -> assertThat(response).contains("Content-Length: 5564") + ); + } + +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/RootControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/RootControllerTest.java new file mode 100644 index 0000000000..2fb43adcf4 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/RootControllerTest.java @@ -0,0 +1,39 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http.RequestFixture; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class RootControllerTest { + + @Test + void process() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.ROOT_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + RootController rootController = new RootController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = rootController.process(httpRequestParser, httpResponseBuilder); + + //then + assertAll( + () -> assertThat(response).contains("HTTP/1.1 200 OK"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Content-Length: 12"), + () -> assertThat(response).contains("Hello world!") + ); + } + +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/StaticControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/StaticControllerTest.java new file mode 100644 index 0000000000..43bcc20e05 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/StaticControllerTest.java @@ -0,0 +1,59 @@ +package nextstep.jwp.presentation; + +import org.apache.coyote.http.RequestFixture; +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class StaticControllerTest { + + @Test + void processCSS() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.CSS_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + StaticController staticController = new StaticController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = staticController.process(httpRequestParser, httpResponseBuilder); + + //then + assertAll( + () -> assertThat(response).contains("HTTP/1.1 200 OK"), + () -> assertThat(response).contains("Content-Type: text/css;charset=utf-8"), + () -> assertThat(response).contains("Content-Length: 211991") + ); + } + + @Test + void processHTML() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.HTML_REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + + StaticController staticController = new StaticController(); + HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + + //when + String response = staticController.process(httpRequestParser, httpResponseBuilder); + + //then + assertAll( + () -> assertThat(response).contains("HTTP/1.1 200 OK"), + () -> assertThat(response).contains("Content-Type: text/html;charset=utf-8"), + () -> assertThat(response).contains("Content-Length: 5564") + ); + } + +} diff --git a/tomcat/src/test/java/nextstep/jwp/presentation/handler/FrontControllerTest.java b/tomcat/src/test/java/nextstep/jwp/presentation/handler/FrontControllerTest.java new file mode 100644 index 0000000000..135dea181f --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/presentation/handler/FrontControllerTest.java @@ -0,0 +1,98 @@ +package nextstep.jwp.presentation.handler; + +import nextstep.jwp.presentation.Controller; +import nextstep.jwp.presentation.GetLoginController; +import nextstep.jwp.presentation.GetRegisterController; +import nextstep.jwp.presentation.PostLoginController; +import nextstep.jwp.presentation.PostRegisterController; +import nextstep.jwp.presentation.RootController; +import nextstep.jwp.presentation.StaticController; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class FrontControllerTest { + + @Test + void findRootController() { + //given + FrontController frontController = new FrontController(); + + //when + Controller controller = frontController.findController("GET", "/"); + + //then + Assertions.assertThat(controller.getClass()).isEqualTo(RootController.class); + } + + @Test + void findGetLoginController() { + //given + FrontController frontController = new FrontController(); + + //when + Controller controller = frontController.findController("GET", "/login"); + + //then + Assertions.assertThat(controller.getClass()).isEqualTo(GetLoginController.class); + } + + @Test + void findGetRegisterController() { + //given + FrontController frontController = new FrontController(); + + //when + Controller controller = frontController.findController("GET", "/register"); + + //then + Assertions.assertThat(controller.getClass()).isEqualTo(GetRegisterController.class); + } + + @Test + void findStaticControllerWithCss() { + //given + FrontController frontController = new FrontController(); + + //when + Controller controller = frontController.findController("GET", "/css/styles.css"); + + //then + Assertions.assertThat(controller.getClass()).isEqualTo(StaticController.class); + } + + @Test + void findStaticControllerWithHTML() { + //given + FrontController frontController = new FrontController(); + + //when + Controller controller = frontController.findController("GET", "/index.html"); + + //then + Assertions.assertThat(controller.getClass()).isEqualTo(StaticController.class); + } + + @Test + void findPostLoginController() { + //given + FrontController frontController = new FrontController(); + + //when + Controller controller = frontController.findController("POST", "/login"); + + //then + Assertions.assertThat(controller.getClass()).isEqualTo(PostLoginController.class); + } + + @Test + void findPostRegisterController() { + //given + FrontController frontController = new FrontController(); + + //when + Controller controller = frontController.findController("POST", "/register"); + + //then + Assertions.assertThat(controller.getClass()).isEqualTo(PostRegisterController.class); + } +} 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 deleted file mode 100644 index 1944b05f9d..0000000000 --- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java +++ /dev/null @@ -1,244 +0,0 @@ -package nextstep.org.apache.coyote.http11; - -import nextstep.jwp.model.User; -import org.apache.coyote.http11.Http11Processor; -import org.apache.coyote.http11.cookie.HttpCookie; -import org.apache.coyote.http11.cookie.SessionManager; -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 static org.assertj.core.api.Assertions.assertThat; - -class Http11ProcessorTest { - - @Test - void index() throws IOException { - // given - final String request = String.join("\r\n", - "GET / HTTP/1.1 ", - "Host: localhost:8080 ", - "Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Accept: text/css,*/*;q=0.1 ", - "Connection: keep-alive ", - "", - ""); - - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - - // when - processor.process(socket); - var expected = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Set-Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 5564 ", - "", - new String(Files.readAllBytes(new File(resource.getFile()).toPath()))); - - // then - assertThat(socket.output()).isEqualTo(expected); - } - - @Test - void index_css() { - // given - final String request = String.join("\r\n", - "GET /css/styles.css HTTP/1.1 ", - "Host: localhost:8080 ", - "Accept: text/css,*/*;q=0.1 ", - "Connection: keep-alive ", - "", - ""); - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - - // when - processor.process(socket); - - // then - assertThat(socket.output()).contains("HTTP/1.1 200 OK "); - assertThat(socket.output()).contains("Content-Type: text/css;charset=utf-8 "); - } - - @Test - void index_js() { - // given - final String request = String.join("\r\n", - "GET scripts.js HTTP/1.1 ", - "Host: localhost:8080 ", - "Accept: text/css,*/*;q=0.1 ", - "Connection: keep-alive ", - "", - ""); - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - - // when - processor.process(socket); - - // then - assertThat(socket.output()).contains("HTTP/1.1 200 OK "); - assertThat(socket.output()).contains("Content-Type: Application/javascript;charset=utf-8 "); - } - - @Test - void login_success_redirect_index() throws IOException { - // given - final String request = String.join("\r\n", - "GET /login?account=gugu&password=password HTTP/1.1 ", - "Host: localhost:8080", - "Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Accept: text/html,*/*;q=0.1 ", - "Connection: keep-alive ", - "", - ""); - - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - - // when - processor.process(socket); - var expected = String.join("\r\n", - "HTTP/1.1 302 Redirected ", - "Set-Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 5564 ", - "", - new String(Files.readAllBytes(new File(resource.getFile()).toPath()))); - - // then - assertThat(socket.output()).isEqualTo(expected); - } - - @Test - void login_failed_redirect_401() throws IOException { - // given - final String request = String.join("\r\n", - "GET /login?account=judy&password=j HTTP/1.1 ", - "Host: localhost:8080", - "Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Accept: text/html,*/*;q=0.1 ", - "Connection: keep-alive ", - "", - ""); - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - final URL resource = getClass().getClassLoader().getResource("static/401.html"); - - // when - processor.process(socket); - - // then - var expected = String.join("\r\n", - "HTTP/1.1 401 Unauthorized ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 2426 ", - "", - new String(Files.readAllBytes(new File(resource.getFile()).toPath()))); - - assertThat(socket.output()).isEqualTo(expected); - } - - @Test - void login_not_saved_jsession() throws IOException { - // given - final String request = String.join("\r\n", - "GET /login HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Content-Type: application/x-www-form-urlencoded ", - "Accept: */* ", - "", - ""); - - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - final URL resource = getClass().getClassLoader().getResource("static/login.html"); - - // when - processor.process(socket); - var expected = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Set-Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 3796 ", - "", - new String(Files.readAllBytes(new File(resource.getFile()).toPath()))); - - assertThat(socket.output()).isEqualTo(expected); - } - - @Test - void login_saved_jsession_redirect_index() throws IOException { - // given - final User judy = new User("judy", "judy", "judy@naver.com"); - final HttpCookie httpCookie = new HttpCookie(); - httpCookie.changeJSessionId("74262fcd-872c-4bcb-ace8-4bb003882818"); - SessionManager.add(judy, httpCookie); - - final String request = String.join("\r\n", "GET /login HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Content-Type: application/x-www-form-urlencoded ", - "Accept: */* ", - "", - ""); - - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - - // when - processor.process(socket); - var expected = String.join("\r\n", - "HTTP/1.1 302 Redirected ", - "Set-Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 5564 ", - "", - new String(Files.readAllBytes(new File(resource.getFile()).toPath()))); - - assertThat(socket.output()).isEqualTo(expected); - } - - @Test - void register_post() throws IOException { - // given - final String request = String.join("\r\n", - "POST /register HTTP/1.1 ", - "Host: localhost:8080 ", - "Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Connection: keep-alive ", - "Content-Length: 80 ", - "Content-Type: application/x-www-form-urlencoded ", - "Accept: */* ", - "", - "account=gugu&password=password&email=hkkang%40woowahan.com "); - - final var socket = new StubSocket(request); - final var processor = new Http11Processor(socket); - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - - // when - processor.process(socket); - var expected = String.join("\r\n", - "HTTP/1.1 302 Redirected ", - "Set-Cookie: JSESSIONID=74262fcd-872c-4bcb-ace8-4bb003882818 ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 5564 ", - "", - new String(Files.readAllBytes(new File(resource.getFile()).toPath()))); - - assertThat(socket.output()).isEqualTo(expected); - } -} diff --git a/tomcat/src/test/java/org/apache/coyote/http/ContentTypeTest.java b/tomcat/src/test/java/org/apache/coyote/http/ContentTypeTest.java new file mode 100644 index 0000000000..2af1892d34 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http/ContentTypeTest.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http; + +import org.apache.coyote.http11.ContentType; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ContentTypeTest { + + @Test + void findType() { + //given + List path = List.of("/css/style.css", "/favicon.ico", "/js/index.js", "/index.html"); + List contentType = List.of("text/css", "text/css", "text/javascript", "text/html"); + + //when + String type = ContentType.findType(path.get(0)); + + //then + for (int index = 0; index < path.size(); index++) { + assertEquals(contentType.get(index), ContentType.findType(path.get(index))); + } + } + +} diff --git a/tomcat/src/test/java/org/apache/coyote/http/HttpRequestParserTest.java b/tomcat/src/test/java/org/apache/coyote/http/HttpRequestParserTest.java new file mode 100644 index 0000000000..82ec4e4329 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http/HttpRequestParserTest.java @@ -0,0 +1,118 @@ +package org.apache.coyote.http; + +import org.apache.coyote.http11.HttpRequestParser; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HttpRequestParserTest { + + @Test + void accept() throws IOException { + //given + InputStream inputStream = new ByteArrayInputStream(RequestFixture.REQUEST.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + + //when + httpRequestParser.accept(inputStream); + + //then + assertAll( + () -> assertEquals("GET /index.html HTTP/1.1", httpRequestParser.getStartLine()), + () -> assertEquals("header", httpRequestParser.getHeader().get("header")), + () -> assertEquals("test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46", httpRequestParser.getHeader().get("Cookie")), + () -> assertEquals("message body", httpRequestParser.getMessageBody()) + ); + } + + @Test + void findMethod() throws IOException { + //given + HttpRequestParser httpRequestParser = createHttpRequestParser(RequestFixture.REQUEST); + + //when + String method = httpRequestParser.findMethod(); + + //then + assertEquals("GET", method); + } + + private HttpRequestParser createHttpRequestParser(String request) throws IOException { + InputStream inputStream = new ByteArrayInputStream(request.getBytes()); + HttpRequestParser httpRequestParser = new HttpRequestParser(); + httpRequestParser.accept(inputStream); + return httpRequestParser; + } + + @Test + void findPath() throws IOException { + //given + HttpRequestParser httpRequestParser = createHttpRequestParser(RequestFixture.REQUEST); + + //when + String path = httpRequestParser.findPath(); + + //then + assertEquals("/index.html", path); + } + + @Test + void findProtocol() throws IOException { + //given + HttpRequestParser httpRequestParser = createHttpRequestParser(RequestFixture.REQUEST); + + //when + String protocol = httpRequestParser.findProtocol(); + + //then + assertEquals("HTTP/1.1", protocol); + } + + @Test + void findCookies() throws IOException { + //given + HttpRequestParser httpRequestParser = createHttpRequestParser(RequestFixture.REQUEST); + + //when + Map cookies = httpRequestParser.findCookies(); + + //then + assertAll( + () -> assertEquals("test", cookies.get("test")), + () -> assertEquals("656cef62-e3c4-40bc-a8df-94732920ed46", cookies.get("JSESSIONID")) + ); + } + + @Test + void findQueryStrings() throws IOException { + //given + HttpRequestParser httpRequestParser = createHttpRequestParser(RequestFixture.REQUEST_WITH_QUERY_STRING); + + //when + Map queryStrings = httpRequestParser.findQueryStrings(); + + //then + assertAll( + () -> assertEquals("account", queryStrings.get("account")), + () -> assertEquals("password", queryStrings.get("password")) + ); + } + + @Test + void findPathWithoutQueryString() throws IOException { + //given + HttpRequestParser httpRequestParser = createHttpRequestParser(RequestFixture.REQUEST_WITH_QUERY_STRING); + + //when + String path = httpRequestParser.findPathWithoutQueryString(); + + //then + assertEquals("/login", path); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http/HttpResponseBuilderTest.java b/tomcat/src/test/java/org/apache/coyote/http/HttpResponseBuilderTest.java new file mode 100644 index 0000000000..ecfab8a384 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http/HttpResponseBuilderTest.java @@ -0,0 +1,79 @@ +package org.apache.coyote.http; + +import org.apache.coyote.http11.HttpRequestParser; +import org.apache.coyote.http11.HttpResponseBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class HttpResponseBuilderTest { + + private final HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder(); + private final HttpRequestParser httpRequestParser = new HttpRequestParser(); + + @BeforeEach + void setUp() throws IOException { + InputStream inputStream = new ByteArrayInputStream(RequestFixture.REQUEST.getBytes()); + httpRequestParser.accept(inputStream); + } + + @Test + void buildStaticFileOkResponse() throws IOException { + //when + String response = httpResponseBuilder.buildStaticFileOkResponse(httpRequestParser, httpRequestParser.findPath()); + + //then + assertAll( + () -> assertTrue(response.contains("HTTP/1.1 200 OK")), + () -> assertTrue(response.contains("Content-Type: text/html;charset=utf-8")), + () -> assertTrue(response.contains("Content-Length: 5564")) + ); + } + + @Test + void buildStaticFileRedirectResponse() throws IOException { + //when + String response = httpResponseBuilder.buildStaticFileRedirectResponse(httpRequestParser, httpRequestParser.findPath()); + + //then + assertAll( + () -> assertTrue(response.contains("HTTP/1.1 302 Moved Temporarily")), + () -> assertTrue(response.contains("Content-Type: text/html;charset=utf-8")), + () -> assertTrue(response.contains("Content-Length: 5564")), + () -> assertTrue(response.contains("Location: /index.html")) + ); + } + + @Test + void buildStaticFileNotFoundResponse() throws IOException { + //when + String response = httpResponseBuilder.buildStaticFileNotFoundResponse(httpRequestParser); + + //then + assertAll( + () -> assertTrue(response.contains("HTTP/1.1 404 Not Found")), + () -> assertTrue(response.contains("Content-Type: text/html;charset=utf-8")), + () -> assertTrue(response.contains("Content-Length: 2426")) + ); + } + + @Test + void buildCustomResponse() { + //when + String response = httpResponseBuilder.buildCustomResponse(httpRequestParser, "test"); + + //then + assertAll( + () -> assertTrue(response.contains("HTTP/1.1 200 OK")), + () -> assertTrue(response.contains("Content-Type: text/html;charset=utf-8")), + () -> assertTrue(response.contains("Content-Length: 4")) + ); + } + +} diff --git a/tomcat/src/test/java/org/apache/coyote/http/RequestFixture.java b/tomcat/src/test/java/org/apache/coyote/http/RequestFixture.java new file mode 100644 index 0000000000..f29dbbc163 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http/RequestFixture.java @@ -0,0 +1,81 @@ +package org.apache.coyote.http; + +public class RequestFixture { + + public static final String ROOT_REQUEST = "GET / HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; + + public static final String REQUEST = "GET /index.html HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; + + public static final String REQUEST_WITH_QUERY_STRING = "GET /login?account=account&password=password HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; + + public static final String GET_LOGIN_REQUEST = "GET /login HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; + + public static final String POST_SUCCESS_LOGIN_REQUEST = "POST /login HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test\n" + + "Content-Length: 30\n" + + "\n" + + "account=gugu&password=password"; + + public static final String POST_FAIL_LOGIN_REQUEST = "POST /login HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test\n" + + "Content-Length: 30\n" + + "\n" + + "account=test&password=test"; + + public static final String GET_REGISTER_REQUEST = "GET /register HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; + + public static final String POST_REGISTER_LOGIN_REQUEST = "POST /register HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test\n" + + "Content-Length: 59\n" + + "\n" + + "account=test&password=test&email=test@test.com"; + + public static final String CSS_REQUEST = "GET /css/styles.css HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; + + public static final String HTML_REQUEST = "GET /index.html HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; + + public static final String NOT_FOUND_REQUEST = "GET /sdadpoq312311wopem HTTP/1.1\n" + + "header: header\n" + + "Cookie: test=test; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46\n" + + "Content-Length: 12\n" + + "\n" + + "message body"; +} diff --git a/tomcat/src/test/java/org/apache/coyote/http/SessionManagerTest.java b/tomcat/src/test/java/org/apache/coyote/http/SessionManagerTest.java new file mode 100644 index 0000000000..af58d91cff --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http/SessionManagerTest.java @@ -0,0 +1,24 @@ +package org.apache.coyote.http; + +import nextstep.jwp.model.User; +import org.apache.coyote.http11.SessionManager; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SessionManagerTest { + + @Test + void add() { + SessionManager.add("sessionId", new User("moomin", "password","email")); + + assertTrue(SessionManager.isAlreadyLogin("sessionId")); + } + + @Test + void isAlreadyLogin() { + assertThat(SessionManager.isAlreadyLogin("nonSessionId")).isFalse(); + } + +}