diff --git a/README.md b/README.md index 42edfe0d7d..81c068878e 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,22 @@ - [x] 세션 클래스 구현 - [x] 연결 + +## 3단계 + +- [x] HttpRequest 클래스 구현 +- [x] HttpResponse 클래스 구현 +- [x] Controller 추가 + - [x] Controller 인터페이스 추가 + - [x] AbstractController 추가 + - [x] LoginController 추가 + - [x] RegisterController 추가 + - [x] ResourceController 추가 + - [x] HomeController 추가 +- [x] RequestMapping 추가 + - [x] if문 분기 삭제 + +## 4단계 + +- [x] ThreadPool 적용 +- [x] 동시성 컬렉션 사용 diff --git a/study/src/main/java/thread/stage2/SampleController.java b/study/src/main/java/thread/stage2/SampleController.java index a9636dd934..ce783403f9 100644 --- a/study/src/main/java/thread/stage2/SampleController.java +++ b/study/src/main/java/thread/stage2/SampleController.java @@ -1,5 +1,6 @@ package thread.stage2; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -7,8 +8,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; -import java.util.concurrent.atomic.AtomicInteger; - @Controller public class SampleController { @@ -26,7 +25,7 @@ public SampleController(final HelloWorldService helloWorldService) { @GetMapping("/test") @ResponseBody public String helloWorld() throws InterruptedException { - Thread.sleep(500); + Thread.sleep(500); // thread 0.5초 정지 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 index 78fc4ffa3d..325bf9af0f 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -3,10 +3,11 @@ handlebars: server: tomcat: - accept-count: 1 - max-connections: 1 + accept-count: 1 # 작업큐 사이즈 + max-connections: 2 # 가능한 connection 총 개수 threads: - max: 2 + max: 2 # 생성 가능 thread 총 개수 + min-spare: 1 # 항상 활성화 되어있는 (idle) thread 개수 compression: enabled: true diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java index 0333c18e3b..6297f3dfdf 100644 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ b/study/src/test/java/thread/stage0/SynchronizationTest.java @@ -1,29 +1,24 @@ package thread.stage0; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; /** - * 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다. - * 자바는 공유 데이터에 대한 스레드 접근을 동기화(synchronization)하여 경쟁 조건을 방지한다. - * 동기화된 블록은 하나의 스레드만 접근하여 실행할 수 있다. - * - * Synchronization - * https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html + * 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다. 자바는 공유 데이터에 대한 스레드 접근을 + * 동기화(synchronization)하여 경쟁 조건을 방지한다. 동기화된 블록은 하나의 스레드만 접근하여 실행할 수 있다. + *

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

+ * Guide to the Synchronized Keyword in Java https://www.baeldung.com/java-synchronized */ @Test void testSynchronized() throws InterruptedException { @@ -41,7 +36,7 @@ private static final class SynchronizedMethods { private int sum = 0; - public void calculate() { + public synchronized void calculate() { setSum(getSum() + 1); } diff --git a/study/src/test/java/thread/stage0/ThreadPoolsTest.java b/study/src/test/java/thread/stage0/ThreadPoolsTest.java index 238611ebfe..c94a1cfe7b 100644 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ b/study/src/test/java/thread/stage0/ThreadPoolsTest.java @@ -1,23 +1,20 @@ package thread.stage0; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; - -import static org.assertj.core.api.Assertions.assertThat; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * 스레드 풀은 무엇이고 어떻게 동작할까? - * 테스트를 통과시키고 왜 해당 결과가 나왔는지 생각해보자. - * - * Thread Pools - * https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html - * - * Introduction to Thread Pools in Java - * https://www.baeldung.com/thread-pool-java-and-guava + * 스레드 풀은 무엇이고 어떻게 동작할까? 테스트를 통과시키고 왜 해당 결과가 나왔는지 생각해보자. + *

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

+ * Introduction to Thread Pools in Java https://www.baeldung.com/thread-pool-java-and-guava */ class ThreadPoolsTest { @@ -31,11 +28,29 @@ void testNewFixedThreadPool() { executor.submit(logWithSleep("hello fixed thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; + final int expectedPoolSize = 2; + final int expectedQueueSize = 1; + + assertSoftly(softly -> { + softly.assertThat(executor.getCorePoolSize()).isEqualTo(expectedPoolSize); + softly.assertThat(executor.getMaximumPoolSize()).isEqualTo(expectedPoolSize); + softly.assertThat(executor.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(0); + softly.assertThat(executor.getPoolSize()).isEqualTo(expectedPoolSize); + softly.assertThat(executor.getQueue().size()).isEqualTo(expectedQueueSize); + }); + } + + @Test + void testNewFixedThreadPool_initial() { + final var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2); + + final int expectedCorePoolSize = 2; final int expectedQueueSize = 0; - assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); - assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size()); + assertSoftly(softly -> { + softly.assertThat(executor.getCorePoolSize()).isEqualTo(expectedCorePoolSize); + softly.assertThat(executor.getQueue().size()).isEqualTo(expectedQueueSize); + }); } @Test @@ -46,11 +61,29 @@ void testNewCachedThreadPool() { executor.submit(logWithSleep("hello cached thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. + final int expectedPoolSize = 3; + final int expectedQueueSize = 0; + + assertSoftly(softly -> { + softly.assertThat(executor.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE); + softly.assertThat(executor.getCorePoolSize()).isEqualTo(0); + softly.assertThat(executor.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(60); + softly.assertThat(executor.getPoolSize()).isEqualTo(expectedPoolSize); + softly.assertThat(executor.getQueue().size()).isEqualTo(expectedQueueSize); + }); + } + + @Test + void testNewCachedThreadPool_initial() { + final var executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); + final int expectedPoolSize = 0; final int expectedQueueSize = 0; - assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); - assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size()); + assertSoftly(softly -> { + softly.assertThat(executor.getPoolSize()).isEqualTo(expectedPoolSize); + softly.assertThat(executor.getQueue().size()).isEqualTo(expectedQueueSize); + }); } private Runnable logWithSleep(final String message) { diff --git a/study/src/test/java/thread/stage1/ConcurrencyTest.java b/study/src/test/java/thread/stage1/ConcurrencyTest.java index f5e8ee070a..c118e38ef7 100644 --- a/study/src/test/java/thread/stage1/ConcurrencyTest.java +++ b/study/src/test/java/thread/stage1/ConcurrencyTest.java @@ -1,19 +1,15 @@ package thread.stage1; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + /** - * 스레드를 다룰 때 어떤 상황을 조심해야 할까? - * - 상태를 가진 한 객체를 여러 스레드에서 동시에 접근할 경우 - * - static 변수를 가진 객체를 여러 스레드에서 동시에 접근할 경우 - * - * 위 경우는 동기화(synchronization)를 적용시키거나 객체가 상태를 갖지 않도록 한다. - * 객체를 불변 객체로 만드는 방법도 있다. - * - * 웹서버는 여러 사용자가 동시에 접속을 시도하기 때문에 동시성 이슈가 생길 수 있다. - * 어떤 사례가 있는지 아래 테스트 코드를 통해 알아보자. + * 스레드를 다룰 때 어떤 상황을 조심해야 할까? - 상태를 가진 한 객체를 여러 스레드에서 동시에 접근할 경우 - static 변수를 가진 객체를 여러 스레드에서 동시에 접근할 경우 + *

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

+ * 웹서버는 여러 사용자가 동시에 접속을 시도하기 때문에 동시성 이슈가 생길 수 있다. 어떤 사례가 있는지 아래 테스트 코드를 통해 알아보자. */ class ConcurrencyTest { diff --git a/study/src/test/java/thread/stage2/AppTest.java b/study/src/test/java/thread/stage2/AppTest.java index e253c4a249..2f5dbc0c94 100644 --- a/study/src/test/java/thread/stage2/AppTest.java +++ b/study/src/test/java/thread/stage2/AppTest.java @@ -1,25 +1,19 @@ package thread.stage2; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; 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 - * - 테스트 결과값 + * 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 { @@ -32,7 +26,7 @@ void test() throws Exception { for (final var thread : threads) { thread.start(); - Thread.sleep(50); + Thread.sleep(50); // thread 0.05초 정지 } for (final var thread : threads) { diff --git a/study/src/test/java/thread/stage2/TestHttpUtils.java b/study/src/test/java/thread/stage2/TestHttpUtils.java index ca37f2f8bb..aa648e896e 100644 --- a/study/src/test/java/thread/stage2/TestHttpUtils.java +++ b/study/src/test/java/thread/stage2/TestHttpUtils.java @@ -11,7 +11,7 @@ public class TestHttpUtils { private static final HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) - .connectTimeout(Duration.ofSeconds(1)) + .connectTimeout(Duration.ofSeconds(1)) // connection 1초 지나도 못 맺으면 HttpConnectTimeoutException .build(); public static HttpResponse send(final String path) { diff --git a/tomcat/src/main/java/common/ResponseStatus.java b/tomcat/src/main/java/common/ResponseStatus.java new file mode 100644 index 0000000000..14eef9db6b --- /dev/null +++ b/tomcat/src/main/java/common/ResponseStatus.java @@ -0,0 +1,31 @@ +package common; + +import static java.lang.Integer.MIN_VALUE; + +public enum ResponseStatus { + OK(200, "OK"), + FOUND(302, "Found"), + BAD_REQUEST(400, "Bad Request"), + UNAUTHORIZED(401, "Unauthorized"), + NOT_FOUND(404, "Not Found"), + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + + EMPTY_RESPONSE_STATUS(MIN_VALUE, "Empty Response Status"), + ; + + private final int code; + private final String response; + + ResponseStatus(int code, String response) { + this.code = code; + this.response = response; + } + + public String codeMessage() { + return String.valueOf(code); + } + + public String responseMessage() { + return response; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java new file mode 100644 index 0000000000..3f1833e8e1 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -0,0 +1,64 @@ +package nextstep.jwp.controller; + +import static org.apache.coyote.request.line.HttpMethod.DELETE; +import static org.apache.coyote.request.line.HttpMethod.GET; +import static org.apache.coyote.request.line.HttpMethod.PATCH; +import static org.apache.coyote.request.line.HttpMethod.POST; +import static org.apache.coyote.request.line.HttpMethod.PUT; + +import nextstep.jwp.exception.NotFoundException; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public abstract class AbstractController implements Controller { + + @Override + public boolean canProcess(HttpRequest httpRequest) { + return true; + } + + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) { + if (httpRequest.consistsOf(POST)) { + doPost(httpRequest, httpResponse); + return; + } + if (httpRequest.consistsOf(GET)) { + doGet(httpRequest, httpResponse); + return; + } + if (httpRequest.consistsOf(DELETE)) { + doDelete(httpRequest, httpResponse); + return; + } + if (httpRequest.consistsOf(PUT)) { + doDelete(httpRequest, httpResponse); + return; + } + if (httpRequest.consistsOf(PATCH)) { + doDelete(httpRequest, httpResponse); + return; + } + throw new NotFoundException(); + } + + protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) { + throw new NotFoundException(); + } + + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + throw new NotFoundException(); + } + + protected void doDelete(HttpRequest httpRequest, HttpResponse httpResponse) { + throw new NotFoundException(); + } + + protected void doPut(HttpRequest httpRequest, HttpResponse httpResponse) { + throw new NotFoundException(); + } + + protected void doPatch(HttpRequest httpRequest, HttpResponse httpResponse) { + throw new NotFoundException(); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/Controller.java b/tomcat/src/main/java/nextstep/jwp/controller/Controller.java new file mode 100644 index 0000000000..0cd2430df1 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/Controller.java @@ -0,0 +1,10 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public interface Controller { + boolean canProcess(HttpRequest httpRequest); + + void service(HttpRequest httpRequest, HttpResponse httpResponse); +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/ExceptionHandler.java b/tomcat/src/main/java/nextstep/jwp/controller/ExceptionHandler.java new file mode 100644 index 0000000000..c95634f5d9 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/ExceptionHandler.java @@ -0,0 +1,34 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.NOT_FOUND; +import static common.ResponseStatus.UNAUTHORIZED; + +import nextstep.jwp.exception.AuthException; +import nextstep.jwp.exception.NotFoundException; +import org.apache.coyote.response.HttpResponse; + +public class ExceptionHandler { + + private static final String UNAUTHORIZED_URL = "/401.html"; + private static final String NOT_FOUND_URL = "/404.html"; + + public void handle(HttpResponse httpResponse, Exception e) { + if (e instanceof AuthException) { + ResourceManager manager = ResourceManager.from(UNAUTHORIZED_URL); + httpResponse.setResponseResource( + UNAUTHORIZED, + manager.extractResourceType(), + manager.readResourceContent() + ); + return; + } + if (e instanceof NotFoundException) { + ResourceManager manager = ResourceManager.from(NOT_FOUND_URL); + httpResponse.setResponseResource( + NOT_FOUND, + manager.extractResourceType(), + manager.readResourceContent() + ); + } + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java new file mode 100644 index 0000000000..6c387245d8 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java @@ -0,0 +1,21 @@ +package nextstep.jwp.controller; + +import common.ResponseStatus; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public class HomeController extends AbstractController { + + private static final String DEFAULT_BODY = "Hello world!"; + private static final String URL = "/"; + + @Override + public boolean canProcess(HttpRequest httpRequest) { + return httpRequest.consistsOf(URL); + } + + @Override + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + httpResponse.setResponseMessage(ResponseStatus.OK, DEFAULT_BODY); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/IndexController.java b/tomcat/src/main/java/nextstep/jwp/controller/IndexController.java new file mode 100644 index 0000000000..875c30fcde --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/IndexController.java @@ -0,0 +1,36 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.OK; + +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public class IndexController extends AbstractController { + + private static final String URL = "/index"; + private static final String EXTENSION = ".html"; + + @Override + public boolean canProcess(HttpRequest httpRequest) { + return httpRequest.consistsOf(URL) || httpRequest.consistsOf(URL + EXTENSION); + } + + @Override + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + String requestUrl = toRequestUrl(httpRequest); + ResourceManager manager = ResourceManager.from(requestUrl); + httpResponse.setResponseResource( + OK, + manager.extractResourceType(), + manager.readResourceContent() + ); + } + + private String toRequestUrl(HttpRequest httpRequest) { + String location = httpRequest.requestUri(); + if (location.contains(EXTENSION)) { + return location; + } + return location + EXTENSION; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java new file mode 100644 index 0000000000..104b8ef49e --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -0,0 +1,71 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.FOUND; +import static common.ResponseStatus.OK; +import static org.apache.coyote.response.header.HttpHeader.SET_COOKIE; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.service.AuthService; +import org.apache.catalina.SessionManager; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public class LoginController extends AbstractController { + + private static final String ACCOUNT = "account"; + private static final String PASSWORD = "password"; + private static final String REDIRECT_URL = "/index.html"; + private static final String JSESSIONID_COOKIE = "JSESSIONID="; + private static final String URL = "/login"; + private static final String EXTENSION = ".html"; + + private final AuthService authService; + + LoginController(AuthService authService) { + this.authService = authService; + } + + public LoginController() { + this(new AuthService(SessionManager.getInstance(), InMemoryUserRepository.getInstance())); + } + + @Override + public boolean canProcess(HttpRequest httpRequest) { + return httpRequest.consistsOf(URL); + } + + + @Override + protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) { + doLogin(httpResponse, httpRequest.getBodyValue(ACCOUNT), httpRequest.getBodyValue(PASSWORD)); + } + + private void doLogin(HttpResponse httpResponse, String account, String password) { + String sessionId = authService.login(account, password); + httpResponse.setResponseRedirect(FOUND, REDIRECT_URL); + httpResponse.setResponseHeader(SET_COOKIE, JSESSIONID_COOKIE + sessionId); + } + + @Override + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + if (httpRequest.hasQueryString()) { + doLogin(httpResponse, httpRequest.getQueryStringValue(ACCOUNT), httpRequest.getQueryStringValue(PASSWORD)); + return; + } + if (httpRequest.hasSessionId() && authService.isLoggedIn(httpRequest.sessionId())) { + httpResponse.setResponseRedirect(FOUND, REDIRECT_URL); + return; + } + String resourceUrl = toResourceUrl(httpRequest); + ResourceManager manager = ResourceManager.from(resourceUrl); + httpResponse.setResponseResource( + OK, + manager.extractResourceType(), + manager.readResourceContent() + ); + } + + private String toResourceUrl(HttpRequest httpRequest) { + return httpRequest.requestUri() + EXTENSION; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java new file mode 100644 index 0000000000..495e65e733 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -0,0 +1,64 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.FOUND; +import static common.ResponseStatus.OK; +import static org.apache.coyote.response.header.HttpHeader.SET_COOKIE; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.service.AuthService; +import org.apache.catalina.SessionManager; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public class RegisterController extends AbstractController { + + private static final String REDIRECT_URL = "/index.html"; + private static final String JSESSIONID_COOKIE = "JSESSIONID="; + private static final String ACCOUNT = "account"; + private static final String PASSWORD = "password"; + private static final String EMAIL = "email"; + private static final String URL = "/register"; + private static final String EXTENSION = ".html"; + + private final AuthService authService; + + public RegisterController(AuthService authService) { + this.authService = authService; + } + + public RegisterController() { + this(new AuthService(SessionManager.getInstance(), InMemoryUserRepository.getInstance())); + } + + @Override + public boolean canProcess(HttpRequest httpRequest) { + return httpRequest.consistsOf(URL); + } + + + @Override + protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) { + String sessionId = authService.register( + httpRequest.getBodyValue(ACCOUNT), + httpRequest.getBodyValue(PASSWORD), + httpRequest.getBodyValue(EMAIL) + ); + httpResponse.setResponseRedirect(FOUND, REDIRECT_URL); + httpResponse.setResponseHeader(SET_COOKIE, JSESSIONID_COOKIE + sessionId); + } + + @Override + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + String resourceUrl = toResourceUrl(httpRequest); + ResourceManager manager = ResourceManager.from(resourceUrl); + httpResponse.setResponseResource( + OK, + manager.extractResourceType(), + manager.readResourceContent() + ); + } + + private String toResourceUrl(HttpRequest httpRequest) { + return httpRequest.requestUri() + EXTENSION; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java new file mode 100644 index 0000000000..e84008c029 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java @@ -0,0 +1,31 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.OK; + +import nextstep.jwp.exception.NotFoundException; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public class ResourceController extends AbstractController { + + @Override + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + String resourceUrl = httpRequest.requestUri(); + ResourceManager manager = ResourceManager.from(resourceUrl); + String resourceType = manager.extractResourceType(); + + validateResourceType(resourceType); + + httpResponse.setResponseResource( + OK, + resourceType, + manager.readResourceContent() + ); + } + + private void validateResourceType(String resourceType) { + if (resourceType.equals("html")) { + throw new NotFoundException(); + } + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/ResourceManager.java b/tomcat/src/main/java/nextstep/jwp/controller/ResourceManager.java new file mode 100644 index 0000000000..07ae7dab73 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/ResourceManager.java @@ -0,0 +1,49 @@ +package nextstep.jwp.controller; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import nextstep.jwp.exception.FileNotFoundException; + +public class ResourceManager { + + private static final String PATH = "static"; + private static final String EXTENSION_SIGN = "."; + private static final String PREFIX = "/"; + + private final File file; + + private ResourceManager(File file) { + this.file = file; + } + + public static ResourceManager from(String resourceUrl) { + if (!resourceUrl.startsWith(PREFIX)) { + resourceUrl = PREFIX + resourceUrl; + } + URL path = ResourceManager.class.getClassLoader().getResource(PATH + resourceUrl); + validatePath(path); + return new ResourceManager(new File(path.getFile())); + } + + private static void validatePath(URL path) { + if (path == null) { + throw new FileNotFoundException("존재하지 않는 파일의 경로입니다."); + } + } + + public String extractResourceType() { + String fileName = file.getName(); + int extensionSignIndex = fileName.lastIndexOf(EXTENSION_SIGN); + return fileName.substring(extensionSignIndex + 1); + } + + public String readResourceContent() { + try { + return new String(Files.readAllBytes(file.toPath())); + } catch (IOException e) { + throw new FileNotFoundException("파일을 불러올 수 없습니다."); + } + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java b/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java index e1d78f2dd4..acb8f8fbfb 100644 --- a/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java @@ -1,5 +1,7 @@ package nextstep.jwp.db; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -7,21 +9,45 @@ public class InMemoryUserRepository { - private static final Map database = new ConcurrentHashMap<>(); + private Long id; + private final Map database; - static { - final User user = new User(1L, "gugu", "password", "hkkang@woowahan.com"); - database.put(user.getAccount(), user); + private InMemoryUserRepository(Map database, Long initialId) { + this.database = database; + this.id = initialId; } - public static void save(User user) { - database.put(user.getAccount(), user); + public static InMemoryUserRepository init() { + var repository = new InMemoryUserRepository(new ConcurrentHashMap<>(), 1L); + repository.save(new User("gugu", "password", "hkkang@woowahan.com")); + return repository; } - public static Optional findByAccount(String account) { - return Optional.ofNullable(database.get(account)); + public static InMemoryUserRepository getInstance() { + return LazyHolder.INSTANCE; } - private InMemoryUserRepository() { + public synchronized Long save(User user) { + database.put(id, user); + user.setId(id); + return id++; + } + + public Optional findByAccount(String account) { + return database.values().stream() + .filter(user -> user.getAccount().equals(account)) + .findAny(); + } + + public List findAll() { + return new ArrayList<>(database.values()); + } + + public void deleteAll() { + database.clear(); + } + + private static class LazyHolder { + private static final InMemoryUserRepository INSTANCE = InMemoryUserRepository.init(); } } diff --git a/tomcat/src/main/java/nextstep/jwp/dto/ResponseDto.java b/tomcat/src/main/java/nextstep/jwp/dto/ResponseDto.java deleted file mode 100644 index 0ed08070f1..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/dto/ResponseDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package nextstep.jwp.dto; - -public class ResponseDto { - - private final String code; - private final String location; - private final String id; - - public ResponseDto(String code, String location, String id) { - this.code = code; - this.location = location; - this.id = id; - } - - public String code() { - return code; - } - - public String location() { - return location; - } - - public String id() { - return id; - } -} diff --git a/tomcat/src/main/java/nextstep/jwp/exception/AuthException.java b/tomcat/src/main/java/nextstep/jwp/exception/AuthException.java new file mode 100644 index 0000000000..c90709fd88 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/exception/AuthException.java @@ -0,0 +1,11 @@ +package nextstep.jwp.exception; + +public class AuthException extends UncheckedServletException { + + public AuthException() { + } + + public AuthException(String message) { + super(message); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/exception/FileNotFoundException.java b/tomcat/src/main/java/nextstep/jwp/exception/FileNotFoundException.java new file mode 100644 index 0000000000..ebd425b1c3 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/exception/FileNotFoundException.java @@ -0,0 +1,11 @@ +package nextstep.jwp.exception; + +public class FileNotFoundException extends NotFoundException { + + public FileNotFoundException() { + } + + public FileNotFoundException(String message) { + super(message); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/exception/NotFoundException.java b/tomcat/src/main/java/nextstep/jwp/exception/NotFoundException.java new file mode 100644 index 0000000000..b4e8266297 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/exception/NotFoundException.java @@ -0,0 +1,11 @@ +package nextstep.jwp.exception; + +public class NotFoundException extends UncheckedServletException { + + public NotFoundException() { + } + + public NotFoundException(String message) { + super(message); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/exception/UncheckedServletException.java b/tomcat/src/main/java/nextstep/jwp/exception/UncheckedServletException.java index 63806f2e61..7a15df5df5 100644 --- a/tomcat/src/main/java/nextstep/jwp/exception/UncheckedServletException.java +++ b/tomcat/src/main/java/nextstep/jwp/exception/UncheckedServletException.java @@ -2,7 +2,10 @@ public class UncheckedServletException extends RuntimeException { - public UncheckedServletException(Exception e) { - super(e); + public UncheckedServletException() { + } + + public UncheckedServletException(String message) { + super(message); } } diff --git a/tomcat/src/main/java/nextstep/jwp/model/User.java b/tomcat/src/main/java/nextstep/jwp/model/User.java index 48e2e651a7..c2525a3b2b 100644 --- a/tomcat/src/main/java/nextstep/jwp/model/User.java +++ b/tomcat/src/main/java/nextstep/jwp/model/User.java @@ -2,7 +2,7 @@ public class User { - private final Long id; + private Long id; private final String account; private final String password; private final String email; @@ -26,6 +26,14 @@ public String getAccount() { return account; } + public void setId(Long id) { + this.id = id; + } + + public Long id() { + return id; + } + @Override public String toString() { return "User{" + diff --git a/tomcat/src/main/java/nextstep/jwp/service/AuthService.java b/tomcat/src/main/java/nextstep/jwp/service/AuthService.java index 039e3a7e26..eccbba6e8d 100644 --- a/tomcat/src/main/java/nextstep/jwp/service/AuthService.java +++ b/tomcat/src/main/java/nextstep/jwp/service/AuthService.java @@ -1,11 +1,13 @@ package nextstep.jwp.service; +import java.util.Objects; import java.util.UUID; import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.exception.AuthException; import nextstep.jwp.model.User; +import org.apache.catalina.Session; +import org.apache.catalina.SessionManager; import org.apache.coyote.http11.Http11Processor; -import org.apache.coyote.http11.Session; -import org.apache.coyote.http11.SessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,31 +16,50 @@ public class AuthService { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); private final SessionManager sessionManager; + private final InMemoryUserRepository inMemoryUserRepository; - public AuthService(SessionManager sessionManager) { + public AuthService(SessionManager sessionManager, InMemoryUserRepository inMemoryUserRepository) { this.sessionManager = sessionManager; + this.inMemoryUserRepository = inMemoryUserRepository; + } + + public boolean isLoggedIn(String sessionId) { + return Objects.nonNull(sessionManager.findSession(sessionId)); } public String login(String account, String password) { - User user = InMemoryUserRepository.findByAccount(account) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 계정이거나 비밀번호가 틀렸습니다.")); - if (!user.isSamePassword(password)) { - throw new IllegalArgumentException("존재하지 않는 계정이거나 비밀번호가 틀렸습니다."); - } + User user = inMemoryUserRepository.findByAccount(account) + .orElseThrow(() -> new AuthException("존재하지 않는 계정이거나 비밀번호가 틀렸습니다.")); + validatePassword(password, user); + + log.info(user.toString()); Session session = new Session(UUID.randomUUID().toString()); sessionManager.add(session); - return session.getId(); } + private void validatePassword(String password, User user) { + if (!user.isSamePassword(password)) { + throw new AuthException("존재하지 않는 계정이거나 비밀번호가 틀렸습니다."); + } + } + public String register(String account, String password, String email) { + validateAccountDuplication(account); User user = new User(account, password, email); - InMemoryUserRepository.save(user); + inMemoryUserRepository.save(user); Session session = new Session(UUID.randomUUID().toString()); sessionManager.add(session); - return session.getId(); } + + private void validateAccountDuplication(String account) { + if (inMemoryUserRepository.findByAccount(account) + .isPresent()) { + log.info("중복된 계정"); + throw new AuthException("중복된 계정 입니다."); + } + } } diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e69410f6a9..0459ce097a 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,56 +1,8 @@ package org.apache.catalina; -import jakarta.servlet.http.HttpSession; - -import java.io.IOException; - -/** - * A Manager manages the pool of Sessions that are associated with a - * particular Container. Different Manager implementations may support - * value-added features such as the persistent storage of session data, - * as well as migrating sessions for distributable web applications. - *

- * In order for a Manager implementation to successfully operate - * with a Context implementation that implements reloading, it - * must obey the following constraints: - *

- * - * @author Craig R. McClanahan - */ public interface Manager { - /** - * Add this Session to the set of active Sessions for this Manager. - * - * @param session Session to be added - */ - void add(HttpSession session); - - /** - * Return the active Session, associated with this Manager, with the - * specified session id (if any); otherwise return null. - * - * @param id The session id for the session to be returned - * - * @exception IllegalStateException if a new session cannot be - * instantiated for any reason - * @exception IOException if an input/output error occurs while - * processing this request - * - * @return the request session or {@code null} if a session with the - * requested ID could not be found - */ - HttpSession findSession(String id) throws IOException; + void add(Session session); - /** - * Remove this Session from the active Sessions for this Manager. - * - * @param session Session to be removed - */ - void remove(HttpSession session); + Session findSession(String id); } diff --git a/tomcat/src/main/java/org/apache/catalina/RequestMapping.java b/tomcat/src/main/java/org/apache/catalina/RequestMapping.java new file mode 100644 index 0000000000..5f028f2968 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/RequestMapping.java @@ -0,0 +1,60 @@ +package org.apache.catalina; + +import java.util.List; +import nextstep.jwp.controller.Controller; +import nextstep.jwp.controller.ExceptionHandler; +import nextstep.jwp.controller.HomeController; +import nextstep.jwp.controller.IndexController; +import nextstep.jwp.controller.LoginController; +import nextstep.jwp.controller.RegisterController; +import nextstep.jwp.controller.ResourceController; +import nextstep.jwp.exception.NotFoundException; +import nextstep.jwp.exception.UncheckedServletException; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public class RequestMapping { + + private final List controllers; + private final ExceptionHandler exceptionHandler; + + RequestMapping(List controllers, ExceptionHandler exceptionHandler) { + this.controllers = controllers; + this.exceptionHandler = exceptionHandler; + } + + public static RequestMapping init() { + return new RequestMapping( + List.of( + new HomeController(), + new IndexController(), + new LoginController(), + new RegisterController(), + new ResourceController() + ), + new ExceptionHandler() + ); + } + + public void process(HttpRequest httpRequest, HttpResponse httpResponse) { + try { + mapAndProcess(httpRequest, httpResponse); + validateResponse(httpResponse); + } catch (UncheckedServletException e) { + exceptionHandler.handle(httpResponse, e); + } + } + + private void mapAndProcess(HttpRequest httpRequest, HttpResponse httpResponse) { + controllers.stream() + .filter(controller -> controller.canProcess(httpRequest)) + .findAny() + .ifPresent(controller -> controller.service(httpRequest, httpResponse)); + } + + private void validateResponse(HttpResponse httpResponse) { + if (!httpResponse.isDetermined()) { + throw new NotFoundException(); + } + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Session.java b/tomcat/src/main/java/org/apache/catalina/Session.java similarity index 83% rename from tomcat/src/main/java/org/apache/coyote/http11/Session.java rename to tomcat/src/main/java/org/apache/catalina/Session.java index 4a4659bded..235d22f10a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Session.java +++ b/tomcat/src/main/java/org/apache/catalina/Session.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.catalina; public class Session { diff --git a/tomcat/src/main/java/org/apache/catalina/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/SessionManager.java new file mode 100644 index 0000000000..ff23b11ad8 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/SessionManager.java @@ -0,0 +1,31 @@ +package org.apache.catalina; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionManager implements Manager { + + private static final Map SESSIONS = new ConcurrentHashMap<>(); + + public static SessionManager getInstance() { + return LazyHolder.INSTANCE; + } + + @Override + public void add(Session session) { + SESSIONS.put(session.getId(), session); + } + + @Override + public Session findSession(String id) { + return SESSIONS.get(id); + } + + public void deleteAll() { + SESSIONS.clear(); + } + + private static class LazyHolder { + private static final SessionManager INSTANCE = new SessionManager(); + } +} 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 9da5e7e7f0..197364a40e 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -1,14 +1,15 @@ package org.apache.catalina.connector; -import org.apache.coyote.http11.Http11Processor; -import org.apache.coyote.http11.SessionManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.io.UncheckedIOException; import java.net.ServerSocket; import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.apache.catalina.RequestMapping; +import org.apache.coyote.http11.Http11Processor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Connector implements Runnable { @@ -16,16 +17,21 @@ public class Connector implements Runnable { private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_ACCEPT_COUNT = 100; + private static final int DEFAULT_THREADS = 250; private final ServerSocket serverSocket; + private final ExecutorService executorService; + private final RequestMapping requestMapping; private boolean stopped; public Connector() { - this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); + this(RequestMapping.init(), DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_THREADS); } - public Connector(final int port, final int acceptCount) { + public Connector(final RequestMapping requestMapping, final int port, final int acceptCount, final int maxThreads) { + this.requestMapping = requestMapping; this.serverSocket = createServerSocket(port, acceptCount); + this.executorService = Executors.newFixedThreadPool(maxThreads); this.stopped = false; } @@ -39,6 +45,20 @@ private ServerSocket createServerSocket(final int port, final int acceptCount) { } } + private int checkPort(final int port) { + final var MIN_PORT = 1; + final var MAX_PORT = 65535; + + if (port < MIN_PORT || MAX_PORT < port) { + return DEFAULT_PORT; + } + return port; + } + + private int checkAcceptCount(final int acceptCount) { + return Math.max(acceptCount, DEFAULT_ACCEPT_COUNT); + } + public void start() { var thread = new Thread(this); thread.setDaemon(true); @@ -57,18 +77,19 @@ public void run() { private void connect() { try { - process(serverSocket.accept(), new SessionManager()); + process(serverSocket.accept()); } catch (IOException e) { log.error(e.getMessage(), e); } } - private void process(final Socket connection, SessionManager sessionManager) { + private void process(final Socket connection) { if (connection == null) { return; } - var processor = new Http11Processor(connection, sessionManager); - new Thread(processor).start(); + var processor = new Http11Processor(connection, requestMapping); + executorService.submit(processor); + log.info("thread start"); } public void stop() { @@ -79,18 +100,4 @@ public void stop() { log.error(e.getMessage(), e); } } - - private int checkPort(final int port) { - final var MIN_PORT = 1; - final var MAX_PORT = 65535; - - if (port < MIN_PORT || MAX_PORT < port) { - return DEFAULT_PORT; - } - return port; - } - - private int checkAcceptCount(final int acceptCount) { - return Math.max(acceptCount, DEFAULT_ACCEPT_COUNT); - } } 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 a99a484521..1b037f8a8c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,37 +1,29 @@ package org.apache.coyote.http11; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.coyote.http11.request.line.HttpMethod.GET; -import static org.apache.coyote.http11.request.line.HttpMethod.POST; -import static org.apache.coyote.http11.response.header.HttpHeader.SET_COOKIE; -import static org.apache.coyote.http11.response.line.ResponseStatus.FOUND; -import static org.apache.coyote.http11.response.line.ResponseStatus.OK; -import static org.apache.coyote.http11.response.line.ResponseStatus.UNAUTHORIZED; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import nextstep.jwp.exception.UncheckedServletException; -import nextstep.jwp.service.AuthService; -import org.apache.coyote.Processor; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.response.HttpResponse; +import org.apache.catalina.RequestMapping; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.HttpRequestReader; +import org.apache.coyote.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final String DEFAULT_BODY = "Hello world!"; - private static final String HTTP_VERSION = "HTTP/1.1"; private final Socket connection; - private final SessionManager sessionManager; + private final RequestMapping requestMapping; - public Http11Processor(final Socket connection, SessionManager sessionManager) { + public Http11Processor(final Socket connection, RequestMapping requestMapping) { this.connection = connection; - this.sessionManager = sessionManager; + this.requestMapping = requestMapping; } @Override @@ -47,11 +39,11 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream() ) { HttpRequest httpRequest = readHttpRequest(reader); - HttpResponse httpResponse = new HttpResponse(HTTP_VERSION); + HttpResponse httpResponse = new HttpResponse(httpRequest.httpVersion()); - String response = process(httpRequest, httpResponse); + requestMapping.process(httpRequest, httpResponse); - outputStream.write(response.getBytes()); + outputStream.write(httpResponse.responseMessage().getBytes()); outputStream.flush(); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); @@ -59,74 +51,12 @@ public void process(final Socket connection) { } private HttpRequest readHttpRequest(BufferedReader reader) throws IOException { - String requestLine = HttpRequestReader.readLine(reader); - String requestHeader = HttpRequestReader.readHeader(reader); + HttpRequestReader httpRequestReader = new HttpRequestReader(); + String requestLine = httpRequestReader.readLine(reader); + String requestHeader = httpRequestReader.readHeader(reader); HttpRequest request = HttpRequest.of(requestLine, requestHeader); - request.setRequestBody(HttpRequestReader.readBody(reader, request.contentLength())); + request.setRequestBody(httpRequestReader.readBody(reader, request.contentLength())); return request; } - - private String process(HttpRequest request, HttpResponse response) { - AuthService authService = new AuthService(sessionManager); - - if (request.consistsOf(GET, "/")) { - response.setResponseMessage(OK, DEFAULT_BODY); - return response.responseMessage(); - } - - if (request.consistsOf(POST, "/login", "/login.html")) { - String account = request.getBodyValue("account"); - String password = request.getBodyValue("password"); - - return processLogin(response, authService, account, password); - } - - if (request.consistsOf(GET, "/login", "/login.html") & request.hasQueryString()) { - String account = request.getQueryStringValue("account"); - String password = request.getQueryStringValue("password"); - - return processLogin(response, authService, account, password); - } - - if (request.consistsOf(GET, "/login", "/login.html") & request.hasSessionId()) { - String sessionId = request.sessionId(); - Session session = sessionManager.findSession(sessionId); - - if (session != null) { - response.setResponseRedirect(FOUND, "/index.html"); - return response.responseMessage(); - } - } - - if (request.consistsOf(POST, "/register", "/register.html") & request.hasBody()) { - return processRegister(request, response, authService); - } - - response.setResponseResource(OK, request.requestUri()); - return response.responseMessage(); - } - - private String processLogin(HttpResponse response, AuthService authService, String account, String password) { - try { - String sessionId = authService.login(account, password); - response.setResponseRedirect(FOUND, "/index.html"); - response.setResponseHeader(SET_COOKIE, "JSESSIONID=" + sessionId); - return response.responseMessage(); - } catch (IllegalArgumentException e) { - response.setResponseResource(UNAUTHORIZED, "/401.html"); - return response.responseMessage(); - } - } - - private String processRegister(HttpRequest request, HttpResponse response, AuthService authService) { - String account = request.getBodyValue("account"); - String password = request.getBodyValue("password"); - String email = request.getBodyValue("email"); - - String sessionId = authService.register(account, password, email); - response.setResponseRedirect(FOUND, "/index.html"); - response.setResponseHeader(SET_COOKIE, "JSESSIONID=" + sessionId); - return response.responseMessage(); - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Manager.java b/tomcat/src/main/java/org/apache/coyote/http11/Manager.java deleted file mode 100644 index 1d23074f27..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/Manager.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.apache.coyote.http11; - -public interface Manager { - - void add(Session session); - - Session findSession(String id); -} diff --git a/tomcat/src/main/java/org/apache/coyote/Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Processor.java similarity index 86% rename from tomcat/src/main/java/org/apache/coyote/Processor.java rename to tomcat/src/main/java/org/apache/coyote/http11/Processor.java index 6604ab83de..d1a40192fc 100644 --- a/tomcat/src/main/java/org/apache/coyote/Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Processor.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.coyote; +package org.apache.coyote.http11; import java.net.Socket; @@ -24,9 +24,8 @@ public interface Processor { /** - * Process a connection. This is called whenever an event occurs (e.g. more - * data arrives) that allows processing to continue for a connection that is - * not currently being processed. + * Process a connection. This is called whenever an event occurs (e.g. more data arrives) that allows processing to + * continue for a connection that is not currently being processed. */ void process(Socket socket); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java deleted file mode 100644 index 4f1d45981e..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.apache.coyote.http11; - -import java.util.HashMap; -import java.util.Map; - -public class SessionManager implements Manager { - - private static final Map SESSIONS = new HashMap<>(); - - public SessionManager() { - } - - @Override - public void add(Session session) { - SESSIONS.put(session.getId(), session); - } - - @Override - public Session findSession(String id) { - return SESSIONS.get(id); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/FileManager.java b/tomcat/src/main/java/org/apache/coyote/http11/response/FileManager.java deleted file mode 100644 index 958d633d5c..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/FileManager.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.apache.coyote.http11.response; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; - -public class FileManager { - - private static final String PATH = "static"; - private static final String EXTENSION_SIGN = "."; - private static final String HTML_EXTENSION = ".html"; - private static final String PREFIX = "/"; - - private final File file; - - public FileManager(File file) { - this.file = file; - } - - public static FileManager from(String location) { - if (!location.contains(EXTENSION_SIGN)) { - location = location + HTML_EXTENSION; - } - if (!location.startsWith(PREFIX)) { - location = PREFIX + location; - } - URL path = FileManager.class.getClassLoader().getResource(PATH + location); - - validatePath(path); - return new FileManager(new File(path.getFile())); - } - - private static void validatePath(URL path) { - if (path == null) { - throw new IllegalArgumentException("존재하지 않는 파일의 경로입니다."); - } - } - - public String extractFileExtension() { - String fileName = file.getName(); - int extensionSignIndex = fileName.lastIndexOf(EXTENSION_SIGN); - return fileName.substring(extensionSignIndex + 1); - } - - public String readFileContent() { - try { - return new String(Files.readAllBytes(file.toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("파일을 불러올 수 없습니다."); - } - } - - public File file() { - return file; - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/line/ResponseStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/response/line/ResponseStatus.java deleted file mode 100644 index 17111b6573..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/line/ResponseStatus.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.apache.coyote.http11.response.line; - -public enum ResponseStatus { - OK(200, "OK"), - FOUND(302, "Found"), - UNAUTHORIZED(401, "Unauthorized"), - ; - - private final int code; - private final String message; - - ResponseStatus(int code, String message) { - this.code = code; - this.message = message; - } - - public int code() { - return code; - } - - public String message() { - return message; - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java similarity index 77% rename from tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java rename to tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index 3a49a9fd2e..49bff03758 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -1,11 +1,10 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.request; -import java.util.Arrays; import java.util.Objects; -import org.apache.coyote.http11.request.body.RequestBody; -import org.apache.coyote.http11.request.header.RequestHeader; -import org.apache.coyote.http11.request.line.HttpMethod; -import org.apache.coyote.http11.request.line.RequestLine; +import org.apache.coyote.request.body.RequestBody; +import org.apache.coyote.request.header.RequestHeader; +import org.apache.coyote.request.line.HttpMethod; +import org.apache.coyote.request.line.RequestLine; public class HttpRequest { @@ -13,11 +12,11 @@ public class HttpRequest { private final RequestHeader requestHeader; private RequestBody requestBody; - public HttpRequest(RequestLine requestLine, RequestHeader requestHeader) { + private HttpRequest(RequestLine requestLine, RequestHeader requestHeader) { this(requestLine, requestHeader, null); } - public HttpRequest(RequestLine requestLine, RequestHeader requestHeader, RequestBody requestBody) { + private HttpRequest(RequestLine requestLine, RequestHeader requestHeader, RequestBody requestBody) { this.requestLine = requestLine; this.requestHeader = requestHeader; this.requestBody = requestBody; @@ -34,9 +33,12 @@ public boolean hasBody() { return requestHeader.hasContent(); } - public boolean consistsOf(HttpMethod httpMethod, String... requestUris) { - return Arrays.stream(requestUris) - .anyMatch(requestUri -> requestLine.consistsOf(httpMethod, requestUri)); + public boolean consistsOf(HttpMethod httpMethod) { + return requestLine.consistsOf(httpMethod); + } + + public boolean consistsOf(String requestUri) { + return requestLine.consistsOf(requestUri); } public boolean hasQueryString() { @@ -59,6 +61,10 @@ public String requestUri() { return requestLine.requestUri(); } + public String httpVersion() { + return requestLine.httpVersion(); + } + public RequestLine requestLine() { return requestLine; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestReader.java similarity index 58% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpRequestReader.java rename to tomcat/src/main/java/org/apache/coyote/request/HttpRequestReader.java index 0c0e32d973..82b288c0f5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestReader.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestReader.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.request; import java.io.BufferedReader; import java.io.IOException; @@ -8,20 +8,20 @@ public class HttpRequestReader { private static final String END_OF_PARAGRAPH = ""; - public static String readLine(BufferedReader reader) throws IOException { + public String readLine(BufferedReader reader) throws IOException { return reader.readLine(); } - public static String readHeader(BufferedReader reader) throws IOException { + public String readHeader(BufferedReader reader) throws IOException { String line; StringJoiner joiner = new StringJoiner(System.lineSeparator()); - while ((line = reader.readLine()) != null & !END_OF_PARAGRAPH.equals(line)) { + while ((line = reader.readLine()) != null && !END_OF_PARAGRAPH.equals(line)) { joiner.add(line); } return joiner.toString(); } - public static String readBody(BufferedReader reader, int length) throws IOException { + public String readBody(BufferedReader reader, int length) throws IOException { char[] buffer = new char[length]; reader.read(buffer, 0, length); return new String(buffer); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/body/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/body/RequestBody.java similarity index 82% rename from tomcat/src/main/java/org/apache/coyote/http11/request/body/RequestBody.java rename to tomcat/src/main/java/org/apache/coyote/request/body/RequestBody.java index 106df75d29..a29166524b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/body/RequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/request/body/RequestBody.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request.body; +package org.apache.coyote.request.body; import java.util.Arrays; import java.util.HashMap; @@ -15,7 +15,7 @@ public class RequestBody { private final Map parametersMap; - public RequestBody(Map parametersMap) { + private RequestBody(Map parametersMap) { this.parametersMap = parametersMap; } @@ -24,14 +24,17 @@ public static RequestBody from(String requestBody) { .map(parameter -> parameter.split(FIELD_AND_VALUE_DELIMITER)) .collect(Collectors.toMap( fieldAndValue -> fieldAndValue[BODY_FIELD_INDEX].trim(), - fieldAndValue -> fieldAndValue[BODY_VALUE_INDEX].trim(), - (prev, update) -> update + fieldAndValue -> fieldAndValue[BODY_VALUE_INDEX].trim() )); return new RequestBody(parametersMap); } - public String get(String key) { - return parametersMap.get(key); + public String get(String name) { + String field = parametersMap.get(name); + if (field == null) { + throw new IllegalArgumentException("body에 존재하지 않는 field 입니다."); + } + return field; } public Map parametersMap() { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/header/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/request/header/HttpCookie.java similarity index 91% rename from tomcat/src/main/java/org/apache/coyote/http11/request/header/HttpCookie.java rename to tomcat/src/main/java/org/apache/coyote/request/header/HttpCookie.java index e23f45a2a7..9df6cf7f68 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/header/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/request/header/HttpCookie.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request.header; +package org.apache.coyote.request.header; import java.util.Arrays; import java.util.HashMap; @@ -15,6 +15,10 @@ public class HttpCookie { private final Map parametersMap; + public HttpCookie() { + this(new HashMap<>()); + } + public HttpCookie(Map parametersMap) { this.parametersMap = parametersMap; } @@ -24,13 +28,12 @@ public static HttpCookie from(String cookieContent) { .map(parameter -> parameter.split(FIELD_VALUE_DELIMITER)) .collect(Collectors.toMap( fieldAndValue -> fieldAndValue[FIELD_INDEX].trim(), - fieldAndValue -> fieldAndValue[VALUE_INDEX].trim(), - (prev, update) -> update + fieldAndValue -> fieldAndValue[VALUE_INDEX].trim() )); return new HttpCookie(parametersMap); } - public boolean containsCookie(String field) { + public boolean contains(String field) { return parametersMap.containsKey(field); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/header/RequestHeader.java b/tomcat/src/main/java/org/apache/coyote/request/header/RequestHeader.java similarity index 80% rename from tomcat/src/main/java/org/apache/coyote/http11/request/header/RequestHeader.java rename to tomcat/src/main/java/org/apache/coyote/request/header/RequestHeader.java index 075661d715..f84e0044b2 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/header/RequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/request/header/RequestHeader.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request.header; +package org.apache.coyote.request.header; import java.util.Arrays; import java.util.HashMap; @@ -15,13 +15,13 @@ public class RequestHeader { private static final String CONTENT_LENGTH = "Content-Length"; private static final String JSESSIONID = "JSESSIONID"; private static final String COOKIE = "Cookie"; - private static final String MINIMUM_LENGTH = "0"; + private static final int EMPTY_CONTENT_LENGTH = 0; private final Map headersMap; private final HttpCookie httpCookie; - public RequestHeader(Map headersMap) { - this(headersMap, null); + private RequestHeader(Map headersMap) { + this(headersMap, new HttpCookie()); } public RequestHeader(Map headersMap, HttpCookie httpCookie) { @@ -34,8 +34,7 @@ public static RequestHeader from(String headers) { .map(headerLine -> headerLine.split(DELIMITER, LIMIT)) .collect(Collectors.toMap( fieldAndValue -> fieldAndValue[HEADER_FIELD_INDEX].trim(), - fieldAndValue -> fieldAndValue[HEADER_VALUE_INDEX].trim(), - (prev, update) -> update + fieldAndValue -> fieldAndValue[HEADER_VALUE_INDEX].trim() )); if (headersMap.containsKey(COOKIE)) { @@ -45,25 +44,25 @@ public static RequestHeader from(String headers) { } public boolean hasContent() { - return contentLength() > 0; + return contentLength() > EMPTY_CONTENT_LENGTH; } public int contentLength() { - String contentLength = Objects.requireNonNullElse(headersMap.get(CONTENT_LENGTH), MINIMUM_LENGTH); + String contentLength = Objects.requireNonNullElse( + headersMap.get(CONTENT_LENGTH), + String.valueOf(EMPTY_CONTENT_LENGTH) + ); return Integer.parseInt(contentLength); } public boolean hasSessionId() { if (httpCookie != null) { - return httpCookie.containsCookie(JSESSIONID); + return httpCookie.contains(JSESSIONID); } return false; } public String sessionId() { - if (!hasSessionId()) { - throw new IllegalArgumentException("세션 Id가 존재하지 않습니다."); - } return httpCookie.get(JSESSIONID); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/line/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/request/line/HttpMethod.java similarity index 56% rename from tomcat/src/main/java/org/apache/coyote/http11/request/line/HttpMethod.java rename to tomcat/src/main/java/org/apache/coyote/request/line/HttpMethod.java index 854a7b4832..c5ce5a3be6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/line/HttpMethod.java +++ b/tomcat/src/main/java/org/apache/coyote/request/line/HttpMethod.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request.line; +package org.apache.coyote.request.line; public enum HttpMethod { GET, POST, DELETE, PATCH, PUT diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/line/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/request/line/RequestLine.java similarity index 87% rename from tomcat/src/main/java/org/apache/coyote/http11/request/line/RequestLine.java rename to tomcat/src/main/java/org/apache/coyote/request/line/RequestLine.java index 8420644477..aa568052b1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/line/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/request/line/RequestLine.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request.line; +package org.apache.coyote.request.line; import static java.util.stream.Collectors.toList; @@ -17,7 +17,7 @@ public class RequestLine { private final RequestUri requestUri; private final String httpVersion; - public RequestLine( + private RequestLine( HttpMethod httpMethod, RequestUri requestUri, String httpVersion @@ -40,7 +40,15 @@ public static RequestLine from(String requestLine) { } public boolean consistsOf(HttpMethod httpMethod, String uri) { - return this.httpMethod.equals(httpMethod) & requestUri.consistsOf(uri); + return this.httpMethod.equals(httpMethod) && requestUri.consistsOf(uri); + } + + public boolean consistsOf(HttpMethod httpMethod) { + return this.httpMethod.equals(httpMethod); + } + + public boolean consistsOf(String uri) { + return requestUri.consistsOf(uri); } public boolean hasQueryString() { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/line/RequestUri.java b/tomcat/src/main/java/org/apache/coyote/request/line/RequestUri.java similarity index 91% rename from tomcat/src/main/java/org/apache/coyote/http11/request/line/RequestUri.java rename to tomcat/src/main/java/org/apache/coyote/request/line/RequestUri.java index ad272a54cb..c3f5730d73 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/line/RequestUri.java +++ b/tomcat/src/main/java/org/apache/coyote/request/line/RequestUri.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request.line; +package org.apache.coyote.request.line; import java.util.Arrays; import java.util.HashMap; @@ -17,7 +17,7 @@ public class RequestUri { private final String uri; private final Map queryStringsMap; - public RequestUri(String uri, Map queryStringsMap) { + private RequestUri(String uri, Map queryStringsMap) { this.uri = uri; this.queryStringsMap = queryStringsMap; } @@ -34,8 +34,7 @@ public static RequestUri from(String requestUri) { .map(queryString -> queryString.split(FIELD_AND_VALUE_DELIMITER)) .collect(Collectors.toMap( fieldAndValue -> fieldAndValue[QUERY_STRING_FIELD_INDEX], - fieldAndValue -> fieldAndValue[QUERY_STRING_VALUE_INDEX], - (prev, update) -> update + fieldAndValue -> fieldAndValue[QUERY_STRING_VALUE_INDEX] )); } return new RequestUri(requestUri, queryStringsMap); @@ -58,7 +57,7 @@ public String uri() { } public Map queryStringsMap() { - return queryStringsMap; + return new HashMap<>(queryStringsMap); } @Override diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java similarity index 54% rename from tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java rename to tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 12d730f9f2..aacfcf7df7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -1,18 +1,18 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.response; +import static common.ResponseStatus.EMPTY_RESPONSE_STATUS; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.coyote.http11.response.header.HttpContentType.TEXT_HTML; -import static org.apache.coyote.http11.response.header.HttpContentType.mimeTypeWithCharset; -import static org.apache.coyote.http11.response.header.HttpHeader.CONTENT_LENGTH; -import static org.apache.coyote.http11.response.header.HttpHeader.CONTENT_TYPE; -import static org.apache.coyote.http11.response.header.HttpHeader.LOCATION; +import static org.apache.coyote.response.header.HttpHeader.CONTENT_LENGTH; +import static org.apache.coyote.response.header.HttpHeader.CONTENT_TYPE; +import static org.apache.coyote.response.header.HttpHeader.LOCATION; +import common.ResponseStatus; import java.util.LinkedHashMap; -import org.apache.coyote.http11.response.body.ResponseBody; -import org.apache.coyote.http11.response.header.HttpHeader; -import org.apache.coyote.http11.response.header.ResponseHeader; -import org.apache.coyote.http11.response.line.ResponseLine; -import org.apache.coyote.http11.response.line.ResponseStatus; +import org.apache.coyote.response.body.ResponseBody; +import org.apache.coyote.response.header.HttpContentType; +import org.apache.coyote.response.header.HttpHeader; +import org.apache.coyote.response.header.ResponseHeader; +import org.apache.coyote.response.line.ResponseLine; public class HttpResponse { @@ -23,10 +23,14 @@ public class HttpResponse { private ResponseBody responseBody; public HttpResponse(String httpVersion) { - this.responseLine = new ResponseLine(httpVersion, null); + this.responseLine = new ResponseLine(httpVersion, EMPTY_RESPONSE_STATUS); this.responseHeader = new ResponseHeader(new LinkedHashMap<>()); } + public boolean isDetermined() { + return !responseLine.hasEmptyResponseStatus(); + } + public String responseMessage() { StringBuilder messageBuilder = new StringBuilder(); messageBuilder.append(responseLine.responseLineMessage()).append(DELIMITER); @@ -49,22 +53,14 @@ public void setResponseMessage(ResponseStatus responseStatus, String bodyMessage private void setBodyAndHeaderByMessage(String message) { responseBody = new ResponseBody(message); - responseHeader.put(CONTENT_TYPE, TEXT_HTML.mimeTypeWithCharset(UTF_8)); + responseHeader.put(CONTENT_TYPE, HttpContentType.TEXT_HTML.mimeTypeWithCharset(UTF_8)); responseHeader.put(CONTENT_LENGTH, String.valueOf(responseBody.measureContentLength())); } - public void setResponseResource(ResponseStatus responseStatus, String resourceUri) { + public void setResponseResource(ResponseStatus responseStatus, String resourceType, String resourceMessage) { responseLine.setResponseStatus(responseStatus); - setBodyAndHeaderByResource(resourceUri); - } - - private void setBodyAndHeaderByResource(String uri) { - FileManager fileManager = FileManager.from(uri); - String fileContent = fileManager.readFileContent(); - String fileExtension = fileManager.extractFileExtension(); - - responseBody = new ResponseBody(fileContent); - responseHeader.put(CONTENT_TYPE, mimeTypeWithCharset(fileExtension, UTF_8)); + responseBody = new ResponseBody(resourceMessage); + responseHeader.put(CONTENT_TYPE, HttpContentType.mimeTypeWithCharset(resourceType, UTF_8)); responseHeader.put(CONTENT_LENGTH, String.valueOf(responseBody.measureContentLength())); } @@ -76,12 +72,4 @@ public void setResponseRedirect(ResponseStatus responseStatus, String redirectUr public void setResponseHeader(HttpHeader field, String value) { responseHeader.put(field, value); } - - public void setResponseBody(String responseBody) { - this.responseBody = new ResponseBody(responseBody); - } - - public void setResponseStatus(ResponseStatus responseStatus) { - responseLine.setResponseStatus(responseStatus); - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/body/ResponseBody.java b/tomcat/src/main/java/org/apache/coyote/response/body/ResponseBody.java similarity index 87% rename from tomcat/src/main/java/org/apache/coyote/http11/response/body/ResponseBody.java rename to tomcat/src/main/java/org/apache/coyote/response/body/ResponseBody.java index fa55c5b4f3..535511b75b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/body/ResponseBody.java +++ b/tomcat/src/main/java/org/apache/coyote/response/body/ResponseBody.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.response.body; +package org.apache.coyote.response.body; public class ResponseBody { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/header/HttpContentType.java b/tomcat/src/main/java/org/apache/coyote/response/header/HttpContentType.java similarity index 96% rename from tomcat/src/main/java/org/apache/coyote/http11/response/header/HttpContentType.java rename to tomcat/src/main/java/org/apache/coyote/response/header/HttpContentType.java index 608ea7d0e2..186655ad73 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/header/HttpContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/response/header/HttpContentType.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.response.header; +package org.apache.coyote.response.header; import java.nio.charset.Charset; import java.util.Arrays; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/header/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/response/header/HttpHeader.java similarity index 87% rename from tomcat/src/main/java/org/apache/coyote/http11/response/header/HttpHeader.java rename to tomcat/src/main/java/org/apache/coyote/response/header/HttpHeader.java index 66a76c38c5..b29bbb2e8b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/header/HttpHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/response/header/HttpHeader.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.response.header; +package org.apache.coyote.response.header; public enum HttpHeader { CONTENT_TYPE("Content-Type"), diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/header/ResponseHeader.java b/tomcat/src/main/java/org/apache/coyote/response/header/ResponseHeader.java similarity index 86% rename from tomcat/src/main/java/org/apache/coyote/http11/response/header/ResponseHeader.java rename to tomcat/src/main/java/org/apache/coyote/response/header/ResponseHeader.java index e4ed976293..472567dfa7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/header/ResponseHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/response/header/ResponseHeader.java @@ -1,5 +1,6 @@ -package org.apache.coyote.http11.response.header; +package org.apache.coyote.response.header; +import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -24,6 +25,6 @@ public List headerMessages() { } public Map headersMap() { - return headersMap; + return new EnumMap<>(headersMap); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/line/ResponseLine.java b/tomcat/src/main/java/org/apache/coyote/response/line/ResponseLine.java similarity index 81% rename from tomcat/src/main/java/org/apache/coyote/http11/response/line/ResponseLine.java rename to tomcat/src/main/java/org/apache/coyote/response/line/ResponseLine.java index 16b0247a16..1f3437b9a9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/line/ResponseLine.java +++ b/tomcat/src/main/java/org/apache/coyote/response/line/ResponseLine.java @@ -1,5 +1,8 @@ -package org.apache.coyote.http11.response.line; +package org.apache.coyote.response.line; +import static common.ResponseStatus.EMPTY_RESPONSE_STATUS; + +import common.ResponseStatus; import java.util.Objects; public class ResponseLine { @@ -14,12 +17,16 @@ public ResponseLine(String httpVersion, ResponseStatus responseStatus) { this.responseStatus = responseStatus; } + public boolean hasEmptyResponseStatus() { + return responseStatus == EMPTY_RESPONSE_STATUS; + } + public String responseLineMessage() { return String.join( DELIMITER, httpVersion, - String.valueOf(responseStatus.code()), - responseStatus.message() + responseStatus.codeMessage(), + responseStatus.responseMessage() ); } diff --git a/tomcat/src/test/java/nextstep/jwp/controller/ExceptionHandlerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/ExceptionHandlerTest.java new file mode 100644 index 0000000000..bcd7ea4eca --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/ExceptionHandlerTest.java @@ -0,0 +1,41 @@ +package nextstep.jwp.controller; + +import static org.assertj.core.api.Assertions.assertThat; + +import nextstep.jwp.exception.AuthException; +import nextstep.jwp.exception.NotFoundException; +import org.apache.coyote.response.HttpResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ExceptionHandlerTest { + + @Test + void AuthException이면_Unauthorized로_처리한다() { + // given + ExceptionHandler exceptionHandler = new ExceptionHandler(); + HttpResponse httpResponse = new HttpResponse("Http/1.1"); + + // when + exceptionHandler.handle(httpResponse, new AuthException()); + + // then + assertThat(httpResponse.responseMessage()).contains("Unauthorized"); + } + + @Test + void NotFoundException이면_NotFound로_처리한다() { + // given + ExceptionHandler exceptionHandler = new ExceptionHandler(); + HttpResponse httpResponse = new HttpResponse("Http/1.1"); + + // when + exceptionHandler.handle(httpResponse, new NotFoundException()); + + // then + assertThat(httpResponse.responseMessage()).contains("Not Found"); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java new file mode 100644 index 0000000000..e568fd44e0 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java @@ -0,0 +1,79 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.OK; +import static org.apache.coyote.request.line.HttpMethod.GET; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import nextstep.jwp.exception.NotFoundException; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.line.HttpMethod; +import org.apache.coyote.response.HttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mockito; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class HomeControllerTest { + + private HttpRequest mockHttpRequest; + private HttpResponse mockHttpResponse; + private HomeController homeController; + + @BeforeEach + void setUp() { + mockHttpRequest = Mockito.mock(HttpRequest.class); + mockHttpResponse = Mockito.mock(HttpResponse.class); + homeController = new HomeController(); + } + + @ParameterizedTest + @CsvSource({"true, true", "false, false"}) + void 처리할_수_있는_요청인지_확인한다(boolean returnValue, boolean expected) { + // given + String uri = anyString(); + when(mockHttpRequest.consistsOf(uri)) + .thenReturn(returnValue); + + // when + boolean actual = homeController.canProcess(mockHttpRequest); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void GET_요청이면_OK로_응답한다() { + // given + when(mockHttpRequest.consistsOf(GET)) + .thenReturn(true); + + // when + homeController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(mockHttpRequest, times(1)).consistsOf(GET); + verify(mockHttpResponse, times(1)).setResponseMessage(OK, "Hello world!"); + } + + @Test + void GET_요청이_아니면_예외를_던진다() { + // given + when(mockHttpRequest.consistsOf(any(HttpMethod.class))) + .thenReturn(false); + + // expect + assertThatThrownBy(() -> homeController.service(mockHttpRequest, mockHttpResponse)) + .isInstanceOf(NotFoundException.class); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/IndexControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/IndexControllerTest.java new file mode 100644 index 0000000000..76a96a8ab8 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/IndexControllerTest.java @@ -0,0 +1,74 @@ +package nextstep.jwp.controller; + +import static org.apache.coyote.request.line.HttpMethod.GET; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import common.ResponseStatus; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class IndexControllerTest { + + @Mock + private HttpRequest mockHttpRequest; + + @Mock + private HttpResponse mockHttpResponse; + + @InjectMocks + private IndexController indexController; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @ParameterizedTest + @CsvSource({"true, true", "false, false"}) + void 처리할_수_있는_요청인지_확인한다(boolean returnValue, boolean expected) { + // given + String uri = anyString(); + when(mockHttpRequest.consistsOf(uri)) + .thenReturn(returnValue); + + // when + boolean actual = indexController.canProcess(mockHttpRequest); + + // then + assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @ValueSource(strings = {"/index", "/index.html"}) + void GET_요청일_때_리소스를_응답한다(String requestUri) { + // given + when(mockHttpRequest.consistsOf(GET)) + .thenReturn(true); + when(mockHttpRequest.requestUri()) + .thenReturn(requestUri); + + // when + indexController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(mockHttpResponse, times(1)).setResponseResource( + any(ResponseStatus.class), anyString(), anyString() + ); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/LoginControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/LoginControllerTest.java new file mode 100644 index 0000000000..11b744db09 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/LoginControllerTest.java @@ -0,0 +1,160 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.FOUND; +import static org.apache.coyote.request.line.HttpMethod.GET; +import static org.apache.coyote.request.line.HttpMethod.POST; +import static org.apache.coyote.response.header.HttpHeader.SET_COOKIE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import common.ResponseStatus; +import nextstep.jwp.exception.NotFoundException; +import nextstep.jwp.service.AuthService; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.line.HttpMethod; +import org.apache.coyote.response.HttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class LoginControllerTest { + + @Mock + private HttpRequest mockHttpRequest; + + @Mock + private HttpResponse mockHttpResponse; + + @Mock + private AuthService authService; + + @InjectMocks + private LoginController loginController; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @ParameterizedTest + @CsvSource({"true, true", "false, false"}) + void 처리할_수_있는_요청인지_확인한다(boolean returnValue, boolean expected) { + // given + String uri = anyString(); + when(mockHttpRequest.consistsOf(uri)) + .thenReturn(returnValue); + + // when + boolean actual = loginController.canProcess(mockHttpRequest); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Nested + class POST_요청일_때 { + + @Test + void 로그인하면_쿠키에_세션을_넣고_FOUND로_리다이렉트할_URL을_응답한다() { + // given + when(mockHttpRequest.consistsOf(POST)) + .thenReturn(true); + when(authService.login(any(), any())) + .thenReturn("sessionId"); + + // when + loginController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(authService, times(1)).login(any(), any()); + InOrder inOrder = Mockito.inOrder(mockHttpResponse); + inOrder.verify(mockHttpResponse, times(1)).setResponseRedirect(FOUND, "/index.html"); + inOrder.verify(mockHttpResponse, times(1)).setResponseHeader(SET_COOKIE, "JSESSIONID=sessionId"); + } + } + + @Nested + class GET_요청일_때 { + + @Test + void query_String_으로_로그인하면_쿠키에_세션을_넣고_FOUND로_리다이렉트할_URL을_응답한다() { + // given + when(mockHttpRequest.consistsOf(GET)) + .thenReturn(true); + when(mockHttpRequest.hasQueryString()) + .thenReturn(true); + when(authService.login(any(), any())) + .thenReturn("sessionId"); + + // when + loginController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(authService, times(1)).login(any(), any()); + InOrder inOrder = Mockito.inOrder(mockHttpResponse); + inOrder.verify(mockHttpResponse, times(1)).setResponseRedirect(FOUND, "/index.html"); + inOrder.verify(mockHttpResponse, times(1)).setResponseHeader(SET_COOKIE, "JSESSIONID=sessionId"); + } + + @Test + void session으로_기존_로그인을_확인하고_FOUND로_리다이렉트할_URL을_응답한다() { + // given + when(mockHttpRequest.consistsOf(GET)) + .thenReturn(true); + when(mockHttpRequest.hasSessionId()) + .thenReturn(true); + when(authService.isLoggedIn(any())) + .thenReturn(true); + + // when + loginController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(mockHttpResponse, times(1)).setResponseRedirect(FOUND, "/index.html"); + } + + @Test + void 리소스를_응답한다() { + // given + when(mockHttpRequest.consistsOf(GET)) + .thenReturn(true); + when(mockHttpRequest.requestUri()) + .thenReturn("/login"); + + // when + loginController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(mockHttpResponse, times(1)).setResponseResource( + any(ResponseStatus.class), anyString(), anyString() + ); + } + } + + @Test + void 다른_요청이면_예외를_던진다() { + // given + when(mockHttpRequest.consistsOf(any(HttpMethod.class))) + .thenReturn(false); + + // when + assertThatThrownBy(() -> loginController.service(mockHttpRequest, mockHttpResponse)) + .isInstanceOf(NotFoundException.class); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java new file mode 100644 index 0000000000..178734f8bd --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java @@ -0,0 +1,114 @@ +package nextstep.jwp.controller; + +import static common.ResponseStatus.FOUND; +import static org.apache.coyote.request.line.HttpMethod.GET; +import static org.apache.coyote.request.line.HttpMethod.POST; +import static org.apache.coyote.response.header.HttpHeader.SET_COOKIE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import common.ResponseStatus; +import nextstep.jwp.exception.NotFoundException; +import nextstep.jwp.service.AuthService; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.line.HttpMethod; +import org.apache.coyote.response.HttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class RegisterControllerTest { + + @Mock + private HttpRequest mockHttpRequest; + + @Mock + private HttpResponse mockHttpResponse; + + @Mock + private AuthService authService; + + @InjectMocks + private RegisterController registerController; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @ParameterizedTest + @CsvSource({"true, true", "false, false"}) + void 처리할_수_있는_요청인지_확인한다(boolean returnValue, boolean expected) { + // given + String uri = anyString(); + when(mockHttpRequest.consistsOf(uri)) + .thenReturn(returnValue); + + // when + boolean actual = registerController.canProcess(mockHttpRequest); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void POST_요청일_때_body_값으로_회원가입하면_쿠키에_세션을_넣고_FOUND로_리다이렉트할_URL을_응답한다() { + // given + when(mockHttpRequest.consistsOf(POST)) + .thenReturn(true); + when(authService.register(any(), any(), any())) + .thenReturn("sessionId"); + + // when + registerController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(authService, times(1)).register(any(), any(), any()); + InOrder inOrder = Mockito.inOrder(mockHttpResponse); + inOrder.verify(mockHttpResponse, times(1)).setResponseRedirect(FOUND, "/index.html"); + inOrder.verify(mockHttpResponse, times(1)).setResponseHeader(SET_COOKIE, "JSESSIONID=sessionId"); + } + + @Test + void GET_요청일_때_리소스를_응답한다() { + // given + when(mockHttpRequest.consistsOf(GET)) + .thenReturn(true); + when(mockHttpRequest.requestUri()) + .thenReturn("/register"); + + // when + registerController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(mockHttpResponse, times(1)).setResponseResource( + any(ResponseStatus.class), anyString(), anyString() + ); + } + + @Test + void 다른_요청이면_예외를_던진다() { + // given + when(mockHttpRequest.consistsOf(any(HttpMethod.class))) + .thenReturn(false); + + // when + assertThatThrownBy(() -> registerController.service(mockHttpRequest, mockHttpResponse)) + .isInstanceOf(NotFoundException.class); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java new file mode 100644 index 0000000000..2a2e0562a1 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java @@ -0,0 +1,78 @@ +package nextstep.jwp.controller; + +import static org.apache.coyote.request.line.HttpMethod.GET; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import common.ResponseStatus; +import nextstep.jwp.exception.NotFoundException; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.line.HttpMethod; +import org.apache.coyote.response.HttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ResourceControllerTest { + + @Mock + private HttpRequest mockHttpRequest; + + @Mock + private HttpResponse mockHttpResponse; + + private ResourceController resourceController; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + resourceController = new ResourceController(); + } + + @Test + void 처리할_수_있는_요청인지_확인한다() { + // when + boolean actual = resourceController.canProcess(mockHttpRequest); + + // then + assertThat(actual).isTrue(); + } + + @Test + void GET_요청일_때_리소스를_응답한다() { + // given + when(mockHttpRequest.consistsOf(GET)) + .thenReturn(true); + when(mockHttpRequest.requestUri()) + .thenReturn("css/styles.css"); + + // when + resourceController.service(mockHttpRequest, mockHttpResponse); + + // then + verify(mockHttpResponse, times(1)).setResponseResource( + any(ResponseStatus.class), anyString(), anyString() + ); + } + + @Test + void 다른_요청이면_예외를_던진다() { + // given + when(mockHttpRequest.consistsOf(any(HttpMethod.class))) + .thenReturn(false); + + // expect + assertThatThrownBy(() -> resourceController.service(mockHttpRequest, mockHttpResponse)) + .isInstanceOf(NotFoundException.class); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/FileManagerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/ResourceManagerTest.java similarity index 62% rename from tomcat/src/test/java/org/apache/coyote/http11/FileManagerTest.java rename to tomcat/src/test/java/nextstep/jwp/controller/ResourceManagerTest.java index 567fd48965..a9b7c45cb6 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/FileManagerTest.java +++ b/tomcat/src/test/java/nextstep/jwp/controller/ResourceManagerTest.java @@ -1,67 +1,52 @@ -package org.apache.coyote.http11; +package nextstep.jwp.controller; -import static org.apache.coyote.http11.response.FileManager.from; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.io.File; -import org.apache.coyote.http11.response.FileManager; +import nextstep.jwp.exception.FileNotFoundException; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class FileManagerTest { +class ResourceManagerTest { @Test - void location으로_생성한다() { + void 존재하지_않는_자원의_location_으로_생성할_경우_예외를_던진다() { // given - String location = "css/styles.css"; - - // when - FileManager fileManager = from(location); - - // then - File file = fileManager.file(); - assertThat(file.getName()).isEqualTo("styles.css"); - } - - @Test - void 존재하지_않는_파일의_location_으로_생성할_경우_예외를_던진다() { - // given - String location = "wrong.file"; + String wrongLocation = "/wrong.file"; // expect - assertThatThrownBy(() -> from(location)) - .isInstanceOf(IllegalArgumentException.class) + assertThatThrownBy(() -> ResourceManager.from(wrongLocation)) + .isInstanceOf(FileNotFoundException.class) .hasMessage("존재하지 않는 파일의 경로입니다."); } @Test - void 파일_확장자를_반환한다() { + void 자원_타입을_반환한다() { // given String location = "css/styles.css"; - FileManager fileManager = from(location); + ResourceManager manager = ResourceManager.from(location); // when - String fileExtension = fileManager.extractFileExtension(); + String resourceType = manager.extractResourceType(); // then - assertThat(fileExtension).isEqualTo("css"); + assertThat(resourceType).isEqualTo("css"); } @Test - void file의_내용을_반환한다() { + void resource의_내용을_읽는다() { // given String location = "js/scripts.js"; - FileManager fileManager = from(location); + ResourceManager manager = ResourceManager.from(location); // when - String fileContent = fileManager.readFileContent(); + String resourceContent = manager.readResourceContent(); // then - assertThat(fileContent).isEqualTo( + assertThat(resourceContent).isEqualTo( "/*!\n" + " * Start Bootstrap - SB Admin v7.0.2 (https://startbootstrap.com/template/sb-admin)\n" + " * Copyright 2013-2021 Start Bootstrap\n" @@ -89,15 +74,4 @@ class FileManagerTest { + "\n" + "});\n"); } - - @Test - void file의_내용을_반환할_수_없는_경우_예외를_던진다() { - // given - FileManager fileManager = new FileManager(new File("wrong.file")); - - // when - assertThatThrownBy(fileManager::readFileContent) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("파일을 불러올 수 없습니다."); - } } diff --git a/tomcat/src/test/java/nextstep/jwp/db/InMemoryUserRepositoryTest.java b/tomcat/src/test/java/nextstep/jwp/db/InMemoryUserRepositoryTest.java new file mode 100644 index 0000000000..9052148420 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/db/InMemoryUserRepositoryTest.java @@ -0,0 +1,104 @@ +package nextstep.jwp.db; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import nextstep.jwp.model.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class InMemoryUserRepositoryTest { + + private InMemoryUserRepository inMemoryUserRepository; + + @BeforeEach + void tearDown() { + inMemoryUserRepository = InMemoryUserRepository.getInstance(); + inMemoryUserRepository.deleteAll(); + } + + @Test + void 회원을_저장한다() { + // given + User user = new User("huchu", "huchu123", "huchu@naver.com"); + + // when + Long id = inMemoryUserRepository.save(user); + + // then + assertThat(id).isNotNull(); + } + + @Test + void 계정으로_회원을_조회한다() { + // given + User user = new User("huchu", "huchu123", "huchu@naver.com"); + inMemoryUserRepository.save(user); + + // when + User foundUser = inMemoryUserRepository.findByAccount("huchu").get(); + + // then + assertThat(foundUser).isEqualTo(user); + } + + @Test + void 모든_회원을_조회한다() { + // given + User user = new User("huchu", "huchu123", "huchu@naver.com"); + inMemoryUserRepository.save(user); + + // when + List users = inMemoryUserRepository.findAll(); + + // then + assertThat(users).hasSize(1); + } + + @Test + void 모든_회원을_삭제한다() { + // given + User user = new User("huchu", "huchu123", "huchu@naver.com"); + inMemoryUserRepository.save(user); + + // when + inMemoryUserRepository.deleteAll(); + + // then + assertThat(inMemoryUserRepository.findAll()).isEmpty(); + } + + @Test + void 멀티스레드_테스트() throws InterruptedException { + // given + int numThreads = 3; + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + + // when + for (int i = 0; i < 1000; i++) { + executorService.submit( + () -> inMemoryUserRepository.save(new User("huchu1", "huchu123", "huchu@naver.com"))); + executorService.submit( + () -> inMemoryUserRepository.save(new User("huchu2", "huchu123", "huchu@naver.com"))); + executorService.submit( + () -> inMemoryUserRepository.save(new User("huchu3", "huchu123", "huchu@naver.com"))); + } + + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + + // then + long memberIdCount = inMemoryUserRepository.findAll().stream() + .map(User::id) + .distinct() + .count(); + assertThat(memberIdCount).isEqualTo(3000); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/service/AuthServiceTest.java b/tomcat/src/test/java/nextstep/jwp/service/AuthServiceTest.java index 62b437351f..2f0550c8a1 100644 --- a/tomcat/src/test/java/nextstep/jwp/service/AuthServiceTest.java +++ b/tomcat/src/test/java/nextstep/jwp/service/AuthServiceTest.java @@ -3,32 +3,40 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import org.apache.coyote.http11.SessionManager; +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.exception.AuthException; +import org.apache.catalina.Session; +import org.apache.catalina.SessionManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class AuthServiceTest { private SessionManager sessionManager; + private InMemoryUserRepository inMemoryUserRepository; @BeforeEach void setUp() { - sessionManager = new SessionManager(); + inMemoryUserRepository = InMemoryUserRepository.getInstance(); + inMemoryUserRepository.deleteAll(); + sessionManager = SessionManager.getInstance(); + sessionManager.deleteAll(); } @Test void 로그인한다() { // given - AuthService authService = new AuthService(sessionManager); - String account = "gugu"; - String password = "password"; + AuthService authService = new AuthService(sessionManager, inMemoryUserRepository); + authService.register("gugu", "password", "hkkang@woowahan.com"); // when - String sessionId = authService.login(account, password); + String sessionId = authService.login("gugu", "password"); // then assertThat(sessionId).isNotEmpty(); @@ -37,33 +45,34 @@ void setUp() { @Test void 존재하지_않는_계정으로_로그인을_할_경우_예외를_던진다() { // given - AuthService authService = new AuthService(sessionManager); + AuthService authService = new AuthService(sessionManager, inMemoryUserRepository); String wrongAccount = "wrongAccount"; String password = "password"; // expect assertThatThrownBy(() -> authService.login(wrongAccount, password)) - .isInstanceOf(IllegalArgumentException.class) + .isInstanceOf(AuthException.class) .hasMessage("존재하지 않는 계정이거나 비밀번호가 틀렸습니다."); } @Test void 틀린_비밀번호로_로그인_할_경우_예외를_던진다() { // given - AuthService authService = new AuthService(sessionManager); + AuthService authService = new AuthService(sessionManager, inMemoryUserRepository); + authService.register("gugu", "password", "hkkang@woowahan.com"); String account = "gugu"; String wrongPassword = "wrongPassword"; // expect assertThatThrownBy(() -> authService.login(account, wrongPassword)) - .isInstanceOf(IllegalArgumentException.class) + .isInstanceOf(AuthException.class) .hasMessage("존재하지 않는 계정이거나 비밀번호가 틀렸습니다."); } @Test void 회원가입한다() { // given - AuthService authService = new AuthService(sessionManager); + AuthService authService = new AuthService(sessionManager, inMemoryUserRepository); String account = "account"; String password = "password"; String email = "account@email.com"; @@ -74,4 +83,32 @@ void setUp() { // then assertThat(sessionId).isNotEmpty(); } + + @Test + void 회원가입할_때_계정이_중복될_경우_예외를_던진다() { + // given + AuthService authService = new AuthService(sessionManager, inMemoryUserRepository); + String duplicateAccount = "duplicateAccount"; + authService.register(duplicateAccount, "password", "account@email.com"); + + // expect + assertThatThrownBy(() -> authService.register(duplicateAccount, "password", "account@emali.com")) + .isInstanceOf(AuthException.class) + .hasMessage("중복된 계정 입니다."); + } + + @ParameterizedTest + @CsvSource({"notLoggedInId, false", "loggedInId, true"}) + void session_Id로_이미_로그인이_되었는지_확인한다(String sessionId, boolean expected) { + // given + AuthService authService = new AuthService(sessionManager, inMemoryUserRepository); + Session session = new Session("loggedInId"); + sessionManager.add(session); + + // when + boolean actual = authService.isLoggedIn(sessionId); + + // then + assertThat(actual).isEqualTo(expected); + } } diff --git a/tomcat/src/test/java/org/apache/MultiThreadsTest.java b/tomcat/src/test/java/org/apache/MultiThreadsTest.java new file mode 100644 index 0000000000..20ad28ab44 --- /dev/null +++ b/tomcat/src/test/java/org/apache/MultiThreadsTest.java @@ -0,0 +1,30 @@ +package org.apache; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class MultiThreadsTest { + + @Test + void test() throws Exception { + int numThreads = 10; + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + + for (int i = 0; i < numThreads; i++) { + executorService.submit(() -> TestHttpUtils.sendRegister( + "huchu", + "huchu123", + "huchu@naver.com" + )); + } + + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + } +} diff --git a/tomcat/src/test/java/org/apache/TestHttpUtils.java b/tomcat/src/test/java/org/apache/TestHttpUtils.java new file mode 100644 index 0000000000..2aa09c6a79 --- /dev/null +++ b/tomcat/src/test/java/org/apache/TestHttpUtils.java @@ -0,0 +1,30 @@ +package org.apache; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; + +public class TestHttpUtils { + + private static final HttpClient httpClient = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1) + .build(); + + public static HttpResponse sendRegister(String account, String password, String email) { + String body = String.format("account=%s&password=%s&email=%s", account, password, email); + + final var request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/register")) + .POST(BodyPublishers.ofString(body)) + .build(); + + try { + return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/tomcat/src/test/java/org/apache/catalina/RequestMappingTest.java b/tomcat/src/test/java/org/apache/catalina/RequestMappingTest.java new file mode 100644 index 0000000000..36c97ba51d --- /dev/null +++ b/tomcat/src/test/java/org/apache/catalina/RequestMappingTest.java @@ -0,0 +1,75 @@ +package org.apache.catalina; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import nextstep.jwp.controller.Controller; +import nextstep.jwp.controller.ExceptionHandler; +import nextstep.jwp.exception.UncheckedServletException; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class RequestMappingTest { + + @Mock + Controller controller; + + @Mock + HttpRequest mockHttpRequest; + + @Mock + ExceptionHandler exceptionHandler; + + @Mock + HttpResponse mockHttpResponse; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void 요청에_대한_응답을_처리한다() { + // given + RequestMapping requestMapping = new RequestMapping(List.of(controller), exceptionHandler); + + when(controller.canProcess(any())) + .thenReturn(true); + when(mockHttpResponse.isDetermined()) + .thenReturn(true); + + // when + requestMapping.process(mockHttpRequest, mockHttpResponse); + + // then + verify(controller, times(1)).service(mockHttpRequest, mockHttpResponse); + } + + @Test + void 요청에_대한_응답이_완성되지_않았을_경우_Exception_Handler가_동작한다() { + // given + RequestMapping requestMapping = new RequestMapping(List.of(controller), exceptionHandler); + + when(controller.canProcess(any())) + .thenReturn(false); + when(mockHttpResponse.isDetermined()) + .thenReturn(false); + + // when + requestMapping.process(mockHttpRequest, mockHttpResponse); + + // then + verify(exceptionHandler, times(1)).handle(any(HttpResponse.class), any(UncheckedServletException.class)); + } +} diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java similarity index 73% rename from tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java rename to tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java index 108040fb44..78b4ffcb9e 100644 --- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java @@ -1,4 +1,4 @@ -package nextstep.org.apache.coyote.http11; +package org.apache.coyote.http11; import static org.assertj.core.api.Assertions.assertThat; @@ -6,9 +6,9 @@ import java.io.IOException; import java.net.URL; import java.nio.file.Files; -import org.apache.coyote.http11.Http11Processor; -import org.apache.coyote.http11.Session; -import org.apache.coyote.http11.SessionManager; +import org.apache.catalina.RequestMapping; +import org.apache.catalina.Session; +import org.apache.catalina.SessionManager; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ class Http11ProcessorTest { void process() { // given final var socket = new StubSocket(); - final var processor = new Http11Processor(socket, new SessionManager()); + final var processor = new Http11Processor(socket, RequestMapping.init()); // when processor.process(socket); @@ -51,7 +51,7 @@ void index() throws IOException { ""); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket, new SessionManager()); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); // when processor.process(socket); @@ -90,7 +90,7 @@ void index() throws IOException { "sec-ch-ua-platform: \"macOS\"" ); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket, new SessionManager()); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); // when processor.process(socket); @@ -107,7 +107,7 @@ void index() throws IOException { } @Test - void 회원가입_요청을_처리한다() throws IOException { + void 회원가입_요청을_처리한다() { // given String httpRequest = String.join( System.lineSeparator(), @@ -132,24 +132,24 @@ void index() throws IOException { "sec-ch-ua-mobile: ?0", "sec-ch-ua-platform: \"macOS\"", "", - "account=gugu&password=password&email=hkkang%40woowahan.com" + "account=huchu&password=huchu123&email=huchu@naver.com" ); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket, new SessionManager()); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); // when processor.process(socket); // then - assertThat(socket.output()).containsAnyOf( + assertThat(socket.output()).contains( "HTTP/1.1 302 Found ", "Location: /index.html", - "Set-Cookie: JESSIONID=" + "Set-Cookie: JSESSIONID=" ); } @Test - void 로그인_POST_요청을_처리한다() throws IOException { + void 로그인_POST_요청을_처리한다() { // given String httpRequest = String.join( System.lineSeparator(), @@ -178,16 +178,16 @@ void index() throws IOException { "account=gugu&password=password" ); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket, new SessionManager()); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); // when processor.process(socket); // then - assertThat(socket.output()).containsAnyOf( + assertThat(socket.output()).contains( "HTTP/1.1 302 Found ", "Location: /index.html", - "Set-Cookie: JESSIONID=" + "Set-Cookie: JSESSIONID=" ); } @@ -221,7 +221,7 @@ void index() throws IOException { "account=none&password=password" ); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket, new SessionManager()); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); // when processor.process(socket); @@ -242,7 +242,7 @@ void index() throws IOException { // given String sessionId = "9cd99d8d-040c-496d-b54f-d580fed04bd3"; Session session = new Session(sessionId); - SessionManager sessionManager = new SessionManager(); + SessionManager sessionManager = SessionManager.getInstance(); sessionManager.add(session); String httpRequest = String.join( @@ -257,15 +257,75 @@ void index() throws IOException { "Cookie: JSESSIONID=" + sessionId ); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket, sessionManager); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); // when processor.process(socket); // then - assertThat(socket.output()).containsAnyOf( + assertThat(socket.output()).contains( "HTTP/1.1 302 Found ", "Location: /index.html" ); } + + @Test + void 지원하지_않는_Http_Method_인_경우_404를_반환한다() throws IOException { + String wrongHttpMethod = "DELETE"; + String httpRequest = String.join( + System.lineSeparator(), + wrongHttpMethod + " /login HTTP/1.1", + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "Accept-Encoding: gzip, deflate, br", + "Accept-Language: ko-KR,ko;q=0.9", + "Cache-Control: max-age=0", + "Connection: keep-alive", + "Content-Type: application/x-www-form-urlencoded" + ); + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/404.html"); + var expected = "HTTP/1.1 404 Not Found " + System.lineSeparator() + + "Content-Type: text/html;charset=utf-8 " + System.lineSeparator() + + "Content-Length: 2426 " + System.lineSeparator() + + System.lineSeparator() + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } + + @Test + void 지원하지_않는_uri_인_경우_404를_반환한다() throws IOException { + String wrongUri = "/huchu"; + String httpRequest = String.join( + System.lineSeparator(), + "GET " + wrongUri + " HTTP/1.1", + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "Accept-Encoding: gzip, deflate, br", + "Accept-Language: ko-KR,ko;q=0.9", + "Cache-Control: max-age=0", + "Connection: keep-alive", + "Content-Type: application/x-www-form-urlencoded" + ); + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket, RequestMapping.init()); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/404.html"); + var expected = "HTTP/1.1 404 Not Found " + System.lineSeparator() + + "Content-Type: text/html;charset=utf-8 " + System.lineSeparator() + + "Content-Length: 2426 " + System.lineSeparator() + + System.lineSeparator() + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseLineTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseLineTest.java deleted file mode 100644 index 6737f32e9b..0000000000 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseLineTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.apache.coyote.http11.response; - -import static org.apache.coyote.http11.response.line.ResponseStatus.OK; -import static org.assertj.core.api.Assertions.assertThat; - -import org.apache.coyote.http11.response.line.ResponseLine; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class ResponseLineTest { - - @Test - void ResponseLine을_문자열_메시지로_만든다() { - // given - ResponseLine responseLine = new ResponseLine("HTTP/1.1", OK); - - // when - String message = responseLine.responseLineMessage(); - - // then - assertThat(message).isEqualTo("HTTP/1.1 200 OK"); - } -} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java b/tomcat/src/test/java/org/apache/coyote/request/HttpCookieTest.java similarity index 63% rename from tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java rename to tomcat/src/test/java/org/apache/coyote/request/HttpCookieTest.java index e4649bdfe4..f83f8b0b4a 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/HttpCookieTest.java @@ -1,10 +1,10 @@ -package org.apache.coyote.http11; +package org.apache.coyote.request; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; import java.util.Map; -import org.apache.coyote.http11.request.header.HttpCookie; +import org.apache.coyote.request.header.HttpCookie; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -37,9 +37,35 @@ class HttpCookieTest { HttpCookie cookie = HttpCookie.from(httpCookie); // when - boolean actual = cookie.containsCookie("JSESSIONID"); + boolean actual = cookie.contains("JSESSIONID"); // then assertThat(actual).isTrue(); } + + @Test + void session_Id를_꺼낸다() { + // given + String httpCookie = "JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46 "; + HttpCookie cookie = HttpCookie.from(httpCookie); + + // when + String sessionId = cookie.get("JSESSIONID"); + + // then + assertThat(sessionId).isEqualTo("656cef62-e3c4-40bc-a8df-94732920ed46"); + } + + @Test + void session_Id를2_꺼낸다() { + // given + String httpCookie = "JSESSIONIDs=656cef62-e3c4-40bc-a8df-94732920ed46 "; + HttpCookie cookie = HttpCookie.from(httpCookie); + + // when + String sessionId = cookie.get("JSESSIONID"); + + // then + assertThat(sessionId).isNull(); + } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestReaderTest.java b/tomcat/src/test/java/org/apache/coyote/request/HttpRequestReaderTest.java similarity index 84% rename from tomcat/src/test/java/org/apache/coyote/http11/HttpRequestReaderTest.java rename to tomcat/src/test/java/org/apache/coyote/request/HttpRequestReaderTest.java index 1233cccfdf..2ccd85df46 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestReaderTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/HttpRequestReaderTest.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.request; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -9,6 +9,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -17,6 +18,13 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class HttpRequestReaderTest { + private HttpRequestReader httpRequestReader; + + @BeforeEach + void setUp() { + httpRequestReader = new HttpRequestReader(); + } + @Test void 줄을_읽는다() { // given @@ -28,7 +36,7 @@ class HttpRequestReaderTest { ) { // when - String requestLine = HttpRequestReader.readLine(reader); + String requestLine = httpRequestReader.readLine(reader); // then assertThat(requestLine).isEqualTo("GET /index.html HTTP/1.1 "); @@ -52,7 +60,7 @@ class HttpRequestReaderTest { ) { // when - String requestLineAndHeader = HttpRequestReader.readHeader(reader); + String requestLineAndHeader = httpRequestReader.readHeader(reader); // then assertThat(requestLineAndHeader).isEqualTo(String.join(System.lineSeparator(), @@ -79,9 +87,9 @@ class HttpRequestReaderTest { ) { // when - String requestLine = HttpRequestReader.readLine(reader); - String requestHeader = HttpRequestReader.readHeader(reader); - String requestBody = HttpRequestReader.readHeader(reader); + String requestLine = httpRequestReader.readLine(reader); + String requestHeader = httpRequestReader.readHeader(reader); + String requestBody = httpRequestReader.readHeader(reader); // then assertSoftly(softly -> { @@ -105,13 +113,13 @@ class HttpRequestReaderTest { InputStream inputStream = new ByteArrayInputStream(request.getBytes()); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)) ) { - HttpRequestReader.readLine(reader); + httpRequestReader.readLine(reader); // when - String paragraph = HttpRequestReader.readHeader(reader); + String paragraph = httpRequestReader.readHeader(reader); // then - assertThat(paragraph).isEqualTo(""); + assertThat(paragraph).isEmpty(); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/HttpRequestTest.java b/tomcat/src/test/java/org/apache/coyote/request/HttpRequestTest.java similarity index 71% rename from tomcat/src/test/java/org/apache/coyote/http11/request/HttpRequestTest.java rename to tomcat/src/test/java/org/apache/coyote/request/HttpRequestTest.java index 4b5a5cfb54..e1c557bf63 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/request/HttpRequestTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/HttpRequestTest.java @@ -1,14 +1,17 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.request; -import static org.apache.coyote.http11.request.line.HttpMethod.POST; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; -import org.apache.coyote.http11.request.header.RequestHeader; -import org.apache.coyote.http11.request.line.RequestLine; +import org.apache.coyote.request.header.RequestHeader; +import org.apache.coyote.request.line.HttpMethod; +import org.apache.coyote.request.line.RequestLine; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @@ -26,14 +29,17 @@ class HttpRequestTest { HttpRequest request = HttpRequest.of(requestLine, requestHeader); // then - assertThat(request).isEqualTo(new HttpRequest - ( - RequestLine.from("GET /index.html HTTP/1.1 "), - RequestHeader.from(String.join(System.lineSeparator(), - "Host: localhost:8080 ", - "Connection: keep-alive ")) - ) - ); + assertSoftly(softly -> { + softly.assertThat(request.requestHeader()).isEqualTo(RequestHeader.from( + String.join( + System.lineSeparator(), + "Host: localhost:8080 ", + "Connection: keep-alive ") + )); + softly.assertThat(request.requestLine()).isEqualTo( + RequestLine.from("GET /index.html HTTP/1.1 ") + ); + }); } @Nested @@ -107,7 +113,7 @@ class 요청에_body가_있는지_확인한다 { } @Test - void Request_Line의_구성을_확인한다() { + void Request_Line의_구성을_URI로_확인한다() { // given String requestLine = "POST /login HTTP/1.1 "; String requestHeader = String.join(System.lineSeparator(), @@ -117,12 +123,30 @@ class 요청에_body가_있는지_확인한다 { HttpRequest request = HttpRequest.of(requestLine, requestHeader); // when - boolean actual = request.consistsOf(POST, "/login", "/login.html"); + boolean actual = request.consistsOf("/login"); // then assertThat(actual).isTrue(); } + @ParameterizedTest + @CsvSource({"GET, false", "POST, true"}) + void Request_Line의_구성을_Http_Method로_확인한다(HttpMethod httpMethod, boolean expected) { + // given + String requestLine = "POST /login HTTP/1.1 "; + String requestHeader = String.join(System.lineSeparator(), + "Host: localhost:8080 ", + "Connection: keep-alive ", + "Content-Length: 10 "); + HttpRequest request = HttpRequest.of(requestLine, requestHeader); + + // when + boolean actual = request.consistsOf(httpMethod); + + // then + assertThat(actual).isEqualTo(expected); + } + @Test void Request_Line에_Query_String_이_있는지_확인한다() { // given @@ -179,10 +203,18 @@ class 요청에_body가_있는지_확인한다 { @Test void Session_Id를_얻는다() { // given + String requestLine = "POST /login HTTP/1.1 "; + String requestHeader = String.join(System.lineSeparator(), + "Host: localhost:8080 ", + "Connection: keep-alive ", + "Content-Length: 10 ", + "Cookie: yummy_cookie=choco; tasty_cookie=strawberry; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46 "); + HttpRequest request = HttpRequest.of(requestLine, requestHeader); // when + String sessionId = request.sessionId(); // then - + assertThat(sessionId).isEqualTo("656cef62-e3c4-40bc-a8df-94732920ed46"); } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestBodyTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java similarity index 63% rename from tomcat/src/test/java/org/apache/coyote/http11/request/RequestBodyTest.java rename to tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java index 0d895d8c34..302f30baa2 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestBodyTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java @@ -1,9 +1,10 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.request; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Map; -import org.apache.coyote.http11.request.body.RequestBody; +import org.apache.coyote.request.body.RequestBody; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -21,13 +22,13 @@ class RequestBodyTest { RequestBody body = RequestBody.from(requestBody); // then - assertThat(body).isEqualTo(new RequestBody( + assertThat(body.parametersMap()).isEqualTo( Map.of( "account", "gugu", "password", "password", "email", "hkkang@woowahan.com" ) - )); + ); } @Test @@ -42,4 +43,16 @@ class RequestBodyTest { // then assertThat(value).isEqualTo("gugu"); } + + @Test + void body_field_이름을_입력하고_value를_얻을_때_value가_존재하지_않으면_예외를_던진다() { + // given + String requestBody = "account=gugu"; + RequestBody body = RequestBody.from(requestBody); + + // expect + assertThatThrownBy(() -> body.get("wrongField")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("body에 존재하지 않는 field 입니다."); + } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestHeaderTest.java similarity index 85% rename from tomcat/src/test/java/org/apache/coyote/http11/request/RequestHeaderTest.java rename to tomcat/src/test/java/org/apache/coyote/request/RequestHeaderTest.java index b0b689c450..2544ae7197 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestHeaderTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestHeaderTest.java @@ -1,10 +1,9 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.request; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Map; -import org.apache.coyote.http11.request.header.RequestHeader; +import org.apache.coyote.request.header.RequestHeader; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; @@ -28,12 +27,12 @@ class RequestHeaderTest { RequestHeader header = RequestHeader.from(requestHeader); // then - assertThat(header).isEqualTo(new RequestHeader( + assertThat(header.headersMap()).isEqualTo( Map.of( "Host", "localhost:8080", "Connection", "keep-alive" ) - )); + ); } @Test @@ -154,19 +153,4 @@ class header로_요청에_Content가_있는지_확인한다 { // then assertThat(sessionId).isEqualTo("656cef62-e3c4-40bc-a8df-94732920ed46"); } - - @Test - void Session_Id를_얻을_때_Session_Id가_없는_경우_예외를_던진다() { - // given - String requestHeader = String.join(System.lineSeparator(), - "Host: localhost:8080 ", - "Connection: keep-alive ", - "Content-Length: 10 "); - RequestHeader header = RequestHeader.from(requestHeader); - - // expect - assertThatThrownBy(header::sessionId) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("세션 Id가 존재하지 않습니다."); - } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestLineTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java similarity index 56% rename from tomcat/src/test/java/org/apache/coyote/http11/request/RequestLineTest.java rename to tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java index 71f4bde771..db897304ea 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestLineTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java @@ -1,13 +1,15 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.request; -import static org.apache.coyote.http11.request.line.HttpMethod.GET; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; -import org.apache.coyote.http11.request.line.RequestLine; -import org.apache.coyote.http11.request.line.RequestUri; +import org.apache.coyote.request.line.HttpMethod; +import org.apache.coyote.request.line.RequestLine; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @@ -22,24 +24,40 @@ class RequestLineTest { RequestLine line = RequestLine.from(requestLine); // then - assertThat(line).isEqualTo( - new RequestLine(GET, RequestUri.from("/index.html"), "HTTP/1.1") - ); + assertSoftly(softly -> { + softly.assertThat(line.requestUri()).isEqualTo("/index.html"); + softly.assertThat(line.httpMethod()).isEqualTo(HttpMethod.GET); + softly.assertThat(line.httpVersion()).isEqualTo("HTTP/1.1"); + }); } @Test - void Request_Line의_구성을_확인한다() { + void Request_Line의_구성을_Http_Method와_URI로_확인한다() { // given String requestLine = "GET /index.html HTTP/1.1 "; RequestLine line = RequestLine.from(requestLine); // when - boolean actual = line.consistsOf(GET, "/index.html"); + boolean actual = line.consistsOf(HttpMethod.GET, "/index.html"); // then assertThat(actual).isTrue(); } + @ParameterizedTest + @CsvSource({"GET, true", "POST, false"}) + void Request_Line의_구성을_Http_Method로_확인한다(HttpMethod httpMethod, boolean expected) { + // given + String requestLine = "GET /index.html HTTP/1.1 "; + RequestLine line = RequestLine.from(requestLine); + + // when + boolean actual = line.consistsOf(httpMethod); + + // then + assertThat(actual).isEqualTo(expected); + } + @Test void Request_Line에_Query_String이_있는지_확인한다() { // given diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestUriTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestUriTest.java similarity index 93% rename from tomcat/src/test/java/org/apache/coyote/http11/request/RequestUriTest.java rename to tomcat/src/test/java/org/apache/coyote/request/RequestUriTest.java index fba9aa38e9..0dbf72930c 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestUriTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestUriTest.java @@ -1,8 +1,8 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.request; import static org.assertj.core.api.Assertions.assertThat; -import org.apache.coyote.http11.request.line.RequestUri; +import org.apache.coyote.request.line.RequestUri; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpContentTypeTest.java b/tomcat/src/test/java/org/apache/coyote/response/HttpContentTypeTest.java similarity index 91% rename from tomcat/src/test/java/org/apache/coyote/http11/response/HttpContentTypeTest.java rename to tomcat/src/test/java/org/apache/coyote/response/HttpContentTypeTest.java index 932449dc79..12622c9d89 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpContentTypeTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/HttpContentTypeTest.java @@ -1,9 +1,9 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.response; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; -import org.apache.coyote.http11.response.header.HttpContentType; +import org.apache.coyote.response.header.HttpContentType; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java b/tomcat/src/test/java/org/apache/coyote/response/HttpResponseTest.java similarity index 53% rename from tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java rename to tomcat/src/test/java/org/apache/coyote/response/HttpResponseTest.java index 83eb0237f6..4bae17e83c 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/HttpResponseTest.java @@ -1,10 +1,11 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.response; -import static org.apache.coyote.http11.response.line.ResponseStatus.OK; +import static common.ResponseStatus.OK; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @SuppressWarnings("NonAsciiCharacters") @@ -32,4 +33,33 @@ class HttpResponseTest { ) ); } + + @Nested + class Http_Response가_결정되었는지_확인한다 { + + @Test + void 결정되지_않았을_때() { + // given + HttpResponse httpResponse = new HttpResponse("HTTP/1.1"); + + // when + boolean actual = httpResponse.isDetermined(); + + // then + assertThat(actual).isFalse(); + } + + @Test + void 결정되었을_때() { + // given + HttpResponse httpResponse = new HttpResponse("HTTP/1.1"); + httpResponse.setResponseMessage(OK, "Hello world!"); + + // when + boolean actual = httpResponse.isDetermined(); + + // then + assertThat(actual).isTrue(); + } + } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseBodyTest.java b/tomcat/src/test/java/org/apache/coyote/response/ResponseBodyTest.java similarity index 85% rename from tomcat/src/test/java/org/apache/coyote/http11/response/ResponseBodyTest.java rename to tomcat/src/test/java/org/apache/coyote/response/ResponseBodyTest.java index f88768b53e..62e185bfac 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseBodyTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/ResponseBodyTest.java @@ -1,8 +1,8 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.response; import static org.assertj.core.api.Assertions.assertThat; -import org.apache.coyote.http11.response.body.ResponseBody; +import org.apache.coyote.response.body.ResponseBody; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/response/ResponseHeaderTest.java similarity index 68% rename from tomcat/src/test/java/org/apache/coyote/http11/response/ResponseHeaderTest.java rename to tomcat/src/test/java/org/apache/coyote/response/ResponseHeaderTest.java index 7340279bc4..9ccee919f1 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseHeaderTest.java +++ b/tomcat/src/test/java/org/apache/coyote/response/ResponseHeaderTest.java @@ -1,14 +1,14 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.response; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.coyote.http11.response.header.HttpContentType.TEXT_HTML; -import static org.apache.coyote.http11.response.header.HttpHeader.CONTENT_LENGTH; -import static org.apache.coyote.http11.response.header.HttpHeader.CONTENT_TYPE; +import static org.apache.coyote.response.header.HttpHeader.CONTENT_LENGTH; +import static org.apache.coyote.response.header.HttpHeader.CONTENT_TYPE; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import java.util.Map; -import org.apache.coyote.http11.response.header.ResponseHeader; +import org.apache.coyote.response.header.HttpContentType; +import org.apache.coyote.response.header.ResponseHeader; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ class ResponseHeaderTest { // given ResponseHeader responseHeader = new ResponseHeader( Map.of( - CONTENT_TYPE, TEXT_HTML.mimeTypeWithCharset(UTF_8), + CONTENT_TYPE, HttpContentType.TEXT_HTML.mimeTypeWithCharset(UTF_8), CONTENT_LENGTH, "5" ) ); diff --git a/tomcat/src/test/java/org/apache/coyote/response/ResponseLineTest.java b/tomcat/src/test/java/org/apache/coyote/response/ResponseLineTest.java new file mode 100644 index 0000000000..b745d7c9e8 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/response/ResponseLineTest.java @@ -0,0 +1,42 @@ +package org.apache.coyote.response; + +import static common.ResponseStatus.OK; +import static org.assertj.core.api.Assertions.assertThat; + +import common.ResponseStatus; +import org.apache.coyote.response.line.ResponseLine; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ResponseLineTest { + + @Test + void ResponseLine을_문자열_메시지로_만든다() { + // given + ResponseLine responseLine = new ResponseLine("HTTP/1.1", OK); + + // when + String message = responseLine.responseLineMessage(); + + // then + assertThat(message).isEqualTo("HTTP/1.1 200 OK"); + } + + @ParameterizedTest + @CsvSource({"OK, false", "EMPTY_RESPONSE_STATUS, true"}) + void Response_Status가_비어있는지_확인한다(ResponseStatus responseStatus, boolean expected) { + // given + ResponseLine responseLine = new ResponseLine("HTTP/1.1", responseStatus); + + // when + boolean actual = responseLine.hasEmptyResponseStatus(); + + // then + assertThat(actual).isEqualTo(expected); + } +}