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:
- *
- * - Must implement
Lifecycle
so that the Context can indicate
- * that a restart is required.
- * - Must allow a call to
stop()
to be followed by a call to
- * start()
on the same Manager
instance.
- *
- *
- * @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);
+ }
+}