From 436e7b0b6ca255e61822804c5da7135f97ff0c07 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 16:11:28 +0900 Subject: [PATCH 01/64] =?UTF-8?q?chore:=20.gitignore=20=ED=95=99=EC=8A=B5?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=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 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7ea3082447..d7b5634b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,6 @@ Icon Network Trash Folder Temporary Items .apdisk + +# 학습 테스트 +/study/ From ae9a4f45c61504aa8d2b708c4f52bb6fa5d64d93 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 16:13:17 +0900 Subject: [PATCH 02/64] =?UTF-8?q?feat:=20/index.html=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/UncheckedServletException.java | 4 + .../apache/coyote/http11/Http11Processor.java | 101 +++++++++++++++--- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java b/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java index 64466b42de..a66652ec3d 100644 --- a/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java +++ b/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java @@ -5,4 +5,8 @@ public class UncheckedServletException extends RuntimeException { public UncheckedServletException(Exception e) { super(e); } + + public UncheckedServletException(String message) { + super(message); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index bb14184757..71256cc0ae 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,17 +1,29 @@ package org.apache.coyote.http11; import com.techcourse.exception.UncheckedServletException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; 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); + private static final int REQUEST_LINE_PARAM_COUNT = 3; + private static final Set ALLOWED_METHODS = + Set.of("GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"); + private static final String HTTP_1_1 = "HTTP/1.1"; + private static final String STATIC_DIRNAME = "static"; + private final Socket connection; public Http11Processor(final Socket connection) { @@ -27,16 +39,11 @@ public void run() { @Override public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { + final var outputStream = connection.getOutputStream()) { - final var responseBody = "Hello world!"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); + final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + final String requestLine = readRequestLine(reader); + final String response = makeResponse(requestLine); outputStream.write(response.getBytes()); outputStream.flush(); @@ -44,4 +51,74 @@ public void process(final Socket connection) { log.error(e.getMessage(), e); } } + + private String readRequestLine(BufferedReader reader) { + try { + return reader.readLine(); + } catch (IOException e) { + throw new UncheckedServletException("HTTP 요청의 request line을 읽을 수 없습니다."); + } + } + + private String makeResponse(String requestLine) { + String[] params = requestLine.split(" "); + validateParamCount(params); + + String method = params[0]; + String requestUri = params[1]; + String httpVersion = params[2]; + validateFormat(method, requestUri, httpVersion); + + String responseBody = ""; + if (requestUri.equals("/")) { + responseBody = "Hello world!"; + } + + if (requestUri.equals("/index.html")) { + responseBody = readResponseBodyFromFileName("index.html"); + } + + return String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + } + + private static void validateParamCount(String[] params) { + if (params.length != REQUEST_LINE_PARAM_COUNT) { + throw new UncheckedServletException( + String.format( + "Request line의 인자는 %d개여야 합니다. 입력된 인자 개수 = %d", + REQUEST_LINE_PARAM_COUNT, + params.length + ) + ); + } + } + + private void validateFormat(String method, String requestUri, String httpVersion) { + if (!ALLOWED_METHODS.contains(method)) { + throw new UncheckedServletException(String.format("허용되지 않는 HTTP method입니다. 입력값 = %s", method)); + } + if (!requestUri.startsWith("/")) { + throw new UncheckedServletException(String.format("Request URI는 /로 시작하여야 합니다. 입력값 = %s", requestUri)); + } + if (!httpVersion.equals(HTTP_1_1)) { + throw new UncheckedServletException(String.format("HTTP 버전은 %s만 허용됩니다. 입력값 = %s", HTTP_1_1, httpVersion)); + } + } + + private String readResponseBodyFromFileName(String fileName) { + try { + URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); + Path path = Paths.get(uri); + return Files.readString(path); + } catch (NullPointerException e) { + throw new UncheckedServletException("존재하지 않는 리소스입니다."); + } catch (Exception e) { + throw new UncheckedServletException(e); + } + } } From 8fd0d5db62e53791dd282404b2e8a763e83fa05a Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 16:16:41 +0900 Subject: [PATCH 03/64] =?UTF-8?q?refactor:=20200=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=EC=9D=84=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=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 | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 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 71256cc0ae..2dbbcaa42d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -78,12 +78,7 @@ private String makeResponse(String requestLine) { responseBody = readResponseBodyFromFileName("index.html"); } - return String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); + return makeOkResponseMessageWithBody(responseBody); } private static void validateParamCount(String[] params) { @@ -121,4 +116,15 @@ private String readResponseBodyFromFileName(String fileName) { throw new UncheckedServletException(e); } } + + private String makeOkResponseMessageWithBody(String responseBody) { + return String.join( + "\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody + ); + } } From edab62e713fccf6862b3a17bdfc431525fae2158 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 16:20:43 +0900 Subject: [PATCH 04/64] =?UTF-8?q?refactor:=20index.html=20=EC=9D=B4?= =?UTF-8?q?=EC=99=B8=20=EB=8B=A4=EB=A5=B8=20=ED=8C=8C=EC=9D=BC=EB=AA=85?= =?UTF-8?q?=EC=9D=B4=20=EB=93=A4=EC=96=B4=EC=99=80=EB=8F=84=20=EB=A6=AC?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=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 --- .../org/apache/coyote/http11/Http11Processor.java | 11 +++-------- 1 file changed, 3 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 2dbbcaa42d..c8604c3032 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -69,16 +69,11 @@ private String makeResponse(String requestLine) { String httpVersion = params[2]; validateFormat(method, requestUri, httpVersion); - String responseBody = ""; - if (requestUri.equals("/")) { - responseBody = "Hello world!"; + if (method.equals("GET") && requestUri.equals("/")) { + return makeOkResponseMessageWithBody("Hello world!"); } - if (requestUri.equals("/index.html")) { - responseBody = readResponseBodyFromFileName("index.html"); - } - - return makeOkResponseMessageWithBody(responseBody); + return makeOkResponseMessageWithBody(readResponseBodyFromFileName(requestUri.substring(1))); } private static void validateParamCount(String[] params) { From 7c28a39cd053c224ab470747e96b9c7175ed5b17 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 16:21:51 +0900 Subject: [PATCH 05/64] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EA=B0=84=EA=B2=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/Http11Processor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 c8604c3032..6915c61cca 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -70,10 +70,10 @@ private String makeResponse(String requestLine) { validateFormat(method, requestUri, httpVersion); if (method.equals("GET") && requestUri.equals("/")) { - return makeOkResponseMessageWithBody("Hello world!"); + return makeOkResponseMessage("Hello world!"); } - return makeOkResponseMessageWithBody(readResponseBodyFromFileName(requestUri.substring(1))); + return makeOkResponseMessage(readBody(requestUri.substring(1))); } private static void validateParamCount(String[] params) { @@ -100,7 +100,7 @@ private void validateFormat(String method, String requestUri, String httpVersion } } - private String readResponseBodyFromFileName(String fileName) { + private String readBody(String fileName) { try { URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); Path path = Paths.get(uri); @@ -112,7 +112,7 @@ private String readResponseBodyFromFileName(String fileName) { } } - private String makeOkResponseMessageWithBody(String responseBody) { + private String makeOkResponseMessage(String responseBody) { return String.join( "\r\n", "HTTP/1.1 200 OK ", From ba2e0fc30e82ba63f76f7434337bfb54231827a7 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 16:23:22 +0900 Subject: [PATCH 06/64] =?UTF-8?q?feat:=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20404.html=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 6915c61cca..bbdc7d319a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -23,6 +23,7 @@ public class Http11Processor implements Runnable, Processor { Set.of("GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"); private static final String HTTP_1_1 = "HTTP/1.1"; private static final String STATIC_DIRNAME = "static"; + private static final String NOT_FOUND_FILENAME = "404.html"; private final Socket connection; @@ -106,7 +107,7 @@ private String readBody(String fileName) { Path path = Paths.get(uri); return Files.readString(path); } catch (NullPointerException e) { - throw new UncheckedServletException("존재하지 않는 리소스입니다."); + return readBody(NOT_FOUND_FILENAME); } catch (Exception e) { throw new UncheckedServletException(e); } From 67a27accf18f148940322811b69ab4042e12110c Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 17:08:27 +0900 Subject: [PATCH 07/64] =?UTF-8?q?feat:=20Query=20String=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1=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 | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 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 bbdc7d319a..845918b5fa 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,6 +1,8 @@ package org.apache.coyote.http11; +import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; +import com.techcourse.model.User; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -9,6 +11,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import org.apache.coyote.Processor; import org.slf4j.Logger; @@ -70,11 +75,27 @@ private String makeResponse(String requestLine) { String httpVersion = params[2]; validateFormat(method, requestUri, httpVersion); - if (method.equals("GET") && requestUri.equals("/")) { + String endpoint = parseEndpoint(requestUri); + Map queryParams = parseQueryParams(requestUri); + + if (method.equals("GET") && endpoint.equals("/")) { return makeOkResponseMessage("Hello world!"); } - return makeOkResponseMessage(readBody(requestUri.substring(1))); + if (method.equals("GET") && endpoint.equals("/login")) { + User user = InMemoryUserRepository.findByAccount(queryParams.get("account")) + .orElseThrow(() -> new UncheckedServletException("아이디 혹은 비밀번호가 일치하지 않습니다.")); + + if (!user.checkPassword(queryParams.get("password"))) { + throw new UncheckedServletException("아이디 혹은 비밀번호가 일치하지 않습니다."); + } + + log.info(user.toString()); + + return makeOkResponseMessage(readBody("login.html")); + } + + return makeOkResponseMessage(readBody(endpoint.substring(1))); } private static void validateParamCount(String[] params) { @@ -101,6 +122,31 @@ private void validateFormat(String method, String requestUri, String httpVersion } } + private String parseEndpoint(String requestUri) { + return requestUri.split("\\?")[0]; + } + + private Map parseQueryParams(String requestUri) { + String[] substrings = requestUri.split("\\?"); + + if (substrings.length == 1) { + return Collections.emptyMap(); + } + + Map queryParams = new HashMap<>(); + String rawQueryParams = requestUri.split("\\?")[1]; + + for (String entry : rawQueryParams.split("&")) { + String[] data = entry.split("="); + if (data.length != 2) { + return Collections.emptyMap(); + } + queryParams.put(data[0], data[1]); + } + + return queryParams; + } + private String readBody(String fileName) { try { URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); From 8a3f7ae53dea883967331eff845fcd30febaadb4 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 17:19:19 +0900 Subject: [PATCH 08/64] =?UTF-8?q?fix:=20=EC=98=AC=EB=B0=94=EB=A5=B8=20Cont?= =?UTF-8?q?ent-Type=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 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 845918b5fa..d38dae43e5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -92,10 +92,10 @@ private String makeResponse(String requestLine) { log.info(user.toString()); - return makeOkResponseMessage(readBody("login.html")); + return makeOkResponseMessage("login.html"); } - return makeOkResponseMessage(readBody(endpoint.substring(1))); + return makeOkResponseMessage(endpoint.substring(1)); } private static void validateParamCount(String[] params) { @@ -147,6 +147,32 @@ private Map parseQueryParams(String requestUri) { return queryParams; } + private String makeOkResponseMessage(String fileName) { + String responseBody = readBody(fileName); + String contentType = ""; + + if (fileName.endsWith(".html")) { + contentType = "text/html"; + } + + if (fileName.endsWith(".css")) { + contentType = "text/css"; + } + + if (fileName.endsWith(".svg")) { + contentType = "image/svg+xml"; + } + + return String.join( + "\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody + ); + } + private String readBody(String fileName) { try { URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); @@ -158,15 +184,4 @@ private String readBody(String fileName) { throw new UncheckedServletException(e); } } - - private String makeOkResponseMessage(String responseBody) { - return String.join( - "\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody - ); - } } From 9fdc5953ce53fc96dd2d39ea10e35dfaf9e48f88 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 17:40:38 +0900 Subject: [PATCH 09/64] =?UTF-8?q?fix:=20id,=20pw=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EC=95=84=EB=8F=84=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EB=B0=9C=EC=83=9D=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95,=20/login=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=A0=91=EC=86=8D=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=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 --- .../com/techcourse/db/InMemoryUserRepository.java | 7 +++++-- .../org/apache/coyote/http11/Http11Processor.java | 12 +++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index d3fa57feeb..513b402c01 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -19,8 +19,11 @@ public static void save(User user) { database.put(user.getAccount(), user); } - public static Optional findByAccount(String account) { - return Optional.ofNullable(database.get(account)); + public static Optional findByAccountAndPassword(String account, String password) { + if (database.containsKey(account) && database.get(account).checkPassword(password)) { + return Optional.ofNullable(database.get(account)); + } + return Optional.empty(); } private InMemoryUserRepository() {} 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 d38dae43e5..7c11b911c0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -2,7 +2,6 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; -import com.techcourse.model.User; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -83,15 +82,14 @@ private String makeResponse(String requestLine) { } if (method.equals("GET") && endpoint.equals("/login")) { - User user = InMemoryUserRepository.findByAccount(queryParams.get("account")) - .orElseThrow(() -> new UncheckedServletException("아이디 혹은 비밀번호가 일치하지 않습니다.")); + if (queryParams.containsKey("account") && queryParams.containsKey("password")) { + String account = queryParams.get("account"); + String password = queryParams.get("password"); - if (!user.checkPassword(queryParams.get("password"))) { - throw new UncheckedServletException("아이디 혹은 비밀번호가 일치하지 않습니다."); + InMemoryUserRepository.findByAccountAndPassword(account, password) + .ifPresent(user -> log.info(user.toString())); } - log.info(user.toString()); - return makeOkResponseMessage("login.html"); } From ddd688cd8ccdebfe4b821078da48ea3ef993ff3f Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 17:50:57 +0900 Subject: [PATCH 10/64] =?UTF-8?q?refactor:=20Response=20Message=EB=A5=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EC=97=90=EC=84=9C=20=EC=83=81=ED=83=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EC=9D=B8=EC=9E=90=EB=A1=9C=20=EB=B0=9B?= =?UTF-8?q?=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 | 10 ++++----- .../apache/coyote/http11/HttpStatusCode.java | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java 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 7c11b911c0..1066a478dc 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -78,7 +78,7 @@ private String makeResponse(String requestLine) { Map queryParams = parseQueryParams(requestUri); if (method.equals("GET") && endpoint.equals("/")) { - return makeOkResponseMessage("Hello world!"); + return makeResponseMessage("Hello world!", HttpStatusCode.OK); } if (method.equals("GET") && endpoint.equals("/login")) { @@ -90,10 +90,10 @@ private String makeResponse(String requestLine) { .ifPresent(user -> log.info(user.toString())); } - return makeOkResponseMessage("login.html"); + return makeResponseMessage("login.html", HttpStatusCode.OK); } - return makeOkResponseMessage(endpoint.substring(1)); + return makeResponseMessage(endpoint.substring(1), HttpStatusCode.OK); } private static void validateParamCount(String[] params) { @@ -145,7 +145,7 @@ private Map parseQueryParams(String requestUri) { return queryParams; } - private String makeOkResponseMessage(String fileName) { + private String makeResponseMessage(String fileName, HttpStatusCode statusCode) { String responseBody = readBody(fileName); String contentType = ""; @@ -163,7 +163,7 @@ private String makeOkResponseMessage(String fileName) { return String.join( "\r\n", - "HTTP/1.1 200 OK ", + "HTTP/1.1 " + statusCode.getValue() + " ", "Content-Type: " + contentType + ";charset=utf-8 ", "Content-Length: " + responseBody.getBytes().length + " ", "", diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java new file mode 100644 index 0000000000..809e6068a7 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11; + +public enum HttpStatusCode { + + OK("200 OK"), + FOUND("302 Found"), + UNAUTHORIZED("401 Unauthorized"), + NOT_FOUND("404 Not Found"), + INTERNAL_SERVER_ERROR("500 Internal Server Error"), + ; + + private final String value; + + HttpStatusCode(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} From 95d88b26d273a75d82b5a4552e2a463c7381c05b Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 17:57:39 +0900 Subject: [PATCH 11/64] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=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 --- .../apache/coyote/http11/Http11Processor.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 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 1066a478dc..c74a73b672 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -78,7 +78,7 @@ private String makeResponse(String requestLine) { Map queryParams = parseQueryParams(requestUri); if (method.equals("GET") && endpoint.equals("/")) { - return makeResponseMessage("Hello world!", HttpStatusCode.OK); + return makeResponseMessageFromText("Hello world!", HttpStatusCode.OK); } if (method.equals("GET") && endpoint.equals("/login")) { @@ -86,14 +86,15 @@ private String makeResponse(String requestLine) { String account = queryParams.get("account"); String password = queryParams.get("password"); - InMemoryUserRepository.findByAccountAndPassword(account, password) - .ifPresent(user -> log.info(user.toString())); + return InMemoryUserRepository.findByAccountAndPassword(account, password) + .map(user -> makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND)) + .orElseGet(() -> makeResponseMessageFromFile("401.html", HttpStatusCode.UNAUTHORIZED)); } - return makeResponseMessage("login.html", HttpStatusCode.OK); + return makeResponseMessageFromFile("login.html", HttpStatusCode.OK); } - return makeResponseMessage(endpoint.substring(1), HttpStatusCode.OK); + return makeResponseMessageFromFile(endpoint.substring(1), HttpStatusCode.OK); } private static void validateParamCount(String[] params) { @@ -145,7 +146,18 @@ private Map parseQueryParams(String requestUri) { return queryParams; } - private String makeResponseMessage(String fileName, HttpStatusCode statusCode) { + private String makeResponseMessageFromText(String content, HttpStatusCode statusCode) { + return String.join( + "\r\n", + "HTTP/1.1 " + statusCode.getValue() + " ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + content.getBytes().length + " ", + "", + content + ); + } + + private String makeResponseMessageFromFile(String fileName, HttpStatusCode statusCode) { String responseBody = readBody(fileName); String contentType = ""; From 5552f95d26b9d951518a99a1be1bdf8e653beab2 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 18:40:40 +0900 Subject: [PATCH 12/64] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=84=20POST=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=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 | 69 ++++++++++++++++--- tomcat/src/main/resources/static/login.html | 2 +- 2 files changed, 62 insertions(+), 9 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 c74a73b672..8447ad1272 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -28,6 +28,7 @@ public class Http11Processor implements Runnable, Processor { private static final String HTTP_1_1 = "HTTP/1.1"; private static final String STATIC_DIRNAME = "static"; private static final String NOT_FOUND_FILENAME = "404.html"; + private static final String CONTENT_LENGTH = "Content-Length"; private final Socket connection; @@ -48,7 +49,17 @@ public void process(final Socket connection) { final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); final String requestLine = readRequestLine(reader); - final String response = makeResponse(requestLine); + Map headers = readHeaders(reader); + String requestBody = null; + + if (headers.containsKey(CONTENT_LENGTH)) { + int contentLength = Integer.parseInt(headers.get(CONTENT_LENGTH)); + char[] buffer = new char[contentLength]; + reader.read(buffer, 0, contentLength); + requestBody = new String(buffer); + } + + final String response = makeResponse(requestLine, requestBody); outputStream.write(response.getBytes()); outputStream.flush(); @@ -65,7 +76,30 @@ private String readRequestLine(BufferedReader reader) { } } - private String makeResponse(String requestLine) { + private Map readHeaders(BufferedReader reader) { + Map headers = new HashMap<>(); + + try { + while (reader.ready()) { + String line = reader.readLine(); + + if (line.isBlank()) { + break; + } + + String[] entry = line.split(": "); + + if (entry.length == 2) { + headers.put(entry[0], entry[1]); + } + } + return headers; + } catch (IOException e) { + throw new UncheckedServletException(e); + } + } + + private String makeResponse(String requestLine, String requestBody) { String[] params = requestLine.split(" "); validateParamCount(params); @@ -76,22 +110,21 @@ private String makeResponse(String requestLine) { String endpoint = parseEndpoint(requestUri); Map queryParams = parseQueryParams(requestUri); + Map requestData = parseRequestBody(requestBody); if (method.equals("GET") && endpoint.equals("/")) { return makeResponseMessageFromText("Hello world!", HttpStatusCode.OK); } - if (method.equals("GET") && endpoint.equals("/login")) { - if (queryParams.containsKey("account") && queryParams.containsKey("password")) { - String account = queryParams.get("account"); - String password = queryParams.get("password"); + if (method.equals("POST") && endpoint.equals("/login")) { + if (requestData.containsKey("account") && requestData.containsKey("password")) { + String account = requestData.get("account"); + String password = requestData.get("password"); return InMemoryUserRepository.findByAccountAndPassword(account, password) .map(user -> makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND)) .orElseGet(() -> makeResponseMessageFromFile("401.html", HttpStatusCode.UNAUTHORIZED)); } - - return makeResponseMessageFromFile("login.html", HttpStatusCode.OK); } return makeResponseMessageFromFile(endpoint.substring(1), HttpStatusCode.OK); @@ -146,6 +179,23 @@ private Map parseQueryParams(String requestUri) { return queryParams; } + private Map parseRequestBody(String requestBody) { + if (requestBody == null) { + return Collections.emptyMap(); + } + + Map requestData = new HashMap<>(); + + for (String entry : requestBody.split("&")) { + String[] data = entry.split("="); + if (data.length == 2) { + requestData.put(data[0], data[1]); + } + } + + return requestData; + } + private String makeResponseMessageFromText(String content, HttpStatusCode statusCode) { return String.join( "\r\n", @@ -158,6 +208,9 @@ private String makeResponseMessageFromText(String content, HttpStatusCode status } private String makeResponseMessageFromFile(String fileName, HttpStatusCode statusCode) { + if (!fileName.contains(".")) { + fileName += ".html"; + } String responseBody = readBody(fileName); String contentType = ""; 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 @@

로그인

-
+
From 67c7fff6a4994657f54a8f7094d128210d8edc21 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Wed, 4 Sep 2024 18:49:10 +0900 Subject: [PATCH 13/64] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/db/InMemoryUserRepository.java | 4 ++++ .../apache/coyote/http11/Http11Processor.java | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index 513b402c01..f839262800 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -26,5 +26,9 @@ public static Optional findByAccountAndPassword(String account, String pas return Optional.empty(); } + public static boolean existsByAccount(String account) { + return database.containsKey(account); + } + private InMemoryUserRepository() {} } 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 8447ad1272..ab56e9d221 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -2,6 +2,7 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; +import com.techcourse.model.User; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -127,6 +128,23 @@ private String makeResponse(String requestLine, String requestBody) { } } + if (method.equals("POST") && endpoint.equals("/register")) { + if (requestData.containsKey("account") + && requestData.containsKey("email") + && requestBody.contains("password") + ) { + String account = requestData.get("account"); + String email = requestData.get("email"); + String password = requestData.get("password"); + + if (!InMemoryUserRepository.existsByAccount(account)) { + InMemoryUserRepository.save(new User(account, password, email)); + } + + return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND); + } + } + return makeResponseMessageFromFile(endpoint.substring(1), HttpStatusCode.OK); } From afdf820835a8efd25fcd87878039c84cb2df49c6 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 10:06:26 +0900 Subject: [PATCH 14/64] =?UTF-8?q?fix:=20.js=20=ED=8C=8C=EC=9D=BC=EC=9D=98?= =?UTF-8?q?=20Content-Type=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 4 ++++ 1 file changed, 4 insertions(+) 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 ab56e9d221..452422fc49 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -244,6 +244,10 @@ private String makeResponseMessageFromFile(String fileName, HttpStatusCode statu contentType = "image/svg+xml"; } + if (fileName.endsWith(".js")) { + contentType = "text/javascript"; + } + return String.join( "\r\n", "HTTP/1.1 " + statusCode.getValue() + " ", From 344aa3de6ea8acfb6fd3cbc4bb0bd431c2287b25 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 10:25:57 +0900 Subject: [PATCH 15/64] =?UTF-8?q?feat:=20HttpCookie=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/HttpCookie.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java new file mode 100644 index 0000000000..36eefd2960 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class HttpCookie { + + private final Map cookies; + + public HttpCookie(String rawCookies) { + this.cookies = parse(rawCookies); + } + + private Map parse(String rawCookies) { + return Arrays.stream(rawCookies.split("; ")) + .map(cookie -> cookie.split("=")) + .filter(data -> data.length == 2) + .collect(Collectors.toMap(data -> data[0], data -> data[1])); + } +} From 69bf219aff699ef50c04d230e95d7fef1a23fa9a Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 11:15:21 +0900 Subject: [PATCH 16/64] =?UTF-8?q?feat:=20Cookie=EC=97=90=20JSESSIONID=20?= =?UTF-8?q?=EA=B0=92=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B8=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 56 +++++++++++----- .../org/apache/coyote/http11/HttpCookie.java | 19 ++++++ .../coyote/http11/Http11ProcessorTest.java | 67 ++++++++++++++++--- .../apache/coyote/http11/HttpCookieTest.java | 27 ++++++++ 4 files changed, 140 insertions(+), 29 deletions(-) create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java 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 452422fc49..d6850c5260 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -15,6 +15,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +32,10 @@ public class Http11Processor implements Runnable, Processor { private static final String STATIC_DIRNAME = "static"; private static final String NOT_FOUND_FILENAME = "404.html"; private static final String CONTENT_LENGTH = "Content-Length"; + private static final String COOKIE = "Cookie"; + private static final String CONTENT_TYPE = "Content-Type"; + private static final String SET_COOKIE = "Set-Cookie"; + private static final String JSESSIONID = "JSESSIONID"; private final Socket connection; @@ -52,6 +58,7 @@ public void process(final Socket connection) { final String requestLine = readRequestLine(reader); Map headers = readHeaders(reader); String requestBody = null; + HttpCookie cookie = null; if (headers.containsKey(CONTENT_LENGTH)) { int contentLength = Integer.parseInt(headers.get(CONTENT_LENGTH)); @@ -60,7 +67,11 @@ public void process(final Socket connection) { requestBody = new String(buffer); } - final String response = makeResponse(requestLine, requestBody); + if (headers.containsKey(COOKIE)) { + cookie = new HttpCookie(headers.get(COOKIE)); + } + + final String response = makeResponse(requestLine, requestBody, cookie); outputStream.write(response.getBytes()); outputStream.flush(); @@ -100,7 +111,7 @@ private Map readHeaders(BufferedReader reader) { } } - private String makeResponse(String requestLine, String requestBody) { + private String makeResponse(String requestLine, String requestBody, HttpCookie cookie) { String[] params = requestLine.split(" "); validateParamCount(params); @@ -123,15 +134,15 @@ private String makeResponse(String requestLine, String requestBody) { String password = requestData.get("password"); return InMemoryUserRepository.findByAccountAndPassword(account, password) - .map(user -> makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND)) - .orElseGet(() -> makeResponseMessageFromFile("401.html", HttpStatusCode.UNAUTHORIZED)); + .map(user -> makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie)) + .orElseGet(() -> makeResponseMessageFromFile("401.html", HttpStatusCode.UNAUTHORIZED, cookie)); } } if (method.equals("POST") && endpoint.equals("/register")) { if (requestData.containsKey("account") - && requestData.containsKey("email") - && requestBody.contains("password") + && requestData.containsKey("email") + && requestBody.contains("password") ) { String account = requestData.get("account"); String email = requestData.get("email"); @@ -141,11 +152,11 @@ private String makeResponse(String requestLine, String requestBody) { InMemoryUserRepository.save(new User(account, password, email)); } - return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND); + return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie); } } - return makeResponseMessageFromFile(endpoint.substring(1), HttpStatusCode.OK); + return makeResponseMessageFromFile(endpoint.substring(1), HttpStatusCode.OK, cookie); } private static void validateParamCount(String[] params) { @@ -218,14 +229,14 @@ private String makeResponseMessageFromText(String content, HttpStatusCode status return String.join( "\r\n", "HTTP/1.1 " + statusCode.getValue() + " ", - "Content-Type: text/html;charset=utf-8 ", + CONTENT_TYPE + ": text/html;charset=utf-8 ", "Content-Length: " + content.getBytes().length + " ", "", content ); } - private String makeResponseMessageFromFile(String fileName, HttpStatusCode statusCode) { + private String makeResponseMessageFromFile(String fileName, HttpStatusCode statusCode, HttpCookie cookie) { if (!fileName.contains(".")) { fileName += ".html"; } @@ -248,14 +259,23 @@ private String makeResponseMessageFromFile(String fileName, HttpStatusCode statu contentType = "text/javascript"; } - return String.join( - "\r\n", - "HTTP/1.1 " + statusCode.getValue() + " ", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody - ); + Map headers = new HashMap<>(); + headers.put(CONTENT_TYPE, contentType + ";charset=utf-8 "); + headers.put(CONTENT_LENGTH, responseBody.getBytes().length + " "); + + if (cookie == null || !cookie.hasCookieWithName(JSESSIONID)) { + HttpCookie httpCookie = new HttpCookie(); + httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); + headers.put(SET_COOKIE, httpCookie.getCookiesAsString()); + } + + String startLineText = "HTTP/1.1 " + statusCode.getValue() + " "; + + String headerText = headers.keySet().stream() + .map(headerName -> headerName + ": " + headers.get(headerName)) + .collect(Collectors.joining(("\r\n"))); + + return String.join("\r\n", startLineText, headerText, "", responseBody); } private String readBody(String fileName) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java index 36eefd2960..b4fb5e4c57 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -1,6 +1,7 @@ package org.apache.coyote.http11; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; @@ -8,14 +9,32 @@ public class HttpCookie { private final Map cookies; + public HttpCookie() { + this.cookies = new HashMap<>(); + } + public HttpCookie(String rawCookies) { this.cookies = parse(rawCookies); } + public void add(String name, String value) { + cookies.put(name, value); + } + private Map parse(String rawCookies) { return Arrays.stream(rawCookies.split("; ")) .map(cookie -> cookie.split("=")) .filter(data -> data.length == 2) .collect(Collectors.toMap(data -> data[0], data -> data[1])); } + + public String getCookiesAsString() { + return cookies.keySet().stream() + .map(key -> key + "=" + cookies.get(key)) + .collect(Collectors.joining("; ")); + } + + public boolean hasCookieWithName(String name) { + return cookies.containsKey(name); + } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java index 2aba8c56e0..ee6e210495 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java @@ -1,14 +1,14 @@ package org.apache.coyote.http11; -import org.junit.jupiter.api.Test; -import support.StubSocket; +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 org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import support.StubSocket; class Http11ProcessorTest { @@ -35,7 +35,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 ", @@ -50,12 +50,57 @@ void index() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/index.html"); - var expected = "HTTP/1.1 200 OK \r\n" + - "Content-Type: text/html;charset=utf-8 \r\n" + - "Content-Length: 5564 \r\n" + - "\r\n"+ - new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - assertThat(socket.output()).isEqualTo(expected); + assertThat(socket.output()).contains( + "HTTP/1.1 200 OK \r\n", + "Content-Type: text/html;charset=utf-8 \r\n", + "Content-Length: 5564 \r\n", + "Set-Cookie: JSESSIONID=", + new String(Files.readAllBytes(new File(resource.getFile()).toPath())) + ); + } + + @DisplayName("JSESSIONID가 요청에 존재하는 경우, Set-Cookie를 응답하지 않는다.") + @Test + void index_JsessionidIncluded() { + // given + final String httpRequest = String.join("\r\n", + "GET /index.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "Cookie: JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + assertThat(socket.output()).doesNotContain("Set-Cookie"); + } + + @DisplayName("쿠키가 존재하나 JSESSIONID 쿠키가 없는 경우, Set-Cookie를 응답한다.") + @Test + void index_CookieExists_JsessionidNotIncluded() { + // given + final String httpRequest = String.join("\r\n", + "GET /index.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "Cookie: OTHER=656cef62-e3c4-40bc-a8df-94732920ed46", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + assertThat(socket.output()).contains("Set-Cookie: JSESSIONID="); } } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java b/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java new file mode 100644 index 0000000000..77b094fdef --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http11; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HttpCookieTest { + + @DisplayName("쿠키 문자열을 파싱하여 인스턴스 생성") + @Test + void construct_Success() { + HttpCookie httpCookie = new HttpCookie("name1=value1; name2=value2"); + assertThat(httpCookie.getCookiesAsString()) + .isIn("name1=value1; name2=value2", "name2=value2; name1=value1"); + } + + @DisplayName("쿠키 정보 추가") + @Test + void add() { + HttpCookie httpCookie = new HttpCookie(); + httpCookie.add("name1", "value1"); + httpCookie.add("name2", "value2"); + assertThat(httpCookie.getCookiesAsString()) + .isIn("name1=value1; name2=value2", "name2=value2; name1=value1"); + } +} From 9ecd5738320e56846814fa8849b5126086f061b3 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 11:25:04 +0900 Subject: [PATCH 17/64] =?UTF-8?q?refactor:=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=9D=BC?= =?UTF-8?q?=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 d6850c5260..b100eeb410 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -229,8 +229,8 @@ private String makeResponseMessageFromText(String content, HttpStatusCode status return String.join( "\r\n", "HTTP/1.1 " + statusCode.getValue() + " ", - CONTENT_TYPE + ": text/html;charset=utf-8 ", - "Content-Length: " + content.getBytes().length + " ", + CONTENT_TYPE + ": " + "text/html;charset=utf-8 ", + CONTENT_LENGTH + ": " + content.getBytes().length + " ", "", content ); From a86407a1aa5870764cf04a5bd02c6d0424efa440 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 11:45:28 +0900 Subject: [PATCH 18/64] =?UTF-8?q?fix:=20jakarta=EC=9D=98=20HttpSession?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=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 --- .../java/org/apache/catalina/Manager.java | 37 ++++++++----------- .../org/apache/catalina/session/Session.java | 5 +++ 2 files changed, 20 insertions(+), 22 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/catalina/session/Session.java diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e69410f6a9..0e0a382ad6 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,18 +1,15 @@ package org.apache.catalina; -import jakarta.servlet.http.HttpSession; - import java.io.IOException; +import org.apache.catalina.session.Session; /** - * A Manager manages the pool of Sessions that are associated with a - * particular Container. Different Manager implementations may support - * value-added features such as the persistent storage of session data, - * as well as migrating sessions for distributable web applications. + * A Manager manages the pool of Sessions that are associated with a particular Container. Different Manager + * implementations may support value-added features such as the persistent storage of session data, as well as migrating + * sessions for distributable web applications. *

- * In order for a Manager implementation to successfully operate - * with a Context implementation that implements reloading, it - * must obey the following constraints: + * In order for a Manager implementation to successfully operate with a Context implementation + * that implements reloading, it must obey the following constraints: *

    *
  • Must implement Lifecycle so that the Context can indicate * that a restart is required. @@ -29,28 +26,24 @@ 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 - * specified session id (if any); otherwise return null. + * Return the active Session, associated with this Manager, with the specified session id (if any); otherwise + * return + * null. * * @param id The session id for the session to be returned - * - * @exception IllegalStateException if a new session cannot be - * instantiated for any reason - * @exception IOException if an input/output error occurs while - * processing this request - * - * @return the request session or {@code null} if a session with the - * requested ID could not be found + * @return the request session or {@code null} if a session with the requested ID could not be found + * @throws IllegalStateException if a new session cannot be instantiated for any reason + * @throws IOException if an input/output error occurs while processing this request */ - 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/org/apache/catalina/session/Session.java b/tomcat/src/main/java/org/apache/catalina/session/Session.java new file mode 100644 index 0000000000..fc151a5e4c --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/session/Session.java @@ -0,0 +1,5 @@ +package org.apache.catalina.session; + +public class Session { + +} From 239b42d964410f5b5b8650d8fe9adcf3a6b467a1 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 14:02:38 +0900 Subject: [PATCH 19/64] =?UTF-8?q?feat:=20=EC=84=B8=EC=85=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/catalina/Manager.java | 9 ++--- .../org/apache/catalina/session/Session.java | 19 ++++++++++ .../catalina/session/SessionManager.java | 36 +++++++++++++++++++ .../apache/coyote/http11/Http11Processor.java | 22 +++++++++++- .../org/apache/coyote/http11/HttpCookie.java | 12 ++++--- 5 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/catalina/session/SessionManager.java diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index 0e0a382ad6..9a627e38a8 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,6 +1,7 @@ package org.apache.catalina; import java.io.IOException; +import java.util.Optional; import org.apache.catalina.session.Session; /** @@ -26,7 +27,7 @@ public interface Manager { * * @param session Session to be added */ - void add(Session session); + void add(String id, Session session); /** * Return the active Session, associated with this Manager, with the specified session id (if any); otherwise @@ -38,12 +39,12 @@ public interface Manager { * @throws IllegalStateException if a new session cannot be instantiated for any reason * @throws IOException if an input/output error occurs while processing this request */ - Session findSession(String id) throws IOException; + Optional findSession(String id) throws IOException; /** * Remove this Session from the active Sessions for this Manager. * - * @param session Session to be removed + * @param id Session to be removed */ - void remove(Session session); + void remove(String id); } diff --git a/tomcat/src/main/java/org/apache/catalina/session/Session.java b/tomcat/src/main/java/org/apache/catalina/session/Session.java index fc151a5e4c..e7826f0d2c 100644 --- a/tomcat/src/main/java/org/apache/catalina/session/Session.java +++ b/tomcat/src/main/java/org/apache/catalina/session/Session.java @@ -1,5 +1,24 @@ package org.apache.catalina.session; +import java.util.HashMap; +import java.util.Map; + public class Session { + private final Map values = new HashMap<>(); + + public Session() { + } + + public void setAttribute(String name, Object value) { + values.put(name, value); + } + + public Object getAttribute(String name) { + return values.get(name); + } + + public void removeAttribute(String name) { + values.remove(name); + } } diff --git a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java new file mode 100644 index 0000000000..d60ea91758 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java @@ -0,0 +1,36 @@ +package org.apache.catalina.session; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.apache.catalina.Manager; + +public class SessionManager implements Manager { + + private static final SessionManager INSTANCE = new SessionManager(new HashMap<>()); + + private final Map sessions; + + private SessionManager(Map sessions) { + this.sessions = sessions; + } + + public static SessionManager getInstance() { + return INSTANCE; + } + + @Override + public void add(String id, Session session) { + sessions.put(id, session); + } + + @Override + public Optional findSession(String id) { + return Optional.ofNullable(sessions.get(id)); + } + + @Override + public void remove(String id) { + sessions.remove(id); + } +} 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 b100eeb410..ed64dff13a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -17,6 +17,8 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import org.apache.catalina.session.Session; +import org.apache.catalina.session.SessionManager; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -128,13 +130,31 @@ private String makeResponse(String requestLine, String requestBody, HttpCookie c return makeResponseMessageFromText("Hello world!", HttpStatusCode.OK); } + if (method.equals("GET") && endpoint.equals("/login")) { + if (cookie != null && cookie.hasCookieWithName(JSESSIONID)) { + SessionManager sessionManager = SessionManager.getInstance(); + String sessionId = cookie.get(JSESSIONID); + if (sessionManager.findSession(sessionId).isPresent()) { + return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie); + } + } + + return makeResponseMessageFromFile("login.html", HttpStatusCode.OK, cookie); + } + if (method.equals("POST") && endpoint.equals("/login")) { if (requestData.containsKey("account") && requestData.containsKey("password")) { String account = requestData.get("account"); String password = requestData.get("password"); return InMemoryUserRepository.findByAccountAndPassword(account, password) - .map(user -> makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie)) + .map(user -> { + SessionManager sessionManager = SessionManager.getInstance(); + Session session = new Session(); + session.setAttribute("user", user); + sessionManager.add(cookie.get(JSESSIONID), session); + return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie); + }) .orElseGet(() -> makeResponseMessageFromFile("401.html", HttpStatusCode.UNAUTHORIZED, cookie)); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java index b4fb5e4c57..f22b0f455f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -17,10 +17,6 @@ public HttpCookie(String rawCookies) { this.cookies = parse(rawCookies); } - public void add(String name, String value) { - cookies.put(name, value); - } - private Map parse(String rawCookies) { return Arrays.stream(rawCookies.split("; ")) .map(cookie -> cookie.split("=")) @@ -28,6 +24,14 @@ private Map parse(String rawCookies) { .collect(Collectors.toMap(data -> data[0], data -> data[1])); } + public void add(String name, String value) { + cookies.put(name, value); + } + + public String get(String name) { + return cookies.get(name); + } + public String getCookiesAsString() { return cookies.keySet().stream() .map(key -> key + "=" + cookies.get(key)) From d326ace7f7ee7c3d75fa4cad4c8081180f112be1 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 15:22:29 +0900 Subject: [PATCH 20/64] =?UTF-8?q?refactor:=20Request=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=B3=84=EB=8F=84=EC=9D=98=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=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 | 192 +++++------------- .../org/apache/coyote/request/HttpHeader.java | 32 +++ .../org/apache/coyote/request/HttpMethod.java | 25 +++ .../apache/coyote/request/HttpRequest.java | 27 +++ .../apache/coyote/request/RequestBody.java | 37 ++++ .../apache/coyote/request/RequestLine.java | 75 +++++++ .../apache/coyote/request/HttpHeaderTest.java | 33 +++ .../coyote/request/RequestBodyTest.java | 31 +++ .../coyote/request/RequestLineTest.java | 70 +++++++ 9 files changed, 381 insertions(+), 141 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/request/HttpMethod.java create mode 100644 tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/request/RequestBody.java create mode 100644 tomcat/src/main/java/org/apache/coyote/request/RequestLine.java create mode 100644 tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java create mode 100644 tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java create mode 100644 tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java 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 ed64dff13a..a783ec5a28 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,5 +1,8 @@ package org.apache.coyote.http11; +import static org.apache.coyote.request.HttpMethod.GET; +import static org.apache.coyote.request.HttpMethod.POST; + import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; import com.techcourse.model.User; @@ -11,15 +14,19 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import org.apache.catalina.session.Session; import org.apache.catalina.session.SessionManager; import org.apache.coyote.Processor; +import org.apache.coyote.request.HttpHeader; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.RequestBody; +import org.apache.coyote.request.RequestLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,10 +34,6 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final int REQUEST_LINE_PARAM_COUNT = 3; - private static final Set ALLOWED_METHODS = - Set.of("GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"); - private static final String HTTP_1_1 = "HTTP/1.1"; private static final String STATIC_DIRNAME = "static"; private static final String NOT_FOUND_FILENAME = "404.html"; private static final String CONTENT_LENGTH = "Content-Length"; @@ -57,23 +60,36 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream()) { final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - final String requestLine = readRequestLine(reader); - Map headers = readHeaders(reader); - String requestBody = null; - HttpCookie cookie = null; - if (headers.containsKey(CONTENT_LENGTH)) { - int contentLength = Integer.parseInt(headers.get(CONTENT_LENGTH)); + RequestLine requestLine = new RequestLine(reader.readLine()); + + List rawHeaders = new ArrayList<>(); + while (reader.ready()) { + String line = reader.readLine(); + if (line.isBlank()) { + break; + } + rawHeaders.add(line); + } + HttpHeader httpHeader = new HttpHeader(rawHeaders); + + RequestBody requestBody = null; + if (httpHeader.contains(CONTENT_LENGTH)) { + int contentLength = Integer.parseInt(httpHeader.get(CONTENT_LENGTH)); char[] buffer = new char[contentLength]; reader.read(buffer, 0, contentLength); - requestBody = new String(buffer); + requestBody = new RequestBody(new String(buffer)); } - if (headers.containsKey(COOKIE)) { - cookie = new HttpCookie(headers.get(COOKIE)); + HttpRequest httpRequest = new HttpRequest(requestLine, httpHeader, requestBody); + + HttpCookie cookie = null; + + if (httpHeader.contains(COOKIE)) { + cookie = new HttpCookie(httpHeader.get(COOKIE)); } - final String response = makeResponse(requestLine, requestBody, cookie); + final String response = makeResponse(httpRequest, cookie); outputStream.write(response.getBytes()); outputStream.flush(); @@ -82,55 +98,12 @@ public void process(final Socket connection) { } } - private String readRequestLine(BufferedReader reader) { - try { - return reader.readLine(); - } catch (IOException e) { - throw new UncheckedServletException("HTTP 요청의 request line을 읽을 수 없습니다."); - } - } - - private Map readHeaders(BufferedReader reader) { - Map headers = new HashMap<>(); - - try { - while (reader.ready()) { - String line = reader.readLine(); - - if (line.isBlank()) { - break; - } - - String[] entry = line.split(": "); - - if (entry.length == 2) { - headers.put(entry[0], entry[1]); - } - } - return headers; - } catch (IOException e) { - throw new UncheckedServletException(e); - } - } - - private String makeResponse(String requestLine, String requestBody, HttpCookie cookie) { - String[] params = requestLine.split(" "); - validateParamCount(params); - - String method = params[0]; - String requestUri = params[1]; - String httpVersion = params[2]; - validateFormat(method, requestUri, httpVersion); - - String endpoint = parseEndpoint(requestUri); - Map queryParams = parseQueryParams(requestUri); - Map requestData = parseRequestBody(requestBody); - - if (method.equals("GET") && endpoint.equals("/")) { + private String makeResponse(HttpRequest httpRequest, HttpCookie cookie) { + if (httpRequest.is(GET, "/")) { return makeResponseMessageFromText("Hello world!", HttpStatusCode.OK); } - if (method.equals("GET") && endpoint.equals("/login")) { + if (httpRequest.is(GET, "/login")) { if (cookie != null && cookie.hasCookieWithName(JSESSIONID)) { SessionManager sessionManager = SessionManager.getInstance(); String sessionId = cookie.get(JSESSIONID); @@ -142,10 +115,12 @@ private String makeResponse(String requestLine, String requestBody, HttpCookie c return makeResponseMessageFromFile("login.html", HttpStatusCode.OK, cookie); } - if (method.equals("POST") && endpoint.equals("/login")) { - if (requestData.containsKey("account") && requestData.containsKey("password")) { - String account = requestData.get("account"); - String password = requestData.get("password"); + if (httpRequest.is(POST, "/login")) { + RequestBody requestBody = httpRequest.getRequestBody(); + + if (requestBody.containsAll("account", "password")) { + String account = requestBody.get("account"); + String password = requestBody.get("password"); return InMemoryUserRepository.findByAccountAndPassword(account, password) .map(user -> { @@ -157,16 +132,17 @@ private String makeResponse(String requestLine, String requestBody, HttpCookie c }) .orElseGet(() -> makeResponseMessageFromFile("401.html", HttpStatusCode.UNAUTHORIZED, cookie)); } + + throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); } - if (method.equals("POST") && endpoint.equals("/register")) { - if (requestData.containsKey("account") - && requestData.containsKey("email") - && requestBody.contains("password") - ) { - String account = requestData.get("account"); - String email = requestData.get("email"); - String password = requestData.get("password"); + if (httpRequest.is(POST, "/register")) { + RequestBody requestBody = httpRequest.getRequestBody(); + + if (requestBody.containsAll("account", "email", "password")) { + String account = requestBody.get("account"); + String email = requestBody.get("email"); + String password = requestBody.get("password"); if (!InMemoryUserRepository.existsByAccount(account)) { InMemoryUserRepository.save(new User(account, password, email)); @@ -176,73 +152,7 @@ private String makeResponse(String requestLine, String requestBody, HttpCookie c } } - return makeResponseMessageFromFile(endpoint.substring(1), HttpStatusCode.OK, cookie); - } - - private static void validateParamCount(String[] params) { - if (params.length != REQUEST_LINE_PARAM_COUNT) { - throw new UncheckedServletException( - String.format( - "Request line의 인자는 %d개여야 합니다. 입력된 인자 개수 = %d", - REQUEST_LINE_PARAM_COUNT, - params.length - ) - ); - } - } - - private void validateFormat(String method, String requestUri, String httpVersion) { - if (!ALLOWED_METHODS.contains(method)) { - throw new UncheckedServletException(String.format("허용되지 않는 HTTP method입니다. 입력값 = %s", method)); - } - if (!requestUri.startsWith("/")) { - throw new UncheckedServletException(String.format("Request URI는 /로 시작하여야 합니다. 입력값 = %s", requestUri)); - } - if (!httpVersion.equals(HTTP_1_1)) { - throw new UncheckedServletException(String.format("HTTP 버전은 %s만 허용됩니다. 입력값 = %s", HTTP_1_1, httpVersion)); - } - } - - private String parseEndpoint(String requestUri) { - return requestUri.split("\\?")[0]; - } - - private Map parseQueryParams(String requestUri) { - String[] substrings = requestUri.split("\\?"); - - if (substrings.length == 1) { - return Collections.emptyMap(); - } - - Map queryParams = new HashMap<>(); - String rawQueryParams = requestUri.split("\\?")[1]; - - for (String entry : rawQueryParams.split("&")) { - String[] data = entry.split("="); - if (data.length != 2) { - return Collections.emptyMap(); - } - queryParams.put(data[0], data[1]); - } - - return queryParams; - } - - private Map parseRequestBody(String requestBody) { - if (requestBody == null) { - return Collections.emptyMap(); - } - - Map requestData = new HashMap<>(); - - for (String entry : requestBody.split("&")) { - String[] data = entry.split("="); - if (data.length == 2) { - requestData.put(data[0], data[1]); - } - } - - return requestData; + return makeResponseMessageFromFile(httpRequest.getPath(), HttpStatusCode.OK, cookie); } private String makeResponseMessageFromText(String content, HttpStatusCode statusCode) { diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java new file mode 100644 index 0000000000..6fd8fd5a4a --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java @@ -0,0 +1,32 @@ +package org.apache.coyote.request; + +import com.techcourse.exception.UncheckedServletException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class HttpHeader { + + private final Map headers; + + public HttpHeader(List rawHeaders) { + this.headers = rawHeaders.stream() + .peek(rawHeader -> { + if (!rawHeader.contains(": ")) { + throw new UncheckedServletException("형식이 올바르지 않은 헤더가 포함되어 있습니다."); + } + }) + .collect(Collectors.toMap( + rawHeader -> rawHeader.substring(0, rawHeader.indexOf(": ")), + rawHeader -> rawHeader.substring(rawHeader.indexOf(": ") + 2) + )); + } + + public String get(String name) { + return headers.get(name); + } + + public boolean contains(String name) { + return headers.containsKey(name); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/request/HttpMethod.java new file mode 100644 index 0000000000..7eab92a282 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpMethod.java @@ -0,0 +1,25 @@ +package org.apache.coyote.request; + +import com.techcourse.exception.UncheckedServletException; +import java.util.Arrays; + +public enum HttpMethod { + + GET, + HEAD, + POST, + PUT, + DELETE, + CONNECT, + OPTIONS, + TRACE, + PATCH, + ; + + public static HttpMethod from(String name) { + return Arrays.stream(values()) + .filter(method -> name.equals(method.name())) + .findFirst() + .orElseThrow(() -> new UncheckedServletException("올바르지 않은 HTTP Method입니다.")); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java new file mode 100644 index 0000000000..da7c906f2c --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -0,0 +1,27 @@ +package org.apache.coyote.request; + +public class HttpRequest { + + private final RequestLine requestLine; + private final HttpHeader httpHeader; + private final RequestBody requestBody; + + public HttpRequest(RequestLine requestLine, HttpHeader httpHeader, RequestBody requestBody) { + this.requestLine = requestLine; + this.httpHeader = httpHeader; + this.requestBody = requestBody; + } + + public boolean is(HttpMethod httpMethod, String path) { + return httpMethod == requestLine.getMethod() + && path.equals(requestLine.getPath()); + } + + public RequestBody getRequestBody() { + return requestBody; + } + + public String getPath() { + return requestLine.getPath(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java new file mode 100644 index 0000000000..3b2b412612 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java @@ -0,0 +1,37 @@ +package org.apache.coyote.request; + +import com.techcourse.exception.UncheckedServletException; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class RequestBody { + + private final Map requestBody; + + public RequestBody(String rawRequestBody) { + this.requestBody = Arrays.stream(rawRequestBody.split("&")) + .peek(rawData -> { + if (!rawData.contains("=")) { + throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); + } + }) + .collect(Collectors.toMap( + data -> data.substring(0, data.indexOf("=")), + data -> data.substring(data.indexOf("=") + 1) + )); + } + + public boolean containsAll(String... names) { + for (String name : names) { + if (!requestBody.containsKey(name)) { + return false; + } + } + return true; + } + + public String get(String name) { + return requestBody.get(name); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java new file mode 100644 index 0000000000..9a44bedc3b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java @@ -0,0 +1,75 @@ +package org.apache.coyote.request; + +import com.techcourse.exception.UncheckedServletException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +public class RequestLine { + + private static final int PARAMETER_COUNT = 3; + + private final HttpMethod method; + private final String path; + private final Map queryParameters; + + public RequestLine(String requestLine) { + validateParameterCount(requestLine); + String[] parameters = requestLine.split(" "); + + validateUri(parameters[1]); + validateHttpVersion(parameters[2]); + + this.method = HttpMethod.from(parameters[0]); + this.path = parseEndpoint(parameters[1]); + this.queryParameters = parseQueryParams(parameters[1]); + } + + private void validateHttpVersion(String httpVersion) { + if (!httpVersion.equals("HTTP/1.1")) { + throw new UncheckedServletException("HTTP 버전은 HTTP/1.1 만 허용됩니다."); + } + } + + private void validateUri(String uri) { + if (!uri.startsWith("/")) { + throw new UncheckedServletException("URI는 / 로 시작해야 합니다."); + } + } + + private void validateParameterCount(String rawRequestLine) { + if (rawRequestLine.split(" ").length != PARAMETER_COUNT) { + throw new UncheckedServletException(String.format("Request line의 인자는 %d개여야 합니다.", PARAMETER_COUNT)); + } + } + + private String parseEndpoint(String uri) { + return uri.split("\\?")[0]; + } + + private Map parseQueryParams(String uri) { + if (!uri.contains("?")) { + return Collections.emptyMap(); + } + + String queryString = uri.split("\\?")[1]; + + return Arrays.stream(queryString.split("&")) + .map(rawQuery -> rawQuery.split("=")) + .filter(query -> query.length == 2) + .collect(Collectors.toMap(query -> query[0], query -> query[1])); + } + + public HttpMethod getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public Map getQueryParameters() { + return queryParameters; + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java new file mode 100644 index 0000000000..98a7e7cf6e --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java @@ -0,0 +1,33 @@ +package org.apache.coyote.request; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.techcourse.exception.UncheckedServletException; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HttpHeaderTest { + + @DisplayName("생성 성공") + @Test + void construct_Success() { + List headers = List.of("Host: localhost:8080", "Connection: keep-alive"); + HttpHeader httpHeader = new HttpHeader(headers); + assertAll( + () -> assertThat(httpHeader.get("Host")).isEqualTo("localhost:8080"), + () -> assertThat(httpHeader.get("Connection")).isEqualTo("keep-alive") + ); + } + + @DisplayName("생성 실패: 올바르지 않은 헤더 포함") + @Test + void construct_Fail() { + List headers = List.of("Host: localhost:8080", "Connectionkeep-alive"); + assertThatThrownBy(() -> new HttpHeader(headers)) + .isInstanceOf(UncheckedServletException.class) + .hasMessage("형식이 올바르지 않은 헤더가 포함되어 있습니다."); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java new file mode 100644 index 0000000000..55575674ea --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestBodyTest.java @@ -0,0 +1,31 @@ +package org.apache.coyote.request; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.techcourse.exception.UncheckedServletException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RequestBodyTest { + + @DisplayName("생성 성공") + @Test + void construct_Success() { + RequestBody requestBody = new RequestBody("name=lee&age=20"); + assertAll( + () -> assertThat(requestBody.get("name")).isEqualTo("lee"), + () -> assertThat(requestBody.get("age")).isEqualTo("20") + ); + } + + @DisplayName("생성 실패: 올바르지 않은 형식") + @Test + void construct_Fail() { + assertThatThrownBy(() -> new RequestBody("name=lee&age20")) + .isInstanceOf(UncheckedServletException.class) + .hasMessage("올바르지 않은 Request Body 형식입니다."); + } + +} diff --git a/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java b/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java new file mode 100644 index 0000000000..88dbdb9495 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/request/RequestLineTest.java @@ -0,0 +1,70 @@ +package org.apache.coyote.request; + +import static org.apache.coyote.request.HttpMethod.GET; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.techcourse.exception.UncheckedServletException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RequestLineTest { + + @DisplayName("생성 성공: query parameter 없음") + @Test + void construct_Success_NoQueryParameter() { + RequestLine requestLine = new RequestLine("GET /test HTTP/1.1"); + + assertAll( + () -> assertThat(requestLine.getMethod()).isEqualTo(GET), + () -> assertThat(requestLine.getPath()).isEqualTo("/test"), + () -> assertThat(requestLine.getQueryParameters()).isEmpty() + ); + } + + @DisplayName("생성 성공: query parameter 있음") + @Test + void construct_Success_WithQueryParameter() { + RequestLine requestLine = new RequestLine("GET /test?name=lee&age=20 HTTP/1.1"); + + assertAll( + () -> assertThat(requestLine.getMethod()).isEqualTo(GET), + () -> assertThat(requestLine.getPath()).isEqualTo("/test"), + () -> assertThat(requestLine.getQueryParameters().get("name")).isEqualTo("lee"), + () -> assertThat(requestLine.getQueryParameters().get("age")).isEqualTo("20") + ); + } + + @DisplayName("생성 실패: 존재하지 않는 메서드") + @Test + void construct_Fail_IllegalMethod() { + assertThatThrownBy(() -> new RequestLine("GETT /test HTTP/1.1")) + .isInstanceOf(UncheckedServletException.class) + .hasMessage("올바르지 않은 HTTP Method입니다."); + } + + @DisplayName("생성 실패: 올바르지 않은 URI") + @Test + void construct_Fail_IllegalUri() { + assertThatThrownBy(() -> new RequestLine("GET test HTTP/1.1")) + .isInstanceOf(UncheckedServletException.class) + .hasMessage("URI는 / 로 시작해야 합니다."); + } + + @DisplayName("생성 실패: 올바르지 않은 HTTP Version") + @Test + void construct_Fail_IllegalHttpVersion() { + assertThatThrownBy(() -> new RequestLine("GET /test HTTP/1.0")) + .isInstanceOf(UncheckedServletException.class) + .hasMessage("HTTP 버전은 HTTP/1.1 만 허용됩니다."); + } + + @DisplayName("생성 실패: 올바르지 않은 인자 개수") + @Test + void construct_Fail_IllegalParameterCount() { + assertThatThrownBy(() -> new RequestLine("GET /test HTTP/1.1 HTTP/1.1")) + .isInstanceOf(UncheckedServletException.class) + .hasMessage("Request line의 인자는 3개여야 합니다."); + } +} From c44ef3811c4b9b340a72f2edce3e5c93bb721185 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 15:41:45 +0900 Subject: [PATCH 21/64] =?UTF-8?q?refactor:=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0,=20body=20=EC=9D=BD=EA=B8=B0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=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 | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 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 a783ec5a28..8b2a98aa9a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -62,24 +62,8 @@ public void process(final Socket connection) { final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); RequestLine requestLine = new RequestLine(reader.readLine()); - - List rawHeaders = new ArrayList<>(); - while (reader.ready()) { - String line = reader.readLine(); - if (line.isBlank()) { - break; - } - rawHeaders.add(line); - } - HttpHeader httpHeader = new HttpHeader(rawHeaders); - - RequestBody requestBody = null; - if (httpHeader.contains(CONTENT_LENGTH)) { - int contentLength = Integer.parseInt(httpHeader.get(CONTENT_LENGTH)); - char[] buffer = new char[contentLength]; - reader.read(buffer, 0, contentLength); - requestBody = new RequestBody(new String(buffer)); - } + HttpHeader httpHeader = new HttpHeader(readRequestHeaders(reader)); + RequestBody requestBody = readRequestBody(reader, httpHeader); HttpRequest httpRequest = new HttpRequest(requestLine, httpHeader, requestBody); @@ -98,6 +82,38 @@ public void process(final Socket connection) { } } + private List readRequestHeaders(BufferedReader reader) { + try { + List rawHeaders = new ArrayList<>(); + + while (reader.ready()) { + String line = reader.readLine(); + if (line.isBlank()) { + break; + } + rawHeaders.add(line); + } + + return rawHeaders; + } catch (IOException e) { + throw new UncheckedServletException("헤더를 읽는 데 실패하였습니다."); + } + } + + private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader) { + try { + if (httpHeader.contains(CONTENT_LENGTH)) { + int contentLength = Integer.parseInt(httpHeader.get(CONTENT_LENGTH)); + char[] buffer = new char[contentLength]; + reader.read(buffer, 0, contentLength); + return new RequestBody(new String(buffer)); + } + return null; + } catch (IOException e) { + throw new UncheckedServletException("Request Body를 읽는 데 실패하였습니다."); + } + } + private String makeResponse(HttpRequest httpRequest, HttpCookie cookie) { if (httpRequest.is(GET, "/")) { return makeResponseMessageFromText("Hello world!", HttpStatusCode.OK); From 1310577aca5611060b3021927734942d6c876647 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 15:47:45 +0900 Subject: [PATCH 22/64] =?UTF-8?q?refactor:=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=EC=9D=84=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 --- .../org/apache/coyote/http11/Http11Processor.java | 14 +++++--------- .../java/org/apache/coyote/http11/HttpCookie.java | 5 +++++ .../org/apache/coyote/request/HttpRequest.java | 4 ++++ 3 files changed, 14 insertions(+), 9 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 8b2a98aa9a..31be4df6b0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -67,13 +67,7 @@ public void process(final Socket connection) { HttpRequest httpRequest = new HttpRequest(requestLine, httpHeader, requestBody); - HttpCookie cookie = null; - - if (httpHeader.contains(COOKIE)) { - cookie = new HttpCookie(httpHeader.get(COOKIE)); - } - - final String response = makeResponse(httpRequest, cookie); + final String response = makeResponse(httpRequest); outputStream.write(response.getBytes()); outputStream.flush(); @@ -114,13 +108,15 @@ private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader } } - private String makeResponse(HttpRequest httpRequest, HttpCookie cookie) { + private String makeResponse(HttpRequest httpRequest) { + HttpCookie cookie = new HttpCookie(httpRequest.getHeader(COOKIE)); + if (httpRequest.is(GET, "/")) { return makeResponseMessageFromText("Hello world!", HttpStatusCode.OK); } if (httpRequest.is(GET, "/login")) { - if (cookie != null && cookie.hasCookieWithName(JSESSIONID)) { + if (cookie.hasCookieWithName(JSESSIONID)) { SessionManager sessionManager = SessionManager.getInstance(); String sessionId = cookie.get(JSESSIONID); if (sessionManager.findSession(sessionId).isPresent()) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java index f22b0f455f..eddf2226cb 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -1,6 +1,7 @@ package org.apache.coyote.http11; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; @@ -18,6 +19,10 @@ public HttpCookie(String rawCookies) { } private Map parse(String rawCookies) { + if (rawCookies == null) { + return Collections.emptyMap(); + } + return Arrays.stream(rawCookies.split("; ")) .map(cookie -> cookie.split("=")) .filter(data -> data.length == 2) diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index da7c906f2c..c975f54a69 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -24,4 +24,8 @@ public RequestBody getRequestBody() { public String getPath() { return requestLine.getPath(); } + + public String getHeader(String name) { + return httpHeader.get(name); + } } From 50f8f489b31398bb16c166f87fa58a2017ad9dcd Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 16:36:53 +0900 Subject: [PATCH 23/64] =?UTF-8?q?refactor:=20HTTP=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=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 | 71 ++++++------------- .../apache/coyote/http11/HttpStatusCode.java | 22 +++--- .../org/apache/coyote/request/HttpHeader.java | 14 ++++ .../apache/coyote/response/ContentType.java | 31 ++++++++ .../apache/coyote/response/HttpHeaders.java | 20 ++++++ .../apache/coyote/response/HttpResponse.java | 39 ++++++++++ 6 files changed, 137 insertions(+), 60 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/response/ContentType.java create mode 100644 tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java create mode 100644 tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java 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 31be4df6b0..4fcf2ed140 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -15,11 +15,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import org.apache.catalina.session.Session; import org.apache.catalina.session.SessionManager; import org.apache.coyote.Processor; @@ -27,6 +24,8 @@ import org.apache.coyote.request.HttpRequest; import org.apache.coyote.request.RequestBody; import org.apache.coyote.request.RequestLine; +import org.apache.coyote.response.ContentType; +import org.apache.coyote.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +66,7 @@ public void process(final Socket connection) { HttpRequest httpRequest = new HttpRequest(requestLine, httpHeader, requestBody); - final String response = makeResponse(httpRequest); + final String response = handle(httpRequest); outputStream.write(response.getBytes()); outputStream.flush(); @@ -108,11 +107,11 @@ private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader } } - private String makeResponse(HttpRequest httpRequest) { + private String handle(HttpRequest httpRequest) { HttpCookie cookie = new HttpCookie(httpRequest.getHeader(COOKIE)); if (httpRequest.is(GET, "/")) { - return makeResponseMessageFromText("Hello world!", HttpStatusCode.OK); + return buildTextMessage("Hello world!", HttpStatusCode.OK); } if (httpRequest.is(GET, "/login")) { @@ -120,11 +119,11 @@ private String makeResponse(HttpRequest httpRequest) { SessionManager sessionManager = SessionManager.getInstance(); String sessionId = cookie.get(JSESSIONID); if (sessionManager.findSession(sessionId).isPresent()) { - return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie); + return buildMessage("index.html", HttpStatusCode.FOUND, cookie); } } - return makeResponseMessageFromFile("login.html", HttpStatusCode.OK, cookie); + return buildMessage("login.html", HttpStatusCode.OK, cookie); } if (httpRequest.is(POST, "/login")) { @@ -140,9 +139,9 @@ private String makeResponse(HttpRequest httpRequest) { Session session = new Session(); session.setAttribute("user", user); sessionManager.add(cookie.get(JSESSIONID), session); - return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie); + return buildMessage("index.html", HttpStatusCode.FOUND, cookie); }) - .orElseGet(() -> makeResponseMessageFromFile("401.html", HttpStatusCode.UNAUTHORIZED, cookie)); + .orElseGet(() -> buildMessage("401.html", HttpStatusCode.UNAUTHORIZED, cookie)); } throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); @@ -160,64 +159,36 @@ private String makeResponse(HttpRequest httpRequest) { InMemoryUserRepository.save(new User(account, password, email)); } - return makeResponseMessageFromFile("index.html", HttpStatusCode.FOUND, cookie); + return buildMessage("index.html", HttpStatusCode.FOUND, cookie); } } - return makeResponseMessageFromFile(httpRequest.getPath(), HttpStatusCode.OK, cookie); + return buildMessage(httpRequest.getPath(), HttpStatusCode.OK, cookie); } - private String makeResponseMessageFromText(String content, HttpStatusCode statusCode) { - return String.join( - "\r\n", - "HTTP/1.1 " + statusCode.getValue() + " ", - CONTENT_TYPE + ": " + "text/html;charset=utf-8 ", - CONTENT_LENGTH + ": " + content.getBytes().length + " ", - "", - content - ); + private String buildTextMessage(String content, HttpStatusCode statusCode) { + HttpResponse httpResponse = new HttpResponse(statusCode, content, ContentType.TEXT_HTML); + return httpResponse.buildMessage(); } - private String makeResponseMessageFromFile(String fileName, HttpStatusCode statusCode, HttpCookie cookie) { + private String buildMessage(String fileName, HttpStatusCode statusCode, HttpCookie cookie) { if (!fileName.contains(".")) { fileName += ".html"; } - String responseBody = readBody(fileName); - String contentType = ""; - - if (fileName.endsWith(".html")) { - contentType = "text/html"; - } - if (fileName.endsWith(".css")) { - contentType = "text/css"; - } - - if (fileName.endsWith(".svg")) { - contentType = "image/svg+xml"; - } - - if (fileName.endsWith(".js")) { - contentType = "text/javascript"; - } + String responseBody = readBody(fileName); + ContentType contentType = ContentType.fromFileExtension( + fileName.substring(fileName.lastIndexOf("."))); - Map headers = new HashMap<>(); - headers.put(CONTENT_TYPE, contentType + ";charset=utf-8 "); - headers.put(CONTENT_LENGTH, responseBody.getBytes().length + " "); + HttpResponse httpResponse = new HttpResponse(statusCode, responseBody, contentType); if (cookie == null || !cookie.hasCookieWithName(JSESSIONID)) { HttpCookie httpCookie = new HttpCookie(); httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); - headers.put(SET_COOKIE, httpCookie.getCookiesAsString()); + httpResponse.addHeader(SET_COOKIE, httpCookie.getCookiesAsString()); } - String startLineText = "HTTP/1.1 " + statusCode.getValue() + " "; - - String headerText = headers.keySet().stream() - .map(headerName -> headerName + ": " + headers.get(headerName)) - .collect(Collectors.joining(("\r\n"))); - - return String.join("\r\n", startLineText, headerText, "", responseBody); + return httpResponse.buildMessage(); } private String readBody(String fileName) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java index 809e6068a7..f5fc18d878 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java @@ -2,20 +2,22 @@ public enum HttpStatusCode { - OK("200 OK"), - FOUND("302 Found"), - UNAUTHORIZED("401 Unauthorized"), - NOT_FOUND("404 Not Found"), - INTERNAL_SERVER_ERROR("500 Internal Server Error"), + OK(200, "OK"), + FOUND(302, "Found"), + UNAUTHORIZED(401, "Unauthorized"), + NOT_FOUND(404, "Not Found"), + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), ; - private final String value; + private final int statusCode; + private final String statusMessage; - HttpStatusCode(String value) { - this.value = value; + HttpStatusCode(int statusCode, String statusMessage) { + this.statusCode = statusCode; + this.statusMessage = statusMessage; } - public String getValue() { - return value; + public String buildMessage() { + return "HTTP/1.1 " + statusCode + " " + statusMessage; } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java index 6fd8fd5a4a..ea5c588059 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java @@ -9,6 +9,10 @@ public class HttpHeader { private final Map headers; + public HttpHeader(Map headers) { + this.headers = headers; + } + public HttpHeader(List rawHeaders) { this.headers = rawHeaders.stream() .peek(rawHeader -> { @@ -29,4 +33,14 @@ public String get(String name) { public boolean contains(String name) { return headers.containsKey(name); } + + public void add(String name, String value) { + headers.put(name, value); + } + + public String buildMessage() { + return headers.keySet().stream() + .map(key -> key + ": " + headers.get(key)) + .collect(Collectors.joining("\r\n")); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/ContentType.java b/tomcat/src/main/java/org/apache/coyote/response/ContentType.java new file mode 100644 index 0000000000..57e8cca4fb --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/response/ContentType.java @@ -0,0 +1,31 @@ +package org.apache.coyote.response; + +import java.util.Arrays; + +public enum ContentType { + + TEXT_HTML(".html", "text/html"), + TEXT_CSS(".css", "text/css"), + IMAGE_SVGXML(".svg", "image/svg+xml"), + TEXT_JAVASCRIPT(".js", "text/javascript"), + ; + + private final String fileExtension; + private final String name; + + ContentType(String fileExtension, String name) { + this.fileExtension = fileExtension; + this.name = name; + } + + public static ContentType fromFileExtension(String fileExtension) { + return Arrays.stream(values()) + .filter(contentType -> fileExtension.equals(contentType.fileExtension)) + .findFirst() + .orElse(TEXT_HTML); + } + + public String getName() { + return name; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java new file mode 100644 index 0000000000..deee1570c5 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java @@ -0,0 +1,20 @@ +package org.apache.coyote.response; + +public enum HttpHeaders { + + CONTENT_LENGTH("Content-Length"), + COOKIE("Cookie"), + CONTENT_TYPE("Content-Type"), + SET_COOKIE("Set-Cookie"), + ; + + private final String name; + + HttpHeaders(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java new file mode 100644 index 0000000000..b0543c5cd7 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -0,0 +1,39 @@ +package org.apache.coyote.response; + +import java.util.HashMap; +import java.util.Map; +import org.apache.coyote.http11.HttpStatusCode; +import org.apache.coyote.request.HttpHeader; + +public class HttpResponse { + + private final HttpStatusCode httpStatusCode; + private final HttpHeader responseHeader; + private final String responseBody; + + public HttpResponse(HttpStatusCode httpStatusCode, String responseBody, ContentType contentType) { + this.httpStatusCode = httpStatusCode; + this.responseHeader = buildInitialHeaders(responseBody, contentType); + this.responseBody = responseBody; + } + + private HttpHeader buildInitialHeaders(String responseBody, ContentType contentType) { + Map headers = new HashMap<>(); + headers.put(HttpHeaders.CONTENT_LENGTH.getName(), responseBody.getBytes().length + " "); + headers.put(HttpHeaders.CONTENT_TYPE.getName(), contentType.getName() + ";charset=utf-8 "); + return new HttpHeader(headers); + } + + public void addHeader(String name, String value) { + responseHeader.add(name, value); + } + + public String buildMessage() { + return String.join("\r\n", + httpStatusCode.buildMessage(), + responseHeader.buildMessage(), + "", + responseBody + ); + } +} From 9c16499701f8d9ea388084c01a92103611ababad Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 16:53:53 +0900 Subject: [PATCH 24/64] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B0=8F=20=EC=8B=9C=EA=B7=B8=EB=8B=88=EC=B2=98?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/Http11Processor.java | 12 ++++++------ .../java/org/apache/coyote/http11/HttpCookie.java | 4 ++-- .../java/org/apache/coyote/request/RequestLine.java | 10 +++++----- .../org/apache/coyote/http11/HttpCookieTest.java | 4 ++-- 4 files changed, 15 insertions(+), 15 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 4fcf2ed140..2dda14195d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -115,7 +115,7 @@ private String handle(HttpRequest httpRequest) { } if (httpRequest.is(GET, "/login")) { - if (cookie.hasCookieWithName(JSESSIONID)) { + if (cookie.contains(JSESSIONID)) { SessionManager sessionManager = SessionManager.getInstance(); String sessionId = cookie.get(JSESSIONID); if (sessionManager.findSession(sessionId).isPresent()) { @@ -176,28 +176,28 @@ private String buildMessage(String fileName, HttpStatusCode statusCode, HttpCook fileName += ".html"; } - String responseBody = readBody(fileName); + String responseBody = readFile(fileName); ContentType contentType = ContentType.fromFileExtension( fileName.substring(fileName.lastIndexOf("."))); HttpResponse httpResponse = new HttpResponse(statusCode, responseBody, contentType); - if (cookie == null || !cookie.hasCookieWithName(JSESSIONID)) { + if (cookie == null || !cookie.contains(JSESSIONID)) { HttpCookie httpCookie = new HttpCookie(); httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); - httpResponse.addHeader(SET_COOKIE, httpCookie.getCookiesAsString()); + httpResponse.addHeader(SET_COOKIE, httpCookie.buildMessage()); } return httpResponse.buildMessage(); } - private String readBody(String fileName) { + private String readFile(String fileName) { try { URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); Path path = Paths.get(uri); return Files.readString(path); } catch (NullPointerException e) { - return readBody(NOT_FOUND_FILENAME); + return readFile(NOT_FOUND_FILENAME); } catch (Exception e) { throw new UncheckedServletException(e); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java index eddf2226cb..1b7f49e0f0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -37,13 +37,13 @@ public String get(String name) { return cookies.get(name); } - public String getCookiesAsString() { + public String buildMessage() { return cookies.keySet().stream() .map(key -> key + "=" + cookies.get(key)) .collect(Collectors.joining("; ")); } - public boolean hasCookieWithName(String name) { + public boolean contains(String name) { return cookies.containsKey(name); } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java index 9a44bedc3b..5b03f66526 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestLine.java @@ -14,15 +14,15 @@ public class RequestLine { private final String path; private final Map queryParameters; - public RequestLine(String requestLine) { - validateParameterCount(requestLine); - String[] parameters = requestLine.split(" "); + public RequestLine(String rawRequestLine) { + validateParameterCount(rawRequestLine); + String[] parameters = rawRequestLine.split(" "); validateUri(parameters[1]); validateHttpVersion(parameters[2]); this.method = HttpMethod.from(parameters[0]); - this.path = parseEndpoint(parameters[1]); + this.path = parsePath(parameters[1]); this.queryParameters = parseQueryParams(parameters[1]); } @@ -44,7 +44,7 @@ private void validateParameterCount(String rawRequestLine) { } } - private String parseEndpoint(String uri) { + private String parsePath(String uri) { return uri.split("\\?")[0]; } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java b/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java index 77b094fdef..d2565483cf 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java @@ -11,7 +11,7 @@ class HttpCookieTest { @Test void construct_Success() { HttpCookie httpCookie = new HttpCookie("name1=value1; name2=value2"); - assertThat(httpCookie.getCookiesAsString()) + assertThat(httpCookie.buildMessage()) .isIn("name1=value1; name2=value2", "name2=value2; name1=value1"); } @@ -21,7 +21,7 @@ void add() { HttpCookie httpCookie = new HttpCookie(); httpCookie.add("name1", "value1"); httpCookie.add("name2", "value2"); - assertThat(httpCookie.getCookiesAsString()) + assertThat(httpCookie.buildMessage()) .isIn("name1=value1; name2=value2", "name2=value2; name1=value1"); } } From 83023c129a90dfac4a9857c26f33617192793929 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 16:55:24 +0900 Subject: [PATCH 25/64] =?UTF-8?q?refactor:=20inline=20variable=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 4 +--- 1 file changed, 1 insertion(+), 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 2dda14195d..c9274f97db 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -64,9 +64,7 @@ public void process(final Socket connection) { HttpHeader httpHeader = new HttpHeader(readRequestHeaders(reader)); RequestBody requestBody = readRequestBody(reader, httpHeader); - HttpRequest httpRequest = new HttpRequest(requestLine, httpHeader, requestBody); - - final String response = handle(httpRequest); + final String response = handle(new HttpRequest(requestLine, httpHeader, requestBody)); outputStream.write(response.getBytes()); outputStream.flush(); From d93162622fa9cbc33dbdd4b1fcacf5f82ae3aeca Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 17:18:18 +0900 Subject: [PATCH 26/64] =?UTF-8?q?refactor:=20=ED=95=B8=EB=93=A4=EB=9F=AC?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/db/InMemoryUserRepository.java | 19 ++-- .../catalina/session/SessionManager.java | 4 + .../apache/coyote/http11/Http11Processor.java | 98 +++++++++++-------- 3 files changed, 75 insertions(+), 46 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index f839262800..01beba031b 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -1,9 +1,8 @@ package com.techcourse.db; +import com.techcourse.exception.UncheckedServletException; import com.techcourse.model.User; - import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; public class InMemoryUserRepository { @@ -19,16 +18,22 @@ public static void save(User user) { database.put(user.getAccount(), user); } - public static Optional findByAccountAndPassword(String account, String password) { - if (database.containsKey(account) && database.get(account).checkPassword(password)) { - return Optional.ofNullable(database.get(account)); + public static boolean existsByAccountAndPassword(String account, String password) { + return database.containsKey(account) + && database.get(account).checkPassword(password); + } + + public static User getByAccount(String account) { + if (database.containsKey(account)) { + return database.get(account); } - return Optional.empty(); + throw new UncheckedServletException("유저가 존재하지 않습니다."); } public static boolean existsByAccount(String account) { return database.containsKey(account); } - private InMemoryUserRepository() {} + private InMemoryUserRepository() { + } } diff --git a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java index d60ea91758..0cba6521ca 100644 --- a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java @@ -33,4 +33,8 @@ public Optional findSession(String id) { public void remove(String id) { sessions.remove(id); } + + public boolean hasId(String id) { + return sessions.containsKey(id); + } } 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 c9274f97db..d5e523d4a6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -40,11 +40,14 @@ public class Http11Processor implements Runnable, Processor { private static final String CONTENT_TYPE = "Content-Type"; private static final String SET_COOKIE = "Set-Cookie"; private static final String JSESSIONID = "JSESSIONID"; + private static final String USER_SESSION_NAME = "user"; private final Socket connection; + private final SessionManager sessionManager; public Http11Processor(final Socket connection) { this.connection = connection; + this.sessionManager = SessionManager.getInstance(); } @Override @@ -113,60 +116,77 @@ private String handle(HttpRequest httpRequest) { } if (httpRequest.is(GET, "/login")) { - if (cookie.contains(JSESSIONID)) { - SessionManager sessionManager = SessionManager.getInstance(); - String sessionId = cookie.get(JSESSIONID); - if (sessionManager.findSession(sessionId).isPresent()) { - return buildMessage("index.html", HttpStatusCode.FOUND, cookie); - } - } - - return buildMessage("login.html", HttpStatusCode.OK, cookie); + return getLoginPage(httpRequest); } if (httpRequest.is(POST, "/login")) { - RequestBody requestBody = httpRequest.getRequestBody(); - - if (requestBody.containsAll("account", "password")) { - String account = requestBody.get("account"); - String password = requestBody.get("password"); - - return InMemoryUserRepository.findByAccountAndPassword(account, password) - .map(user -> { - SessionManager sessionManager = SessionManager.getInstance(); - Session session = new Session(); - session.setAttribute("user", user); - sessionManager.add(cookie.get(JSESSIONID), session); - return buildMessage("index.html", HttpStatusCode.FOUND, cookie); - }) - .orElseGet(() -> buildMessage("401.html", HttpStatusCode.UNAUTHORIZED, cookie)); - } - - throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); + return login(httpRequest); } if (httpRequest.is(POST, "/register")) { - RequestBody requestBody = httpRequest.getRequestBody(); + return saveUser(httpRequest); + } - if (requestBody.containsAll("account", "email", "password")) { - String account = requestBody.get("account"); - String email = requestBody.get("email"); - String password = requestBody.get("password"); + return buildMessage(httpRequest.getPath(), HttpStatusCode.OK, cookie); + } - if (!InMemoryUserRepository.existsByAccount(account)) { - InMemoryUserRepository.save(new User(account, password, email)); - } + private String buildTextMessage(String content, HttpStatusCode statusCode) { + HttpResponse httpResponse = new HttpResponse(statusCode, content, ContentType.TEXT_HTML); + return httpResponse.buildMessage(); + } + private String getLoginPage(HttpRequest request) { + HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); + + if (cookie.contains(JSESSIONID)) { + if (sessionManager.hasId(cookie.get(JSESSIONID))) { return buildMessage("index.html", HttpStatusCode.FOUND, cookie); } } - return buildMessage(httpRequest.getPath(), HttpStatusCode.OK, cookie); + return buildMessage("login.html", HttpStatusCode.OK, cookie); } - private String buildTextMessage(String content, HttpStatusCode statusCode) { - HttpResponse httpResponse = new HttpResponse(statusCode, content, ContentType.TEXT_HTML); - return httpResponse.buildMessage(); + private String login(HttpRequest request) { + HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); + RequestBody requestBody = request.getRequestBody(); + + if (requestBody.containsAll("account", "password")) { + String account = requestBody.get("account"); + String password = requestBody.get("password"); + + if (InMemoryUserRepository.existsByAccountAndPassword(account, password)) { + User user = InMemoryUserRepository.getByAccount(account); + Session session = new Session(); + session.setAttribute(USER_SESSION_NAME, user); + sessionManager.add(cookie.get(JSESSIONID), session); + return buildMessage("index.html", HttpStatusCode.FOUND, cookie); + } + + return buildMessage("401.html", HttpStatusCode.UNAUTHORIZED, cookie); + } + + throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); + } + + private String saveUser(HttpRequest request) { + HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); + RequestBody requestBody = request.getRequestBody(); + + if (requestBody.containsAll("account", "email", "password")) { + String account = requestBody.get("account"); + String email = requestBody.get("email"); + String password = requestBody.get("password"); + + if (!InMemoryUserRepository.existsByAccount(account)) { + InMemoryUserRepository.save(new User(account, password, email)); + return buildMessage("index.html", HttpStatusCode.FOUND, cookie); + } + + throw new UncheckedServletException("이미 존재하는 ID입니다."); + } + + throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); } private String buildMessage(String fileName, HttpStatusCode statusCode, HttpCookie cookie) { From 875729eba7260b1044582893d44a802641193e65 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 17:20:17 +0900 Subject: [PATCH 27/64] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 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 d5e523d4a6..1473c6294c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -108,26 +108,27 @@ private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader } } - private String handle(HttpRequest httpRequest) { - HttpCookie cookie = new HttpCookie(httpRequest.getHeader(COOKIE)); + private String handle(HttpRequest request) { + HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); + RequestBody requestBody = request.getRequestBody(); - if (httpRequest.is(GET, "/")) { + if (request.is(GET, "/")) { return buildTextMessage("Hello world!", HttpStatusCode.OK); } - if (httpRequest.is(GET, "/login")) { - return getLoginPage(httpRequest); + if (request.is(GET, "/login")) { + return getLoginPage(cookie); } - if (httpRequest.is(POST, "/login")) { - return login(httpRequest); + if (request.is(POST, "/login")) { + return login(cookie, requestBody); } - if (httpRequest.is(POST, "/register")) { - return saveUser(httpRequest); + if (request.is(POST, "/register")) { + return saveUser(cookie, requestBody); } - return buildMessage(httpRequest.getPath(), HttpStatusCode.OK, cookie); + return buildMessage(request.getPath(), HttpStatusCode.OK, cookie); } private String buildTextMessage(String content, HttpStatusCode statusCode) { @@ -135,9 +136,7 @@ private String buildTextMessage(String content, HttpStatusCode statusCode) { return httpResponse.buildMessage(); } - private String getLoginPage(HttpRequest request) { - HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); - + private String getLoginPage(HttpCookie cookie) { if (cookie.contains(JSESSIONID)) { if (sessionManager.hasId(cookie.get(JSESSIONID))) { return buildMessage("index.html", HttpStatusCode.FOUND, cookie); @@ -147,10 +146,7 @@ private String getLoginPage(HttpRequest request) { return buildMessage("login.html", HttpStatusCode.OK, cookie); } - private String login(HttpRequest request) { - HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); - RequestBody requestBody = request.getRequestBody(); - + private String login(HttpCookie cookie, RequestBody requestBody) { if (requestBody.containsAll("account", "password")) { String account = requestBody.get("account"); String password = requestBody.get("password"); @@ -169,10 +165,7 @@ private String login(HttpRequest request) { throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); } - private String saveUser(HttpRequest request) { - HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); - RequestBody requestBody = request.getRequestBody(); - + private String saveUser(HttpCookie cookie, RequestBody requestBody) { if (requestBody.containsAll("account", "email", "password")) { String account = requestBody.get("account"); String email = requestBody.get("email"); From c2d23dee517ab48b4b3634c13464ae8967aafcd3 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 17:20:38 +0900 Subject: [PATCH 28/64] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/Http11Processor.java | 8 ++++---- .../main/java/org/apache/coyote/request/HttpRequest.java | 2 +- 2 files changed, 5 insertions(+), 5 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 1473c6294c..0275b4cd1c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -112,19 +112,19 @@ private String handle(HttpRequest request) { HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); RequestBody requestBody = request.getRequestBody(); - if (request.is(GET, "/")) { + if (request.pointsTo(GET, "/")) { return buildTextMessage("Hello world!", HttpStatusCode.OK); } - if (request.is(GET, "/login")) { + if (request.pointsTo(GET, "/login")) { return getLoginPage(cookie); } - if (request.is(POST, "/login")) { + if (request.pointsTo(POST, "/login")) { return login(cookie, requestBody); } - if (request.is(POST, "/register")) { + if (request.pointsTo(POST, "/register")) { return saveUser(cookie, requestBody); } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index c975f54a69..995b8b6f2d 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -12,7 +12,7 @@ public HttpRequest(RequestLine requestLine, HttpHeader httpHeader, RequestBody r this.requestBody = requestBody; } - public boolean is(HttpMethod httpMethod, String path) { + public boolean pointsTo(HttpMethod httpMethod, String path) { return httpMethod == requestLine.getMethod() && path.equals(requestLine.getPath()); } From af08606c8fe5c8af7b74771a9d377dfb0f7089e2 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 17:32:09 +0900 Subject: [PATCH 29/64] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20if=EB=AC=B8=20depth=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 6 ++---- 1 file changed, 2 insertions(+), 4 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 0275b4cd1c..42dc495206 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -137,10 +137,8 @@ private String buildTextMessage(String content, HttpStatusCode statusCode) { } private String getLoginPage(HttpCookie cookie) { - if (cookie.contains(JSESSIONID)) { - if (sessionManager.hasId(cookie.get(JSESSIONID))) { - return buildMessage("index.html", HttpStatusCode.FOUND, cookie); - } + if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { + return buildMessage("index.html", HttpStatusCode.FOUND, cookie); } return buildMessage("login.html", HttpStatusCode.OK, cookie); From 7fee36c09060ccae79a4a98c46d8bcad42387e3a Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:11:13 +0900 Subject: [PATCH 30/64] =?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=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/db/InMemoryUserRepository.java | 2 +- .../apache/coyote/http11/Http11Processor.java | 73 ++++++------------- .../{request => http11}/HttpHeader.java | 2 +- .../apache/coyote/request/HttpRequest.java | 2 + .../apache/coyote/response/ContentType.java | 4 +- .../apache/coyote/response/HttpResponse.java | 31 +++++++- .../{http11 => response}/HttpStatusCode.java | 2 +- .../org/apache/coyote/util/FileReader.java | 34 +++++++++ .../apache/coyote/request/HttpHeaderTest.java | 1 + 9 files changed, 94 insertions(+), 57 deletions(-) rename tomcat/src/main/java/org/apache/coyote/{request => http11}/HttpHeader.java (97%) rename tomcat/src/main/java/org/apache/coyote/{http11 => response}/HttpStatusCode.java (93%) create mode 100644 tomcat/src/main/java/org/apache/coyote/util/FileReader.java diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index 01beba031b..a38e7c1da1 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -18,7 +18,7 @@ public static void save(User user) { database.put(user.getAccount(), user); } - public static boolean existsByAccountAndPassword(String account, String password) { + public static boolean exists(String account, String password) { return database.containsKey(account) && database.get(account).checkPassword(password); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 42dc495206..26299538d0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -10,22 +10,17 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import org.apache.catalina.session.Session; import org.apache.catalina.session.SessionManager; import org.apache.coyote.Processor; -import org.apache.coyote.request.HttpHeader; import org.apache.coyote.request.HttpRequest; import org.apache.coyote.request.RequestBody; import org.apache.coyote.request.RequestLine; import org.apache.coyote.response.ContentType; import org.apache.coyote.response.HttpResponse; +import org.apache.coyote.response.HttpStatusCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,12 +28,8 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final String STATIC_DIRNAME = "static"; - private static final String NOT_FOUND_FILENAME = "404.html"; private static final String CONTENT_LENGTH = "Content-Length"; private static final String COOKIE = "Cookie"; - private static final String CONTENT_TYPE = "Content-Type"; - private static final String SET_COOKIE = "Set-Cookie"; private static final String JSESSIONID = "JSESSIONID"; private static final String USER_SESSION_NAME = "user"; @@ -128,7 +119,8 @@ private String handle(HttpRequest request) { return saveUser(cookie, requestBody); } - return buildMessage(request.getPath(), HttpStatusCode.OK, cookie); + return HttpResponse.ofStaticFile(request.getPath().substring(1), HttpStatusCode.OK, cookie) + .buildMessage(); } private String buildTextMessage(String content, HttpStatusCode statusCode) { @@ -138,10 +130,12 @@ private String buildTextMessage(String content, HttpStatusCode statusCode) { private String getLoginPage(HttpCookie cookie) { if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { - return buildMessage("index.html", HttpStatusCode.FOUND, cookie); + return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND, cookie) + .buildMessage(); } - return buildMessage("login.html", HttpStatusCode.OK, cookie); + return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK, cookie) + .buildMessage(); } private String login(HttpCookie cookie, RequestBody requestBody) { @@ -149,15 +143,15 @@ private String login(HttpCookie cookie, RequestBody requestBody) { String account = requestBody.get("account"); String password = requestBody.get("password"); - if (InMemoryUserRepository.existsByAccountAndPassword(account, password)) { + if (InMemoryUserRepository.exists(account, password)) { User user = InMemoryUserRepository.getByAccount(account); - Session session = new Session(); - session.setAttribute(USER_SESSION_NAME, user); - sessionManager.add(cookie.get(JSESSIONID), session); - return buildMessage("index.html", HttpStatusCode.FOUND, cookie); + saveSession(cookie, user); + return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND, cookie) + .buildMessage(); } - return buildMessage("401.html", HttpStatusCode.UNAUTHORIZED, cookie); + return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED, cookie) + .buildMessage(); } throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); @@ -170,8 +164,11 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { String password = requestBody.get("password"); if (!InMemoryUserRepository.existsByAccount(account)) { - InMemoryUserRepository.save(new User(account, password, email)); - return buildMessage("index.html", HttpStatusCode.FOUND, cookie); + User user = new User(account, password, email); + InMemoryUserRepository.save(user); + saveSession(cookie, user); + return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND, cookie) + .buildMessage(); } throw new UncheckedServletException("이미 존재하는 ID입니다."); @@ -180,35 +177,9 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); } - private String buildMessage(String fileName, HttpStatusCode statusCode, HttpCookie cookie) { - if (!fileName.contains(".")) { - fileName += ".html"; - } - - String responseBody = readFile(fileName); - ContentType contentType = ContentType.fromFileExtension( - fileName.substring(fileName.lastIndexOf("."))); - - HttpResponse httpResponse = new HttpResponse(statusCode, responseBody, contentType); - - if (cookie == null || !cookie.contains(JSESSIONID)) { - HttpCookie httpCookie = new HttpCookie(); - httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); - httpResponse.addHeader(SET_COOKIE, httpCookie.buildMessage()); - } - - return httpResponse.buildMessage(); - } - - private String readFile(String fileName) { - try { - URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); - Path path = Paths.get(uri); - return Files.readString(path); - } catch (NullPointerException e) { - return readFile(NOT_FOUND_FILENAME); - } catch (Exception e) { - throw new UncheckedServletException(e); - } + private void saveSession(HttpCookie cookie, User user) { + Session session = new Session(); + session.setAttribute(USER_SESSION_NAME, user); + sessionManager.add(cookie.get(JSESSIONID), session); } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java similarity index 97% rename from tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java rename to tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java index ea5c588059..ade8ef33c5 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java @@ -1,4 +1,4 @@ -package org.apache.coyote.request; +package org.apache.coyote.http11; import com.techcourse.exception.UncheckedServletException; import java.util.List; diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index 995b8b6f2d..b397e9a057 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -1,5 +1,7 @@ package org.apache.coyote.request; +import org.apache.coyote.http11.HttpHeader; + public class HttpRequest { private final RequestLine requestLine; diff --git a/tomcat/src/main/java/org/apache/coyote/response/ContentType.java b/tomcat/src/main/java/org/apache/coyote/response/ContentType.java index 57e8cca4fb..df256417f2 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/response/ContentType.java @@ -18,7 +18,9 @@ public enum ContentType { this.name = name; } - public static ContentType fromFileExtension(String fileExtension) { + public static ContentType fromFileName(String fileName) { + String fileExtension = fileName.substring(fileName.lastIndexOf(".")); + return Arrays.stream(values()) .filter(contentType -> fileExtension.equals(contentType.fileExtension)) .findFirst() diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index b0543c5cd7..ecf3967d56 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -2,11 +2,18 @@ import java.util.HashMap; import java.util.Map; -import org.apache.coyote.http11.HttpStatusCode; -import org.apache.coyote.request.HttpHeader; +import java.util.UUID; +import org.apache.coyote.http11.HttpCookie; +import org.apache.coyote.http11.HttpHeader; +import org.apache.coyote.util.FileReader; public class HttpResponse { + private static final FileReader FILE_READER = FileReader.getInstance(); + + private static final String JSESSIONID = "JSESSIONID"; + private static final String SET_COOKIE = "Set-Cookie"; + private final HttpStatusCode httpStatusCode; private final HttpHeader responseHeader; private final String responseBody; @@ -17,6 +24,26 @@ public HttpResponse(HttpStatusCode httpStatusCode, String responseBody, ContentT this.responseBody = responseBody; } + public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStatusCode, HttpCookie cookie) { + if (!fileName.contains(".")) { + fileName += ".html"; + } + + HttpResponse response = new HttpResponse( + httpStatusCode, + FILE_READER.read(fileName), + ContentType.fromFileName(fileName) + ); + + if (!cookie.contains(JSESSIONID)) { + HttpCookie httpCookie = new HttpCookie(); + httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); + response.addHeader(SET_COOKIE, httpCookie.buildMessage()); + } + + return response; + } + private HttpHeader buildInitialHeaders(String responseBody, ContentType contentType) { Map headers = new HashMap<>(); headers.put(HttpHeaders.CONTENT_LENGTH.getName(), responseBody.getBytes().length + " "); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/response/HttpStatusCode.java similarity index 93% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java rename to tomcat/src/main/java/org/apache/coyote/response/HttpStatusCode.java index f5fc18d878..3427953693 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpStatusCode.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.response; public enum HttpStatusCode { diff --git a/tomcat/src/main/java/org/apache/coyote/util/FileReader.java b/tomcat/src/main/java/org/apache/coyote/util/FileReader.java new file mode 100644 index 0000000000..1a2b6e18bd --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/util/FileReader.java @@ -0,0 +1,34 @@ +package org.apache.coyote.util; + +import com.techcourse.exception.UncheckedServletException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FileReader { + + private static final FileReader INSTANCE = new FileReader(); + + private static final String STATIC_DIRNAME = "static"; + + private FileReader() { + } + + public static FileReader getInstance() { + return INSTANCE; + } + + public String read(String fileName) { + try { + System.out.println("STATIC_DIRNAME + \"/\" + fileName = " + STATIC_DIRNAME + "/" + fileName); + URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); + Path path = Paths.get(uri); + return Files.readString(path); + } catch (NullPointerException e) { + throw new UncheckedServletException("파일이 존재하지 않습니다."); + } catch (Exception e) { + throw new UncheckedServletException(e); + } + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java index 98a7e7cf6e..7aa1e6c9ed 100644 --- a/tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java +++ b/tomcat/src/test/java/org/apache/coyote/request/HttpHeaderTest.java @@ -6,6 +6,7 @@ import com.techcourse.exception.UncheckedServletException; import java.util.List; +import org.apache.coyote.http11.HttpHeader; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From fe6992b14c88fc2eb0a0a48b56803a34580b9875 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:18:03 +0900 Subject: [PATCH 31/64] =?UTF-8?q?refactor:=20set-cookie=20=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=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 | 18 ++++++++++++------ .../apache/coyote/response/HttpResponse.java | 11 ++++++----- 2 files changed, 18 insertions(+), 11 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 26299538d0..dca167f59a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -119,7 +119,8 @@ private String handle(HttpRequest request) { return saveUser(cookie, requestBody); } - return HttpResponse.ofStaticFile(request.getPath().substring(1), HttpStatusCode.OK, cookie) + return HttpResponse.ofStaticFile(request.getPath().substring(1), HttpStatusCode.OK) + .cookie(cookie) .buildMessage(); } @@ -130,11 +131,13 @@ private String buildTextMessage(String content, HttpStatusCode statusCode) { private String getLoginPage(HttpCookie cookie) { if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { - return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND, cookie) + return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND) + .cookie(cookie) .buildMessage(); } - return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK, cookie) + return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK) + .cookie(cookie) .buildMessage(); } @@ -146,11 +149,13 @@ private String login(HttpCookie cookie, RequestBody requestBody) { if (InMemoryUserRepository.exists(account, password)) { User user = InMemoryUserRepository.getByAccount(account); saveSession(cookie, user); - return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND, cookie) + return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND) + .cookie(cookie) .buildMessage(); } - return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED, cookie) + return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED) + .cookie(cookie) .buildMessage(); } @@ -167,7 +172,8 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { User user = new User(account, password, email); InMemoryUserRepository.save(user); saveSession(cookie, user); - return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND, cookie) + return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND) + .cookie(cookie) .buildMessage(); } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index ecf3967d56..0dbe756d8f 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -24,24 +24,25 @@ public HttpResponse(HttpStatusCode httpStatusCode, String responseBody, ContentT this.responseBody = responseBody; } - public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStatusCode, HttpCookie cookie) { + public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStatusCode) { if (!fileName.contains(".")) { fileName += ".html"; } - HttpResponse response = new HttpResponse( + return new HttpResponse( httpStatusCode, FILE_READER.read(fileName), ContentType.fromFileName(fileName) ); + } + public HttpResponse cookie(HttpCookie cookie) { if (!cookie.contains(JSESSIONID)) { HttpCookie httpCookie = new HttpCookie(); httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); - response.addHeader(SET_COOKIE, httpCookie.buildMessage()); + this.addHeader(SET_COOKIE, httpCookie.buildMessage()); } - - return response; + return this; } private HttpHeader buildInitialHeaders(String responseBody, ContentType contentType) { From ce217618dc8a0f734489f47f590758263e807830 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:24:12 +0900 Subject: [PATCH 32/64] =?UTF-8?q?fix:=20Location=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=EB=A1=9C=20=EC=98=AC=EB=B0=94=EB=A5=B4=EA=B2=8C=20=EB=A6=AC?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EB=A0=89=EC=85=98=20=ED=95=98=EB=8F=84?= =?UTF-8?q?=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 --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 6 +++--- .../main/java/org/apache/coyote/response/HttpResponse.java | 6 ++++++ tomcat/src/main/java/org/apache/coyote/util/FileReader.java | 1 - 3 files changed, 9 insertions(+), 4 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 dca167f59a..af2c7158ae 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -131,7 +131,7 @@ private String buildTextMessage(String content, HttpStatusCode statusCode) { private String getLoginPage(HttpCookie cookie) { if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { - return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND) + return HttpResponse.ofRedirection("/index.html") .cookie(cookie) .buildMessage(); } @@ -149,7 +149,7 @@ private String login(HttpCookie cookie, RequestBody requestBody) { if (InMemoryUserRepository.exists(account, password)) { User user = InMemoryUserRepository.getByAccount(account); saveSession(cookie, user); - return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND) + return HttpResponse.ofRedirection("/index.html") .cookie(cookie) .buildMessage(); } @@ -172,7 +172,7 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { User user = new User(account, password, email); InMemoryUserRepository.save(user); saveSession(cookie, user); - return HttpResponse.ofStaticFile("index.html", HttpStatusCode.FOUND) + return HttpResponse.ofRedirection("/index.html") .cookie(cookie) .buildMessage(); } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 0dbe756d8f..90767b39f2 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -36,6 +36,12 @@ public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStat ); } + public static HttpResponse ofRedirection(String path) { + HttpResponse response = new HttpResponse(HttpStatusCode.FOUND, "", ContentType.TEXT_HTML); + response.addHeader("Location", path); + return response; + } + public HttpResponse cookie(HttpCookie cookie) { if (!cookie.contains(JSESSIONID)) { HttpCookie httpCookie = new HttpCookie(); diff --git a/tomcat/src/main/java/org/apache/coyote/util/FileReader.java b/tomcat/src/main/java/org/apache/coyote/util/FileReader.java index 1a2b6e18bd..dc379cc99b 100644 --- a/tomcat/src/main/java/org/apache/coyote/util/FileReader.java +++ b/tomcat/src/main/java/org/apache/coyote/util/FileReader.java @@ -21,7 +21,6 @@ public static FileReader getInstance() { public String read(String fileName) { try { - System.out.println("STATIC_DIRNAME + \"/\" + fileName = " + STATIC_DIRNAME + "/" + fileName); URI uri = getClass().getClassLoader().getResource(STATIC_DIRNAME + "/" + fileName).toURI(); Path path = Paths.get(uri); return Files.readString(path); From 851ee855ee2bb6c3d6ee59c9ceaad9236a98ffd2 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:25:37 +0900 Subject: [PATCH 33/64] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 6 +++--- .../main/java/org/apache/coyote/response/HttpResponse.java | 2 +- 2 files changed, 4 insertions(+), 4 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 af2c7158ae..9d7c594fda 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -131,7 +131,7 @@ private String buildTextMessage(String content, HttpStatusCode statusCode) { private String getLoginPage(HttpCookie cookie) { if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { - return HttpResponse.ofRedirection("/index.html") + return HttpResponse.redirectTo("/index.html") .cookie(cookie) .buildMessage(); } @@ -149,7 +149,7 @@ private String login(HttpCookie cookie, RequestBody requestBody) { if (InMemoryUserRepository.exists(account, password)) { User user = InMemoryUserRepository.getByAccount(account); saveSession(cookie, user); - return HttpResponse.ofRedirection("/index.html") + return HttpResponse.redirectTo("/index.html") .cookie(cookie) .buildMessage(); } @@ -172,7 +172,7 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { User user = new User(account, password, email); InMemoryUserRepository.save(user); saveSession(cookie, user); - return HttpResponse.ofRedirection("/index.html") + return HttpResponse.redirectTo("/index.html") .cookie(cookie) .buildMessage(); } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 90767b39f2..a3ebbbf51d 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -36,7 +36,7 @@ public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStat ); } - public static HttpResponse ofRedirection(String path) { + public static HttpResponse redirectTo(String path) { HttpResponse response = new HttpResponse(HttpStatusCode.FOUND, "", ContentType.TEXT_HTML); response.addHeader("Location", path); return response; From db1cba5c5f11aba7e757ada5573ed85b2efc85bf Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:31:12 +0900 Subject: [PATCH 34/64] =?UTF-8?q?fix:=20request=20line=20=EB=A7=88?= =?UTF-8?q?=EC=A7=80=EB=A7=89=EC=97=90=20=EA=B3=B5=EB=B0=B1=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 --- .../main/java/org/apache/coyote/response/HttpStatusCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/response/HttpStatusCode.java index 3427953693..adda5482a6 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpStatusCode.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpStatusCode.java @@ -18,6 +18,6 @@ public enum HttpStatusCode { } public String buildMessage() { - return "HTTP/1.1 " + statusCode + " " + statusMessage; + return "HTTP/1.1 " + statusCode + " " + statusMessage + " "; } } From 5735196255bb00a1155f39d90d5ebefb84d7d84b Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:31:31 +0900 Subject: [PATCH 35/64] =?UTF-8?q?refactor:=20=EB=8B=A8=EC=88=9C=20?= =?UTF-8?q?=ED=85=8D=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=9D=91=EB=8B=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/Http11Processor.java | 8 ++------ .../java/org/apache/coyote/response/HttpResponse.java | 6 ++++++ 2 files changed, 8 insertions(+), 6 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 9d7c594fda..9feb2a4767 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -104,7 +104,8 @@ private String handle(HttpRequest request) { RequestBody requestBody = request.getRequestBody(); if (request.pointsTo(GET, "/")) { - return buildTextMessage("Hello world!", HttpStatusCode.OK); + return HttpResponse.ofContent("Hello world!") + .buildMessage(); } if (request.pointsTo(GET, "/login")) { @@ -124,11 +125,6 @@ private String handle(HttpRequest request) { .buildMessage(); } - private String buildTextMessage(String content, HttpStatusCode statusCode) { - HttpResponse httpResponse = new HttpResponse(statusCode, content, ContentType.TEXT_HTML); - return httpResponse.buildMessage(); - } - private String getLoginPage(HttpCookie cookie) { if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { return HttpResponse.redirectTo("/index.html") diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index a3ebbbf51d..4e54d480bc 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -24,6 +24,10 @@ public HttpResponse(HttpStatusCode httpStatusCode, String responseBody, ContentT this.responseBody = responseBody; } + public static HttpResponse ofContent(String content) { + return new HttpResponse(HttpStatusCode.OK, content, ContentType.TEXT_HTML); + } + public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStatusCode) { if (!fileName.contains(".")) { fileName += ".html"; @@ -63,6 +67,8 @@ public void addHeader(String name, String value) { } public String buildMessage() { + System.out.println("httpStatusCode = " + httpStatusCode.buildMessage()); + return String.join("\r\n", httpStatusCode.buildMessage(), responseHeader.buildMessage(), From 8fab15d371c362925bd60968a7938588188c34ec Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:31:39 +0900 Subject: [PATCH 36/64] =?UTF-8?q?test:=20=EA=B9=A8=EC=A7=80=EB=8A=94=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=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/Http11ProcessorTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java index ee6e210495..adfbdc803e 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java @@ -22,14 +22,12 @@ void process() { processor.process(socket); // then - var expected = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 12 ", - "", - "Hello world!"); - - assertThat(socket.output()).isEqualTo(expected); + assertThat(socket.output()).contains( + "HTTP/1.1 200 OK \r\n", + "Content-Type: text/html;charset=utf-8 \r\n", + "Content-Length: 12 \r\n", + "Hello world!" + ); } @Test From 74bdc403a12644097124759a7ad4aeba27b33230 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Thu, 5 Sep 2024 18:33:06 +0900 Subject: [PATCH 37/64] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/Http11Processor.java | 15 +++++++-------- .../org/apache/coyote/response/HttpResponse.java | 2 +- 2 files changed, 8 insertions(+), 9 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 9feb2a4767..57dee3e09d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -18,7 +18,6 @@ import org.apache.coyote.request.HttpRequest; import org.apache.coyote.request.RequestBody; import org.apache.coyote.request.RequestLine; -import org.apache.coyote.response.ContentType; import org.apache.coyote.response.HttpResponse; import org.apache.coyote.response.HttpStatusCode; import org.slf4j.Logger; @@ -105,7 +104,7 @@ private String handle(HttpRequest request) { if (request.pointsTo(GET, "/")) { return HttpResponse.ofContent("Hello world!") - .buildMessage(); + .build(); } if (request.pointsTo(GET, "/login")) { @@ -122,19 +121,19 @@ private String handle(HttpRequest request) { return HttpResponse.ofStaticFile(request.getPath().substring(1), HttpStatusCode.OK) .cookie(cookie) - .buildMessage(); + .build(); } private String getLoginPage(HttpCookie cookie) { if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { return HttpResponse.redirectTo("/index.html") .cookie(cookie) - .buildMessage(); + .build(); } return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK) .cookie(cookie) - .buildMessage(); + .build(); } private String login(HttpCookie cookie, RequestBody requestBody) { @@ -147,12 +146,12 @@ private String login(HttpCookie cookie, RequestBody requestBody) { saveSession(cookie, user); return HttpResponse.redirectTo("/index.html") .cookie(cookie) - .buildMessage(); + .build(); } return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED) .cookie(cookie) - .buildMessage(); + .build(); } throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); @@ -170,7 +169,7 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { saveSession(cookie, user); return HttpResponse.redirectTo("/index.html") .cookie(cookie) - .buildMessage(); + .build(); } throw new UncheckedServletException("이미 존재하는 ID입니다."); diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 4e54d480bc..fb389b520c 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -66,7 +66,7 @@ public void addHeader(String name, String value) { responseHeader.add(name, value); } - public String buildMessage() { + public String build() { System.out.println("httpStatusCode = " + httpStatusCode.buildMessage()); return String.join("\r\n", From a70c5654c64923800ea9f6bad535daa65a787bb1 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 10:15:43 +0900 Subject: [PATCH 38/64] =?UTF-8?q?feat:=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?= =?UTF-8?q?=EC=97=90=20=EC=A0=91=EA=B7=BC=ED=95=98=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20404=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=B4=EC=97=AC=EC=A4=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/response/HttpResponse.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index fb389b520c..238f6b0530 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -1,5 +1,6 @@ package org.apache.coyote.response; +import com.techcourse.exception.UncheckedServletException; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -13,6 +14,7 @@ public class HttpResponse { private static final String JSESSIONID = "JSESSIONID"; private static final String SET_COOKIE = "Set-Cookie"; + private static final String NOT_FOUND_FILENAME = "404.html"; private final HttpStatusCode httpStatusCode; private final HttpHeader responseHeader; @@ -33,11 +35,19 @@ public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStat fileName += ".html"; } - return new HttpResponse( - httpStatusCode, - FILE_READER.read(fileName), - ContentType.fromFileName(fileName) - ); + try { + return new HttpResponse( + httpStatusCode, + FILE_READER.read(fileName), + ContentType.fromFileName(fileName) + ); + } catch (UncheckedServletException e) { + return new HttpResponse( + HttpStatusCode.NOT_FOUND, + FILE_READER.read(NOT_FOUND_FILENAME), + ContentType.fromFileName(fileName) + ); + } } public static HttpResponse redirectTo(String path) { From bb2190273b25e1c01f74aed0ea245dfeee3368b8 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 10:16:39 +0900 Subject: [PATCH 39/64] =?UTF-8?q?refactor:=20Location=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=EB=A5=BC=20enum=EC=9C=BC=EB=A1=9C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/apache/coyote/response/HttpHeaders.java | 1 + .../src/main/java/org/apache/coyote/response/HttpResponse.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java index deee1570c5..441c17078b 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java @@ -6,6 +6,7 @@ public enum HttpHeaders { COOKIE("Cookie"), CONTENT_TYPE("Content-Type"), SET_COOKIE("Set-Cookie"), + LOCATION("Location"), ; private final String name; diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 238f6b0530..521d7c98bf 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -52,7 +52,7 @@ public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStat public static HttpResponse redirectTo(String path) { HttpResponse response = new HttpResponse(HttpStatusCode.FOUND, "", ContentType.TEXT_HTML); - response.addHeader("Location", path); + response.addHeader(HttpHeaders.LOCATION.getName(), path); return response; } From 436b85ce3653ce918763f29cb0adafa9d310d76c Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 10:16:50 +0900 Subject: [PATCH 40/64] =?UTF-8?q?chore:=20print=EB=AC=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/apache/coyote/response/HttpResponse.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 521d7c98bf..12907fb416 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -77,8 +77,6 @@ public void addHeader(String name, String value) { } public String build() { - System.out.println("httpStatusCode = " + httpStatusCode.buildMessage()); - return String.join("\r\n", httpStatusCode.buildMessage(), responseHeader.buildMessage(), From af4c48afd9158d157e3c9d33fda4fd30643b8215 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 10:25:57 +0900 Subject: [PATCH 41/64] =?UTF-8?q?refactor:=20Set-Cookie=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=EB=A5=BC=20=EC=83=81=EC=88=98=EA=B0=80=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20enum=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/apache/coyote/response/HttpResponse.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 12907fb416..2464a4cceb 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -13,7 +13,6 @@ public class HttpResponse { private static final FileReader FILE_READER = FileReader.getInstance(); private static final String JSESSIONID = "JSESSIONID"; - private static final String SET_COOKIE = "Set-Cookie"; private static final String NOT_FOUND_FILENAME = "404.html"; private final HttpStatusCode httpStatusCode; @@ -60,7 +59,7 @@ public HttpResponse cookie(HttpCookie cookie) { if (!cookie.contains(JSESSIONID)) { HttpCookie httpCookie = new HttpCookie(); httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); - this.addHeader(SET_COOKIE, httpCookie.buildMessage()); + this.addHeader(HttpHeaders.SET_COOKIE.getName(), httpCookie.buildMessage()); } return this; } From 33af31df8fd9803a6b526e31c4507a27b4481590 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 15:03:18 +0900 Subject: [PATCH 42/64] =?UTF-8?q?test:=20FileTest=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 42 ++++++++++++------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..9d85fad7f1 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,53 +1,51 @@ 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.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; +import java.nio.file.Paths; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; /** - * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. - * File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. + * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. */ @DisplayName("File 클래스 학습 테스트") class FileTest { /** * resource 디렉터리 경로 찾기 - * - * File 객체를 생성하려면 파일의 경로를 알아야 한다. - * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. - * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? + *

    + * File 객체를 생성하려면 파일의 경로를 알아야 한다. 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. resource 디렉터리의 경로는 어떻게 알아낼 수 + * 있을까? */ @Test void resource_디렉터리에_있는_파일의_경로를_찾는다() { final String fileName = "nextstep.txt"; - // todo - final String actual = ""; + URL url = getClass().getClassLoader().getResource(fileName); + final String actual = url.toString(); assertThat(actual).endsWith(fileName); } /** * 파일 내용 읽기 - * - * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. - * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. + *

    + * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws URISyntaxException, IOException { final String fileName = "nextstep.txt"; - // todo - final Path path = null; - - // todo - final List actual = Collections.emptyList(); + URL url = getClass().getClassLoader().getResource(fileName); + final Path path = Paths.get(url.toURI()); + final List actual = Files.readAllLines(path); assertThat(actual).containsOnly("nextstep"); } From f69e93d6a4b8c28f010c55e299ffabd306275329 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 15:09:17 +0900 Subject: [PATCH 43/64] =?UTF-8?q?test:=20IOStreamTest=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/IOStreamTest.java | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..b6af9dcbb4 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -48,11 +48,7 @@ class OutputStream_학습_테스트 { void OutputStream은_데이터를_바이트로_처리한다() throws IOException { final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112}; final OutputStream outputStream = new ByteArrayOutputStream(bytes.length); - - /** - * todo - * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 - */ + outputStream.write(bytes); final String actual = outputStream.toString(); @@ -72,13 +68,12 @@ class OutputStream_학습_테스트 { @Test void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException { final OutputStream outputStream = mock(BufferedOutputStream.class); - /** * todo * flush를 사용해서 테스트를 통과시킨다. * ByteArrayOutputStream과 어떤 차이가 있을까? */ - + outputStream.flush(); verify(outputStream, atLeastOnce()).flush(); outputStream.close(); } @@ -96,6 +91,7 @@ class OutputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + outputStream.close(); verify(outputStream, atLeastOnce()).close(); } @@ -128,7 +124,8 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + final String actual = bufferedReader.readLine(); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -148,7 +145,7 @@ class InputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ - + inputStream.close(); verify(inputStream, atLeastOnce()).close(); } } @@ -169,12 +166,12 @@ class FilterStream_학습_테스트 { * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? */ @Test - void 필터인_BufferedInputStream를_사용해보자() { + void 필터인_BufferedInputStream를_사용해보자() throws IOException { final String text = "필터에 연결해보자."; final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; + final InputStream bufferedInputStream = new BufferedInputStream(inputStream); - final byte[] actual = new byte[0]; + final byte[] actual = bufferedInputStream.readAllBytes(); assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); @@ -197,7 +194,7 @@ class InputStreamReader_학습_테스트 { * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. */ @Test - void BufferedReader를_사용하여_문자열을_읽어온다() { + void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException { final String emoji = String.join("\r\n", "😀😃😄😁😆😅😂🤣🥲☺️😊", "😇🙂🙃😉😌😍🥰😘😗😙😚", @@ -206,6 +203,10 @@ class InputStreamReader_학습_테스트 { final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); final StringBuilder actual = new StringBuilder(); + final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + while (bufferedReader.ready()) { + actual.append(bufferedReader.readLine()).append("\r\n"); + } assertThat(actual).hasToString(emoji); } From a321dd70cc02213b2080fe39f4ca60de641faeab Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 15:15:12 +0900 Subject: [PATCH 44/64] =?UTF-8?q?chore:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EB=8B=A4=EC=8B=9C=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EA=B4=80=EB=A6=AC=20=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index d7b5634b8e..7ea3082447 100644 --- a/.gitignore +++ b/.gitignore @@ -168,6 +168,3 @@ Icon Network Trash Folder Temporary Items .apdisk - -# 학습 테스트 -/study/ From 9728731ddfc5f29508396f84b2a73217182342cf Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 11:11:09 +0900 Subject: [PATCH 45/64] fix: remove implementation logback-classic on gradle (#501) (cherry picked from commit fed02f6f5f4308400e55c160d9495cad010f5bfb) --- study/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/study/build.gradle b/study/build.gradle index 5c69542f84..87a1f0313c 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -19,7 +19,6 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'ch.qos.logback:logback-classic:1.5.7' implementation 'org.apache.commons:commons-lang3:3.14.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1' From 6a3782ad298e5693b4755353d386f2627f09a37a Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 13:51:07 +0900 Subject: [PATCH 46/64] fix: add threads min-spare configuration on properties (#502) (cherry picked from commit 7e9135698878932274ddc1f523ba817ed9c56c70) --- study/src/main/resources/application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..e3503a5fb9 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -6,4 +6,5 @@ server: accept-count: 1 max-connections: 1 threads: + min-spare: 2 max: 2 From 03b1b720f20d04bbb6fb9052692dcba4b6896a73 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 13:28:10 +0900 Subject: [PATCH 47/64] =?UTF-8?q?chore:=20Thymeleaf=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/study/build.gradle b/study/build.gradle index 87a1f0313c..bb8b420ca5 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -22,6 +22,7 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.14.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.assertj:assertj-core:3.26.0' From df06a968c346e4a23323c0028b8fb2c6cbb89b01 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 13:28:37 +0900 Subject: [PATCH 48/64] =?UTF-8?q?feat:=20=ED=9C=B4=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8B=B1=20=EC=BA=90=EC=8B=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/cachecontrol/CacheWebConfig.java | 1 + .../ResponseHeaderInterceptor.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 study/src/main/java/cache/com/example/cachecontrol/ResponseHeaderInterceptor.java 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..257b096aa6 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 ResponseHeaderInterceptor()); } } diff --git a/study/src/main/java/cache/com/example/cachecontrol/ResponseHeaderInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/ResponseHeaderInterceptor.java new file mode 100644 index 0000000000..257f7122fa --- /dev/null +++ b/study/src/main/java/cache/com/example/cachecontrol/ResponseHeaderInterceptor.java @@ -0,0 +1,18 @@ +package cache.com.example.cachecontrol; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +public class ResponseHeaderInterceptor implements HandlerInterceptor { + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) throws Exception { + + response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache, private"); + HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); + } +} From c0b08eeaca7120b8fadb1ed85c3ca0238c0c22d8 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 13:29:43 +0900 Subject: [PATCH 49/64] =?UTF-8?q?chore:=20HTTP=20=EC=95=95=EC=B6=95=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/main/resources/application.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index e3503a5fb9..8b74bdfd88 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -8,3 +8,6 @@ server: threads: min-spare: 2 max: 2 + compression: + enabled: true + min-response-size: 10 From 2b3dcafb1ee4e2e490d16a52becce2edf507d4fc Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 13:51:29 +0900 Subject: [PATCH 50/64] =?UTF-8?q?feat:=20ETag=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/etag/EtagFilterConfiguration.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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..3ebbfd140b 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 filter + = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); + filter.addUrlPatterns("/etag"); + return filter; + } } From 69ec4d5ee6f5818eb426a6d581a98d1114d43049 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 14:12:49 +0900 Subject: [PATCH 51/64] =?UTF-8?q?feat:=20.js=EC=99=80=20.css=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=80=201=EB=85=84=20=EB=8F=99=EC=95=88=20?= =?UTF-8?q?=EC=BA=90=EC=8B=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cachecontrol/CacheResponseInterceptor.java | 17 +++++++++++++++++ .../example/cachecontrol/CacheWebConfig.java | 6 +++++- ...tor.java => NoCacheResponseInterceptor.java} | 2 +- .../example/etag/EtagFilterConfiguration.java | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java rename study/src/main/java/cache/com/example/cachecontrol/{ResponseHeaderInterceptor.java => NoCacheResponseInterceptor.java} (89%) diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java new file mode 100644 index 0000000000..befa4d2104 --- /dev/null +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java @@ -0,0 +1,17 @@ +package cache.com.example.cachecontrol; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.web.servlet.HandlerInterceptor; + +public class CacheResponseInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + + response.addHeader(HttpHeaders.CACHE_CONTROL, "max-age=31536000, public"); + return HandlerInterceptor.super.preHandle(request, response, handler); + } +} 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 257b096aa6..bf27833c6f 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -9,6 +9,10 @@ public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { - registry.addInterceptor(new ResponseHeaderInterceptor()); + registry.addInterceptor(new CacheResponseInterceptor()) + .addPathPatterns("/**/*.js", "/**/*.css"); + + registry.addInterceptor(new NoCacheResponseInterceptor()) + .excludePathPatterns("/**/*.js", "/**/*.css"); } } diff --git a/study/src/main/java/cache/com/example/cachecontrol/ResponseHeaderInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/NoCacheResponseInterceptor.java similarity index 89% rename from study/src/main/java/cache/com/example/cachecontrol/ResponseHeaderInterceptor.java rename to study/src/main/java/cache/com/example/cachecontrol/NoCacheResponseInterceptor.java index 257f7122fa..8515436b74 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/ResponseHeaderInterceptor.java +++ b/study/src/main/java/cache/com/example/cachecontrol/NoCacheResponseInterceptor.java @@ -6,7 +6,7 @@ import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; -public class ResponseHeaderInterceptor implements HandlerInterceptor { +public class NoCacheResponseInterceptor implements HandlerInterceptor { @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 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 3ebbfd140b..19bbd1f7d2 100644 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java @@ -12,7 +12,7 @@ public class EtagFilterConfiguration { public FilterRegistrationBean shallowEtagHeaderFilter() { FilterRegistrationBean filter = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); - filter.addUrlPatterns("/etag"); + filter.addUrlPatterns("/etag", "*.js", "*.css"); return filter; } } From e3e1f407b1c4a5bd6d4b3b5c682eafc6d4152f1f Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Fri, 6 Sep 2024 15:38:13 +0900 Subject: [PATCH 52/64] =?UTF-8?q?refactor:=20interceptor=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=EB=A5=BC=20=EC=A7=80=EC=9A=B0=EA=B3=A0=20Web?= =?UTF-8?q?ContentIntercepor=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cachecontrol/CacheResponseInterceptor.java | 17 ----------------- .../example/cachecontrol/CacheWebConfig.java | 16 ++++++++++++---- .../NoCacheResponseInterceptor.java | 18 ------------------ 3 files changed, 12 insertions(+), 39 deletions(-) delete mode 100644 study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java delete mode 100644 study/src/main/java/cache/com/example/cachecontrol/NoCacheResponseInterceptor.java diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java deleted file mode 100644 index befa4d2104..0000000000 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheResponseInterceptor.java +++ /dev/null @@ -1,17 +0,0 @@ -package cache.com.example.cachecontrol; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.HandlerInterceptor; - -public class CacheResponseInterceptor implements HandlerInterceptor { - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - - response.addHeader(HttpHeaders.CACHE_CONTROL, "max-age=31536000, public"); - return HandlerInterceptor.super.preHandle(request, response, handler); - } -} 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 bf27833c6f..af7378d20e 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -1,18 +1,26 @@ package cache.com.example.cachecontrol; +import static java.util.concurrent.TimeUnit.DAYS; + import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.WebContentInterceptor; @Configuration public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { - registry.addInterceptor(new CacheResponseInterceptor()) - .addPathPatterns("/**/*.js", "/**/*.css"); - - registry.addInterceptor(new NoCacheResponseInterceptor()) + WebContentInterceptor noCacheIntercepter = new WebContentInterceptor(); + noCacheIntercepter.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**"); + registry.addInterceptor(noCacheIntercepter) .excludePathPatterns("/**/*.js", "/**/*.css"); + + WebContentInterceptor cacheIntercepter = new WebContentInterceptor(); + cacheIntercepter.addCacheMapping(CacheControl.maxAge(365, DAYS).cachePublic(), "/**"); + registry.addInterceptor(cacheIntercepter) + .addPathPatterns("/**/*.js", "/**/*.css"); } } diff --git a/study/src/main/java/cache/com/example/cachecontrol/NoCacheResponseInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/NoCacheResponseInterceptor.java deleted file mode 100644 index 8515436b74..0000000000 --- a/study/src/main/java/cache/com/example/cachecontrol/NoCacheResponseInterceptor.java +++ /dev/null @@ -1,18 +0,0 @@ -package cache.com.example.cachecontrol; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -public class NoCacheResponseInterceptor implements HandlerInterceptor { - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) throws Exception { - - response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache, private"); - HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); - } -} From 99ebc013f336b521716b7b25f20ea10b9277dd00 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 20:07:53 +0900 Subject: [PATCH 53/64] =?UTF-8?q?refactor:=20=ED=97=A4=EB=8D=94=EC=9D=98?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=EC=9D=84=20enum=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=BA=BC=EB=82=B4=EC=98=A4=EB=8F=84=EB=A1=9D=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 --- .../java/org/apache/coyote/http11/Http11Processor.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 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 57dee3e09d..b3120ac068 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -18,6 +18,7 @@ import org.apache.coyote.request.HttpRequest; import org.apache.coyote.request.RequestBody; import org.apache.coyote.request.RequestLine; +import org.apache.coyote.response.HttpHeaders; import org.apache.coyote.response.HttpResponse; import org.apache.coyote.response.HttpStatusCode; import org.slf4j.Logger; @@ -27,8 +28,6 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final String CONTENT_LENGTH = "Content-Length"; - private static final String COOKIE = "Cookie"; private static final String JSESSIONID = "JSESSIONID"; private static final String USER_SESSION_NAME = "user"; @@ -86,8 +85,8 @@ private List readRequestHeaders(BufferedReader reader) { private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader) { try { - if (httpHeader.contains(CONTENT_LENGTH)) { - int contentLength = Integer.parseInt(httpHeader.get(CONTENT_LENGTH)); + if (httpHeader.contains(HttpHeaders.CONTENT_LENGTH.getName())) { + int contentLength = Integer.parseInt(httpHeader.get(HttpHeaders.CONTENT_LENGTH.getName())); char[] buffer = new char[contentLength]; reader.read(buffer, 0, contentLength); return new RequestBody(new String(buffer)); @@ -99,7 +98,7 @@ private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader } private String handle(HttpRequest request) { - HttpCookie cookie = new HttpCookie(request.getHeader(COOKIE)); + HttpCookie cookie = new HttpCookie(request.getHeader(HttpHeaders.COOKIE.getName())); RequestBody requestBody = request.getRequestBody(); if (request.pointsTo(GET, "/")) { From f32bde8bd41ba66474241731d5a33896df06db91 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 20:12:42 +0900 Subject: [PATCH 54/64] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/catalina/session/Session.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tomcat/src/main/java/org/apache/catalina/session/Session.java b/tomcat/src/main/java/org/apache/catalina/session/Session.java index e7826f0d2c..e8ca00013b 100644 --- a/tomcat/src/main/java/org/apache/catalina/session/Session.java +++ b/tomcat/src/main/java/org/apache/catalina/session/Session.java @@ -13,12 +13,4 @@ public Session() { public void setAttribute(String name, Object value) { values.put(name, value); } - - public Object getAttribute(String name) { - return values.get(name); - } - - public void removeAttribute(String name) { - values.remove(name); - } } From 2ec136683657642ae5cf6daec6b3e62a7af18d51 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 20:17:58 +0900 Subject: [PATCH 55/64] =?UTF-8?q?refactor:=20depth=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 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 b3120ac068..75e434d1ee 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -65,36 +65,28 @@ public void process(final Socket connection) { } } - private List readRequestHeaders(BufferedReader reader) { - try { - List rawHeaders = new ArrayList<>(); - - while (reader.ready()) { - String line = reader.readLine(); - if (line.isBlank()) { - break; - } - rawHeaders.add(line); - } + private List readRequestHeaders(BufferedReader reader) throws IOException { + List rawHeaders = new ArrayList<>(); - return rawHeaders; - } catch (IOException e) { - throw new UncheckedServletException("헤더를 읽는 데 실패하였습니다."); + while (reader.ready()) { + String line = reader.readLine(); + if (line.isBlank()) { + break; + } + rawHeaders.add(line); } + + return rawHeaders; } - private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader) { - try { - if (httpHeader.contains(HttpHeaders.CONTENT_LENGTH.getName())) { - int contentLength = Integer.parseInt(httpHeader.get(HttpHeaders.CONTENT_LENGTH.getName())); - char[] buffer = new char[contentLength]; - reader.read(buffer, 0, contentLength); - return new RequestBody(new String(buffer)); - } - return null; - } catch (IOException e) { - throw new UncheckedServletException("Request Body를 읽는 데 실패하였습니다."); + private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader) throws IOException { + if (httpHeader.contains(HttpHeaders.CONTENT_LENGTH.getName())) { + int contentLength = Integer.parseInt(httpHeader.get(HttpHeaders.CONTENT_LENGTH.getName())); + char[] buffer = new char[contentLength]; + reader.read(buffer, 0, contentLength); + return new RequestBody(new String(buffer)); } + return null; } private String handle(HttpRequest request) { From 65a1130bf62804cf9ee69e6d8c250e36e3fef344 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 20:22:32 +0900 Subject: [PATCH 56/64] =?UTF-8?q?refactor:=20request=EC=97=90=EC=84=9C=20c?= =?UTF-8?q?ookie=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=A9=94=EC=84=9C=EB=93=9C=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 --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 2 +- .../main/java/org/apache/coyote/request/HttpRequest.java | 6 ++++-- 2 files changed, 5 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 75e434d1ee..2f110619c6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -90,7 +90,7 @@ private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader } private String handle(HttpRequest request) { - HttpCookie cookie = new HttpCookie(request.getHeader(HttpHeaders.COOKIE.getName())); + HttpCookie cookie = request.getCookie(); RequestBody requestBody = request.getRequestBody(); if (request.pointsTo(GET, "/")) { diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index b397e9a057..e6341bc419 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -1,6 +1,8 @@ package org.apache.coyote.request; +import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeader; +import org.apache.coyote.response.HttpHeaders; public class HttpRequest { @@ -27,7 +29,7 @@ public String getPath() { return requestLine.getPath(); } - public String getHeader(String name) { - return httpHeader.get(name); + public HttpCookie getCookie() { + return new HttpCookie(httpHeader.get(HttpHeaders.COOKIE.getName())); } } From f9b3f5bfdc01cea400dbd7bd584fb6080dd755dd Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 20:30:24 +0900 Subject: [PATCH 57/64] =?UTF-8?q?refactor:=20HttpCookie=EC=97=90=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=20=EA=B4=80=EB=A0=A8=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 6 ++--- .../org/apache/coyote/http11/HttpCookie.java | 22 ++++++++++--------- .../apache/coyote/response/HttpResponse.java | 6 ++--- .../apache/coyote/http11/HttpCookieTest.java | 10 ++++----- 4 files changed, 20 insertions(+), 24 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 2f110619c6..2bfa2ff937 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -27,8 +27,6 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - - private static final String JSESSIONID = "JSESSIONID"; private static final String USER_SESSION_NAME = "user"; private final Socket connection; @@ -116,7 +114,7 @@ private String handle(HttpRequest request) { } private String getLoginPage(HttpCookie cookie) { - if (cookie.contains(JSESSIONID) && sessionManager.hasId(cookie.get(JSESSIONID))) { + if (cookie.hasSession() && sessionManager.hasId(cookie.getSession())) { return HttpResponse.redirectTo("/index.html") .cookie(cookie) .build(); @@ -172,6 +170,6 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { private void saveSession(HttpCookie cookie, User user) { Session session = new Session(); session.setAttribute(USER_SESSION_NAME, user); - sessionManager.add(cookie.get(JSESSIONID), session); + sessionManager.add(cookie.getSession(), session); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java index 1b7f49e0f0..76a57cea4a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -8,6 +8,8 @@ public class HttpCookie { + private static final String JSESSIONID = "JSESSIONID"; + private final Map cookies; public HttpCookie() { @@ -29,21 +31,21 @@ private Map parse(String rawCookies) { .collect(Collectors.toMap(data -> data[0], data -> data[1])); } - public void add(String name, String value) { - cookies.put(name, value); - } - - public String get(String name) { - return cookies.get(name); - } - public String buildMessage() { return cookies.keySet().stream() .map(key -> key + "=" + cookies.get(key)) .collect(Collectors.joining("; ")); } - public boolean contains(String name) { - return cookies.containsKey(name); + public boolean hasSession() { + return cookies.containsKey(JSESSIONID); + } + + public String getSession() { + return cookies.get(JSESSIONID); + } + + public void setSession(String value) { + cookies.put(JSESSIONID, value); } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 2464a4cceb..34108533b1 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -11,8 +11,6 @@ public class HttpResponse { private static final FileReader FILE_READER = FileReader.getInstance(); - - private static final String JSESSIONID = "JSESSIONID"; private static final String NOT_FOUND_FILENAME = "404.html"; private final HttpStatusCode httpStatusCode; @@ -56,9 +54,9 @@ public static HttpResponse redirectTo(String path) { } public HttpResponse cookie(HttpCookie cookie) { - if (!cookie.contains(JSESSIONID)) { + if (!cookie.hasSession()) { HttpCookie httpCookie = new HttpCookie(); - httpCookie.add(JSESSIONID, UUID.randomUUID().toString()); + httpCookie.setSession(UUID.randomUUID().toString()); this.addHeader(HttpHeaders.SET_COOKIE.getName(), httpCookie.buildMessage()); } return this; diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java b/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java index d2565483cf..a1b0f0e975 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/HttpCookieTest.java @@ -15,13 +15,11 @@ void construct_Success() { .isIn("name1=value1; name2=value2", "name2=value2; name1=value1"); } - @DisplayName("쿠키 정보 추가") + @DisplayName("쿠키에 세션 추가") @Test - void add() { + void addSession() { HttpCookie httpCookie = new HttpCookie(); - httpCookie.add("name1", "value1"); - httpCookie.add("name2", "value2"); - assertThat(httpCookie.buildMessage()) - .isIn("name1=value1; name2=value2", "name2=value2; name1=value1"); + httpCookie.setSession("value1"); + assertThat(httpCookie.buildMessage()).isEqualTo("JSESSIONID=value1"); } } From f6848e41c6cbe59c6653b693d66f8d4faca166f3 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 21:25:44 +0900 Subject: [PATCH 58/64] =?UTF-8?q?refactor:=20Set-Cookie=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/catalina/session/Session.java | 11 ++- .../catalina/session/SessionManager.java | 5 ++ .../apache/coyote/http11/Http11Processor.java | 68 +++++++++---------- .../org/apache/coyote/http11/HttpCookie.java | 6 ++ .../apache/coyote/request/HttpRequest.java | 8 +++ .../apache/coyote/response/HttpResponse.java | 15 ++-- 6 files changed, 63 insertions(+), 50 deletions(-) diff --git a/tomcat/src/main/java/org/apache/catalina/session/Session.java b/tomcat/src/main/java/org/apache/catalina/session/Session.java index e8ca00013b..f99067b5cc 100644 --- a/tomcat/src/main/java/org/apache/catalina/session/Session.java +++ b/tomcat/src/main/java/org/apache/catalina/session/Session.java @@ -1,16 +1,21 @@ package org.apache.catalina.session; +import com.techcourse.model.User; import java.util.HashMap; import java.util.Map; public class Session { + private static final String USER_SESSION_NAME = "user"; + private final Map values = new HashMap<>(); - public Session() { + private Session() { } - public void setAttribute(String name, Object value) { - values.put(name, value); + public static Session ofUser(User user) { + Session session = new Session(); + session.values.put(USER_SESSION_NAME, user); + return session; } } diff --git a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java index 0cba6521ca..459784883d 100644 --- a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.apache.catalina.Manager; public class SessionManager implements Manager { @@ -37,4 +38,8 @@ public void remove(String id) { public boolean hasId(String id) { return sessions.containsKey(id); } + + public String generateId() { + return UUID.randomUUID().toString(); + } } 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 2bfa2ff937..6e744c5b1f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -27,7 +27,6 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final String USER_SESSION_NAME = "user"; private final Socket connection; private final SessionManager sessionManager; @@ -88,65 +87,60 @@ private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader } private String handle(HttpRequest request) { - HttpCookie cookie = request.getCookie(); - RequestBody requestBody = request.getRequestBody(); - if (request.pointsTo(GET, "/")) { return HttpResponse.ofContent("Hello world!") .build(); } if (request.pointsTo(GET, "/login")) { - return getLoginPage(cookie); + return getLoginPage(request); } if (request.pointsTo(POST, "/login")) { - return login(cookie, requestBody); + return login(request); } if (request.pointsTo(POST, "/register")) { - return saveUser(cookie, requestBody); + return saveUser(request); } return HttpResponse.ofStaticFile(request.getPath().substring(1), HttpStatusCode.OK) - .cookie(cookie) .build(); } - private String getLoginPage(HttpCookie cookie) { - if (cookie.hasSession() && sessionManager.hasId(cookie.getSession())) { - return HttpResponse.redirectTo("/index.html") - .cookie(cookie) - .build(); + private String getLoginPage(HttpRequest request) { + if (request.hasSession() && sessionManager.hasId(request.getSession())) { + return HttpResponse.redirectTo("/index.html").build(); } - return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK) - .cookie(cookie) - .build(); + return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK).build(); } - private String login(HttpCookie cookie, RequestBody requestBody) { - if (requestBody.containsAll("account", "password")) { - String account = requestBody.get("account"); - String password = requestBody.get("password"); + private String login(HttpRequest request) { + RequestBody requestBody = request.getRequestBody(); - if (InMemoryUserRepository.exists(account, password)) { - User user = InMemoryUserRepository.getByAccount(account); - saveSession(cookie, user); - return HttpResponse.redirectTo("/index.html") - .cookie(cookie) - .build(); - } + if (!requestBody.containsAll("account", "password")) { + throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); + } + + String account = requestBody.get("account"); + String password = requestBody.get("password"); - return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED) - .cookie(cookie) + if (InMemoryUserRepository.exists(account, password)) { + User user = InMemoryUserRepository.getByAccount(account); + String sessionId = saveSessionAndGetId(user); + sessionManager.add(sessionId, Session.ofUser(user)); + return HttpResponse.redirectTo("/index.html") + .setCookie(HttpCookie.ofSessionId(sessionId)) .build(); } - throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); + return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED).build(); } - private String saveUser(HttpCookie cookie, RequestBody requestBody) { + private String saveUser(HttpRequest request) { + RequestBody requestBody = request.getRequestBody(); + if (requestBody.containsAll("account", "email", "password")) { String account = requestBody.get("account"); String email = requestBody.get("email"); @@ -155,9 +149,9 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { if (!InMemoryUserRepository.existsByAccount(account)) { User user = new User(account, password, email); InMemoryUserRepository.save(user); - saveSession(cookie, user); + String sessionId = saveSessionAndGetId(user); return HttpResponse.redirectTo("/index.html") - .cookie(cookie) + .setCookie(HttpCookie.ofSessionId(sessionId)) .build(); } @@ -167,9 +161,9 @@ private String saveUser(HttpCookie cookie, RequestBody requestBody) { throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); } - private void saveSession(HttpCookie cookie, User user) { - Session session = new Session(); - session.setAttribute(USER_SESSION_NAME, user); - sessionManager.add(cookie.getSession(), session); + private String saveSessionAndGetId(User user) { + String sessionId = sessionManager.generateId(); + sessionManager.add(sessionId, Session.ofUser(user)); + return sessionId; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java index 76a57cea4a..ab8760c800 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -16,6 +16,12 @@ public HttpCookie() { this.cookies = new HashMap<>(); } + public static HttpCookie ofSessionId(String sessionId) { + HttpCookie cookie = new HttpCookie(); + cookie.setSession(sessionId); + return cookie; + } + public HttpCookie(String rawCookies) { this.cookies = parse(rawCookies); } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index e6341bc419..589b8ac9fa 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -32,4 +32,12 @@ public String getPath() { public HttpCookie getCookie() { return new HttpCookie(httpHeader.get(HttpHeaders.COOKIE.getName())); } + + public boolean hasSession() { + return getCookie().hasSession(); + } + + public String getSession() { + return getCookie().getSession(); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 34108533b1..f1b743c888 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -3,7 +3,6 @@ import com.techcourse.exception.UncheckedServletException; import java.util.HashMap; import java.util.Map; -import java.util.UUID; import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeader; import org.apache.coyote.util.FileReader; @@ -53,15 +52,6 @@ public static HttpResponse redirectTo(String path) { return response; } - public HttpResponse cookie(HttpCookie cookie) { - if (!cookie.hasSession()) { - HttpCookie httpCookie = new HttpCookie(); - httpCookie.setSession(UUID.randomUUID().toString()); - this.addHeader(HttpHeaders.SET_COOKIE.getName(), httpCookie.buildMessage()); - } - return this; - } - private HttpHeader buildInitialHeaders(String responseBody, ContentType contentType) { Map headers = new HashMap<>(); headers.put(HttpHeaders.CONTENT_LENGTH.getName(), responseBody.getBytes().length + " "); @@ -73,6 +63,11 @@ public void addHeader(String name, String value) { responseHeader.add(name, value); } + public HttpResponse setCookie(HttpCookie cookie) { + responseHeader.add(HttpHeaders.SET_COOKIE.getName(), cookie.buildMessage()); + return this; + } + public String build() { return String.join("\r\n", httpStatusCode.buildMessage(), From 7511686785b2eb1326cc76ba5505591b403ced40 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 21:26:21 +0900 Subject: [PATCH 59/64] =?UTF-8?q?refactor:=20if=EB=AC=B8=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 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 6e744c5b1f..bd1391286d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -141,24 +141,24 @@ private String login(HttpRequest request) { private String saveUser(HttpRequest request) { RequestBody requestBody = request.getRequestBody(); - if (requestBody.containsAll("account", "email", "password")) { - String account = requestBody.get("account"); - String email = requestBody.get("email"); - String password = requestBody.get("password"); - - if (!InMemoryUserRepository.existsByAccount(account)) { - User user = new User(account, password, email); - InMemoryUserRepository.save(user); - String sessionId = saveSessionAndGetId(user); - return HttpResponse.redirectTo("/index.html") - .setCookie(HttpCookie.ofSessionId(sessionId)) - .build(); - } + if (!requestBody.containsAll("account", "email", "password")) { + throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); + } + + String account = requestBody.get("account"); + String email = requestBody.get("email"); + String password = requestBody.get("password"); - throw new UncheckedServletException("이미 존재하는 ID입니다."); + if (!InMemoryUserRepository.existsByAccount(account)) { + User user = new User(account, password, email); + InMemoryUserRepository.save(user); + String sessionId = saveSessionAndGetId(user); + return HttpResponse.redirectTo("/index.html") + .setCookie(HttpCookie.ofSessionId(sessionId)) + .build(); } - throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); + throw new UncheckedServletException("이미 존재하는 ID입니다."); } private String saveSessionAndGetId(User user) { From 24a76556249414ad337b509d107192a5c6d35f69 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 21:34:32 +0900 Subject: [PATCH 60/64] refactor: HttpHeaders -> HttpHeaderType --- .../java/org/apache/coyote/http11/Http11Processor.java | 6 +++--- .../main/java/org/apache/coyote/request/HttpRequest.java | 4 ++-- .../response/{HttpHeaders.java => HttpHeaderType.java} | 4 ++-- .../java/org/apache/coyote/response/HttpResponse.java | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) rename tomcat/src/main/java/org/apache/coyote/response/{HttpHeaders.java => HttpHeaderType.java} (83%) 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 bd1391286d..e98b2b39d0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -18,7 +18,7 @@ import org.apache.coyote.request.HttpRequest; import org.apache.coyote.request.RequestBody; import org.apache.coyote.request.RequestLine; -import org.apache.coyote.response.HttpHeaders; +import org.apache.coyote.response.HttpHeaderType; import org.apache.coyote.response.HttpResponse; import org.apache.coyote.response.HttpStatusCode; import org.slf4j.Logger; @@ -77,8 +77,8 @@ private List readRequestHeaders(BufferedReader reader) throws IOExceptio } private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader) throws IOException { - if (httpHeader.contains(HttpHeaders.CONTENT_LENGTH.getName())) { - int contentLength = Integer.parseInt(httpHeader.get(HttpHeaders.CONTENT_LENGTH.getName())); + if (httpHeader.contains(HttpHeaderType.CONTENT_LENGTH.getName())) { + int contentLength = Integer.parseInt(httpHeader.get(HttpHeaderType.CONTENT_LENGTH.getName())); char[] buffer = new char[contentLength]; reader.read(buffer, 0, contentLength); return new RequestBody(new String(buffer)); diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index 589b8ac9fa..88db338e22 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -2,7 +2,7 @@ import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeader; -import org.apache.coyote.response.HttpHeaders; +import org.apache.coyote.response.HttpHeaderType; public class HttpRequest { @@ -30,7 +30,7 @@ public String getPath() { } public HttpCookie getCookie() { - return new HttpCookie(httpHeader.get(HttpHeaders.COOKIE.getName())); + return new HttpCookie(httpHeader.get(HttpHeaderType.COOKIE.getName())); } public boolean hasSession() { diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/response/HttpHeaderType.java similarity index 83% rename from tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java rename to tomcat/src/main/java/org/apache/coyote/response/HttpHeaderType.java index 441c17078b..893393241a 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpHeaderType.java @@ -1,6 +1,6 @@ package org.apache.coyote.response; -public enum HttpHeaders { +public enum HttpHeaderType { CONTENT_LENGTH("Content-Length"), COOKIE("Cookie"), @@ -11,7 +11,7 @@ public enum HttpHeaders { private final String name; - HttpHeaders(String name) { + HttpHeaderType(String name) { this.name = name; } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index f1b743c888..7babd6ad40 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -48,14 +48,14 @@ public static HttpResponse ofStaticFile(String fileName, HttpStatusCode httpStat public static HttpResponse redirectTo(String path) { HttpResponse response = new HttpResponse(HttpStatusCode.FOUND, "", ContentType.TEXT_HTML); - response.addHeader(HttpHeaders.LOCATION.getName(), path); + response.addHeader(HttpHeaderType.LOCATION.getName(), path); return response; } private HttpHeader buildInitialHeaders(String responseBody, ContentType contentType) { Map headers = new HashMap<>(); - headers.put(HttpHeaders.CONTENT_LENGTH.getName(), responseBody.getBytes().length + " "); - headers.put(HttpHeaders.CONTENT_TYPE.getName(), contentType.getName() + ";charset=utf-8 "); + headers.put(HttpHeaderType.CONTENT_LENGTH.getName(), responseBody.getBytes().length + " "); + headers.put(HttpHeaderType.CONTENT_TYPE.getName(), contentType.getName() + ";charset=utf-8 "); return new HttpHeader(headers); } @@ -64,7 +64,7 @@ public void addHeader(String name, String value) { } public HttpResponse setCookie(HttpCookie cookie) { - responseHeader.add(HttpHeaders.SET_COOKIE.getName(), cookie.buildMessage()); + responseHeader.add(HttpHeaderType.SET_COOKIE.getName(), cookie.buildMessage()); return this; } From 4f1a76fbd0079d4882429d2c03d2a3df38ba9818 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 21:34:57 +0900 Subject: [PATCH 61/64] =?UTF-8?q?refactor:=20=EB=B0=98=EB=B3=B5=EB=AC=B8?= =?UTF-8?q?=EC=9D=84=20=EC=8A=A4=ED=8A=B8=EB=A6=BC=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/request/RequestBody.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java index 3b2b412612..58060e30da 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java @@ -23,12 +23,8 @@ public RequestBody(String rawRequestBody) { } public boolean containsAll(String... names) { - for (String name : names) { - if (!requestBody.containsKey(name)) { - return false; - } - } - return true; + return Arrays.stream(names) + .allMatch(requestBody::containsKey); } public String get(String name) { From 45851e462e283ce79c6aa441153aeb2f9c5383c5 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 21:38:47 +0900 Subject: [PATCH 62/64] =?UTF-8?q?refactor:=20=EA=B0=81=EC=A2=85=20delimite?= =?UTF-8?q?rs=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/HttpCookie.java | 10 ++++++---- .../java/org/apache/coyote/http11/HttpHeader.java | 10 ++++++---- .../java/org/apache/coyote/request/RequestBody.java | 11 +++++++---- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java index ab8760c800..42e02e302d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -9,6 +9,8 @@ public class HttpCookie { private static final String JSESSIONID = "JSESSIONID"; + private static final String COOKIE_DELIMITER = "; "; + private static final String NAME_VALUE_DELIMITER = "="; private final Map cookies; @@ -31,16 +33,16 @@ private Map parse(String rawCookies) { return Collections.emptyMap(); } - return Arrays.stream(rawCookies.split("; ")) - .map(cookie -> cookie.split("=")) + return Arrays.stream(rawCookies.split(COOKIE_DELIMITER)) + .map(cookie -> cookie.split(NAME_VALUE_DELIMITER)) .filter(data -> data.length == 2) .collect(Collectors.toMap(data -> data[0], data -> data[1])); } public String buildMessage() { return cookies.keySet().stream() - .map(key -> key + "=" + cookies.get(key)) - .collect(Collectors.joining("; ")); + .map(key -> key + NAME_VALUE_DELIMITER + cookies.get(key)) + .collect(Collectors.joining(COOKIE_DELIMITER)); } public boolean hasSession() { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java index ade8ef33c5..333a3c6094 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java @@ -7,6 +7,8 @@ public class HttpHeader { + private static final String DELIMITER = ": "; + private final Map headers; public HttpHeader(Map headers) { @@ -16,13 +18,13 @@ public HttpHeader(Map headers) { public HttpHeader(List rawHeaders) { this.headers = rawHeaders.stream() .peek(rawHeader -> { - if (!rawHeader.contains(": ")) { + if (!rawHeader.contains(DELIMITER)) { throw new UncheckedServletException("형식이 올바르지 않은 헤더가 포함되어 있습니다."); } }) .collect(Collectors.toMap( - rawHeader -> rawHeader.substring(0, rawHeader.indexOf(": ")), - rawHeader -> rawHeader.substring(rawHeader.indexOf(": ") + 2) + rawHeader -> rawHeader.substring(0, rawHeader.indexOf(DELIMITER)), + rawHeader -> rawHeader.substring(rawHeader.indexOf(DELIMITER) + 2) )); } @@ -40,7 +42,7 @@ public void add(String name, String value) { public String buildMessage() { return headers.keySet().stream() - .map(key -> key + ": " + headers.get(key)) + .map(key -> key + DELIMITER + headers.get(key)) .collect(Collectors.joining("\r\n")); } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java index 58060e30da..7cfcae8d1d 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestBody.java @@ -7,18 +7,21 @@ public class RequestBody { + private static final String DATA_DELIMITER = "&"; + private static final String NAME_VALUE_DELIMITER = "="; + private final Map requestBody; public RequestBody(String rawRequestBody) { - this.requestBody = Arrays.stream(rawRequestBody.split("&")) + this.requestBody = Arrays.stream(rawRequestBody.split(DATA_DELIMITER)) .peek(rawData -> { - if (!rawData.contains("=")) { + if (!rawData.contains(NAME_VALUE_DELIMITER)) { throw new UncheckedServletException("올바르지 않은 Request Body 형식입니다."); } }) .collect(Collectors.toMap( - data -> data.substring(0, data.indexOf("=")), - data -> data.substring(data.indexOf("=") + 1) + data -> data.substring(0, data.indexOf(NAME_VALUE_DELIMITER)), + data -> data.substring(data.indexOf(NAME_VALUE_DELIMITER) + 1) )); } From e5eac8277c2e3b43392b539258f09712e5cb6068 Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 21:47:03 +0900 Subject: [PATCH 63/64] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20build()=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 28 ++++++++----------- .../apache/coyote/response/HttpResponse.java | 6 +++- 2 files changed, 17 insertions(+), 17 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 e98b2b39d0..9673140e99 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -53,7 +53,7 @@ public void process(final Socket connection) { HttpHeader httpHeader = new HttpHeader(readRequestHeaders(reader)); RequestBody requestBody = readRequestBody(reader, httpHeader); - final String response = handle(new HttpRequest(requestLine, httpHeader, requestBody)); + HttpResponse response = handle(new HttpRequest(requestLine, httpHeader, requestBody)); outputStream.write(response.getBytes()); outputStream.flush(); @@ -86,10 +86,9 @@ private RequestBody readRequestBody(BufferedReader reader, HttpHeader httpHeader return null; } - private String handle(HttpRequest request) { + private HttpResponse handle(HttpRequest request) { if (request.pointsTo(GET, "/")) { - return HttpResponse.ofContent("Hello world!") - .build(); + return HttpResponse.ofContent("Hello world!"); } if (request.pointsTo(GET, "/login")) { @@ -104,19 +103,18 @@ private String handle(HttpRequest request) { return saveUser(request); } - return HttpResponse.ofStaticFile(request.getPath().substring(1), HttpStatusCode.OK) - .build(); + return HttpResponse.ofStaticFile(request.getPath().substring(1), HttpStatusCode.OK); } - private String getLoginPage(HttpRequest request) { + private HttpResponse getLoginPage(HttpRequest request) { if (request.hasSession() && sessionManager.hasId(request.getSession())) { - return HttpResponse.redirectTo("/index.html").build(); + return HttpResponse.redirectTo("/index.html"); } - return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK).build(); + return HttpResponse.ofStaticFile("login.html", HttpStatusCode.OK); } - private String login(HttpRequest request) { + private HttpResponse login(HttpRequest request) { RequestBody requestBody = request.getRequestBody(); if (!requestBody.containsAll("account", "password")) { @@ -131,14 +129,13 @@ private String login(HttpRequest request) { String sessionId = saveSessionAndGetId(user); sessionManager.add(sessionId, Session.ofUser(user)); return HttpResponse.redirectTo("/index.html") - .setCookie(HttpCookie.ofSessionId(sessionId)) - .build(); + .setCookie(HttpCookie.ofSessionId(sessionId)); } - return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED).build(); + return HttpResponse.ofStaticFile("401.html", HttpStatusCode.UNAUTHORIZED); } - private String saveUser(HttpRequest request) { + private HttpResponse saveUser(HttpRequest request) { RequestBody requestBody = request.getRequestBody(); if (!requestBody.containsAll("account", "email", "password")) { @@ -154,8 +151,7 @@ private String saveUser(HttpRequest request) { InMemoryUserRepository.save(user); String sessionId = saveSessionAndGetId(user); return HttpResponse.redirectTo("/index.html") - .setCookie(HttpCookie.ofSessionId(sessionId)) - .build(); + .setCookie(HttpCookie.ofSessionId(sessionId)); } throw new UncheckedServletException("이미 존재하는 ID입니다."); diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 7babd6ad40..610110e414 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -68,7 +68,11 @@ public HttpResponse setCookie(HttpCookie cookie) { return this; } - public String build() { + public byte[] getBytes() { + return this.build().getBytes(); + } + + private String build() { return String.join("\r\n", httpStatusCode.buildMessage(), responseHeader.buildMessage(), From 6a2027b05749453ceaae59f08c7ba88694b9d14e Mon Sep 17 00:00:00 2001 From: takoyakimchi Date: Tue, 10 Sep 2024 21:47:54 +0900 Subject: [PATCH 64/64] =?UTF-8?q?refactor:=20CRLF=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java | 3 ++- .../src/main/java/org/apache/coyote/response/HttpResponse.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java index 333a3c6094..bde99a59a0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeader.java @@ -8,6 +8,7 @@ public class HttpHeader { private static final String DELIMITER = ": "; + private static final String CRLF = "\r\n"; private final Map headers; @@ -43,6 +44,6 @@ public void add(String name, String value) { public String buildMessage() { return headers.keySet().stream() .map(key -> key + DELIMITER + headers.get(key)) - .collect(Collectors.joining("\r\n")); + .collect(Collectors.joining(CRLF)); } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 610110e414..82af18407a 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -11,6 +11,7 @@ public class HttpResponse { private static final FileReader FILE_READER = FileReader.getInstance(); private static final String NOT_FOUND_FILENAME = "404.html"; + private static final String CRLF = "\r\n"; private final HttpStatusCode httpStatusCode; private final HttpHeader responseHeader; @@ -73,7 +74,7 @@ public byte[] getBytes() { } private String build() { - return String.join("\r\n", + return String.join(CRLF, httpStatusCode.buildMessage(), responseHeader.buildMessage(), "",