diff --git a/README.md b/README.md
index b24f542e33..44611aad18 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,24 @@
# 톰캣 구현하기
+
+## 요구사항
+
+### 1단계
+
+- [x] GET /index.html 요청에 대한 응답을 반환한다.
+- [x] CSS를 지원한다.
+- [x] Query String을 파싱한다.
+ - [x] 아이디, 비밀번호가 일치하면 콘솔창에 로그로 회원을 조회한 결과를 출력한다.
+
+### 2단계
+
+- [x] 로그인 여부에 따라 다른 페이지로 이동시킨다.
+ - [x] 로그인에 성공하면 응답 헤더에 http status code를 302로 반환하고 /index.html로 리다이렉트 한다.
+ - [x] 로그인에 실패하면 401.html로 리다이렉트한다.
+- [x] POST 방식으로 회원가입을 한다.
+ - [x] 회원가입 페이지는 GET으로 요청한다.
+ - [x] 회원가입을 완료하면 index.html로 리다이렉트한다.
+ - [x] 로그인 페이지도 버튼을 눌렀을 때 POST 방식으로 전송하도록 변경한다.
+- [ ] 서버에서 HTTP 응답을 전달할 때 응답 헤더에 Set-Cookie를 추가한다.
+ - [ ] Cookie에 JSESSIONID가 없으면 응답 헤더에 Set-Cookie를 반환해준다.
+- [ ] 쿠키에서 전달 받은 JSESSIONID의 값으로 로그인 여부를 체크한다.
+ - [ ] 로그인된 상태에서 /login 페이지에 접근하면 index.html 페이지로 리다이렉트 처리한다.
diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java
index e1b6cca042..c051623cfc 100644
--- a/study/src/test/java/study/FileTest.java
+++ b/study/src/test/java/study/FileTest.java
@@ -1,53 +1,54 @@
package study;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collections;
+import java.nio.file.Paths;
import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
/**
- * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
- * File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
+ * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
*/
@DisplayName("File 클래스 학습 테스트")
class FileTest {
/**
* resource 디렉터리 경로 찾기
- *
- * File 객체를 생성하려면 파일의 경로를 알아야 한다.
- * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다.
- * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까?
+ *
+ * File 객체를 생성하려면 파일의 경로를 알아야 한다. 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. resource 디렉터리의 경로는 어떻게 알아낼 수
+ * 있을까?
*/
@Test
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
final String fileName = "nextstep.txt";
- // todo
- final String actual = "";
+ final ClassLoader classLoader = getClass().getClassLoader();
+ final URL resource = classLoader.getResource(fileName);
+ final String actual = resource.getFile();
assertThat(actual).endsWith(fileName);
}
/**
* 파일 내용 읽기
- *
- * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
- * File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
+ *
+ * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
- void 파일의_내용을_읽는다() {
+ void 파일의_내용을_읽는다() throws IOException {
final String fileName = "nextstep.txt";
- // todo
- final Path path = null;
+ final ClassLoader classLoader = getClass().getClassLoader();
+ final URL resource = classLoader.getResource(fileName);
+ final String resourceFile = resource.getFile();
- // todo
- final List actual = Collections.emptyList();
+ final Path path = Paths.get(resourceFile);
+ final List actual = Files.readAllLines(path);
assertThat(actual).containsOnly("nextstep");
}
diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java
index 47a79356b6..39f1f0ac85 100644
--- a/study/src/test/java/study/IOStreamTest.java
+++ b/study/src/test/java/study/IOStreamTest.java
@@ -1,45 +1,50 @@
package study;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import java.io.*;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
/**
- * 자바는 스트림(Stream)으로부터 I/O를 사용한다.
- * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
- *
- * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다.
- * FilterStream은 InputStream이나 OutputStream에 연결될 수 있다.
- * FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환)
- *
- * Stream은 데이터를 바이트로 읽고 쓴다.
- * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다.
- * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다.
+ * 자바는 스트림(Stream)으로부터 I/O를 사용한다. 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
+ *
+ * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. FilterStream은 읽거나 쓰는
+ * 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환)
+ *
+ * Stream은 데이터를 바이트로 읽고 쓴다. 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을
+ * 처리할 수 있다.
*/
@DisplayName("Java I/O Stream 클래스 학습 테스트")
class IOStreamTest {
/**
* OutputStream 학습하기
- *
- * 자바의 기본 출력 클래스는 java.io.OutputStream이다.
- * OutputStream의 write(int b) 메서드는 기반 메서드이다.
+ *
+ * 자바의 기본 출력 클래스는 java.io.OutputStream이다. OutputStream의 write(int b) 메서드는 기반 메서드이다.
* public abstract void write(int b) throws IOException;
*/
@Nested
class OutputStream_학습_테스트 {
/**
- * OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다.
- * OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.
- * 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
- * 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.
- *
+ * OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다. OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를
+ * 사용한다. 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때, 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때
+ * 사용한다.
+ *
* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다.
* write(byte[] data)와 write(byte b[], int off, int len) 메서드는
* 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
@@ -53,6 +58,7 @@ class OutputStream_학습_테스트 {
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
+ outputStream.write(bytes, 0, bytes.length);
final String actual = outputStream.toString();
@@ -61,13 +67,10 @@ class OutputStream_학습_테스트 {
}
/**
- * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
- * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
- *
- * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
- * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
- * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
- * 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다.
+ * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
+ *
+ * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. Stream은
+ * 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다.
*/
@Test
void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException {
@@ -78,14 +81,14 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
+ outputStream.flush();
verify(outputStream, atLeastOnce()).flush();
outputStream.close();
}
/**
- * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
- * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
+ * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
*/
@Test
void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
@@ -96,6 +99,8 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ try (outputStream) {
+ }
verify(outputStream, atLeastOnce()).close();
}
@@ -103,32 +108,30 @@ class OutputStream_학습_테스트 {
/**
* InputStream 학습하기
- *
- * 자바의 기본 입력 클래스는 java.io.InputStream이다.
- * InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
- * InputStream의 read() 메서드는 기반 메서드이다.
+ *
+ * 자바의 기본 입력 클래스는 java.io.InputStream이다. InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다. InputStream의 read() 메서드는 기반
+ * 메서드이다.
* public abstract int read() throws IOException;
- *
+ *
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
*/
@Nested
class InputStream_학습_테스트 {
/**
- * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다.
- * int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다.
+ * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다.
* 그리고 Stream 끝에 도달하면 -1을 반환한다.
*/
@Test
void InputStream은_데이터를_바이트로_읽는다() throws IOException {
- byte[] bytes = {-16, -97, -92, -87};
+ final byte[] bytes = {-16, -97, -92, -87};
final InputStream inputStream = new ByteArrayInputStream(bytes);
/**
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
- final String actual = "";
+ final String actual = new String(inputStream.readAllBytes());
assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
@@ -136,8 +139,7 @@ class InputStream_학습_테스트 {
}
/**
- * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
- * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
+ * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
*/
@Test
void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
@@ -148,6 +150,8 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ try (inputStream) {
+ }
verify(inputStream, atLeastOnce()).close();
}
@@ -155,26 +159,25 @@ class InputStream_학습_테스트 {
/**
* FilterStream 학습하기
- *
- * 필터는 필터 스트림, reader, writer로 나뉜다.
- * 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
- * reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.
+ *
+ * 필터는 필터 스트림, reader, writer로 나뉜다. 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된
+ * 텍스트를 처리하는 데 사용된다.
*/
@Nested
class FilterStream_학습_테스트 {
/**
- * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다.
- * InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다.
- * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
+ * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다. InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다. 버퍼 크기를 지정하지
+ * 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@Test
- void 필터인_BufferedInputStream를_사용해보자() {
+ void 필터인_BufferedInputStream를_사용해보자() throws IOException {
final String text = "필터에 연결해보자.";
final InputStream inputStream = new ByteArrayInputStream(text.getBytes());
- final InputStream bufferedInputStream = null;
+ final InputStream bufferedInputStream = new BufferedInputStream(inputStream);
- final byte[] actual = new byte[0];
+ final byte[] actual = new byte[text.getBytes().length];
+ bufferedInputStream.read(actual);
assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
@@ -182,30 +185,35 @@ class FilterStream_학습_테스트 {
}
/**
- * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다.
- * 문자열이 아닌 바이트 단위로 처리하려니 불편하다.
- * 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다.
- * reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다.
- * 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다.
+ * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. 문자열이 아닌 바이트 단위로 처리하려니 불편하다. 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. reader,
+ * writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다.
*/
@Nested
class InputStreamReader_학습_테스트 {
/**
- * InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다.
- * 읽어온 문자(char)를 문자열(String)로 처리하자.
- * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
+ * InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다. 읽어온 문자(char)를 문자열(String)로 처리하자. 필터인 BufferedReader를 사용하면
+ * readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
*/
@Test
- void BufferedReader를_사용하여_문자열을_읽어온다() {
+ void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException {
final String emoji = String.join("\r\n",
"😀😃😄😁😆😅😂🤣🥲☺️😊",
"😇🙂🙃😉😌😍🥰😘😗😙😚",
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
+ final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
+ final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
final StringBuilder actual = new StringBuilder();
+ while (true) {
+ final String readLine = bufferedReader.readLine();
+ if (readLine == null) {
+ break;
+ }
+ actual.append(readLine).append("\r\n");
+ }
assertThat(actual).hasToString(emoji);
}
diff --git a/tomcat/src/main/java/handler/Controller.java b/tomcat/src/main/java/handler/Controller.java
new file mode 100644
index 0000000000..366a044f40
--- /dev/null
+++ b/tomcat/src/main/java/handler/Controller.java
@@ -0,0 +1,9 @@
+package handler;
+
+import org.apache.coyote.http11.HttpRequest;
+import org.apache.coyote.http11.HttpResponse;
+
+public interface Controller {
+
+ String run(final HttpRequest httpRequest, final HttpResponse httpResponse);
+}
diff --git a/tomcat/src/main/java/handler/RequestHandler.java b/tomcat/src/main/java/handler/RequestHandler.java
new file mode 100644
index 0000000000..758e1f48a3
--- /dev/null
+++ b/tomcat/src/main/java/handler/RequestHandler.java
@@ -0,0 +1,6 @@
+package handler;
+
+public interface RequestHandler {
+
+ Controller getHandler(final String requestUri);
+}
diff --git a/tomcat/src/main/java/handler/RequestHandlerMapping.java b/tomcat/src/main/java/handler/RequestHandlerMapping.java
new file mode 100644
index 0000000000..8bf5931ad8
--- /dev/null
+++ b/tomcat/src/main/java/handler/RequestHandlerMapping.java
@@ -0,0 +1,23 @@
+package handler;
+
+import java.util.HashMap;
+import java.util.Map;
+import nextstep.jwp.controller.IndexController;
+import nextstep.jwp.controller.LoginController;
+import nextstep.jwp.controller.RegisterController;
+
+public class RequestHandlerMapping implements RequestHandler {
+
+ private final Map handlerMapping = new HashMap<>();
+
+ public RequestHandlerMapping() {
+ handlerMapping.put("/", new IndexController());
+ handlerMapping.put("/login", new LoginController());
+ handlerMapping.put("/register", new RegisterController());
+ }
+
+ @Override
+ public Controller getHandler(final String requestUri) {
+ return handlerMapping.get(requestUri);
+ }
+}
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..7ec0c42496
--- /dev/null
+++ b/tomcat/src/main/java/nextstep/jwp/controller/IndexController.java
@@ -0,0 +1,15 @@
+package nextstep.jwp.controller;
+
+import handler.Controller;
+import org.apache.coyote.http11.HttpRequest;
+import org.apache.coyote.http11.HttpResponse;
+import org.apache.coyote.http11.HttpStatusCode;
+
+public class IndexController implements Controller {
+
+ @Override
+ public String run(final HttpRequest httpRequest, final HttpResponse httpResponse) {
+ httpResponse.setStatusCode(HttpStatusCode.OK);
+ return "/index.html";
+ }
+}
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..451acfbab7
--- /dev/null
+++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java
@@ -0,0 +1,40 @@
+package nextstep.jwp.controller;
+
+import handler.Controller;
+import java.util.Map;
+import nextstep.jwp.db.InMemoryUserRepository;
+import org.apache.coyote.http11.HttpMethod;
+import org.apache.coyote.http11.HttpRequest;
+import org.apache.coyote.http11.HttpResponse;
+import org.apache.coyote.http11.HttpStatusCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LoginController implements Controller {
+
+ private static final Logger log = LoggerFactory.getLogger(LoginController.class);
+
+ @Override
+ public String run(final HttpRequest httpRequest, final HttpResponse httpResponse) {
+ final HttpMethod method = httpRequest.getMethod();
+ if (method.equals(HttpMethod.GET)) {
+ httpResponse.setStatusCode(HttpStatusCode.OK);
+ return "/login.html";
+ }
+ final Map requestBody = httpRequest.getRequestBody();
+ final String account = requestBody.get("account");
+ final String password = requestBody.get("password");
+ final boolean isSuccess = InMemoryUserRepository.findByAccount(account)
+ .filter(user -> user.checkPassword(password))
+ .isPresent();
+ if (isSuccess) {
+ httpResponse.setStatusCode(HttpStatusCode.FOUND);
+ httpResponse.setHeader("Location", "/");
+ log.info("로그인 성공! 로그인 아이디: " + account);
+ return "/index.html";
+ }
+ httpResponse.setStatusCode(HttpStatusCode.FOUND);
+ httpResponse.setHeader("Location", "/401.html");
+ return "/401.html";
+ }
+}
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..55b03f6847
--- /dev/null
+++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java
@@ -0,0 +1,33 @@
+package nextstep.jwp.controller;
+
+import handler.Controller;
+import java.util.Map;
+import nextstep.jwp.db.InMemoryUserRepository;
+import nextstep.jwp.model.User;
+import org.apache.coyote.http11.HttpMethod;
+import org.apache.coyote.http11.HttpRequest;
+import org.apache.coyote.http11.HttpResponse;
+import org.apache.coyote.http11.HttpStatusCode;
+
+public class RegisterController implements Controller {
+
+ @Override
+ public String run(final HttpRequest httpRequest, final HttpResponse httpResponse) {
+ final HttpMethod method = httpRequest.getMethod();
+ if (method.equals(HttpMethod.GET)) {
+ httpResponse.setStatusCode(HttpStatusCode.OK);
+ return "/register.html";
+ }
+ if (method.equals(HttpMethod.POST)) {
+ final Map requestBody = httpRequest.getRequestBody();
+ final String account = requestBody.get("account");
+ final String password = requestBody.get("password");
+ final String email = requestBody.get("email");
+ InMemoryUserRepository.save(new User(account, password, email));
+ httpResponse.setStatusCode(HttpStatusCode.FOUND);
+ httpResponse.setHeader("Location", "/index.html");
+ return "/index.html";
+ }
+ return "/index.html";
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java
index 3b2c4dda7c..6e71945e45 100644
--- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java
+++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java
@@ -1,13 +1,12 @@
package org.apache.catalina.connector;
-import org.apache.coyote.http11.Http11Processor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
+import org.apache.coyote.http11.Http11Processor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class Connector implements Runnable {
@@ -33,13 +32,13 @@ private ServerSocket createServerSocket(final int port, final int acceptCount) {
final int checkedPort = checkPort(port);
final int checkedAcceptCount = checkAcceptCount(acceptCount);
return new ServerSocket(checkedPort, checkedAcceptCount);
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new UncheckedIOException(e);
}
}
public void start() {
- var thread = new Thread(this);
+ final var thread = new Thread(this);
thread.setDaemon(true);
thread.start();
stopped = false;
@@ -57,7 +56,7 @@ public void run() {
private void connect() {
try {
process(serverSocket.accept());
- } catch (IOException e) {
+ } catch (final IOException e) {
log.error(e.getMessage(), e);
}
}
@@ -66,7 +65,7 @@ private void process(final Socket connection) {
if (connection == null) {
return;
}
- var processor = new Http11Processor(connection);
+ final var processor = new Http11Processor(connection);
new Thread(processor).start();
}
@@ -74,7 +73,7 @@ public void stop() {
stopped = true;
try {
serverSocket.close();
- } catch (IOException e) {
+ } catch (final IOException e) {
log.error(e.getMessage(), e);
}
}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/BodyParser.java b/tomcat/src/main/java/org/apache/coyote/http11/BodyParser.java
new file mode 100644
index 0000000000..6ad8bc90dc
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/BodyParser.java
@@ -0,0 +1,8 @@
+package org.apache.coyote.http11;
+
+import java.util.Map;
+
+public interface BodyParser {
+
+ Map parse(final String body);
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/FormBodyParser.java b/tomcat/src/main/java/org/apache/coyote/http11/FormBodyParser.java
new file mode 100644
index 0000000000..df83c500b9
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/FormBodyParser.java
@@ -0,0 +1,23 @@
+package org.apache.coyote.http11;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class FormBodyParser implements BodyParser {
+
+ private static final String PARAMETER_DELIMITER = "&";
+ private static final String VALUE_DELIMITER = "=";
+
+ @Override
+ public Map parse(final String body) {
+ final Map parameters = new HashMap<>();
+ final String[] splitParameters = body.split(PARAMETER_DELIMITER);
+ for (final String splitParameter : splitParameters) {
+ final String[] parameter = splitParameter.split(VALUE_DELIMITER);
+ final String key = parameter[0];
+ final String value = parameter[1];
+ parameters.put(key, value);
+ }
+ return parameters;
+ }
+}
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 7f1b2c7e96..ddc4730fad 100644
--- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
+++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
@@ -1,16 +1,27 @@
package org.apache.coyote.http11;
-import nextstep.jwp.exception.UncheckedServletException;
-import org.apache.coyote.Processor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import static java.util.stream.Collectors.joining;
+import handler.Controller;
+import handler.RequestHandler;
+import handler.RequestHandlerMapping;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.net.Socket;
+import java.util.Map;
+import org.apache.coyote.Processor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class Http11Processor implements Runnable, Processor {
+ private static final String EXTENSION_DELIMITER = ".";
+
private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);
+ private static final HttpRequestParser httpRequestParser = new HttpRequestParser();
+ private static final RequestHandler requestHandler = new RequestHandlerMapping();
+ private static final ViewResolver viewResolver = new ViewResolver();
private final Socket connection;
@@ -26,22 +37,60 @@ public void run() {
@Override
public void process(final Socket connection) {
- try (final var inputStream = connection.getInputStream();
+ try (final var bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
final var outputStream = connection.getOutputStream()) {
+ final HttpRequest httpRequest = httpRequestParser.parse(bufferedReader);
+ final HttpResponse httpResponse = new HttpResponse();
- final var responseBody = "Hello world!";
+ final String viewName = getViewName(httpRequest, httpResponse);
+ final String responseBody = viewResolver.read(viewName);
- final var response = String.join("\r\n",
- "HTTP/1.1 200 OK ",
- "Content-Type: text/html;charset=utf-8 ",
- "Content-Length: " + responseBody.getBytes().length + " ",
- "",
- responseBody);
+ httpResponse.setHeader("Content-Type", httpRequest.getContentType());
+ httpResponse.setHeader("Content-Length", String.valueOf(responseBody.getBytes().length));
+ httpResponse.setResponseBody(responseBody);
+ final String response = makeResponseString(httpResponse);
outputStream.write(response.getBytes());
outputStream.flush();
- } catch (IOException | UncheckedServletException e) {
+ } catch (final IOException e) {
log.error(e.getMessage(), e);
}
}
+
+ private String getViewName(final HttpRequest httpRequest, final HttpResponse httpResponse) {
+ final String requestUri = httpRequest.getRequestUri();
+
+ if (isFileRequest(requestUri)) {
+ httpResponse.setStatusCode(HttpStatusCode.OK);
+ return requestUri;
+ }
+ final Controller controller = requestHandler.getHandler(requestUri);
+ return controller.run(httpRequest, httpResponse);
+ }
+
+ private boolean isFileRequest(final String requestUri) {
+ return requestUri.contains(EXTENSION_DELIMITER);
+ }
+
+ private String makeResponseString(final HttpResponse httpResponse) {
+ return String.join(System.lineSeparator(),
+ makeResponseCode(httpResponse),
+ makeResponseHeaders(httpResponse),
+ "",
+ httpResponse.getResponseBody());
+ }
+
+ private String makeResponseCode(final HttpResponse httpResponse) {
+ final int code = httpResponse.getStatusCode().getCode();
+ final String message = httpResponse.getStatusCode().getMessage();
+ return "HTTP/1.1 " + code + " " + message + " ";
+ }
+
+ private String makeResponseHeaders(final HttpResponse httpResponse) {
+ final Map headers = httpResponse.getHeaders();
+ return headers.entrySet()
+ .stream()
+ .map(entry -> entry.getKey() + ": " + entry.getValue() + " ")
+ .collect(joining(System.lineSeparator()));
+ }
}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java
new file mode 100644
index 0000000000..74f200c3d6
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java
@@ -0,0 +1,35 @@
+package org.apache.coyote.http11;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class HttpHeaders {
+
+ private static final String EMPTY_VALUE = "";
+ private static final String CONTENT_TYPE_DELIMITER = ",";
+
+ private final Map headers = new HashMap<>();
+
+ public HttpHeaders() {
+ }
+
+ public void add(final String key, final String value) {
+ headers.put(key, value);
+ }
+
+ public String get(final String headerName) {
+ return headers.getOrDefault(headerName, EMPTY_VALUE);
+ }
+
+ public String getContentType() {
+ String value = headers.getOrDefault("Content-Type", EMPTY_VALUE);
+ if (value.isEmpty()) {
+ value = headers.get("Accept");
+ }
+ return value.split(CONTENT_TYPE_DELIMITER)[0];
+ }
+
+ public Map getHeaders() {
+ return headers;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpMethod.java
new file mode 100644
index 0000000000..0036fc2671
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpMethod.java
@@ -0,0 +1,13 @@
+package org.apache.coyote.http11;
+
+public enum HttpMethod {
+ GET,
+ POST,
+ PUT,
+ PATCH,
+ DELETE,
+ OPTIONS,
+ HEAD,
+ CONNECT,
+ TRACE
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java
new file mode 100644
index 0000000000..29da5adfab
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java
@@ -0,0 +1,44 @@
+package org.apache.coyote.http11;
+
+import java.util.Map;
+
+public class HttpRequest {
+
+ private static final String EMPTY_VALUE = "";
+
+ private final HttpMethod method;
+ private final String requestUri;
+ private final QueryStrings queryStrings;
+ private final HttpHeaders headers;
+ private final Map requestBody;
+
+ public HttpRequest(final HttpMethod method, final String requestUri, final QueryStrings queryStrings,
+ final HttpHeaders headers, final Map requestBody) {
+ this.method = method;
+ this.requestUri = requestUri;
+ this.queryStrings = queryStrings;
+ this.headers = headers;
+ this.requestBody = requestBody;
+ }
+
+ public HttpMethod getMethod() {
+ return method;
+ }
+
+ public String getRequestUri() {
+ return requestUri;
+ }
+
+ public String getContentType() {
+ return headers.getContentType();
+ }
+
+ public String getParameter(final String key) {
+ final Map parameters = queryStrings.getQueryStrings();
+ return parameters.getOrDefault(key, EMPTY_VALUE);
+ }
+
+ public Map getRequestBody() {
+ return requestBody;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java
new file mode 100644
index 0000000000..a728441618
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java
@@ -0,0 +1,114 @@
+package org.apache.coyote.http11;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class HttpRequestParser {
+
+ private static final String REQUEST_LINE_DELIMITER = " ";
+ private static final int HTTP_METHOD_INDEX = 0;
+ private static final int REQUEST_URI_INDEX = 1;
+ private static final String QUERY_STRING_DELIMITER = "?";
+ private static final int NON_EXIST = -1;
+ private static final String QUERY_STRING_VALUE_DELIMITER = "&";
+ private static final String KEY_AND_VALUE_DELIMITER = "=";
+ private static final String EMPTY_LINE = "";
+ private static final String HEADER_DELIMITER = ": ";
+ private static final int EXIST_HEADER_VALUE = 2;
+ private static final int KEY_INDEX = 0;
+ private static final int VALUE_INDEX = 1;
+
+ private final Map bodyParsers = new HashMap<>();
+
+ public HttpRequestParser() {
+ init();
+ }
+
+ private void init() {
+ bodyParsers.put("application/x-www-form-urlencoded", new FormBodyParser());
+ }
+
+ public HttpRequest parse(final BufferedReader reader) {
+ final String firstLine = readLine(reader);
+ final HttpMethod httpMethod = parseHttpMethod(firstLine);
+ final String requestUri = parseRequestUri(firstLine);
+ final QueryStrings queryStrings = parseQueryStrings(firstLine);
+ final HttpHeaders httpHeaders = parseHttpHeaders(reader);
+ final Map requestBody = parseRequestBody(reader, httpHeaders.get("Content-Length"),
+ bodyParsers.get(httpHeaders.getContentType()));
+
+ return new HttpRequest(httpMethod, requestUri, queryStrings, httpHeaders, requestBody);
+ }
+
+ private String readLine(final BufferedReader reader) {
+ try {
+ return reader.readLine();
+ } catch (final IOException e) {
+ throw new UncheckedIOException("요청을 읽어오는데 실패했습니다.", e);
+ }
+ }
+
+ private HttpMethod parseHttpMethod(final String line) {
+ return HttpMethod.valueOf(line.split(REQUEST_LINE_DELIMITER)[HTTP_METHOD_INDEX]);
+ }
+
+ private String parseRequestUri(final String line) {
+ final String requestUri = line.split(REQUEST_LINE_DELIMITER)[REQUEST_URI_INDEX];
+ final int queryStringBeginIndex = requestUri.indexOf(QUERY_STRING_DELIMITER);
+ if (queryStringBeginIndex == NON_EXIST) {
+ return requestUri;
+ }
+ return requestUri.substring(0, queryStringBeginIndex);
+ }
+
+ private QueryStrings parseQueryStrings(final String line) {
+ final QueryStrings queryStrings = new QueryStrings();
+ final String requestUri = line.split(REQUEST_LINE_DELIMITER)[REQUEST_URI_INDEX];
+ final int queryStringBeginIndex = requestUri.indexOf(QUERY_STRING_DELIMITER);
+ if (queryStringBeginIndex == NON_EXIST) {
+ return queryStrings;
+ }
+ final String[] splitQueryStrings = requestUri.substring(queryStringBeginIndex + 1)
+ .split(QUERY_STRING_VALUE_DELIMITER);
+ for (final String splitQueryString : splitQueryStrings) {
+ final String[] splitKeyValue = splitQueryString.split(KEY_AND_VALUE_DELIMITER);
+ final String key = splitKeyValue[KEY_INDEX];
+ final String value = splitKeyValue[VALUE_INDEX];
+ queryStrings.add(key, value);
+ }
+ return queryStrings;
+ }
+
+ private HttpHeaders parseHttpHeaders(final BufferedReader reader) {
+ String line = readLine(reader);
+ final HttpHeaders httpHeaders = new HttpHeaders();
+ while (!EMPTY_LINE.equals(line)) {
+ final String[] header = line.split(HEADER_DELIMITER);
+ if (header.length == EXIST_HEADER_VALUE) {
+ final String key = header[KEY_INDEX];
+ final String value = header[VALUE_INDEX];
+ httpHeaders.add(key, value);
+ }
+ line = readLine(reader);
+ }
+ return httpHeaders;
+ }
+
+ private Map parseRequestBody(final BufferedReader reader, final String contentLength,
+ final BodyParser bodyParser) {
+ if (contentLength.isEmpty()) {
+ return new HashMap<>();
+ }
+ final int length = Integer.parseInt(contentLength);
+ final char[] buffer = new char[length];
+ try {
+ reader.read(buffer, 0, length);
+ } catch (final IOException e) {
+ throw new UncheckedIOException("요청을 읽어오는데 실패했습니다.", e);
+ }
+ return bodyParser.parse(new String(buffer));
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java
new file mode 100644
index 0000000000..00dcadd9e0
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java
@@ -0,0 +1,44 @@
+package org.apache.coyote.http11;
+
+import java.util.Map;
+
+public class HttpResponse {
+
+ private HttpStatusCode statusCode;
+ private final HttpHeaders headers;
+ private String responseBody;
+
+ public HttpResponse() {
+ this(null, new HttpHeaders(), null);
+ }
+
+ public HttpResponse(final HttpStatusCode statusCode, final HttpHeaders headers, final String responseBody) {
+ this.statusCode = statusCode;
+ this.headers = headers;
+ this.responseBody = responseBody;
+ }
+
+ public HttpStatusCode getStatusCode() {
+ return statusCode;
+ }
+
+ public void setStatusCode(final HttpStatusCode statusCode) {
+ this.statusCode = statusCode;
+ }
+
+ public Map getHeaders() {
+ return headers.getHeaders();
+ }
+
+ public void setHeader(final String key, final String value) {
+ headers.add(key, value);
+ }
+
+ public String getResponseBody() {
+ return responseBody;
+ }
+
+ public void setResponseBody(final String responseBody) {
+ this.responseBody = responseBody;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java
new file mode 100644
index 0000000000..74de995f65
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java
@@ -0,0 +1,25 @@
+package org.apache.coyote.http11;
+
+public enum HttpStatusCode {
+
+ OK(200, "OK"),
+ FOUND(302, "FOUND"),
+ UNAUTHORIZED(401, "UNAUTHORIZED"),
+ ;
+
+ private final int code;
+ private final String message;
+
+ HttpStatusCode(final int code, final String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/QueryStrings.java b/tomcat/src/main/java/org/apache/coyote/http11/QueryStrings.java
new file mode 100644
index 0000000000..10d24491bc
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/QueryStrings.java
@@ -0,0 +1,20 @@
+package org.apache.coyote.http11;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class QueryStrings {
+
+ private final Map queryStrings = new HashMap<>();
+
+ public QueryStrings() {
+ }
+
+ public void add(final String key, final String value) {
+ queryStrings.put(key, value);
+ }
+
+ public Map getQueryStrings() {
+ return queryStrings;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ViewResolver.java b/tomcat/src/main/java/org/apache/coyote/http11/ViewResolver.java
new file mode 100644
index 0000000000..2555307889
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/ViewResolver.java
@@ -0,0 +1,37 @@
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+public class ViewResolver {
+
+ private final ClassLoader classLoader = getClass().getClassLoader();
+
+ public String read(final String fileResource) {
+ final URL resource = classLoader.getResource("static" + fileResource);
+ final String resourceFile = getFile(resource);
+ final Path path = Paths.get(resourceFile);
+ final List fileLines = readFileLines(path);
+ return String.join(System.lineSeparator(), fileLines);
+ }
+
+ private String getFile(final URL resource) {
+ if (resource == null) {
+ throw new IllegalArgumentException("경로가 올바르지 않습니다.");
+ }
+ return resource.getFile();
+ }
+
+ private List readFileLines(final Path path) {
+ try {
+ return Files.readAllLines(path);
+ } catch (final IOException e) {
+ throw new UncheckedIOException("요청을 읽어오는데 실패했습니다.", e);
+ }
+ }
+}
diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html
index f4ed9de875..1bf3bff134 100644
--- a/tomcat/src/main/resources/static/login.html
+++ b/tomcat/src/main/resources/static/login.html
@@ -1,66 +1,70 @@
-
-
-
-
-
-
- 로그인
-
-
-
-
-
-
-
-
-
-
-
-
로그인
-
-
+
+
+
+
+
+
+ 로그인
+
+
+
+
+
+
+
+
+
+
+
+
로그인
+
+
+
+
-
+
-
+
+
+
+
diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java
index 512b919f09..000e9a6e64 100644
--- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java
+++ b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java
@@ -1,20 +1,21 @@
package nextstep.org.apache.coyote.http11;
-import support.StubSocket;
-import org.apache.coyote.http11.Http11Processor;
-import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
-import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
-
-import static org.assertj.core.api.Assertions.assertThat;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.apache.coyote.http11.Http11Processor;
+import org.junit.jupiter.api.Test;
+import support.StubSocket;
class Http11ProcessorTest {
@Test
- void process() {
+ void 루트경로로_요청을_보내면_index_페이지를_응답한다() throws IOException {
// given
final var socket = new StubSocket();
final var processor = new Http11Processor(socket);
@@ -23,23 +24,31 @@ void process() {
processor.process(socket);
// then
- var expected = String.join("\r\n",
+ final URL resource = getClass().getClassLoader().getResource("static/index.html");
+ final String resourceFile = resource.getFile();
+ final Path path = Paths.get(resourceFile);
+ final List fileLines = Files.readAllLines(path);
+
+ final String responseBody = String.join(System.lineSeparator(), fileLines);
+
+ final var expected = String.join(System.lineSeparator(),
"HTTP/1.1 200 OK ",
- "Content-Type: text/html;charset=utf-8 ",
- "Content-Length: 12 ",
+ "Content-Length: " + responseBody.getBytes().length + " ",
+ "Content-Type: text/html ",
"",
- "Hello world!");
+ responseBody);
assertThat(socket.output()).isEqualTo(expected);
}
@Test
- void index() throws IOException {
+ void index_페이지를_응답한다() throws IOException {
// given
- final String httpRequest= String.join("\r\n",
+ final String httpRequest = String.join(System.lineSeparator(),
"GET /index.html HTTP/1.1 ",
"Host: localhost:8080 ",
"Connection: keep-alive ",
+ "Accept: text/html",
"",
"");
@@ -51,11 +60,18 @@ void index() throws IOException {
// then
final URL resource = getClass().getClassLoader().getResource("static/index.html");
- var expected = "HTTP/1.1 200 OK \r\n" +
- "Content-Type: text/html;charset=utf-8 \r\n" +
- "Content-Length: 5564 \r\n" +
- "\r\n"+
- new String(Files.readAllBytes(new File(resource.getFile()).toPath()));
+ final String resourceFile = resource.getFile();
+ final Path path = Paths.get(resourceFile);
+ final List fileLines = Files.readAllLines(path);
+
+ final String responseBody = String.join(System.lineSeparator(), fileLines);
+
+ final var expected = String.join(System.lineSeparator(),
+ "HTTP/1.1 200 OK ",
+ "Content-Length: " + responseBody.getBytes().length + " ",
+ "Content-Type: text/html ",
+ "",
+ responseBody);
assertThat(socket.output()).isEqualTo(expected);
}
diff --git a/tomcat/src/test/java/support/StubSocket.java b/tomcat/src/test/java/support/StubSocket.java
index 6ba8ba5ef9..f863fd15b5 100644
--- a/tomcat/src/test/java/support/StubSocket.java
+++ b/tomcat/src/test/java/support/StubSocket.java
@@ -20,29 +20,38 @@ public StubSocket(final String request) {
}
public StubSocket() {
- this("GET / HTTP/1.1\r\nHost: localhost:8080\r\n\r\n");
+ this(String.join(System.lineSeparator(),
+ "GET / HTTP/1.1 ",
+ "Host: localhost:8080 ",
+ "Accept: text/html",
+ "",
+ ""));
}
+ @Override
public InetAddress getInetAddress() {
try {
return InetAddress.getLocalHost();
- } catch (UnknownHostException ignored) {
+ } catch (final UnknownHostException ignored) {
return null;
}
}
+ @Override
public int getPort() {
return 8080;
}
+ @Override
public InputStream getInputStream() {
return new ByteArrayInputStream(request.getBytes());
}
+ @Override
public OutputStream getOutputStream() {
return new OutputStream() {
@Override
- public void write(int b) {
+ public void write(final int b) {
outputStream.write(b);
}
};