diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java
index e1b6cca042..080c622473 100644
--- a/study/src/test/java/study/FileTest.java
+++ b/study/src/test/java/study/FileTest.java
@@ -1,54 +1,59 @@
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.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collections;
import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
+import java.util.stream.Collectors;
+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_디렉터리에_있는_파일의_경로를_찾는다() {
+ void resource_디렉터리에_있는_파일의_경로를_찾는다() throws URISyntaxException {
final String fileName = "nextstep.txt";
// todo
- final String actual = "";
+ File file = new File(getClass().getClassLoader().getResource("nextstep.txt").toURI());
+ final String actual = file.getName();
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;
+ String file = getClass().getClassLoader().getResource(fileName).getFile();
+ Path path = Path.of(file);
// todo
- final List actual = Collections.emptyList();
+ BufferedReader bufferedReader = Files.newBufferedReader(path);
+ final List actual = bufferedReader.lines().collect(Collectors.toList());
+ // then
assertThat(actual).containsOnly("nextstep");
}
}
diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java
index 47a79356b6..3864070131 100644
--- a/study/src/test/java/study/IOStreamTest.java
+++ b/study/src/test/java/study/IOStreamTest.java
@@ -1,45 +1,56 @@
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.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+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 {
+ private static final String RESOURCE_PATH = "./src/test/resources/";
+
/**
* 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바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
@@ -48,11 +59,11 @@ class OutputStream_학습_테스트 {
void OutputStream은_데이터를_바이트로_처리한다() throws IOException {
final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112};
final OutputStream outputStream = new ByteArrayOutputStream(bytes.length);
-
/**
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
+ outputStream.write(bytes);
final String actual = outputStream.toString();
@@ -61,13 +72,10 @@ class OutputStream_학습_테스트 {
}
/**
- * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
- * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
- *
- * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
- * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
- * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
- * 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다.
+ * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
+ *
+ * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. Stream은
+ * 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다.
*/
@Test
void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException {
@@ -79,13 +87,87 @@ class OutputStream_학습_테스트 {
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
+ outputStream.flush();
+
verify(outputStream, atLeastOnce()).flush();
outputStream.close();
}
+ @Test
+ void BufferdOutputStream은_특정_버퍼만큼_쌓이지_않았다면_반영하지_않는다() throws IOException {
+ // given
+ String testFilePath = makeTestFile();
+ int inBufferDataSize = 4;
+ byte[] readData = readBytesFromNextStep(inBufferDataSize);
+
+ int flushBufferSize = inBufferDataSize + 1;
+ BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
+ new FileOutputStream(testFilePath), flushBufferSize);
+
+ // when
+ bufferedOutputStream.write(readData);
+ FileInputStream fileInputStream1 = new FileInputStream(testFilePath);
+ int charCount = 0;
+ while (fileInputStream1.read() != -1) {
+ charCount++;
+ }
+
+ //then
+ assertThat(charCount).isZero();
+ fileInputStream1.close();
+ bufferedOutputStream.close();
+ }
+
+ private String makeTestFile() throws IOException {
+ String testFilePath = RESOURCE_PATH + "test.txt";
+
+ File testFile = new File(testFilePath);
+ if (testFile.exists()) {
+ testFile.delete();
+ }
+ FileWriter fileWriter = new FileWriter(testFilePath);
+ fileWriter.write("");
+ fileWriter.close();
+ return testFilePath;
+ }
+
+ private byte[] readBytesFromNextStep(int size) throws IOException {
+ File file = new File(RESOURCE_PATH + "nextstep.txt");
+ FileInputStream fileInputStream = new FileInputStream(file);
+ BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
+ byte[] readData = new byte[size];
+ bufferedInputStream.read(readData);
+ fileInputStream.close();
+ return readData;
+ }
+
+ @Test
+ void BufferdOutputStream은_특정_버퍼만큼_쌓이면_반영한다() throws IOException {
+ // given
+ String testFilePath = makeTestFile();
+ int inBufferDataSize = 4;
+ byte[] readData = readBytesFromNextStep(inBufferDataSize);
+
+ int flushBufferSize = inBufferDataSize;
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(testFilePath), flushBufferSize);
+
+ // when
+ bos.write(readData);
+ FileInputStream fis = new FileInputStream(testFilePath);
+ int charCount = 0;
+ while (fis.read() != -1) {
+ charCount++;
+ }
+
+ //then
+ assertThat(charCount).isEqualTo(flushBufferSize);
+ fis.close();
+ bos.close();
+ }
+
/**
- * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
- * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
+ * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
*/
@Test
void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
@@ -96,6 +178,8 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ try (outputStream) {
+ }
verify(outputStream, atLeastOnce()).close();
}
@@ -103,20 +187,18 @@ 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
@@ -128,7 +210,10 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
- final String actual = "";
+ byte[] readByte = new byte[4];
+ inputStream.read(readByte);
+
+ String actual = new String(readByte);
assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
@@ -136,8 +221,7 @@ class InputStream_학습_테스트 {
}
/**
- * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
- * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
+ * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
*/
@Test
void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
@@ -148,6 +232,8 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ try (inputStream) {
+ }
verify(inputStream, atLeastOnce()).close();
}
@@ -155,26 +241,24 @@ 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 = bufferedInputStream.readAllBytes();
assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
@@ -182,31 +266,31 @@ 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());
+ InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
+ BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
final StringBuilder actual = new StringBuilder();
+ bufferedReader.lines().forEach(s -> actual.append(s + "\r\n"));
+
assertThat(actual).hasToString(emoji);
}
}
diff --git a/tomcat/README.md b/tomcat/README.md
new file mode 100644
index 0000000000..52bb930f2a
--- /dev/null
+++ b/tomcat/README.md
@@ -0,0 +1,20 @@
+## Mission 1 : HTTP 서버 구현하기
+
+1. [x] Get /index.html 응답하기
+2. [x] CSS 지원하기
+ - [x] 정적 파일을 지원하는 ResourceProvider 를 생성한다.
+ - [x] 해당 정적 파일을 읽어온다.
+ - [x] 해당 정적 파일의 타입을 반환한다.
+3. [x] Query String 파싱
+ - [x] 먼저 정적 파일을 찾는다.
+ - [x] 요청을 handle 할 수 있는 핸들러를 찾는다.
+ - [x] 아무것도 못 찾았다면 not Found 페이지 반환.
+
+## Mission 2 : 로그인 구현하기
+
+1. [x] HTTP Status Code 302
+2. [x] POST 방식으로 회원가입 및 로그인
+ - [x] Request 를 생성한다
+ - [x] 요청을 해결할 수 있는 Contoller 를 Mapper 로 엮어둔다.
+3. [x] Cookie에 JSESSIONID 값 저장하기
+4. [x] Session 구현하기
diff --git a/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java b/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java
index 1ca30e8383..d35eb9dbf6 100644
--- a/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java
+++ b/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java
@@ -1,27 +1,29 @@
package nextstep.jwp.db;
-import nextstep.jwp.model.User;
-
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import nextstep.jwp.model.User;
public class InMemoryUserRepository {
private static final Map database = new ConcurrentHashMap<>();
+ private static Long publishedId = 1L;
static {
final User user = new User(1L, "gugu", "password", "hkkang@woowahan.com");
database.put(user.getAccount(), user);
}
- public static void save(User user) {
- database.put(user.getAccount(), user);
+ private InMemoryUserRepository() {
}
public static Optional findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}
- private InMemoryUserRepository() {}
+ public static void save(User user) {
+ User saveUser = new User(++publishedId, user.getAccount(), user.getPassword(), user.getEmail());
+ database.put(saveUser.getAccount(), saveUser);
+ }
}
diff --git a/tomcat/src/main/java/nextstep/jwp/model/User.java b/tomcat/src/main/java/nextstep/jwp/model/User.java
index 4c2a2cd184..06dc83367a 100644
--- a/tomcat/src/main/java/nextstep/jwp/model/User.java
+++ b/tomcat/src/main/java/nextstep/jwp/model/User.java
@@ -22,17 +22,29 @@ public boolean checkPassword(String password) {
return this.password.equals(password);
}
+ public Long getId() {
+ return id;
+ }
+
public String getAccount() {
return account;
}
+ public String getPassword() {
+ return password;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
@Override
public String toString() {
return "User{" +
- "id=" + id +
- ", account='" + account + '\'' +
- ", email='" + email + '\'' +
- ", password='" + password + '\'' +
- '}';
+ "id=" + id +
+ ", account='" + account + '\'' +
+ ", email='" + email + '\'' +
+ ", password='" + password + '\'' +
+ '}';
}
}
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..1dba43ac70 100644
--- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
+++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
@@ -1,21 +1,28 @@
package org.apache.coyote.http11;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.Socket;
import nextstep.jwp.exception.UncheckedServletException;
import org.apache.coyote.Processor;
+import org.apache.coyote.http11.controller.HandlerMapper;
+import org.apache.coyote.http11.request.HttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.net.Socket;
-
public class Http11Processor implements Runnable, Processor {
private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);
private final Socket connection;
+ private final ResourceProvider resourceProvider;
+ private final HandlerMapper handlerMapper;
public Http11Processor(final Socket connection) {
this.connection = connection;
+ this.resourceProvider = new ResourceProvider();
+ this.handlerMapper = new HandlerMapper(this.resourceProvider);
}
@Override
@@ -26,22 +33,29 @@ public void run() {
@Override
public void process(final Socket connection) {
- try (final var inputStream = connection.getInputStream();
- final var outputStream = connection.getOutputStream()) {
-
- final var responseBody = "Hello world!";
-
- 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);
-
+ try (final var inputReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ final var outputStream = connection.getOutputStream()) {
+ HttpRequest httpRequest = HttpRequest.makeRequest(inputReader);
+ String response = getResponse(httpRequest);
outputStream.write(response.getBytes());
outputStream.flush();
} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
}
+
+ private String getResponse(HttpRequest httpRequest) {
+ if (resourceProvider.haveResource(httpRequest.getRequestLine().getPath())) {
+ return resourceProvider.staticResourceResponse(httpRequest.getRequestLine().getPath());
+ }
+ if (handlerMapper.haveAvailableHandler(httpRequest)) {
+ return handlerMapper.controllerResponse(httpRequest);
+ }
+ return String.join("\r\n",
+ "HTTP/1.1 200 OK ",
+ "Content-Type: text/html;charset=utf-8 ",
+ "Content-Length: 12 ",
+ "",
+ "Hello world!");
+ }
}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResourceProvider.java b/tomcat/src/main/java/org/apache/coyote/http11/ResourceProvider.java
new file mode 100644
index 0000000000..cd9c80797e
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/ResourceProvider.java
@@ -0,0 +1,69 @@
+package org.apache.coyote.http11;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.Optional;
+
+public class ResourceProvider {
+
+ public boolean haveResource(String resourcePath) {
+ return findFileURL(resourcePath).isPresent();
+ }
+
+ public String resourceBodyOf(String resourcePath) {
+ try {
+ Optional fileURL = findFileURL(resourcePath);
+ if (fileURL.isPresent()) {
+ URL url = fileURL.get();
+ return new String(Files.readAllBytes(new File(url.getFile()).toPath()));
+ }
+ throw new IllegalArgumentException("파일이 존재하지 않습니다.");
+ } catch (IOException e) {
+ throw new IllegalArgumentException("존재하지 않는 파일 입니다.");
+ }
+ }
+
+ private Optional findFileURL(String resourcePath) {
+ URL resourceURL = getClass().getClassLoader().getResource("static" + resourcePath);
+ if (resourceURL == null) {
+ return Optional.empty();
+ }
+ if (new File(resourceURL.getFile()).isDirectory()) {
+ return Optional.empty();
+ }
+ return Optional.of(resourceURL);
+ }
+
+ public String contentTypeOf(String resourcePath) {
+ File file = getFile(resourcePath);
+ String fileName = file.getName();
+ if (fileName.endsWith(".js")) {
+ return "Content-Type: text/javascript ";
+ }
+ if (fileName.endsWith(".css")) {
+ return "Content-Type: text/css;charset=utf-8";
+ }
+ if (fileName.endsWith(".html")) {
+ return "Content-Type: text/html;charset=utf-8 ";
+ }
+ return "Content-Type: text/plain";
+ }
+
+ private File getFile(String resourcePath) {
+ return new File(
+ findFileURL(resourcePath).orElseThrow((() -> new IllegalArgumentException("파일이 존재하지 않습니다.")))
+ .getFile());
+ }
+
+ public String staticResourceResponse(String resourcePath) {
+ String responseBody = resourceBodyOf(resourcePath);
+ return String.join("\r\n",
+ "HTTP/1.1 200 OK ",
+ contentTypeOf(resourcePath),
+ "Content-Length: " + responseBody.getBytes().length + " ",
+ "",
+ responseBody);
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java
new file mode 100644
index 0000000000..d80994d5ad
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java
@@ -0,0 +1,9 @@
+package org.apache.coyote.http11.controller;
+
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.response.HttpResponse;
+
+public interface Controller {
+
+ HttpResponse extends Object> handle(HttpRequest httpRequest);
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/HandlerMapper.java
new file mode 100644
index 0000000000..c75bbbe67a
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/HandlerMapper.java
@@ -0,0 +1,126 @@
+package org.apache.coyote.http11.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+import org.apache.coyote.http11.ResourceProvider;
+import org.apache.coyote.http11.request.HttpMethod;
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.response.HttpResponse;
+import org.apache.coyote.http11.response.HttpStatusCode;
+import org.apache.coyote.http11.service.LoginService;
+
+public class HandlerMapper {
+
+ private final Map controllerByMapper = new HashMap<>();
+ private final ResourceProvider resourceProvider;
+
+ public HandlerMapper(ResourceProvider resourceProvider) {
+ enrollHandler();
+ this.resourceProvider = resourceProvider;
+ }
+
+ private void enrollHandler() {
+ controllerByMapper.put(
+ request -> "/login".equals(request.getRequestLine().getPath()) &&
+ HttpMethod.POST.equals(request.getRequestLine().getMethod()),
+ new LoginController(new LoginService()));
+
+ controllerByMapper.put(
+ request -> "/register".equals(request.getRequestLine().getPath()) &&
+ HttpMethod.POST.equals(request.getRequestLine().getMethod()),
+ new SignUpController(new LoginService()));
+
+ controllerByMapper.put(
+ request -> "/login".equals(request.getRequestLine().getPath()) &&
+ HttpMethod.GET.equals(request.getRequestLine().getMethod()),
+ new LoginViewController());
+
+ controllerByMapper.put(
+ request -> "/register".equals(request.getRequestLine().getPath()) &&
+ HttpMethod.GET.equals(request.getRequestLine().getMethod()),
+ new SignUpViewController());
+ }
+
+ public boolean haveAvailableHandler(HttpRequest httpRequest) {
+ return controllerByMapper.keySet().stream()
+ .anyMatch(mapper -> mapper.canHandle(httpRequest));
+ }
+
+ private Controller getHandler(HttpRequest httpRequest) {
+ Mapper mapper = controllerByMapper.keySet().stream()
+ .filter(mp -> mp.canHandle(httpRequest))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("해당 요청을 해결할 수 있는 핸들러가 없습니다."));
+ return controllerByMapper.get(mapper);
+ }
+
+ public String controllerResponse(HttpRequest httpRequest) {
+ Controller handler = getHandler(httpRequest);
+ HttpResponse