From 21e98c96f65b1bd87439e1c25222dde042608bd2 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sat, 2 Sep 2023 23:35:06 +0900 Subject: [PATCH 01/31] =?UTF-8?q?feat:=20=EC=BB=A8=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=ED=98=91=EC=83=81=EC=9D=84=20=ED=86=B5=ED=95=B4=20Content-Type?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 78 +++++++++++++++++-- 1 file changed, 70 insertions(+), 8 deletions(-) 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..539f665d0f 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,35 @@ package org.apache.coyote.http11; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import nextstep.jwp.exception.UncheckedServletException; import org.apache.coyote.Processor; 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); + public static final int URL_INDEX = 1; + public static final int HEADER_NAME_INDEX = 0; + public static final int HEADER_VALUE_INDEX = 1; + public static final String EMPTY_LINE = ""; + public static final String RESOURCES_PREFIX = "static"; + public static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; private final Socket connection; @@ -26,15 +45,24 @@ public void run() { @Override public void process(final Socket connection) { - try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { + try ( + InputStream inputStream = connection.getInputStream(); + OutputStream outputStream = connection.getOutputStream(); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader) + ) { + List startLine = Arrays.asList(bufferedReader.readLine().split(" ")); + Map requestHeaders = extractHeaders(bufferedReader); - final var responseBody = "Hello world!"; + String requestedUrl = startLine.get(URL_INDEX); + String responseBody = createResponseBody(requestedUrl); + String contentType = negotiateContent(requestHeaders.get("Accept")); final var response = String.join("\r\n", "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", + String.format("Content-Type: %s;charset=utf-8 ", contentType), + String.format("Content-Length: %s ", + responseBody.getBytes(StandardCharsets.UTF_8).length), "", responseBody); @@ -44,4 +72,38 @@ public void process(final Socket connection) { log.error(e.getMessage(), e); } } + + private String negotiateContent(String acceptHeader) { + if (Objects.isNull(acceptHeader)) { + return "text/html"; + } + List splitUrl = Arrays.asList(acceptHeader.split(",")); + return splitUrl.get(ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX); + } + + private Map extractHeaders(BufferedReader bufferedReader) throws IOException { + Map requestHeaders = new HashMap<>(); + String line; + while (!EMPTY_LINE.equals(line = bufferedReader.readLine())) { + String[] splited = line.split(": "); + requestHeaders.put(splited[HEADER_NAME_INDEX], splited[HEADER_VALUE_INDEX]); + } + return requestHeaders; + } + + private String createResponseBody(String requestURL) { + if (requestURL.equals("/")) { + return "Hello world!"; + } + + String resourceName = RESOURCES_PREFIX + requestURL; + URL resource = getClass().getClassLoader().getResource(resourceName); + Path path = Paths.get(resource.getPath().substring(1)); + + try { + return Files.readString(path); + } catch (IOException | NullPointerException e) { + throw new RuntimeException(e); + } + } } From ffa9d52b2d77d2ec3ad587d650964dfeeaad02f4 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 01:06:00 +0900 Subject: [PATCH 02/31] =?UTF-8?q?test:=20css=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=EC=97=90=20=EB=8C=80=ED=95=9C=20Content-Type?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/Http11ProcessorTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) 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..d10904164c 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 @@ -59,4 +59,33 @@ void index() throws IOException { assertThat(socket.output()).isEqualTo(expected); } + + @Test + void css() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /css/styles.css HTTP/1.1 ", + "Host: localhost:8080 ", + "Accept: text/css,*/*;q=0.1 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/css/styles.css"); + File file = new File(resource.getPath()); + var expected = "HTTP/1.1 200 OK \r\n" + + "Content-Type: text/css;charset=utf-8 \r\n" + + String.format("Content-Length: %d \r\n", file.length()) + + "\r\n" + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } } From 00e7d9007fc13e8f8eb9642d7feed4740912cacf Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 01:27:31 +0900 Subject: [PATCH 03/31] =?UTF-8?q?feat:=20login=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 22 +++++++- .../coyote/http11/Http11ProcessorTest.java | 56 +++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) 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 539f665d0f..012a12d485 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -25,8 +25,8 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); public static final int URL_INDEX = 1; - public static final int HEADER_NAME_INDEX = 0; - public static final int HEADER_VALUE_INDEX = 1; + public static final int KEY_INDEX = 0; + public static final int VALUE_INDEX = 1; public static final String EMPTY_LINE = ""; public static final String RESOURCES_PREFIX = "static"; public static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; @@ -53,8 +53,21 @@ public void process(final Socket connection) { ) { List startLine = Arrays.asList(bufferedReader.readLine().split(" ")); Map requestHeaders = extractHeaders(bufferedReader); + Map queryParams = new HashMap<>(); String requestedUrl = startLine.get(URL_INDEX); + int queryParamIndex = requestedUrl.indexOf('?'); + + if (0 < queryParamIndex) { + String queryParamValues = requestedUrl.substring(queryParamIndex); + Arrays.asList(queryParamValues.split("&")) + .forEach(queryParam -> { + String[] splited = queryParam.split("="); + queryParams.put(splited[KEY_INDEX], splited[VALUE_INDEX]); + }); + requestedUrl = requestedUrl.substring(0, queryParamIndex); + } + String responseBody = createResponseBody(requestedUrl); String contentType = negotiateContent(requestHeaders.get("Accept")); @@ -86,7 +99,7 @@ private Map extractHeaders(BufferedReader bufferedReader) throws String line; while (!EMPTY_LINE.equals(line = bufferedReader.readLine())) { String[] splited = line.split(": "); - requestHeaders.put(splited[HEADER_NAME_INDEX], splited[HEADER_VALUE_INDEX]); + requestHeaders.put(splited[KEY_INDEX], splited[VALUE_INDEX]); } return requestHeaders; } @@ -97,6 +110,9 @@ private String createResponseBody(String requestURL) { } String resourceName = RESOURCES_PREFIX + requestURL; + if (!resourceName.contains(".")) { + resourceName += ".html"; + } URL resource = getClass().getClassLoader().getResource(resourceName); Path path = Paths.get(resource.getPath().substring(1)); 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 d10904164c..3ec63c5cc3 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 @@ -88,4 +88,60 @@ void css() throws IOException { assertThat(socket.output()).isEqualTo(expected); } + + @Test + void login() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /login.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/login.html"); + File file = new File(resource.getPath()); + var expected = "HTTP/1.1 200 OK \r\n" + + "Content-Type: text/html;charset=utf-8 \r\n" + + String.format("Content-Length: %d \r\n", file.length()) + + "\r\n" + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } + + @Test + void loginQueryParamParsingTest() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /login?account=gugu&password=password HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/login.html"); + File file = new File(resource.getPath()); + var expected = "HTTP/1.1 200 OK \r\n" + + "Content-Type: text/html;charset=utf-8 \r\n" + + String.format("Content-Length: %d \r\n", file.length()) + + "\r\n" + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } } From 9f47185d0fed0ec091122790399982303c81af54 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 14:50:50 +0900 Subject: [PATCH 04/31] =?UTF-8?q?feat:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A1=9C=20login=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/jwp/exception/InvalidLoginInfoException.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tomcat/src/main/java/nextstep/jwp/exception/InvalidLoginInfoException.java diff --git a/tomcat/src/main/java/nextstep/jwp/exception/InvalidLoginInfoException.java b/tomcat/src/main/java/nextstep/jwp/exception/InvalidLoginInfoException.java new file mode 100644 index 0000000000..686387570c --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/exception/InvalidLoginInfoException.java @@ -0,0 +1,5 @@ +package nextstep.jwp.exception; + +public class InvalidLoginInfoException extends RuntimeException { + +} From 3dab630454dbee7915d45c0a982faf8b43597ef7 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 14:51:27 +0900 Subject: [PATCH 05/31] =?UTF-8?q?feat:=20login=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C=20handler=20=EB=A7=A4=ED=95=91=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/LoginController.java | 18 ++++++++++++++++++ .../apache/coyote/http11/Http11Processor.java | 12 +++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/LoginController.java 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..b6631027e5 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -0,0 +1,18 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.exception.InvalidLoginInfoException; +import nextstep.jwp.model.User; + +public class LoginController { + + public User login(String account, String password) { + User user = InMemoryUserRepository.findByAccount(account) + .orElseThrow(); + if (user.checkPassword(password)) { + // TODO : 반환 타입 DTO로 변환하기 + return user; + } + throw new InvalidLoginInfoException(); + } +} 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 012a12d485..9f6a1dd1b8 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -16,7 +16,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import nextstep.jwp.controller.LoginController; import nextstep.jwp.exception.UncheckedServletException; +import nextstep.jwp.model.User; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,7 +61,7 @@ public void process(final Socket connection) { int queryParamIndex = requestedUrl.indexOf('?'); if (0 < queryParamIndex) { - String queryParamValues = requestedUrl.substring(queryParamIndex); + String queryParamValues = requestedUrl.substring(queryParamIndex+1); Arrays.asList(queryParamValues.split("&")) .forEach(queryParam -> { String[] splited = queryParam.split("="); @@ -81,6 +83,14 @@ public void process(final Socket connection) { outputStream.write(response.getBytes()); outputStream.flush(); + + // requestURL에 따른 handlerMapper 클래스 생성해서 분리하기 + if (queryParams.containsKey("account") && queryParams.containsKey("password")) { + LoginController loginController = new LoginController(); + User user = loginController.login(queryParams.get("account"), + queryParams.get("password")); + log.info(user.toString()); + } } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } From ae2356b4463497e1760fa5629f176a61bfba5bd3 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 14:52:36 +0900 Subject: [PATCH 06/31] =?UTF-8?q?test:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9D=BC=EB=B6=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 54 +++++---- study/src/test/java/study/IOStreamTest.java | 124 +++++++++++--------- 2 files changed, 98 insertions(+), 80 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..556f4b20c7 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,54 +1,62 @@ 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_디렉터리에_있는_파일의_경로를_찾는다() { + //given final String fileName = "nextstep.txt"; - // todo - final String actual = ""; + //when + URL resource = getClass().getClassLoader().getResource(fileName); + final String actual = resource.getPath(); + //then assertThat(actual).endsWith(fileName); } /** * 파일 내용 읽기 - * - * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. - * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. + *

+ * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test void 파일의_내용을_읽는다() { + //given final String fileName = "nextstep.txt"; - - // todo - final Path path = null; - - // todo - final List actual = Collections.emptyList(); - + URL resource = getClass().getClassLoader().getResource(fileName); + Path path = Paths.get(resource.getPath().substring(1)); + + //when + final List actual; + try { + actual = Files.readAllLines(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + + //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..6e78c15e52 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.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 java.util.Objects; 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에 연결될 수 있다. + * 자바는 스트림(Stream)으로부터 I/O를 사용한다. 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. + *

+ * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. * FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환) - * - * Stream은 데이터를 바이트로 읽고 쓴다. - * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. - * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다. + *

+ * 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바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다. @@ -61,13 +66,11 @@ class OutputStream_학습_테스트 { } /** - * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. - * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. - * - * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. - * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. - * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 - * 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다. + * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. + *

+ * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 + * 전송한다. Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락(deadlock) 상태가 되기 때문에 flush로 + * 해제해야 한다. */ @Test void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException { @@ -84,8 +87,8 @@ class OutputStream_학습_테스트 { } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 + * 발생한다. */ @Test void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -103,21 +106,19 @@ 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 사이의 값으로 변환된다. - * 그리고 Stream 끝에 도달하면 -1을 반환한다. + * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. int 값을 byte 타입으로 변환하면 -128부터 + * 127 사이의 값으로 변환된다. 그리고 Stream 끝에 도달하면 -1을 반환한다. */ @Test void InputStream은_데이터를_바이트로_읽는다() throws IOException { @@ -128,7 +129,21 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + StringBuilder sb = new StringBuilder(); + + try ( + InputStreamReader reader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(reader) + ) { + String readLine = ""; + while (Objects.nonNull(readLine = bufferedReader.readLine())) { + sb.append(readLine); + } + } + + String actual = sb.toString(); + +// String actual = new String(inputStream.readAllBytes(), "UTF-8"); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -136,8 +151,8 @@ class InputStream_학습_테스트 { } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 + * 발생한다. */ @Test void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -155,18 +170,16 @@ 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를_사용해보자() { @@ -182,19 +195,16 @@ 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를_사용하여_문자열을_읽어온다() { From d2b31693124f6832c29f88b1f4bfc6a8abdd0db3 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 16:19:54 +0900 Subject: [PATCH 07/31] =?UTF-8?q?test:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EC=9C=88=EB=8F=84=EC=9A=B0=20=EA=B0=9C=ED=96=89=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=20=EC=9D=B4=EC=8A=88=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/Http11ProcessorTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 3ec63c5cc3..bc98bd4c8f 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,7 +1,6 @@ package nextstep.org.apache.coyote.http11; import support.StubSocket; -import org.apache.coyote.http11.Http11Processor; import org.junit.jupiter.api.Test; import java.io.File; @@ -51,9 +50,11 @@ void index() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/index.html"); + File file = new File(resource.getPath()); var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html;charset=utf-8 \r\n" + - "Content-Length: 5564 \r\n" + +// "Content-Length: 5564 \r\n" + + String.format("Content-Length: %d \r\n", file.length()) + "\r\n"+ new String(Files.readAllBytes(new File(resource.getFile()).toPath())); From d679ed30312370275270b6e9c3d4a28c6a753005 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 16:21:35 +0900 Subject: [PATCH 08/31] =?UTF-8?q?chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/src/main/java/nextstep/Application.java | 2 +- .../main/java/{ => nextstep}/org/apache/catalina/Manager.java | 2 +- .../org/apache/catalina/connector/Connector.java | 4 ++-- .../{ => nextstep}/org/apache/catalina/startup/Tomcat.java | 4 ++-- .../main/java/{ => nextstep}/org/apache/coyote/Processor.java | 2 +- .../org/apache/coyote/http11/Http11Processor.java | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) rename tomcat/src/main/java/{ => nextstep}/org/apache/catalina/Manager.java (98%) rename tomcat/src/main/java/{ => nextstep}/org/apache/catalina/connector/Connector.java (95%) rename tomcat/src/main/java/{ => nextstep}/org/apache/catalina/startup/Tomcat.java (84%) rename tomcat/src/main/java/{ => nextstep}/org/apache/coyote/Processor.java (97%) rename tomcat/src/main/java/{ => nextstep}/org/apache/coyote/http11/Http11Processor.java (98%) diff --git a/tomcat/src/main/java/nextstep/Application.java b/tomcat/src/main/java/nextstep/Application.java index 3dd7593507..ccf946cd1f 100644 --- a/tomcat/src/main/java/nextstep/Application.java +++ b/tomcat/src/main/java/nextstep/Application.java @@ -1,6 +1,6 @@ package nextstep; -import org.apache.catalina.startup.Tomcat; +import nextstep.org.apache.catalina.startup.Tomcat; public class Application { diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/nextstep/org/apache/catalina/Manager.java similarity index 98% rename from tomcat/src/main/java/org/apache/catalina/Manager.java rename to tomcat/src/main/java/nextstep/org/apache/catalina/Manager.java index e69410f6a9..d2a00cbb90 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/nextstep/org/apache/catalina/Manager.java @@ -1,4 +1,4 @@ -package org.apache.catalina; +package nextstep.org.apache.catalina; import jakarta.servlet.http.HttpSession; diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/nextstep/org/apache/catalina/connector/Connector.java similarity index 95% rename from tomcat/src/main/java/org/apache/catalina/connector/Connector.java rename to tomcat/src/main/java/nextstep/org/apache/catalina/connector/Connector.java index 3b2c4dda7c..6df168a492 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/nextstep/org/apache/catalina/connector/Connector.java @@ -1,6 +1,6 @@ -package org.apache.catalina.connector; +package nextstep.org.apache.catalina.connector; -import org.apache.coyote.http11.Http11Processor; +import nextstep.org.apache.coyote.http11.Http11Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java b/tomcat/src/main/java/nextstep/org/apache/catalina/startup/Tomcat.java similarity index 84% rename from tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java rename to tomcat/src/main/java/nextstep/org/apache/catalina/startup/Tomcat.java index 205159e95b..c56485fd2b 100644 --- a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java +++ b/tomcat/src/main/java/nextstep/org/apache/catalina/startup/Tomcat.java @@ -1,6 +1,6 @@ -package org.apache.catalina.startup; +package nextstep.org.apache.catalina.startup; -import org.apache.catalina.connector.Connector; +import nextstep.org.apache.catalina.connector.Connector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/tomcat/src/main/java/org/apache/coyote/Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/Processor.java similarity index 97% rename from tomcat/src/main/java/org/apache/coyote/Processor.java rename to tomcat/src/main/java/nextstep/org/apache/coyote/Processor.java index 6604ab83de..3731e1f362 100644 --- a/tomcat/src/main/java/org/apache/coyote/Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/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 nextstep.org.apache.coyote; import java.net.Socket; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java similarity index 98% rename from tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java rename to tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 9f6a1dd1b8..0725c2d95a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package nextstep.org.apache.coyote.http11; import java.io.BufferedReader; import java.io.IOException; @@ -19,7 +19,7 @@ import nextstep.jwp.controller.LoginController; import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; -import org.apache.coyote.Processor; +import nextstep.org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 3096d44d1dc42317906e5c02493d9309e33678e2 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 16:35:42 +0900 Subject: [PATCH 09/31] =?UTF-8?q?feat:=20handlerMapper=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20loginController=20=EB=A7=A4=ED=95=91=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/HandlerMapper.java | 19 +++++++++++++++ .../apache/coyote/http11/Http11Processor.java | 18 ++++++++------- .../coyote/http11/HandlerMapperTest.java | 23 +++++++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java create mode 100644 tomcat/src/test/java/nextstep/org/apache/coyote/http11/HandlerMapperTest.java diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java new file mode 100644 index 0000000000..2cd1c8abe5 --- /dev/null +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java @@ -0,0 +1,19 @@ +package nextstep.org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; +import nextstep.jwp.controller.LoginController; + +public class HandlerMapper { + + private static final Map handlerMapper = new HashMap<>(); + + static { + LoginController loginController = new LoginController(); + handlerMapper.put("/login", loginController); + } + + public Object mapHandler(String requestedUrl) { + return handlerMapper.get(requestedUrl); + } +} diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 0725c2d95a..4a867f2970 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -34,9 +34,11 @@ public class Http11Processor implements Runnable, Processor { public static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; private final Socket connection; + private final HandlerMapper handlerMapper; public Http11Processor(final Socket connection) { this.connection = connection; + this.handlerMapper = new HandlerMapper(); } @Override @@ -70,6 +72,14 @@ public void process(final Socket connection) { requestedUrl = requestedUrl.substring(0, queryParamIndex); } + Object handler = handlerMapper.mapHandler(requestedUrl); + if (Objects.nonNull(handler) && requestedUrl.equals("/login")) { + LoginController loginController = (LoginController) handler; + User user = loginController.login(queryParams.get("account"), + queryParams.get("password")); + log.info(user.toString()); + } + String responseBody = createResponseBody(requestedUrl); String contentType = negotiateContent(requestHeaders.get("Accept")); @@ -83,14 +93,6 @@ public void process(final Socket connection) { outputStream.write(response.getBytes()); outputStream.flush(); - - // requestURL에 따른 handlerMapper 클래스 생성해서 분리하기 - if (queryParams.containsKey("account") && queryParams.containsKey("password")) { - LoginController loginController = new LoginController(); - User user = loginController.login(queryParams.get("account"), - queryParams.get("password")); - log.info(user.toString()); - } } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/HandlerMapperTest.java b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/HandlerMapperTest.java new file mode 100644 index 0000000000..dfda50e0af --- /dev/null +++ b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/HandlerMapperTest.java @@ -0,0 +1,23 @@ +package nextstep.org.apache.coyote.http11; + +import static org.junit.jupiter.api.Assertions.*; + +import nextstep.jwp.controller.LoginController; +import org.junit.jupiter.api.Test; + +class HandlerMapperTest { + + private HandlerMapper handlerMapper = new HandlerMapper(); + + @Test + void mapHandlerByUrlTest() { + // given + String requestedUrl = "/login"; + + // when + LoginController loginController = (LoginController) handlerMapper.mapHandler(requestedUrl); + + // then + assertNotNull(loginController); + } +} From 2ada182c07839d244f1494df5ee674d0da9f6989 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 18:25:37 +0900 Subject: [PATCH 10/31] =?UTF-8?q?feat:=20Login=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=B0=8F=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/LoginController.java | 20 ++++++++++-------- .../nextstep/jwp/dto/LoginResponseDto.java | 14 +++++++++++++ .../nextstep/jwp/service/LoginService.java | 21 +++++++++++++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/jwp/dto/LoginResponseDto.java create mode 100644 tomcat/src/main/java/nextstep/jwp/service/LoginService.java diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index b6631027e5..6ebc27d191 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -1,18 +1,20 @@ package nextstep.jwp.controller; -import nextstep.jwp.db.InMemoryUserRepository; +import java.util.NoSuchElementException; +import nextstep.jwp.dto.LoginResponseDto; import nextstep.jwp.exception.InvalidLoginInfoException; -import nextstep.jwp.model.User; +import nextstep.jwp.service.LoginService; public class LoginController { - public User login(String account, String password) { - User user = InMemoryUserRepository.findByAccount(account) - .orElseThrow(); - if (user.checkPassword(password)) { - // TODO : 반환 타입 DTO로 변환하기 - return user; + private final LoginService loginService = new LoginService(); + + public LoginResponseDto login(String account, String password) { + try { + loginService.login(account, password); + return new LoginResponseDto("/index.html"); + } catch (NoSuchElementException | InvalidLoginInfoException e) { + return new LoginResponseDto("/401.html"); } - throw new InvalidLoginInfoException(); } } diff --git a/tomcat/src/main/java/nextstep/jwp/dto/LoginResponseDto.java b/tomcat/src/main/java/nextstep/jwp/dto/LoginResponseDto.java new file mode 100644 index 0000000000..25e0b9e82b --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/dto/LoginResponseDto.java @@ -0,0 +1,14 @@ +package nextstep.jwp.dto; + +public class LoginResponseDto { + + private final String redirectUrl; + + public LoginResponseDto(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public String getRedirectUrl() { + return redirectUrl; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/service/LoginService.java b/tomcat/src/main/java/nextstep/jwp/service/LoginService.java new file mode 100644 index 0000000000..056c21b290 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/service/LoginService.java @@ -0,0 +1,21 @@ +package nextstep.jwp.service; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.exception.InvalidLoginInfoException; +import nextstep.jwp.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoginService { + + private final Logger log = LoggerFactory.getLogger(LoginService.class); + + public void login(String account, String password) { + User user = InMemoryUserRepository.findByAccount(account) + .orElseThrow(); + if (!user.checkPassword(password)) { + throw new InvalidLoginInfoException(); + } + log.info(user.toString()); + } +} From 1739dba5fda9e02608fc0af1db22d57b4c8003cb Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 18:35:19 +0900 Subject: [PATCH 11/31] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=97=AC=EB=B6=80=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 45 ++++++++++++------- .../coyote/http11/Http11ProcessorTest.java | 36 +++++++++++---- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 4a867f2970..8ee28f298f 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -17,8 +17,8 @@ import java.util.Map; import java.util.Objects; import nextstep.jwp.controller.LoginController; +import nextstep.jwp.dto.LoginResponseDto; import nextstep.jwp.exception.UncheckedServletException; -import nextstep.jwp.model.User; import nextstep.org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,32 +72,46 @@ public void process(final Socket connection) { requestedUrl = requestedUrl.substring(0, queryParamIndex); } + String response = null; + Object handler = handlerMapper.mapHandler(requestedUrl); - if (Objects.nonNull(handler) && requestedUrl.equals("/login")) { + if (Objects.nonNull(handler) && requestedUrl.equals("/login") + && !queryParams.isEmpty()) { LoginController loginController = (LoginController) handler; - User user = loginController.login(queryParams.get("account"), + LoginResponseDto loginDto = loginController.login(queryParams.get("account"), queryParams.get("password")); - log.info(user.toString()); + + response = String.join("\r\n", + "HTTP/1.1 302 Found ", + String.format("Location: %s ", loginDto.getRedirectUrl()), + ""); } - String responseBody = createResponseBody(requestedUrl); - String contentType = negotiateContent(requestHeaders.get("Accept")); + if (Objects.isNull(response)) { + String contentType = negotiateContent(requestHeaders.get("Accept")); + // Todo: createResponseBody() pageController로 위임해보기 + String responseBody = createResponseBody(requestedUrl); + response = String.join("\r\n", + "HTTP/1.1 200 OK ", + String.format("Content-Type: %s;charset=utf-8 ", contentType), + String.format("Content-Length: %s ", + responseBody.getBytes(StandardCharsets.UTF_8).length), + "", + responseBody); + } - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - String.format("Content-Type: %s;charset=utf-8 ", contentType), - String.format("Content-Length: %s ", - responseBody.getBytes(StandardCharsets.UTF_8).length), - "", - responseBody); - outputStream.write(response.getBytes()); - outputStream.flush(); + writeResponse(outputStream, response); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } + private static void writeResponse(OutputStream outputStream, String response) throws IOException { + outputStream.write(response.getBytes()); + outputStream.flush(); + } + private String negotiateContent(String acceptHeader) { if (Objects.isNull(acceptHeader)) { return "text/html"; @@ -116,6 +130,7 @@ private Map extractHeaders(BufferedReader bufferedReader) throws return requestHeaders; } + private String createResponseBody(String requestURL) { if (requestURL.equals("/")) { return "Hello world!"; 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 bc98bd4c8f..a2b621f1ff 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 @@ -119,7 +119,7 @@ void login() throws IOException { } @Test - void loginQueryParamParsingTest() throws IOException { + void loginSuccessTest() { // given final String httpRequest = String.join("\r\n", "GET /login?account=gugu&password=password HTTP/1.1 ", @@ -135,13 +135,33 @@ void loginQueryParamParsingTest() throws IOException { processor.process(socket); // then - final URL resource = getClass().getClassLoader().getResource("static/login.html"); - File file = new File(resource.getPath()); - var expected = "HTTP/1.1 200 OK \r\n" + - "Content-Type: text/html;charset=utf-8 \r\n" + - String.format("Content-Length: %d \r\n", file.length()) + - "\r\n" + - new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + var expected = "HTTP/1.1 302 Found \r\n" + + "Location: /index.html \r\n" + + ""; + + assertThat(socket.output()).isEqualTo(expected); + } + + @Test + void loginFailTest() { + // given + final String httpRequest = String.join("\r\n", + "GET /login?account=gugu&password=pppp HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + var expected = "HTTP/1.1 302 Found \r\n" + + "Location: /401.html \r\n" + + ""; assertThat(socket.output()).isEqualTo(expected); } From bd79358e0865693c9248661c300353616bae8150 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 20:16:24 +0900 Subject: [PATCH 12/31] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20login=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=97=90=20=EB=8C=80=ED=95=9C=20get,=20post?= =?UTF-8?q?=20=EB=A7=A4=ED=95=91=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/LoginController.java | 2 +- .../apache/coyote/http11/Http11Processor.java | 37 +++++++++++++++---- tomcat/src/main/resources/static/login.html | 2 +- .../coyote/http11/Http11ProcessorTest.java | 12 ++++-- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index 6ebc27d191..f357e119a1 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -13,7 +13,7 @@ public LoginResponseDto login(String account, String password) { try { loginService.login(account, password); return new LoginResponseDto("/index.html"); - } catch (NoSuchElementException | InvalidLoginInfoException e) { + } catch (NoSuchElementException | InvalidLoginInfoException | NullPointerException e) { return new LoginResponseDto("/401.html"); } } diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 8ee28f298f..ba0b30f32e 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -26,6 +26,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + public static final int HTTP_METHOD_INDEX = 0; public static final int URL_INDEX = 1; public static final int KEY_INDEX = 0; public static final int VALUE_INDEX = 1; @@ -57,8 +58,19 @@ public void process(final Socket connection) { ) { List startLine = Arrays.asList(bufferedReader.readLine().split(" ")); Map requestHeaders = extractHeaders(bufferedReader); + + Map parsedBody = new HashMap<>(); + if (requestHeaders.containsKey("Content-Length")) { + String requestBody = extractRequestBody(bufferedReader, requestHeaders); + Arrays.asList(requestBody.split("&")).forEach(parsedData -> { + String[] splited = parsedData.split("="); + parsedBody.put(splited[KEY_INDEX], splited[VALUE_INDEX]); + }); + } + Map queryParams = new HashMap<>(); + String httpMethod = startLine.get(HTTP_METHOD_INDEX); String requestedUrl = startLine.get(URL_INDEX); int queryParamIndex = requestedUrl.indexOf('?'); @@ -74,12 +86,13 @@ public void process(final Socket connection) { String response = null; + // POST Object handler = handlerMapper.mapHandler(requestedUrl); - if (Objects.nonNull(handler) && requestedUrl.equals("/login") - && !queryParams.isEmpty()) { + if (Objects.nonNull(handler) && httpMethod.equals("POST") + && requestedUrl.equals("/login")) { LoginController loginController = (LoginController) handler; - LoginResponseDto loginDto = loginController.login(queryParams.get("account"), - queryParams.get("password")); + LoginResponseDto loginDto = loginController.login(parsedBody.get("account"), + parsedBody.get("password")); response = String.join("\r\n", "HTTP/1.1 302 Found ", @@ -87,7 +100,8 @@ public void process(final Socket connection) { ""); } - if (Objects.isNull(response)) { + // GET + if (httpMethod.equals("GET") && Objects.isNull(response)) { String contentType = negotiateContent(requestHeaders.get("Accept")); // Todo: createResponseBody() pageController로 위임해보기 String responseBody = createResponseBody(requestedUrl); @@ -100,14 +114,21 @@ public void process(final Socket connection) { responseBody); } - writeResponse(outputStream, response); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } - private static void writeResponse(OutputStream outputStream, String response) throws IOException { + private String extractRequestBody(BufferedReader bufferedReader, Map requestHeaders) + throws IOException { + int contentLength = Integer.parseInt(requestHeaders.get("Content-Length")); + char[] buffer = new char[contentLength]; + bufferedReader.read(buffer, 0, contentLength); + return new String(buffer); + } + + private void writeResponse(OutputStream outputStream, String response) throws IOException { outputStream.write(response.getBytes()); outputStream.flush(); } @@ -125,7 +146,7 @@ private Map extractHeaders(BufferedReader bufferedReader) throws String line; while (!EMPTY_LINE.equals(line = bufferedReader.readLine())) { String[] splited = line.split(": "); - requestHeaders.put(splited[KEY_INDEX], splited[VALUE_INDEX]); + requestHeaders.put(splited[KEY_INDEX], splited[VALUE_INDEX].strip()); } return requestHeaders; } diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html index f4ed9de875..bc933357f2 100644 --- a/tomcat/src/main/resources/static/login.html +++ b/tomcat/src/main/resources/static/login.html @@ -20,7 +20,7 @@

로그인

-
+
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 a2b621f1ff..819a34b843 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 @@ -121,12 +121,14 @@ void login() throws IOException { @Test void loginSuccessTest() { // given + final String requestBody = "account=gugu&password=password"; final String httpRequest = String.join("\r\n", - "GET /login?account=gugu&password=password HTTP/1.1 ", + "POST /login HTTP/1.1 ", "Host: localhost:8080 ", "Connection: keep-alive ", + String.format("Content-Length: %d ", requestBody.getBytes().length), "", - ""); + requestBody); final var socket = new StubSocket(httpRequest); final Http11Processor processor = new Http11Processor(socket); @@ -145,12 +147,14 @@ void loginSuccessTest() { @Test void loginFailTest() { // given + final String requestBody = "account=gugu&password=wrongPassword"; final String httpRequest = String.join("\r\n", - "GET /login?account=gugu&password=pppp HTTP/1.1 ", + "POST /login HTTP/1.1 ", "Host: localhost:8080 ", "Connection: keep-alive ", + String.format("Content-Length: %d ", requestBody.getBytes().length), "", - ""); + requestBody); final var socket = new StubSocket(httpRequest); final Http11Processor processor = new Http11Processor(socket); From 99cb964b370d3fd95054cdd896bf7c140cd61d9e Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Sun, 3 Sep 2023 20:36:19 +0900 Subject: [PATCH 13/31] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=9B=84=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89?= =?UTF-8?q?=EC=85=98=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/LoginController.java | 5 ++ .../main/java/nextstep/jwp/model/User.java | 1 + .../nextstep/jwp/service/LoginService.java | 4 ++ .../apache/coyote/http11/HandlerMapper.java | 1 + .../apache/coyote/http11/Http11Processor.java | 19 +++++-- .../coyote/http11/Http11ProcessorTest.java | 56 +++++++++++++++++++ 6 files changed, 82 insertions(+), 4 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index f357e119a1..357aa6f585 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -17,4 +17,9 @@ public LoginResponseDto login(String account, String password) { return new LoginResponseDto("/401.html"); } } + + public LoginResponseDto register(String account, String password, String email) { + loginService.register(account, password, email); + return new LoginResponseDto("/index.html"); + } } diff --git a/tomcat/src/main/java/nextstep/jwp/model/User.java b/tomcat/src/main/java/nextstep/jwp/model/User.java index 4c2a2cd184..8e283805ec 100644 --- a/tomcat/src/main/java/nextstep/jwp/model/User.java +++ b/tomcat/src/main/java/nextstep/jwp/model/User.java @@ -15,6 +15,7 @@ public User(Long id, String account, String password, String email) { } public User(String account, String password, String email) { + // todo: 회원가입 시 빈 입력 값 검증 로직 this(null, account, password, email); } diff --git a/tomcat/src/main/java/nextstep/jwp/service/LoginService.java b/tomcat/src/main/java/nextstep/jwp/service/LoginService.java index 056c21b290..6b18545376 100644 --- a/tomcat/src/main/java/nextstep/jwp/service/LoginService.java +++ b/tomcat/src/main/java/nextstep/jwp/service/LoginService.java @@ -18,4 +18,8 @@ public void login(String account, String password) { } log.info(user.toString()); } + + public void register(String account, String password, String email) { + InMemoryUserRepository.save(new User(account, password, email)); + } } diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java index 2cd1c8abe5..15bdd9cac9 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HandlerMapper.java @@ -11,6 +11,7 @@ public class HandlerMapper { static { LoginController loginController = new LoginController(); handlerMapper.put("/login", loginController); + handlerMapper.put("/register", loginController); } public Object mapHandler(String requestedUrl) { diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index ba0b30f32e..29f32cb9bd 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -75,7 +75,7 @@ public void process(final Socket connection) { int queryParamIndex = requestedUrl.indexOf('?'); if (0 < queryParamIndex) { - String queryParamValues = requestedUrl.substring(queryParamIndex+1); + String queryParamValues = requestedUrl.substring(queryParamIndex + 1); Arrays.asList(queryParamValues.split("&")) .forEach(queryParam -> { String[] splited = queryParam.split("="); @@ -86,7 +86,6 @@ public void process(final Socket connection) { String response = null; - // POST Object handler = handlerMapper.mapHandler(requestedUrl); if (Objects.nonNull(handler) && httpMethod.equals("POST") && requestedUrl.equals("/login")) { @@ -100,7 +99,18 @@ public void process(final Socket connection) { ""); } - // GET + if (Objects.nonNull(handler) && httpMethod.equals("POST") + && requestedUrl.equals("/register")) { + LoginController loginController = (LoginController) handler; + LoginResponseDto loginDto = loginController.register(parsedBody.get("account"), + parsedBody.get("password"), parsedBody.get("email")); + + response = String.join("\r\n", + "HTTP/1.1 302 Found ", + String.format("Location: %s ", loginDto.getRedirectUrl()), + ""); + } + if (httpMethod.equals("GET") && Objects.isNull(response)) { String contentType = negotiateContent(requestHeaders.get("Accept")); // Todo: createResponseBody() pageController로 위임해보기 @@ -120,7 +130,8 @@ public void process(final Socket connection) { } } - private String extractRequestBody(BufferedReader bufferedReader, Map requestHeaders) + private String extractRequestBody(BufferedReader bufferedReader, + Map requestHeaders) throws IOException { int contentLength = Integer.parseInt(requestHeaders.get("Content-Length")); char[] buffer = new char[contentLength]; 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 819a34b843..751b267cf8 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 @@ -118,6 +118,34 @@ void login() throws IOException { assertThat(socket.output()).isEqualTo(expected); } + @Test + void register() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /register.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/register.html"); + File file = new File(resource.getPath()); + var expected = "HTTP/1.1 200 OK \r\n" + + "Content-Type: text/html;charset=utf-8 \r\n" + + String.format("Content-Length: %d \r\n", file.length()) + + "\r\n" + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } + @Test void loginSuccessTest() { // given @@ -169,4 +197,32 @@ void loginFailTest() { assertThat(socket.output()).isEqualTo(expected); } + + @Test + void registerSuccessTest() { + // given + final String requestBody = "account=gugu&password=password&email=hkkang%40woowahan.com"; + final String httpRequest = String.join("\r\n", + "POST /register HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + String.format("Content-Length: %d ", requestBody.getBytes().length), + "Content-Type: application/x-www-form-urlencoded", + "Accept: */*", + "", + requestBody); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + var expected = "HTTP/1.1 302 Found \r\n" + + "Location: /index.html \r\n" + + ""; + + assertThat(socket.output()).isEqualTo(expected); + } } From ce1acc13fee3833093ae9e67cdeb2ddea9555c0d Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Mon, 4 Sep 2023 01:27:56 +0900 Subject: [PATCH 14/31] =?UTF-8?q?feat:=20HttpCookie=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/HttpCookie.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java new file mode 100644 index 0000000000..0f06d724c5 --- /dev/null +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java @@ -0,0 +1,38 @@ +package nextstep.org.apache.coyote.http11; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class HttpCookie { + + public static final String SET_COOKIE_HEADER = "Set-Cookie: %s \r\n"; + private final Map cookie = new HashMap<>(); + public static final String COOKIE_DELIMITER = "; "; + public static final String KEY_VALUE_DELIMITER = "="; + public static final int KEY_INDEX = 0; + public static final int VALUE_INDEX = 1; + + public void parseCookieHeaders(String cookieHeaders) { + Arrays.asList(cookieHeaders.split(COOKIE_DELIMITER)).forEach(header -> { + String[] splited = header.split(KEY_VALUE_DELIMITER); + cookie.put(splited[KEY_INDEX], splited[VALUE_INDEX]); + }); + } + + public void set(String key, String value) { + cookie.put(key, value); + } + + public boolean isEmpty() { + return cookie.isEmpty(); + } + + public String createSetCookieHeader() { + String cookies = cookie.entrySet().stream() + .map(entry -> entry.getKey() + KEY_VALUE_DELIMITER + entry.getValue()) + .collect(Collectors.joining(COOKIE_DELIMITER)); + return String.format(SET_COOKIE_HEADER, cookies); + } +} From 71492a63f8cd36ef4dea6df226ef9aad87b3a1e0 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Mon, 4 Sep 2023 01:28:54 +0900 Subject: [PATCH 15/31] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20cookie=EC=97=90=20JSESSIONID?= =?UTF-8?q?=20=EB=B0=9C=EA=B8=89=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/jwp/controller/LoginController.java | 5 ++++- .../org/apache/coyote/http11/Http11Processor.java | 15 ++++++++++++--- .../apache/coyote/http11/Http11ProcessorTest.java | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index 357aa6f585..48aa71046e 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -1,17 +1,20 @@ package nextstep.jwp.controller; import java.util.NoSuchElementException; +import java.util.UUID; import nextstep.jwp.dto.LoginResponseDto; import nextstep.jwp.exception.InvalidLoginInfoException; import nextstep.jwp.service.LoginService; +import nextstep.org.apache.coyote.http11.HttpCookie; public class LoginController { private final LoginService loginService = new LoginService(); - public LoginResponseDto login(String account, String password) { + public LoginResponseDto login(HttpCookie httpCookie, String account, String password) { try { loginService.login(account, password); + httpCookie.set("JSESSIONID", UUID.randomUUID().toString()); return new LoginResponseDto("/index.html"); } catch (NoSuchElementException | InvalidLoginInfoException | NullPointerException e) { return new LoginResponseDto("/401.html"); diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 29f32cb9bd..bec6761688 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -68,6 +68,11 @@ public void process(final Socket connection) { }); } + HttpCookie httpCookie = new HttpCookie(); + if (requestHeaders.containsKey("Cookie")) { + httpCookie.parseCookieHeaders(requestHeaders.get("Cookie")); + } + Map queryParams = new HashMap<>(); String httpMethod = startLine.get(HTTP_METHOD_INDEX); @@ -90,13 +95,17 @@ public void process(final Socket connection) { if (Objects.nonNull(handler) && httpMethod.equals("POST") && requestedUrl.equals("/login")) { LoginController loginController = (LoginController) handler; - LoginResponseDto loginDto = loginController.login(parsedBody.get("account"), + LoginResponseDto loginDto = loginController.login(httpCookie, parsedBody.get("account"), parsedBody.get("password")); response = String.join("\r\n", "HTTP/1.1 302 Found ", - String.format("Location: %s ", loginDto.getRedirectUrl()), - ""); + String.format("Location: %s \r\n", loginDto.getRedirectUrl())); + + if (!httpCookie.isEmpty()) { + response += httpCookie.createSetCookieHeader(); + } + response += ""; } if (Objects.nonNull(handler) && httpMethod.equals("POST") 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 751b267cf8..51b5a463cb 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 @@ -167,9 +167,9 @@ void loginSuccessTest() { // then var expected = "HTTP/1.1 302 Found \r\n" + "Location: /index.html \r\n" + - ""; + "Set-Cookie: "; - assertThat(socket.output()).isEqualTo(expected); + assertThat(socket.output()).contains(expected); } @Test From 34ead5afe91b4c3891d1d76243ee78c8d4e8f484 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Mon, 4 Sep 2023 14:29:39 +0900 Subject: [PATCH 16/31] =?UTF-8?q?feat:=20session,=20sessionManager=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/org/apache/catalina/Manager.java | 9 +++--- .../org/apache/coyote/http11/Session.java | 26 +++++++++++++++++ .../apache/coyote/http11/SessionManager.java | 28 +++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/org/apache/coyote/http11/Session.java create mode 100644 tomcat/src/main/java/nextstep/org/apache/coyote/http11/SessionManager.java diff --git a/tomcat/src/main/java/nextstep/org/apache/catalina/Manager.java b/tomcat/src/main/java/nextstep/org/apache/catalina/Manager.java index d2a00cbb90..c3dd6088d5 100644 --- a/tomcat/src/main/java/nextstep/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/nextstep/org/apache/catalina/Manager.java @@ -1,8 +1,7 @@ package nextstep.org.apache.catalina; -import jakarta.servlet.http.HttpSession; - import java.io.IOException; +import nextstep.org.apache.coyote.http11.Session; /** * A Manager manages the pool of Sessions that are associated with a @@ -29,7 +28,7 @@ public interface Manager { * * @param session Session to be added */ - void add(HttpSession session); + void add(Session session); /** * Return the active Session, associated with this Manager, with the @@ -45,12 +44,12 @@ public interface Manager { * @return the request session or {@code null} if a session with the * requested ID could not be found */ - HttpSession findSession(String id) throws IOException; + Session findSession(String id) throws IOException; /** * Remove this Session from the active Sessions for this Manager. * * @param session Session to be removed */ - void remove(HttpSession session); + void remove(Session session); } diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Session.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Session.java new file mode 100644 index 0000000000..0622a2ec32 --- /dev/null +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Session.java @@ -0,0 +1,26 @@ +package nextstep.org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class Session { + + private final String id; + private final Map values = new HashMap<>(); + + public Session(final String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public Object getAttribute(final String name) { + return values.get(name); + } + + public void setAttribute(final String name, final Object value) { + values.put(name, value); + } +} diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/SessionManager.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/SessionManager.java new file mode 100644 index 0000000000..cbafda4e68 --- /dev/null +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/SessionManager.java @@ -0,0 +1,28 @@ +package nextstep.org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; +import nextstep.org.apache.catalina.Manager; + +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); + } + + @Override + public void remove(Session session) { + SESSIONS.remove(session.getId()); + } +} + From 46767ebb20a6ad0e0bcd48cd756e0e16278f8e65 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Mon, 4 Sep 2023 14:30:17 +0900 Subject: [PATCH 17/31] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20session=EC=97=90=20user?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EB=93=B1=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/LoginController.java | 6 +- .../nextstep/jwp/service/LoginService.java | 14 ++++- .../org/apache/coyote/http11/HttpCookie.java | 7 ++- .../coyote/http11/Http11ProcessorTest.java | 56 +++++++++++++++++-- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index 48aa71046e..04c979af46 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -1,11 +1,11 @@ package nextstep.jwp.controller; import java.util.NoSuchElementException; -import java.util.UUID; import nextstep.jwp.dto.LoginResponseDto; import nextstep.jwp.exception.InvalidLoginInfoException; import nextstep.jwp.service.LoginService; import nextstep.org.apache.coyote.http11.HttpCookie; +import nextstep.org.apache.coyote.http11.Session; public class LoginController { @@ -13,8 +13,8 @@ public class LoginController { public LoginResponseDto login(HttpCookie httpCookie, String account, String password) { try { - loginService.login(account, password); - httpCookie.set("JSESSIONID", UUID.randomUUID().toString()); + Session loginSession = loginService.login(account, password); + httpCookie.set("JSESSIONID", loginSession.getId()); return new LoginResponseDto("/index.html"); } catch (NoSuchElementException | InvalidLoginInfoException | NullPointerException e) { return new LoginResponseDto("/401.html"); diff --git a/tomcat/src/main/java/nextstep/jwp/service/LoginService.java b/tomcat/src/main/java/nextstep/jwp/service/LoginService.java index 6b18545376..f2f1c0a678 100644 --- a/tomcat/src/main/java/nextstep/jwp/service/LoginService.java +++ b/tomcat/src/main/java/nextstep/jwp/service/LoginService.java @@ -1,8 +1,11 @@ package nextstep.jwp.service; +import java.util.UUID; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.InvalidLoginInfoException; import nextstep.jwp.model.User; +import nextstep.org.apache.coyote.http11.Session; +import nextstep.org.apache.coyote.http11.SessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,13 +13,22 @@ public class LoginService { private final Logger log = LoggerFactory.getLogger(LoginService.class); - public void login(String account, String password) { + public Session login(String account, String password) { User user = InMemoryUserRepository.findByAccount(account) .orElseThrow(); if (!user.checkPassword(password)) { throw new InvalidLoginInfoException(); } log.info(user.toString()); + return createSession(user); + } + + private Session createSession(User user) { + SessionManager sessionManager = new SessionManager(); + Session session = new Session(UUID.randomUUID().toString()); + session.setAttribute(user.getAccount(), user); + sessionManager.add(session); + return session; } public void register(String account, String password, String email) { diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java index 0f06d724c5..630ab614a5 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java @@ -8,12 +8,13 @@ public class HttpCookie { public static final String SET_COOKIE_HEADER = "Set-Cookie: %s \r\n"; - private final Map cookie = new HashMap<>(); public static final String COOKIE_DELIMITER = "; "; public static final String KEY_VALUE_DELIMITER = "="; public static final int KEY_INDEX = 0; public static final int VALUE_INDEX = 1; + private final Map cookie = new HashMap<>(); + public void parseCookieHeaders(String cookieHeaders) { Arrays.asList(cookieHeaders.split(COOKIE_DELIMITER)).forEach(header -> { String[] splited = header.split(KEY_VALUE_DELIMITER); @@ -25,6 +26,10 @@ public void set(String key, String value) { cookie.put(key, value); } + public String get(String key) { + return cookie.get(key); + } + public boolean isEmpty() { return cookie.isEmpty(); } 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 51b5a463cb..d0a4372781 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,14 +1,18 @@ package nextstep.org.apache.coyote.http11; -import support.StubSocket; -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.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import nextstep.jwp.model.User; +import org.junit.jupiter.api.Test; +import support.StubSocket; class Http11ProcessorTest { @@ -35,7 +39,7 @@ void process() { @Test void index() throws IOException { // given - final String httpRequest= String.join("\r\n", + final String httpRequest = String.join("\r\n", "GET /index.html HTTP/1.1 ", "Host: localhost:8080 ", "Connection: keep-alive ", @@ -55,7 +59,7 @@ void index() throws IOException { "Content-Type: text/html;charset=utf-8 \r\n" + // "Content-Length: 5564 \r\n" + String.format("Content-Length: %d \r\n", file.length()) + - "\r\n"+ + "\r\n" + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); assertThat(socket.output()).isEqualTo(expected); @@ -225,4 +229,44 @@ void registerSuccessTest() { assertThat(socket.output()).isEqualTo(expected); } + + @Test + void sessionTest() { + // given + final String requestBody = "account=gugu&password=password"; + final String httpRequest = String.join("\r\n", + "POST /login HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + String.format("Content-Length: %d ", requestBody.getBytes().length), + "", + requestBody); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + SessionManager sessionManager = new SessionManager(); + String output = socket.output(); + + // Todo : httpHeaderParser 구현해서 분리하기 + Map requestHeaders = new HashMap<>(); + List splited = Arrays.asList(output.split("\r\n")); + List headers = splited.subList(1, splited.size()); + for (String line : headers) { + System.out.println("line: " + line); + String[] splitedLine = line.split(": "); + requestHeaders.put(splitedLine[0], splitedLine[1].strip()); + } + + HttpCookie httpCookie = new HttpCookie(); + httpCookie.parseCookieHeaders(requestHeaders.get("Set-Cookie")); + String jSessionId = httpCookie.get("JSESSIONID"); + User user = (User) sessionManager.findSession(jSessionId).getAttribute("gugu"); + + assertThat(user).isNotNull(); + } } From f03a1a7a02eb8880748f1ac073e708ac132a1a1e Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Mon, 4 Sep 2023 15:19:40 +0900 Subject: [PATCH 18/31] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=90=9C=20=EC=83=81=ED=83=9C=EB=A1=9C=20login=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A0=91=EA=B7=BC=20=EC=8B=9C=20index.htm?= =?UTF-8?q?l=EB=A1=9C=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 12 ++++ .../org/apache/coyote/http11/HttpCookie.java | 4 ++ .../coyote/http11/Http11ProcessorTest.java | 58 +++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index bec6761688..72145f6ff0 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -122,7 +122,19 @@ public void process(final Socket connection) { if (httpMethod.equals("GET") && Objects.isNull(response)) { String contentType = negotiateContent(requestHeaders.get("Accept")); + // Todo: createResponseBody() pageController로 위임해보기 + // Todo: 헤더에 담긴 sessionId 유효성 검증 + if (requestedUrl.contains("/login") && httpCookie.hasCookie("JSESSIONID")) { + response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Location: /index.html ", + ""); + + writeResponse(outputStream, response); + return; + } + String responseBody = createResponseBody(requestedUrl); response = String.join("\r\n", "HTTP/1.1 200 OK ", diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java index 630ab614a5..4a2ddd7567 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java @@ -34,6 +34,10 @@ public boolean isEmpty() { return cookie.isEmpty(); } + public boolean hasCookie(String key) { + return cookie.containsKey(key); + } + public String createSetCookieHeader() { String cookies = cookie.entrySet().stream() .map(entry -> entry.getKey() + KEY_VALUE_DELIMITER + entry.getValue()) 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 d0a4372781..20b375ee86 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 @@ -269,4 +269,62 @@ void sessionTest() { assertThat(user).isNotNull(); } + + @Test + void loginRedirectionWithSessionTest() { + // given + String jSessionId = loginAndGetSessionId(); + + final String httpGetRequest = String.join("\r\n", + "GET /login.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + String.format("Cookie: JSESSIONID=%s", jSessionId), + "", + ""); + + // when + final var socket = new StubSocket(httpGetRequest); + final Http11Processor processor = new Http11Processor(socket); + + processor.process(socket); + + // then + var expected = "HTTP/1.1 302 Found \r\n" + + "Location: /index.html \r\n" + + ""; + + assertThat(socket.output()).isEqualTo(expected); + } + + private String loginAndGetSessionId() { + final String requestBody = "account=gugu&password=password"; + final String httpRequest = String.join("\r\n", + "POST /login HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + String.format("Content-Length: %d ", requestBody.getBytes().length), + "", + requestBody); + + var preSocket = new StubSocket(httpRequest); + Http11Processor preProcessor = new Http11Processor(preSocket); + + preProcessor.process(preSocket); + String preOutput = preSocket.output(); + + // Todo : httpHeaderParser 구현해서 분리하기 + Map requestHeaders = new HashMap<>(); + List splited = Arrays.asList(preOutput.split("\r\n")); + List headers = splited.subList(1, splited.size()); + for (String line : headers) { + System.out.println("line: " + line); + String[] splitedLine = line.split(": "); + requestHeaders.put(splitedLine[0], splitedLine[1].strip()); + } + + HttpCookie httpCookie = new HttpCookie(); + httpCookie.parseCookieHeaders(requestHeaders.get("Set-Cookie")); + return httpCookie.get("JSESSIONID"); + } } From 6a30330c8bcd08524bfdc8adb9af7c31b64f5f63 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Mon, 4 Sep 2023 16:20:01 +0900 Subject: [PATCH 19/31] =?UTF-8?q?feat:=20=EC=9A=B4=EC=98=81=EC=B2=B4?= =?UTF-8?q?=EC=A0=9C=20=ED=8C=8C=EC=9D=BC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EC=B0=A8=EC=9D=B4=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=8B=A4=ED=8C=A8=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/Http11Processor.java | 9 ++++----- .../org/apache/coyote/http11/Http11ProcessorTest.java | 10 ++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 72145f6ff0..f0bd6d53f4 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -1,6 +1,7 @@ package nextstep.org.apache.coyote.http11; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -9,8 +10,6 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -95,7 +94,8 @@ public void process(final Socket connection) { if (Objects.nonNull(handler) && httpMethod.equals("POST") && requestedUrl.equals("/login")) { LoginController loginController = (LoginController) handler; - LoginResponseDto loginDto = loginController.login(httpCookie, parsedBody.get("account"), + LoginResponseDto loginDto = loginController.login(httpCookie, + parsedBody.get("account"), parsedBody.get("password")); response = String.join("\r\n", @@ -194,10 +194,9 @@ private String createResponseBody(String requestURL) { resourceName += ".html"; } URL resource = getClass().getClassLoader().getResource(resourceName); - Path path = Paths.get(resource.getPath().substring(1)); try { - return Files.readString(path); + return new String(Files.readAllBytes(new File(resource.getFile()).toPath())); } catch (IOException | NullPointerException e) { throw new RuntimeException(e); } 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 20b375ee86..562125d86a 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 @@ -54,7 +54,7 @@ void index() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/index.html"); - File file = new File(resource.getPath()); + File file = new File(resource.getFile()); var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html;charset=utf-8 \r\n" + // "Content-Length: 5564 \r\n" + @@ -84,7 +84,7 @@ void css() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/css/styles.css"); - File file = new File(resource.getPath()); + File file = new File(resource.getFile()); var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/css;charset=utf-8 \r\n" + String.format("Content-Length: %d \r\n", file.length()) + @@ -112,7 +112,7 @@ void login() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/login.html"); - File file = new File(resource.getPath()); + File file = new File(resource.getFile()); var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html;charset=utf-8 \r\n" + String.format("Content-Length: %d \r\n", file.length()) + @@ -140,7 +140,7 @@ void register() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/register.html"); - File file = new File(resource.getPath()); + File file = new File(resource.getFile()); var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html;charset=utf-8 \r\n" + String.format("Content-Length: %d \r\n", file.length()) + @@ -257,7 +257,6 @@ void sessionTest() { List splited = Arrays.asList(output.split("\r\n")); List headers = splited.subList(1, splited.size()); for (String line : headers) { - System.out.println("line: " + line); String[] splitedLine = line.split(": "); requestHeaders.put(splitedLine[0], splitedLine[1].strip()); } @@ -318,7 +317,6 @@ private String loginAndGetSessionId() { List splited = Arrays.asList(preOutput.split("\r\n")); List headers = splited.subList(1, splited.size()); for (String line : headers) { - System.out.println("line: " + line); String[] splitedLine = line.split(": "); requestHeaders.put(splitedLine[0], splitedLine[1].strip()); } From cb877f662a94719dad26964ad24965e3a508966f Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Tue, 5 Sep 2023 13:57:20 +0900 Subject: [PATCH 20/31] =?UTF-8?q?refactor:=20httpRequest=20=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20startLine=20=ED=8C=8C=EC=8B=B1=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=B1=85=EC=9E=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 22 +++----- .../org/apache/coyote/http11/StartLine.java | 55 +++++++++++++++++++ 2 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index f0bd6d53f4..bf0a5940fb 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -25,8 +25,6 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - public static final int HTTP_METHOD_INDEX = 0; - public static final int URL_INDEX = 1; public static final int KEY_INDEX = 0; public static final int VALUE_INDEX = 1; public static final String EMPTY_LINE = ""; @@ -55,7 +53,7 @@ public void process(final Socket connection) { InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader) ) { - List startLine = Arrays.asList(bufferedReader.readLine().split(" ")); + StartLine startLine = new StartLine(bufferedReader.readLine()); Map requestHeaders = extractHeaders(bufferedReader); Map parsedBody = new HashMap<>(); @@ -73,25 +71,19 @@ public void process(final Socket connection) { } Map queryParams = new HashMap<>(); - - String httpMethod = startLine.get(HTTP_METHOD_INDEX); - String requestedUrl = startLine.get(URL_INDEX); - int queryParamIndex = requestedUrl.indexOf('?'); - - if (0 < queryParamIndex) { - String queryParamValues = requestedUrl.substring(queryParamIndex + 1); - Arrays.asList(queryParamValues.split("&")) + if (startLine.hasQueryString()) { + Arrays.asList(startLine.getQueryString().split("&")) .forEach(queryParam -> { String[] splited = queryParam.split("="); queryParams.put(splited[KEY_INDEX], splited[VALUE_INDEX]); }); - requestedUrl = requestedUrl.substring(0, queryParamIndex); } String response = null; + String requestedUrl = startLine.getUrl(); Object handler = handlerMapper.mapHandler(requestedUrl); - if (Objects.nonNull(handler) && httpMethod.equals("POST") + if (Objects.nonNull(handler) && startLine.getHttpMethod().equals("POST") && requestedUrl.equals("/login")) { LoginController loginController = (LoginController) handler; LoginResponseDto loginDto = loginController.login(httpCookie, @@ -108,7 +100,7 @@ public void process(final Socket connection) { response += ""; } - if (Objects.nonNull(handler) && httpMethod.equals("POST") + if (Objects.nonNull(handler) && startLine.getHttpMethod().equals("POST") && requestedUrl.equals("/register")) { LoginController loginController = (LoginController) handler; LoginResponseDto loginDto = loginController.register(parsedBody.get("account"), @@ -120,7 +112,7 @@ public void process(final Socket connection) { ""); } - if (httpMethod.equals("GET") && Objects.isNull(response)) { + if (startLine.getHttpMethod().equals("GET") && Objects.isNull(response)) { String contentType = negotiateContent(requestHeaders.get("Accept")); // Todo: createResponseBody() pageController로 위임해보기 diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java new file mode 100644 index 0000000000..0c6145da4a --- /dev/null +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java @@ -0,0 +1,55 @@ +package nextstep.org.apache.coyote.http11; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class StartLine { + + private static final String START_LINE_DELIMITER = " "; + public static final int HTTP_METHOD_INDEX = 0; + public static final int URL_INDEX = 1; + public static final int HTTP_VERSION_INDEX = 2; + public static final String QUERY_PARAM_DELIMITER = "?"; + + private String httpMethod; + private String url; + private String httpVersion; + private String queryString = null; + + public StartLine(String startLine) { + List parsed = Arrays.asList(startLine.split(START_LINE_DELIMITER)); + httpMethod = parsed.get(HTTP_METHOD_INDEX); + parseUrl(parsed); + httpVersion = parsed.get(HTTP_VERSION_INDEX); + } + + private void parseUrl(List parsed) { + url = parsed.get(URL_INDEX); + if (url.contains(QUERY_PARAM_DELIMITER)) { + int queryParamIndex = url.indexOf(QUERY_PARAM_DELIMITER); + url = parsed.get(URL_INDEX).substring(0, queryParamIndex); + queryString = parsed.get(queryParamIndex + 1); + } + } + + public boolean hasQueryString() { + return Objects.nonNull(queryString); + } + + public String getHttpMethod() { + return httpMethod; + } + + public String getUrl() { + return url; + } + + public String getQueryString() { + return queryString; + } + + public String getHttpVersion() { + return httpVersion; + } +} From d89f258c528aae8c6b7362f7a5b74108cf3143fa Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Tue, 5 Sep 2023 23:18:30 +0900 Subject: [PATCH 21/31] =?UTF-8?q?test:=20cache=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A7=84=ED=96=89=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cachecontrol/CacheInterceptor.java | 20 +++++++++++++++++++ .../example/cachecontrol/CacheWebConfig.java | 1 + .../example/etag/EtagFilterConfiguration.java | 14 +++++++++---- .../version/CacheBustingWebConfig.java | 10 +++++++++- study/src/main/resources/application.yml | 3 +++ study/src/main/resources/static/js/index.js | 2 +- 6 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 study/src/main/java/cache/com/example/cachecontrol/CacheInterceptor.java diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/CacheInterceptor.java new file mode 100644 index 0000000000..fde3571192 --- /dev/null +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheInterceptor.java @@ -0,0 +1,20 @@ +package cache.com.example.cachecontrol; + +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.http.CacheControl; +import org.springframework.web.servlet.HandlerInterceptor; + +public class CacheInterceptor implements HandlerInterceptor { + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) throws Exception { + if (Objects.isNull(response.getHeader("Cache-Control"))) { + response.addHeader("Cache-Control", + CacheControl.noCache().cachePrivate().getHeaderValue()); + } + HandlerInterceptor.super.afterCompletion(request, response, handler, ex); + } +} diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java index 305b1f1e1e..eaac52447f 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -9,5 +9,6 @@ public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(new CacheInterceptor()); } } diff --git a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java index 41ef7a3d9a..d4b86b7825 100644 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java @@ -1,12 +1,18 @@ package cache.com.example.etag; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ShallowEtagHeaderFilter; @Configuration public class EtagFilterConfiguration { -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + FilterRegistrationBean filterRegistrationBean = + new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); + filterRegistrationBean.addUrlPatterns("/etag", "/resources/*"); + return filterRegistrationBean; + } } diff --git a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java index 6da6d2c795..d6f293ee45 100644 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java @@ -1,7 +1,9 @@ package cache.com.example.version; +import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -20,6 +22,12 @@ public CacheBustingWebConfig(ResourceVersion version) { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .addResourceLocations("classpath:/static/") + // ETag를 사용하니까 Last-Modified는 불필요하지 않을까? + // Last-Modified를 추가하면 캐싱 외에도 크롤러에게 마지막 수정 시간을 알려주어 크롤링 빈도를 조정할 수 있다. + // 자세한 내용은 아래 링크를 참고하자. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#sect1 + .setUseLastModified(true) + .setCacheControl(CacheControl.maxAge(365L, TimeUnit.DAYS).cachePublic()); } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..385c11d5f1 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -7,3 +7,6 @@ server: max-connections: 1 threads: max: 2 + compression: + enabled: true + min-response-size: 10 diff --git a/study/src/main/resources/static/js/index.js b/study/src/main/resources/static/js/index.js index 5893f9d0d5..6be02374db 100644 --- a/study/src/main/resources/static/js/index.js +++ b/study/src/main/resources/static/js/index.js @@ -1 +1 @@ -console.log('hello world'); \ No newline at end of file +console.log('hello world'); From f2a5b44beead0594d59fd609062c78a1f01661d1 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 16:15:15 +0900 Subject: [PATCH 22/31] =?UTF-8?q?style:=20=EC=83=81=EC=88=98=20=EB=85=BC?= =?UTF-8?q?=EB=A6=AC=EC=A0=81=20=EA=B5=AC=EB=B6=84=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EA=B3=B5=EB=B0=B1=20=EB=9D=BC=EC=9D=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/org/apache/coyote/http11/Http11Processor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index bf0a5940fb..5c7fa67f23 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -25,6 +25,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + public static final int KEY_INDEX = 0; public static final int VALUE_INDEX = 1; public static final String EMPTY_LINE = ""; From 53ba652d96d4306587ad658ccfb7629dd0043261 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 16:18:51 +0900 Subject: [PATCH 23/31] =?UTF-8?q?style:=20=EC=A7=81=EA=B4=80=EC=84=B1=20?= =?UTF-8?q?=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/org/apache/coyote/http11/Http11Processor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 5c7fa67f23..0b819b3e45 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -29,7 +29,7 @@ public class Http11Processor implements Runnable, Processor { public static final int KEY_INDEX = 0; public static final int VALUE_INDEX = 1; public static final String EMPTY_LINE = ""; - public static final String RESOURCES_PREFIX = "static"; + public static final String RESOURCES_PATH_PREFIX = "static"; public static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; private final Socket connection; @@ -182,7 +182,7 @@ private String createResponseBody(String requestURL) { return "Hello world!"; } - String resourceName = RESOURCES_PREFIX + requestURL; + String resourceName = RESOURCES_PATH_PREFIX + requestURL; if (!resourceName.contains(".")) { resourceName += ".html"; } From e42d489fbf663d5e2b002823bfcdad39b244df28 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 16:23:17 +0900 Subject: [PATCH 24/31] =?UTF-8?q?refactor:=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=EA=B0=80=20=EC=9E=98=EB=AA=BB=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=EB=90=9C=20=EC=83=81=EC=88=98=EB=93=A4=EC=9D=84=20?= =?UTF-8?q?=EC=A0=84=EB=B6=80=20private=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/Http11Processor.java | 10 +++++----- .../nextstep/org/apache/coyote/http11/HttpCookie.java | 10 +++++----- .../nextstep/org/apache/coyote/http11/StartLine.java | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 0b819b3e45..86dffaa300 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -26,11 +26,11 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - public static final int KEY_INDEX = 0; - public static final int VALUE_INDEX = 1; - public static final String EMPTY_LINE = ""; - public static final String RESOURCES_PATH_PREFIX = "static"; - public static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + private static final String EMPTY_LINE = ""; + private static final String RESOURCES_PATH_PREFIX = "static"; + private static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; private final Socket connection; private final HandlerMapper handlerMapper; diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java index 4a2ddd7567..59b0c462da 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java @@ -7,11 +7,11 @@ public class HttpCookie { - public static final String SET_COOKIE_HEADER = "Set-Cookie: %s \r\n"; - public static final String COOKIE_DELIMITER = "; "; - public static final String KEY_VALUE_DELIMITER = "="; - public static final int KEY_INDEX = 0; - public static final int VALUE_INDEX = 1; + private static final String SET_COOKIE_HEADER = "Set-Cookie: %s \r\n"; + private static final String COOKIE_DELIMITER = "; "; + private static final String KEY_VALUE_DELIMITER = "="; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; private final Map cookie = new HashMap<>(); diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java index 0c6145da4a..ffb313eb63 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java @@ -7,10 +7,10 @@ public class StartLine { private static final String START_LINE_DELIMITER = " "; - public static final int HTTP_METHOD_INDEX = 0; - public static final int URL_INDEX = 1; - public static final int HTTP_VERSION_INDEX = 2; - public static final String QUERY_PARAM_DELIMITER = "?"; + private static final int HTTP_METHOD_INDEX = 0; + private static final int URL_INDEX = 1; + private static final int HTTP_VERSION_INDEX = 2; + private static final String QUERY_PARAM_DELIMITER = "?"; private String httpMethod; private String url; From 3bbf65945a355a1c3230f0c291a28de3db3c5a6e Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 21:05:22 +0900 Subject: [PATCH 25/31] =?UTF-8?q?refactor:=20startLine=EC=97=90=EC=84=9C?= =?UTF-8?q?=20url=20=EB=B3=80=EC=88=98=EB=AA=85=EC=9D=84=20path=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 12 +++++------ .../org/apache/coyote/http11/StartLine.java | 20 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 86dffaa300..42f3df839f 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -81,11 +81,11 @@ public void process(final Socket connection) { } String response = null; - String requestedUrl = startLine.getUrl(); + String requestPath = startLine.getPath(); - Object handler = handlerMapper.mapHandler(requestedUrl); + Object handler = handlerMapper.mapHandler(requestPath); if (Objects.nonNull(handler) && startLine.getHttpMethod().equals("POST") - && requestedUrl.equals("/login")) { + && requestPath.equals("/login")) { LoginController loginController = (LoginController) handler; LoginResponseDto loginDto = loginController.login(httpCookie, parsedBody.get("account"), @@ -102,7 +102,7 @@ public void process(final Socket connection) { } if (Objects.nonNull(handler) && startLine.getHttpMethod().equals("POST") - && requestedUrl.equals("/register")) { + && requestPath.equals("/register")) { LoginController loginController = (LoginController) handler; LoginResponseDto loginDto = loginController.register(parsedBody.get("account"), parsedBody.get("password"), parsedBody.get("email")); @@ -118,7 +118,7 @@ public void process(final Socket connection) { // Todo: createResponseBody() pageController로 위임해보기 // Todo: 헤더에 담긴 sessionId 유효성 검증 - if (requestedUrl.contains("/login") && httpCookie.hasCookie("JSESSIONID")) { + if (requestPath.contains("/login") && httpCookie.hasCookie("JSESSIONID")) { response = String.join("\r\n", "HTTP/1.1 302 Found ", "Location: /index.html ", @@ -128,7 +128,7 @@ public void process(final Socket connection) { return; } - String responseBody = createResponseBody(requestedUrl); + String responseBody = createResponseBody(requestPath); response = String.join("\r\n", "HTTP/1.1 200 OK ", String.format("Content-Type: %s;charset=utf-8 ", contentType), diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java index ffb313eb63..d2db69776d 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/StartLine.java @@ -8,27 +8,27 @@ public class StartLine { private static final String START_LINE_DELIMITER = " "; private static final int HTTP_METHOD_INDEX = 0; - private static final int URL_INDEX = 1; + private static final int REQUEST_TARGET_INDEX = 1; private static final int HTTP_VERSION_INDEX = 2; private static final String QUERY_PARAM_DELIMITER = "?"; private String httpMethod; - private String url; + private String path; private String httpVersion; private String queryString = null; public StartLine(String startLine) { List parsed = Arrays.asList(startLine.split(START_LINE_DELIMITER)); httpMethod = parsed.get(HTTP_METHOD_INDEX); - parseUrl(parsed); + parsePath(parsed); httpVersion = parsed.get(HTTP_VERSION_INDEX); } - private void parseUrl(List parsed) { - url = parsed.get(URL_INDEX); - if (url.contains(QUERY_PARAM_DELIMITER)) { - int queryParamIndex = url.indexOf(QUERY_PARAM_DELIMITER); - url = parsed.get(URL_INDEX).substring(0, queryParamIndex); + private void parsePath(List parsed) { + path = parsed.get(REQUEST_TARGET_INDEX); + if (path.contains(QUERY_PARAM_DELIMITER)) { + int queryParamIndex = path.indexOf(QUERY_PARAM_DELIMITER); + path = parsed.get(REQUEST_TARGET_INDEX).substring(0, queryParamIndex); queryString = parsed.get(queryParamIndex + 1); } } @@ -41,8 +41,8 @@ public String getHttpMethod() { return httpMethod; } - public String getUrl() { - return url; + public String getPath() { + return path; } public String getQueryString() { From b87e00dfbae00f77110b708bd4ea5b46f04093da Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 21:15:08 +0900 Subject: [PATCH 26/31] =?UTF-8?q?style:=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B7=B8=EB=8B=88=EC=B2=98=20=EA=B0=80=EB=8F=85?= =?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/org/apache/coyote/http11/Http11Processor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 42f3df839f..7812165db9 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -144,9 +144,10 @@ public void process(final Socket connection) { } } - private String extractRequestBody(BufferedReader bufferedReader, - Map requestHeaders) - throws IOException { + private String extractRequestBody( + BufferedReader bufferedReader, + Map requestHeaders + ) throws IOException { int contentLength = Integer.parseInt(requestHeaders.get("Content-Length")); char[] buffer = new char[contentLength]; bufferedReader.read(buffer, 0, contentLength); From e879d0f2938fbfc3ff6a6fb01a8b883b256735a4 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 21:24:20 +0900 Subject: [PATCH 27/31] =?UTF-8?q?refactor:=20contentType=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/org/apache/coyote/http11/Http11Processor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 7812165db9..b0ade1ea54 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -114,7 +114,7 @@ public void process(final Socket connection) { } if (startLine.getHttpMethod().equals("GET") && Objects.isNull(response)) { - String contentType = negotiateContent(requestHeaders.get("Accept")); + String contentType = selectFirstContentTypeOrDefault(requestHeaders.get("Accept")); // Todo: createResponseBody() pageController로 위임해보기 // Todo: 헤더에 담긴 sessionId 유효성 검증 @@ -159,7 +159,7 @@ private void writeResponse(OutputStream outputStream, String response) throws IO outputStream.flush(); } - private String negotiateContent(String acceptHeader) { + private String selectFirstContentTypeOrDefault(String acceptHeader) { if (Objects.isNull(acceptHeader)) { return "text/html"; } From 2840ed44f69064aa750b9ef68445fe2d60c1925f Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 21:27:10 +0900 Subject: [PATCH 28/31] =?UTF-8?q?refactor:=20=EC=A0=81=EC=A0=88=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/org/apache/coyote/http11/Http11Processor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index b0ade1ea54..992c3e71ff 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -163,8 +163,8 @@ private String selectFirstContentTypeOrDefault(String acceptHeader) { if (Objects.isNull(acceptHeader)) { return "text/html"; } - List splitUrl = Arrays.asList(acceptHeader.split(",")); - return splitUrl.get(ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX); + List acceptHeaderValues = Arrays.asList(acceptHeader.split(",")); + return acceptHeaderValues.get(ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX); } private Map extractHeaders(BufferedReader bufferedReader) throws IOException { From 301ad15ae72749378172eee54e0953e8348aa91e Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Wed, 6 Sep 2023 22:46:33 +0900 Subject: [PATCH 29/31] =?UTF-8?q?refactor:=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20404=20status=EC=99=80=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 28 +++++++++++++------ .../coyote/http11/Http11ProcessorTest.java | 28 +++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 992c3e71ff..4bbc61359c 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -129,6 +129,20 @@ public void process(final Socket connection) { } String responseBody = createResponseBody(requestPath); + if (Objects.isNull(responseBody)) { + responseBody = createResponseBody("/404.html"); + + response = String.join("\r\n", + "HTTP/1.1 404 Not Found ", + String.format("Content-Type: %s;charset=utf-8 ", contentType), + String.format("Content-Length: %s ", + responseBody.getBytes(StandardCharsets.UTF_8).length), + "", + responseBody); + writeResponse(outputStream, response); + return; + } + response = String.join("\r\n", "HTTP/1.1 200 OK ", String.format("Content-Type: %s;charset=utf-8 ", contentType), @@ -177,22 +191,20 @@ private Map extractHeaders(BufferedReader bufferedReader) throws return requestHeaders; } - - private String createResponseBody(String requestURL) { - if (requestURL.equals("/")) { + private String createResponseBody(String requestPath) throws IOException { + if (requestPath.equals("/")) { return "Hello world!"; } - String resourceName = RESOURCES_PATH_PREFIX + requestURL; + String resourceName = RESOURCES_PATH_PREFIX + requestPath; if (!resourceName.contains(".")) { resourceName += ".html"; } URL resource = getClass().getClassLoader().getResource(resourceName); - try { - return new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException | NullPointerException e) { - throw new RuntimeException(e); + if (Objects.isNull(resource)) { + return null; } + return new String(Files.readAllBytes(new File(resource.getFile()).toPath())); } } 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 562125d86a..05dda79a10 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 @@ -296,6 +296,34 @@ void loginRedirectionWithSessionTest() { assertThat(socket.output()).isEqualTo(expected); } + @Test + void wrongPathReturnNotFoundPageTest() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /nofile.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/404.html"); + File file = new File(resource.getFile()); + var expected = "HTTP/1.1 404 Not Found \r\n" + + "Content-Type: text/html;charset=utf-8 \r\n" + + String.format("Content-Length: %d \r\n", file.length()) + + "\r\n" + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } + private String loginAndGetSessionId() { final String requestBody = "account=gugu&password=password"; final String httpRequest = String.join("\r\n", From b27ca881e657f0daa5ecf079be2067ee3d7dcd43 Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Thu, 7 Sep 2023 01:05:43 +0900 Subject: [PATCH 30/31] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20values=20=ED=8C=8C=EC=8B=B1=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?util=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 17 +++++++------ .../org/apache/coyote/http11/HttpCookie.java | 21 +++++++--------- .../org/apache/coyote/http11/HttpUtil.java | 24 +++++++++++++++++++ 3 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpUtil.java diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 4bbc61359c..8306f531b4 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -1,5 +1,7 @@ package nextstep.org.apache.coyote.http11; +import static nextstep.org.apache.coyote.http11.HttpUtil.*; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -31,6 +33,8 @@ public class Http11Processor implements Runnable, Processor { private static final String EMPTY_LINE = ""; private static final String RESOURCES_PATH_PREFIX = "static"; private static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; + private static final String FORM_VALUES_DELIMITER = "&"; + private static final String FORM_KEY_VALUE_DELIMITER = "="; private final Socket connection; private final HandlerMapper handlerMapper; @@ -60,10 +64,8 @@ public void process(final Socket connection) { Map parsedBody = new HashMap<>(); if (requestHeaders.containsKey("Content-Length")) { String requestBody = extractRequestBody(bufferedReader, requestHeaders); - Arrays.asList(requestBody.split("&")).forEach(parsedData -> { - String[] splited = parsedData.split("="); - parsedBody.put(splited[KEY_INDEX], splited[VALUE_INDEX]); - }); + parseMultipleValues(parsedBody, + requestBody, FORM_VALUES_DELIMITER, FORM_KEY_VALUE_DELIMITER); } HttpCookie httpCookie = new HttpCookie(); @@ -73,11 +75,8 @@ public void process(final Socket connection) { Map queryParams = new HashMap<>(); if (startLine.hasQueryString()) { - Arrays.asList(startLine.getQueryString().split("&")) - .forEach(queryParam -> { - String[] splited = queryParam.split("="); - queryParams.put(splited[KEY_INDEX], splited[VALUE_INDEX]); - }); + parseMultipleValues(queryParams, + startLine.getQueryString(), "&", "="); } String response = null; diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java index 59b0c462da..48bfa7fb91 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpCookie.java @@ -1,6 +1,7 @@ package nextstep.org.apache.coyote.http11; -import java.util.Arrays; +import static nextstep.org.apache.coyote.http11.HttpUtil.parseMultipleValues; + import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; @@ -8,18 +9,14 @@ public class HttpCookie { private static final String SET_COOKIE_HEADER = "Set-Cookie: %s \r\n"; - private static final String COOKIE_DELIMITER = "; "; - private static final String KEY_VALUE_DELIMITER = "="; - private static final int KEY_INDEX = 0; - private static final int VALUE_INDEX = 1; + private static final String COOKIE_VALUES_DELIMITER = "; "; + private static final String COOKIE_KEY_VALUE_DELIMITER = "="; private final Map cookie = new HashMap<>(); - public void parseCookieHeaders(String cookieHeaders) { - Arrays.asList(cookieHeaders.split(COOKIE_DELIMITER)).forEach(header -> { - String[] splited = header.split(KEY_VALUE_DELIMITER); - cookie.put(splited[KEY_INDEX], splited[VALUE_INDEX]); - }); + public void parseCookieHeaders(String cookieHeaderValues) { + parseMultipleValues(cookie, + cookieHeaderValues, COOKIE_VALUES_DELIMITER, COOKIE_KEY_VALUE_DELIMITER); } public void set(String key, String value) { @@ -40,8 +37,8 @@ public boolean hasCookie(String key) { public String createSetCookieHeader() { String cookies = cookie.entrySet().stream() - .map(entry -> entry.getKey() + KEY_VALUE_DELIMITER + entry.getValue()) - .collect(Collectors.joining(COOKIE_DELIMITER)); + .map(entry -> entry.getKey() + COOKIE_KEY_VALUE_DELIMITER + entry.getValue()) + .collect(Collectors.joining(COOKIE_VALUES_DELIMITER)); return String.format(SET_COOKIE_HEADER, cookies); } } diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpUtil.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpUtil.java new file mode 100644 index 0000000000..ae8314dbb6 --- /dev/null +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/HttpUtil.java @@ -0,0 +1,24 @@ +package nextstep.org.apache.coyote.http11; + +import java.util.Arrays; +import java.util.Map; + +public class HttpUtil { + + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + + private HttpUtil() { + } + + public static void parseMultipleValues( + Map parsedValues, + String multipleValues, + String valuesDelimiter, String keyValueDelimiter + ) { + Arrays.asList(multipleValues.split(valuesDelimiter)).forEach(header -> { + String[] splited = header.split(keyValueDelimiter); + parsedValues.put(splited[KEY_INDEX], splited[VALUE_INDEX]); + }); + } +} From 3c0a31071758a38a173f6b8ab37fafaea793d83e Mon Sep 17 00:00:00 2001 From: MoonJeWoong Date: Thu, 7 Sep 2023 11:18:08 +0900 Subject: [PATCH 31/31] =?UTF-8?q?refactor:=20404=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=ED=96=89=20=EC=8B=9C=20NPE=20=EB=B0=9C=EC=83=9D=EC=9C=84?= =?UTF-8?q?=ED=97=98=20Optional=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java index 8306f531b4..c14153beb1 100644 --- a/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/nextstep/org/apache/coyote/http11/Http11Processor.java @@ -1,6 +1,6 @@ package nextstep.org.apache.coyote.http11; -import static nextstep.org.apache.coyote.http11.HttpUtil.*; +import static nextstep.org.apache.coyote.http11.HttpUtil.parseMultipleValues; import java.io.BufferedReader; import java.io.File; @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import nextstep.jwp.controller.LoginController; import nextstep.jwp.dto.LoginResponseDto; import nextstep.jwp.exception.UncheckedServletException; @@ -35,6 +36,7 @@ public class Http11Processor implements Runnable, Processor { private static final int ACCEPT_HEADER_BEST_CONTENT_TYPE_INDEX = 0; private static final String FORM_VALUES_DELIMITER = "&"; private static final String FORM_KEY_VALUE_DELIMITER = "="; + private static final String NOT_FOUND_DEFALULT_MESSAGE = "404 Not Found"; private final Socket connection; private final HandlerMapper handlerMapper; @@ -127,17 +129,18 @@ public void process(final Socket connection) { return; } - String responseBody = createResponseBody(requestPath); - if (Objects.isNull(responseBody)) { - responseBody = createResponseBody("/404.html"); + Optional responseBody = createResponseBody(requestPath); + if (responseBody.isEmpty()) { + String notFoundPageBody = createResponseBody("/404.html") + .orElse(NOT_FOUND_DEFALULT_MESSAGE); response = String.join("\r\n", "HTTP/1.1 404 Not Found ", String.format("Content-Type: %s;charset=utf-8 ", contentType), String.format("Content-Length: %s ", - responseBody.getBytes(StandardCharsets.UTF_8).length), + notFoundPageBody.getBytes(StandardCharsets.UTF_8).length), "", - responseBody); + notFoundPageBody); writeResponse(outputStream, response); return; } @@ -146,9 +149,9 @@ public void process(final Socket connection) { "HTTP/1.1 200 OK ", String.format("Content-Type: %s;charset=utf-8 ", contentType), String.format("Content-Length: %s ", - responseBody.getBytes(StandardCharsets.UTF_8).length), + responseBody.get().getBytes(StandardCharsets.UTF_8).length), "", - responseBody); + responseBody.get()); } writeResponse(outputStream, response); @@ -190,9 +193,9 @@ private Map extractHeaders(BufferedReader bufferedReader) throws return requestHeaders; } - private String createResponseBody(String requestPath) throws IOException { + private Optional createResponseBody(String requestPath) throws IOException { if (requestPath.equals("/")) { - return "Hello world!"; + return Optional.of("Hello world!"); } String resourceName = RESOURCES_PATH_PREFIX + requestPath; @@ -202,8 +205,8 @@ private String createResponseBody(String requestPath) throws IOException { URL resource = getClass().getClassLoader().getResource(resourceName); if (Objects.isNull(resource)) { - return null; + return Optional.empty(); } - return new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + return Optional.of(new String(Files.readAllBytes(new File(resource.getFile()).toPath()))); } }