* File 객체를 생성하려면 파일의 경로를 알아야 한다. * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? */ @Test - void resource_디렉터리에_있는_파일의_경로를_찾는다() { + void resource_디렉터리에_있는_파일의_경로를_찾는다() throws URISyntaxException { final String fileName = "nextstep.txt"; + /* - // todo - final String actual = ""; + 1. URL에서 경로 별도 분리 + // 리소스 파일의 URL을 얻어옴 + final URL resource = getClass().getClassLoader().getResource(fileName); + + // URL에서 경로를 추출함 + final String actual = resource.getPath();*/ + + // 2. 정적 경로 고정 + //Path path = Paths.get("src/test/resources", fileName); + + // 3. 동적 경로 할당 + final Path path = Paths.get(getClass().getClassLoader().getResource(fileName).toURI()); + System.out.println(path.toAbsolutePath()); + + final String actual = path.toString(); assertThat(actual).endsWith(fileName); } /** * 파일 내용 읽기 - * + *
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
- void 파일의_내용을_읽는다() {
+ void 파일의_내용을_읽는다() throws IOException, URISyntaxException {
final String fileName = "nextstep.txt";
- // todo
- final Path path = null;
+ final Path path = Paths.get(getClass().getClassLoader().getResource(fileName).toURI());
- // todo
- final List
* InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다.
* FilterStream은 InputStream이나 OutputStream에 연결될 수 있다.
* FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환)
- *
+ *
* Stream은 데이터를 바이트로 읽고 쓴다.
* 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다.
* Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다.
@@ -26,7 +27,7 @@ class IOStreamTest {
/**
* OutputStream 학습하기
- *
+ *
* 자바의 기본 출력 클래스는 java.io.OutputStream이다.
* OutputStream의 write(int b) 메서드는 기반 메서드이다.
*
* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다.
*
* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
* flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
* Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
@@ -79,6 +80,7 @@ class OutputStream_학습_테스트 {
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
+ outputStream.flush();
verify(outputStream, atLeastOnce()).flush();
outputStream.close();
}
@@ -96,6 +98,13 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ // 1. close
+ // outputStream.close();
+
+ // 2. try-with-resources
+ try (outputStream) {
+
+ }
verify(outputStream, atLeastOnce()).close();
}
@@ -103,12 +112,12 @@ class OutputStream_학습_테스트 {
/**
* InputStream 학습하기
- *
+ *
* 자바의 기본 입력 클래스는 java.io.InputStream이다.
* InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
* InputStream의 read() 메서드는 기반 메서드이다.
*
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
*/
@Nested
@@ -128,7 +137,9 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
- final String actual = "";
+
+ inputStream.read(bytes);
+ final String actual = new String(bytes, StandardCharsets.UTF_8);
assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
@@ -149,13 +160,14 @@ class InputStream_학습_테스트 {
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ inputStream.close();
verify(inputStream, atLeastOnce()).close();
}
}
/**
* FilterStream 학습하기
- *
+ *
* 필터는 필터 스트림, reader, writer로 나뉜다.
* 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
* reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.
@@ -169,12 +181,13 @@ class FilterStream_학습_테스트 {
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@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());
@@ -197,16 +210,25 @@ class InputStreamReader_학습_테스트 {
* 필터인 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();
+ String line;
+
+ // BufferedReader에서 문자열을 읽어서 StringBuilder에 추가
+ while ((line = bufferedReader.readLine()) != null) {
+ actual.append(line).append("\r\n");
+ }
+
assertThat(actual).hasToString(emoji);
}
}
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 bb14184757..fb5547cf53 100644
--- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
+++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
@@ -1,15 +1,32 @@
package org.apache.coyote.http11;
-import com.techcourse.exception.UncheckedServletException;
+import com.techcourse.db.InMemoryUserRepository;
+import com.techcourse.model.User;
import org.apache.coyote.Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.net.Socket;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
public class Http11Processor implements Runnable, Processor {
+ public static final String QUERY_PARAMETER = "?";
+ public static final String STATIC = "/static";
+ public static final String HTML = ".html";
+ public static final String CSS = ".css";
+ public static final String JS = ".js";
+ public static final String SVG = ".svg";
+
private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);
private final Socket connection;
@@ -27,21 +44,127 @@ public void run() {
@Override
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
- final var outputStream = connection.getOutputStream()) {
+ final var outputStream = connection.getOutputStream();
+ final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
+ final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
+
+ final String line = bufferedReader.readLine();
+
+ if (line == null) {
+ return;
+ }
+
+ final String[] httpRequestLine = line.split(" ");
+ final String method = httpRequestLine[0];
+ final String requestURL = httpRequestLine[1];
+
+ String contentType;
+ byte[] responseBody;
+
+ String httpStatus = "200 OK ";
+ String resourcePath = determineResourcePath(requestURL);
+ contentType = determineContentType(resourcePath);
- final var responseBody = "Hello world!";
+ URL resource = getClass().getResource(resourcePath);
+
+ if (resource == null) {
+ resource = getClass().getResource("/static/404.html");
+ httpStatus = "404 NOT FOUND ";
+ }
+
+ responseBody = Files.readAllBytes(new File(resource.getFile()).toPath());
final var response = String.join("\r\n",
- "HTTP/1.1 200 OK ",
- "Content-Type: text/html;charset=utf-8 ",
- "Content-Length: " + responseBody.getBytes().length + " ",
+ "HTTP/1.1 " + httpStatus,
+ "Content-Type: " + contentType,
+ "Content-Length: " + responseBody.length + " ",
"",
- responseBody);
+ "");
outputStream.write(response.getBytes());
+ outputStream.write(responseBody);
outputStream.flush();
- } catch (IOException | UncheckedServletException e) {
+
+ } catch (IOException e) {
log.error(e.getMessage(), e);
}
}
+
+ private String determineResourcePath(String requestURL) {
+ if (requestURL.equals("/") || requestURL.equals("/index.html")) {
+ return "/static/index.html";
+ }
+
+ if (requestURL.endsWith(SVG)) {
+ return STATIC + "/assets/img/error-404-monochrome.svg";
+ }
+
+ if (requestURL.contains(QUERY_PARAMETER)) {
+ String path = parseLoginQueryString(requestURL);
+ return STATIC + path + HTML;
+ }
+
+ if (requestURL.endsWith(HTML) || requestURL.endsWith(CSS) || requestURL.endsWith(JS)) {
+ return STATIC + requestURL;
+ }
+
+ return STATIC + requestURL + HTML;
+ }
+
+ private String determineContentType(String resourcePath) {
+ if (resourcePath.endsWith(CSS)) {
+ return "text/css ";
+ }
+
+ if (resourcePath.endsWith(JS)) {
+ return "application/javascript ";
+ }
+
+ if (resourcePath.endsWith(SVG)) {
+ return "image/svg+xml ";
+ }
+
+ return "text/html;charset=utf-8 ";
+ }
+
+ private String parseLoginQueryString(String requestURL) {
+ int index = requestURL.indexOf(QUERY_PARAMETER);
+ findUser(parseUserInfo(requestURL.substring(index + 1)));
+
+ return requestURL.substring(0, index);
+ }
+
+ private Mappublic abstract void write(int b) throws IOException;
@@ -39,7 +40,7 @@ class OutputStream_학습_테스트 {
* OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.
* 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
* 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.
- *
+ * write(byte[] data)
와 write(byte b[], int off, int len)
메서드는
* 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
@@ -53,7 +54,7 @@ class OutputStream_학습_테스트 {
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
-
+ outputStream.write(bytes);
final String actual = outputStream.toString();
assertThat(actual).isEqualTo("nextstep");
@@ -63,7 +64,7 @@ class OutputStream_학습_테스트 {
/**
* 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
* BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
- *
+ * public abstract int read() throws IOException;
- *
+ *