From fed02f6f5f4308400e55c160d9495cad010f5bfb Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 11:11:09 +0900 Subject: [PATCH 01/14] fix: remove implementation logback-classic on gradle (#501) --- 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 7e9135698878932274ddc1f523ba817ed9c56c70 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 13:51:07 +0900 Subject: [PATCH 02/14] fix: add threads min-spare configuration on properties (#502) --- 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 0a57c01f6bf6c4b134bedc80563a3119b4544c81 Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Wed, 4 Sep 2024 17:32:55 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20http=20=EC=84=9C=EB=B2=84=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 | 124 ++++++++++++++++-- tomcat/src/main/resources/static/favicon.ico | Bin 0 -> 15406 bytes 2 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 tomcat/src/main/resources/static/favicon.ico 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..8171a1d80f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,20 +1,41 @@ package org.apache.coyote.http11; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.Socket; - public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + private static final int REQUEST_RESOURCE_POSITION = 1; + private static final String DEFAULT_RESPONSE_BODY = "Hello world!"; + private static final String DEFAULT_EXTENSION = "html"; + private static final String QUERY_PARAM_DELIMITER = "?"; + private static final String ROOT_PATH = "/"; + private static final String PARAMETER_DELIMITER = "&"; + private static final String KEY_VALUE_DELIMITER = "="; + private static final String STATIC_RESOURCE_PATH = "static"; + private static final String EXTENSION_DELIMITER_REGEX = "\\."; + private static final String HTTP_VERSION = "HTTP/1.1"; + private static final String OK_STATUS = "200 OK"; + private static final String CONTENT_TYPE_HEADER = "Content-Type: "; + private static final String TEXT_TYPE_PREFIX = "text/"; + private static final String CHARSET_UTF_8 = ";charset=utf-8"; + private static final String CONTENT_LENGTH_HEADER = "Content-Length: "; private final Socket connection; - public Http11Processor(final Socket connection) { + public Http11Processor(Socket connection) { this.connection = connection; } @@ -28,15 +49,8 @@ public void run() { public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - - final var responseBody = "Hello world!"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); + String resource = parseRequestResource(inputStream); + String response = getResponse(resource); outputStream.write(response.getBytes()); outputStream.flush(); @@ -44,4 +58,88 @@ public void process(final Socket connection) { log.error(e.getMessage(), e); } } + + private String parseRequestResource(InputStream inputStream) throws IOException { + BufferedReader request = new BufferedReader(new InputStreamReader(inputStream)); + String firstLine = request.readLine(); + return firstLine.split(" ")[REQUEST_RESOURCE_POSITION]; + } + + private String getResponse(String resource) throws IOException { + String responseBody = DEFAULT_RESPONSE_BODY; + String extension = DEFAULT_EXTENSION; + + if (resource.contains(QUERY_PARAM_DELIMITER)) { + resource = handleQueryParameters(resource); + } + + if (!resource.equals(ROOT_PATH)) { + responseBody = loadResourceContent(resource); + extension = getExtension(resource); + } + + return joinResponse(responseBody, extension); + } + + private String handleQueryParameters(String resource) { + int index = resource.indexOf(QUERY_PARAM_DELIMITER); + String resourcePage = resource.substring(0, index); + String queryString = resource.substring(index + 1); + + if (isLoginPage(resourcePage)) { + loginPageResponse(queryString); + } + + return resourcePage + "." + DEFAULT_EXTENSION; + } + + private boolean isLoginPage(String resourcePage) { + return resourcePage.equals("/login"); + } + + private void loginPageResponse(String queryString) { + String account = extractQueryParameter(queryString, "account"); + String password = extractQueryParameter(queryString, "password"); + findUserByInfo(account, password); + } + + private String extractQueryParameter(String queryString, String parameter) { + for (String pair : queryString.split(PARAMETER_DELIMITER)) { + String[] keyValue = pair.split(KEY_VALUE_DELIMITER); + if (keyValue[0].equals(parameter) && keyValue.length > 1) { + return keyValue[1]; + } + } + return ""; + } + + private static void findUserByInfo(String account, String password) { + InMemoryUserRepository.findByAccount(account) + .filter(user -> user.checkPassword(password)) + .ifPresent(user -> log.info(user.toString())); + } + + private String loadResourceContent(String resource) throws IOException { + String resourcePath = Objects.requireNonNull(getClass().getClassLoader() + .getResource(STATIC_RESOURCE_PATH + resource)) + .getPath(); + + try (FileInputStream file = new FileInputStream(resourcePath)) { + return new String(file.readAllBytes(), StandardCharsets.UTF_8); + } + } + + private static String getExtension(String resource) { + String[] parts = resource.split(EXTENSION_DELIMITER_REGEX); + return parts[parts.length - 1]; + } + + private static String joinResponse(String responseBody, String extension) { + return String.join("\r\n", + HTTP_VERSION + " " + OK_STATUS + " ", + CONTENT_TYPE_HEADER + TEXT_TYPE_PREFIX + extension + CHARSET_UTF_8 + " ", + CONTENT_LENGTH_HEADER + responseBody.getBytes().length + " ", + "", + responseBody); + } } diff --git a/tomcat/src/main/resources/static/favicon.ico b/tomcat/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..02c4481e335fb9bbf67966535acc55f5acf06dc6 GIT binary patch literal 15406 zcmeHNX>?RY7Vd;?Oi)1;)0nY?N5|%*-0Rl;fzzoY^5Cx8` zIta*W5Qc%UXp~KK*q1Db%rHMdLydD$z%!&vi*LsuF15T>w<&r?X69wBMnTZ7nz1x zn8Q}j4?};kh15Q_h?a6W9ebQ^iNf#U2WeUVS>@3gNo5~el1jH)2A6eL3XCbD0H$5e z&k8Q2ERjcuy05)MV}}1o8Hr`G+{jEQc^)zZZxJ1kJY6<_LqXHlQN2;~s6LlQnd_e|Jwp!CF2!Jj_F`tyWArS;KY z)+;c#p7k;PcFJ0cDXeh9FK}@k+B(Orqw&M7^v4~yE96+AJ?zWNR6B7xnUCFZ<+YCo z+MXOZoudD7LD0x?27iyw&Qr+hLS0^gf$2a4TKtxCB-^}w%J~H!#;WtWe>wEGTXKNR zM{jevMR=F8K?7R+mbQWShlYL(fUi>j%0Aq33uO~Tn z+5j83?Z1$rvwjrvD(trI#H*AeZIX33tzS7-N?KpM!_Ne9>A?m5=|TVc?WH==MU)ux z6|LZY7cuUi_<0w_{$7MO(12FzOB-x$mXT~7k&#@QX-O{ao1IPU6^;M(|C(CI6w+$e zQ_jUQduj-CcJQ zb<8D7n0m$G4^{fP-g-7`ur*K*LsY)#^C`o6fy1zFo1f>sB2`O2&tC`Ddr?n!#DQxR zy7p^oGk+Jgn7Nr+zO{wgEZ9wdSay?#a;`UPYs?y zujHQgX#b#jihVhFnwsZO+Mpj?FpL>@v4~9MCv{_ux!5`HtIt`M3OS84KM;G48lT>8 zj2U=@fuU6zGSqVm>F|5_RMj)p{+9sz;Bfuz&v7YuyqDcp)P& zVgikrXr;KLPVq~*!>6kJNV}y6ZDaTLwo2=k7+SFm%tP?fLoTuu{@kgp>69GuV|(xY zxh)=&vMTe049IFaImbzdkY(RX)N&DrbmjG~uCMHG$O_+gRZE`S$F$n2ypGqL+(WBe zm(JLU{af9JST6GLFWQ*cFGL=?;nEg)uv|MnXWND>Wqhd6Cu0XEdCg~R6zitP_7bOh z@S)AW?-(cB?9Fj6>RQa$ToWTbn;mv&O(qS>71VSc+II!;ftaU-=xrwPSWq+`;y?3y663% zEl0rv8qk6VyvlJ>>yfb{*E{)NHC+|`7XBA|fY$y~MLO?~pMC#7>UU4^_XFU#2-#|V zTE8oG{%~inJm#MIt;WIq^#1&%!T>wB+qLF=t95Gq#yYY42XA~-Ie64-OiRAeI!CN% zkD0h%GIo%AsOI_GsQmv|I^;<>MQa+y#TR)J7US}lP2_JrNOjGH)STzd9CMuBX8YdG zIU(fPu5iH|qFg~8+Cam<&ENqqWGDq^$DgV>aoB}MmgEa9`7HSY=MB7&SN|!jLRjDtm~7QR`3*1Ue7ORQs`#t7{7^Xj!P(M&*%SnoU=a0 zXTl3yu4N>Zp67Go9!sLtGAZF=V{fsl>HD3aAkN9HV*SP32je*$l@CGR`s^nP-A1=V za_M~Z-10r?$<}Wf^F5qrHOfuy$F|AwWt{Ez@{jnabeI~l&MYohb;nf$qaH2a4*)xb zZ020;2e&A7j3q5yQ>D5oZqgOVa7P+if^S5Z z|JKO*EXl*E-A=Qk_6uA(2HuLmE z-=2uDP&4yfLpe=#9~l2>OMLM&F8JBcV6EEi{r;ImY0rGl58r#0&(yh2V_eNVj-H7B z3)LGrmzurtcWS%vb85Hjpg3dV9d&2}4QOQ_{kbop{nk^;ptFYOLMum%zQ9Mq&#;~7 zuGOA9N}ck>I7>I4u$FAp1d%0M5iB@>d$ z+PI88S1|4@cRn(V*XEPS*$!I?95_|n0qW*tl=G%~k34)s@FHh{xj=5ZLF9BAnQ%t8 z599x2rD5-0Tdaj0`zqtt-J6i}to!;Ln=a1PH{krOPVP8vH4E-2omhn6L%RIF#`5Sj)@{UL| z*R5mMlHc1WbjbDGYuYi2JK~JpA&)UD&K@O>dIcWbl{Db}3G%Q<;XYrB&X;xFKP#r1 z{W674=+^VcY(piAbCagO@3ZG31q_%hWHm}(BH|lgV%3s`7z(;DX3*Vx&rjN8W;+AP zGoL06a-_}P*dV^ez;`6ky!UG9^kp4%Ko`FMXf}O=e!d@eVn46C(;vyX;&AqFSjX<@ zeAxzF&}m>_6lZN;>VBBAkQ3l_aOGj@^rbFf=YqSYhh`hT)CKGw@$-6e_l=nn5B#m3 zzXKiGK%?Z*dlx!A_zOFJUHgHq;AMxYdGJM(OX1??!+Rvha;y$m#;BoiyD+({Sv& z1Pprk1a{jVpi6_e5V1b)`k@<7;6PunKVm#3roe$ys8h}fU0I!SzM=p=^GJH|LIz|( zrcwuD2k3T#UF=_ML)e2g;|Ix$vkT(?YEfwp-4cU*)=Qa7UALZfYp<~7u3Nu#e19T& zmCtJCG-~l?uIKMd?D2y=2GWe*B!2nCT(@1ghnh@WNe#y?5^)N?0}cchIle< zSI%947qJdxL1x=Ud-UTS)q3Y~7x5cnPVIOd3whI{5xE;TZ%a~yu7hl#rV8%w4ED2vaa)DWcscG`z4Gb( zsfYCs9|=Dr)~Xhm%G6cC{k6_K;Qk>?_(%hOrZQde<@W>VgNVgAo Date: Thu, 5 Sep 2024 21:24:08 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=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 --- .../main/java/org/apache/coyote/Session.java | 18 ++ .../apache/coyote/http11/Http11Processor.java | 192 +++++++++++++++--- tomcat/src/main/resources/static/login.html | 2 +- 3 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/Session.java diff --git a/tomcat/src/main/java/org/apache/coyote/Session.java b/tomcat/src/main/java/org/apache/coyote/Session.java new file mode 100644 index 0000000000..fc15d250ed --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/Session.java @@ -0,0 +1,18 @@ +package org.apache.coyote; + +import java.util.HashMap; +import java.util.Map; +import com.techcourse.model.User; + +public class Session { + + private static final Map session = new HashMap<>(); + + public static void save(String uuid, User user) { + session.put(uuid, user); + } + + public static boolean containsSession(String uuid) { + return session.containsKey(uuid); + } +} 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 8171a1d80f..3ac8e7aea9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -3,14 +3,18 @@ import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.UUID; import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; +import com.techcourse.model.User; import org.apache.coyote.Processor; +import org.apache.coyote.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,11 +31,14 @@ public class Http11Processor implements Runnable, Processor { private static final String STATIC_RESOURCE_PATH = "static"; private static final String EXTENSION_DELIMITER_REGEX = "\\."; private static final String HTTP_VERSION = "HTTP/1.1"; - private static final String OK_STATUS = "200 OK"; + private static final String STATUS_CODE_200 = "200 OK"; + private static final String STATUS_CODE_302 = "302 Found"; + private static final String STATUS_CODE_401 = "401 Unauthorized"; private static final String CONTENT_TYPE_HEADER = "Content-Type: "; private static final String TEXT_TYPE_PREFIX = "text/"; private static final String CHARSET_UTF_8 = ";charset=utf-8"; private static final String CONTENT_LENGTH_HEADER = "Content-Length: "; + private static final String LOCATION_HEADER = "Location: "; private final Socket connection; @@ -49,58 +56,154 @@ public void run() { public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - String resource = parseRequestResource(inputStream); - String response = getResponse(resource); + BufferedReader request = new BufferedReader(new InputStreamReader(inputStream)); + String firstLine = request.readLine(); + System.out.println(firstLine); + String resource = ""; + String response = ""; + if (firstLine.split(" ")[0].equals("GET") && !parseRequestResource(firstLine).contains("/login")) { + resource = parseRequestResource(firstLine); + response = getResponse(resource); + } + + if (firstLine.split(" ")[0].equals("GET") && parseRequestResource(firstLine).contains("/login")) { + String line; + while (!(line = request.readLine()).isEmpty()) { + if (line.contains("Cookie")) { + String uuid = ""; + for (String v : line.split(";")) { + if (v.contains("JSESSIONID")) { + uuid = v.split("=")[1]; + } + } + if (Session.containsSession(uuid)) { + response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Location: " + "/index.html"); + } else { + resource = parseRequestResource(firstLine); + response = getResponse(resource); + } + } else { + resource = parseRequestResource(firstLine); + response = getResponse(resource); + } + } + } + + if (firstLine.split(" ")[0].equals("POST")) { + String line; + int contentLength = 0; + while (!(line = request.readLine()).isEmpty()) { + if (line.contains("Content-Length")) { + contentLength = Integer.parseInt(line.split(" ")[1]); + } + } + + char[] buffer = new char[contentLength]; + request.read(buffer, 0, contentLength); + String requestBody = new String(buffer); + if (parseRequestResource(firstLine).contains("login")) { + String account = requestBody.split("&")[0].split("=")[1]; + String password = requestBody.split("&")[1].split("=")[1]; + if (findUserByInfo(account, password)) { + UUID uuid = UUID.randomUUID(); + response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Location: " + "/index.html", + "Set-Cookie: JSESSIONID=" + uuid); + Session.save(uuid.toString(), InMemoryUserRepository.findByAccount(account).get()); + } + if (!findUserByInfo(account, password)) { + response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Location: " + "/401.html"); + } + } + + if (parseRequestResource(firstLine).contains("register")) { + String account = requestBody.split("&")[0].split("=")[1]; + String email = requestBody.split("&")[1].split("=")[1]; + String password = requestBody.split("&")[2].split("=")[1]; + User user = new User(account, password, email); + InMemoryUserRepository.save(user); + response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Location: " + "/index.html"); + } + + } outputStream.write(response.getBytes()); outputStream.flush(); + } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } - private String parseRequestResource(InputStream inputStream) throws IOException { - BufferedReader request = new BufferedReader(new InputStreamReader(inputStream)); - String firstLine = request.readLine(); + private String parseRequestResource(String firstLine) throws IOException { return firstLine.split(" ")[REQUEST_RESOURCE_POSITION]; } private String getResponse(String resource) throws IOException { + String statusCode = STATUS_CODE_200; String responseBody = DEFAULT_RESPONSE_BODY; String extension = DEFAULT_EXTENSION; if (resource.contains(QUERY_PARAM_DELIMITER)) { - resource = handleQueryParameters(resource); + Result result = handleQueryParameters(resource); + resource = result.resourceWithExtension; + statusCode = result.statusCode; } if (!resource.equals(ROOT_PATH)) { + resource = addExtensionWhenDoesNotExists(resource); responseBody = loadResourceContent(resource); extension = getExtension(resource); } - return joinResponse(responseBody, extension); + return joinResponse(statusCode, responseBody, extension); } - private String handleQueryParameters(String resource) { + private Result handleQueryParameters(String resource) { int index = resource.indexOf(QUERY_PARAM_DELIMITER); String resourcePage = resource.substring(0, index); String queryString = resource.substring(index + 1); + String statusCode = STATUS_CODE_200; if (isLoginPage(resourcePage)) { - loginPageResponse(queryString); + statusCode = getStatusCodeByLoginPageResponse(queryString, statusCode); } - return resourcePage + "." + DEFAULT_EXTENSION; + String resourceWithExtension = resourcePage + "." + DEFAULT_EXTENSION; + + return new Result(resourceWithExtension, statusCode); + } + + private String getStatusCodeByLoginPageResponse(String queryString, String statusCode) { + if (loginPageResponse(queryString)) { + statusCode = STATUS_CODE_302; + } + + if (!loginPageResponse(queryString)) { + statusCode = STATUS_CODE_401; + } + + return statusCode; + } + + private record Result(String resourceWithExtension, String statusCode) { } private boolean isLoginPage(String resourcePage) { - return resourcePage.equals("/login"); + return resourcePage.contains("/login"); } - private void loginPageResponse(String queryString) { + private boolean loginPageResponse(String queryString) { String account = extractQueryParameter(queryString, "account"); String password = extractQueryParameter(queryString, "password"); - findUserByInfo(account, password); + return findUserByInfo(account, password); } private String extractQueryParameter(String queryString, String parameter) { @@ -113,10 +216,22 @@ private String extractQueryParameter(String queryString, String parameter) { return ""; } - private static void findUserByInfo(String account, String password) { - InMemoryUserRepository.findByAccount(account) + private boolean findUserByInfo(String account, String password) { + return InMemoryUserRepository.findByAccount(account) .filter(user -> user.checkPassword(password)) - .ifPresent(user -> log.info(user.toString())); + .map(user -> { + log.info(user.toString()); + return true; + }) + .orElse(false); + } + + private String addExtensionWhenDoesNotExists(String resource) { + if (hasNotExtension(resource)) { + resource += "." + DEFAULT_EXTENSION; + } + + return resource; } private String loadResourceContent(String resource) throws IOException { @@ -129,17 +244,42 @@ private String loadResourceContent(String resource) throws IOException { } } - private static String getExtension(String resource) { + private boolean hasNotExtension(String resource) { + return !resource.contains("."); + } + + private String getExtension(String resource) { String[] parts = resource.split(EXTENSION_DELIMITER_REGEX); return parts[parts.length - 1]; } - private static String joinResponse(String responseBody, String extension) { - return String.join("\r\n", - HTTP_VERSION + " " + OK_STATUS + " ", - CONTENT_TYPE_HEADER + TEXT_TYPE_PREFIX + extension + CHARSET_UTF_8 + " ", - CONTENT_LENGTH_HEADER + responseBody.getBytes().length + " ", - "", - responseBody); + private String joinResponse(String statusCode, String responseBody, String extension) { + String redirectHeader = getRedirectHeader(statusCode); + + List headers = new ArrayList<>(); + headers.add(HTTP_VERSION + " " + statusCode + " "); + headers.add(CONTENT_TYPE_HEADER + TEXT_TYPE_PREFIX + extension + CHARSET_UTF_8 + " "); + headers.add(CONTENT_LENGTH_HEADER + responseBody.getBytes().length + " "); + + if (!redirectHeader.isBlank()) { + headers.set(0, HTTP_VERSION + " " + "302" + " "); + headers.add(redirectHeader + " "); + } + + return String.join("\r\n", headers) + "\r\n\r\n" + responseBody; + } + + private String getRedirectHeader(String statusCode) { + String redirectHeader = ""; + + if (STATUS_CODE_302.equals(statusCode)) { + redirectHeader = LOCATION_HEADER + "/index.html"; + } + + if (STATUS_CODE_401.equals(statusCode)) { + redirectHeader = LOCATION_HEADER + "/401.html"; + } + + return redirectHeader; } } 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 e192357eb9b68c51ea3a4bba2b1106852d080f03 Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Fri, 6 Sep 2024 16:03:11 +0900 Subject: [PATCH 05/14] =?UTF-8?q?refactor:=20was,=20controller,=20session?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AbstractController.java | 23 ++ .../com/techcourse/controller/Controller.java | 9 + .../techcourse/controller/HomeController.java | 24 ++ .../controller/LoginController.java | 138 ++++++++++ .../controller/NotFoundController.java | 22 ++ .../controller/RegisterController.java | 63 +++++ .../techcourse/controller/RequestMapping.java | 41 +++ .../controller/StaticResourceController.java | 67 +++++ .../java/org/apache/catalina/Manager.java | 21 +- .../main/java/org/apache/coyote/Session.java | 18 -- .../apache/coyote/http11/Http11Processor.java | 252 +----------------- .../org/apache/coyote/http11/HttpCookie.java | 32 +++ .../org/apache/coyote/http11/HttpRequest.java | 68 +++++ .../apache/coyote/http11/HttpResponse.java | 73 +++++ .../org/apache/coyote/http11/RequestLine.java | 34 +++ .../org/apache/coyote/session/Session.java | 22 ++ .../apache/coyote/session/SessionManager.java | 33 +++ .../coyote/http11/Http11ProcessorTest.java | 11 +- 18 files changed, 674 insertions(+), 277 deletions(-) create mode 100644 tomcat/src/main/java/com/techcourse/controller/AbstractController.java create mode 100644 tomcat/src/main/java/com/techcourse/controller/Controller.java create mode 100644 tomcat/src/main/java/com/techcourse/controller/HomeController.java create mode 100644 tomcat/src/main/java/com/techcourse/controller/LoginController.java create mode 100644 tomcat/src/main/java/com/techcourse/controller/NotFoundController.java create mode 100644 tomcat/src/main/java/com/techcourse/controller/RegisterController.java create mode 100644 tomcat/src/main/java/com/techcourse/controller/RequestMapping.java create mode 100644 tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/Session.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java create mode 100644 tomcat/src/main/java/org/apache/coyote/session/Session.java create mode 100644 tomcat/src/main/java/org/apache/coyote/session/SessionManager.java diff --git a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java new file mode 100644 index 0000000000..5ce675d570 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java @@ -0,0 +1,23 @@ +package com.techcourse.controller; + +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; + +public abstract class AbstractController implements Controller { + + @Override + public void service(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception { + String method = request.getMethod(); + if ("GET".equals(method)) { + doGet(request, response); + } + + if ("POST".equals(method)) { + doPost(request, response); + } + } + + protected abstract void doGet(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception; + + protected abstract void doPost(HttpRequest request, HttpResponse.HttpResponseBuilder response); +} diff --git a/tomcat/src/main/java/com/techcourse/controller/Controller.java b/tomcat/src/main/java/com/techcourse/controller/Controller.java new file mode 100644 index 0000000000..dba9c97dec --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/Controller.java @@ -0,0 +1,9 @@ +package com.techcourse.controller; + +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; + +public interface Controller { + + void service(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception; +} diff --git a/tomcat/src/main/java/com/techcourse/controller/HomeController.java b/tomcat/src/main/java/com/techcourse/controller/HomeController.java new file mode 100644 index 0000000000..c8e0c83ff3 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/HomeController.java @@ -0,0 +1,24 @@ +package com.techcourse.controller; + +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; + +public class HomeController extends AbstractController { + + @Override + protected void doGet(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception { + buildOkResponse("Hello world!", response); + } + + @Override + protected void doPost(HttpRequest request, HttpResponse.HttpResponseBuilder response) { + throw new RuntimeException(); + } + + private void buildOkResponse(String responseBody, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("200 OK") + .withResponseBody(responseBody) + .addHeader("Content-Length", String.valueOf(responseBody.getBytes().length)) + .addHeader("Content-Type", "text/html"); + } +} diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java new file mode 100644 index 0000000000..49e81ea5df --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -0,0 +1,138 @@ +package com.techcourse.controller; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; +import com.techcourse.db.InMemoryUserRepository; +import org.apache.coyote.http11.Http11Processor; +import org.apache.coyote.http11.HttpCookie; +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.session.Session; +import org.apache.coyote.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoginController extends AbstractController { + + private static final String RESOURCE_BASE_PATH = "static"; + private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + + @Override + protected void doGet(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception { + String resource = ensureHtmlExtension(request.getPath()); + String responseBody = loadResourceContent(resource); + if (request.containsCookie()) { + HttpCookie httpCookie = new HttpCookie(request.getCookie()); + handleCookieRequest(httpCookie, responseBody, response); + } + + if (!request.containsCookie()) { + buildOkResponse(responseBody, response); + } + } + + @Override + protected void doPost(HttpRequest request, HttpResponse.HttpResponseBuilder response) { + String requestBody = request.getRequestBody(); + String account = getParameter(requestBody, "account"); + String password = getParameter(requestBody, "password"); + + if (findUserByInfo(account, password)) { + handleSuccessfulLogin(response, account); + } + + if (!findUserByInfo(account, password)) { + handleFailedLogin(response); + } + } + + private void handleCookieRequest(HttpCookie httpCookie, String responseBody, HttpResponse.HttpResponseBuilder response) { + if (httpCookie.containsJSessionId()) { + String sessionId = httpCookie.getJSessionId(); + Session session = SessionManager.getInstance().findSession(sessionId); + if (session == null) { + buildOkResponse(responseBody, response); + return; + } + buildRedirectResponse("/index.html", response); + } + + if (!httpCookie.containsJSessionId()) { + buildOkResponse(responseBody, response); + } + } + + private void handleSuccessfulLogin(HttpResponse.HttpResponseBuilder response, String account) { + String sessionId = UUID.randomUUID().toString(); + Session session = new Session(sessionId); + SessionManager.getInstance().add(session); + + InMemoryUserRepository.findByAccount(account) + .ifPresent(user -> { + session.setAttribute(sessionId, user); + buildRedirectWithCookieResponse("/index.html", sessionId, response); + }); + } + + private void handleFailedLogin(HttpResponse.HttpResponseBuilder response) { + buildRedirectResponse("/401.html", response); + } + + private String getParameter(String requestBody, String key) { + return Arrays.stream(requestBody.split("&")) + .filter(param -> param.startsWith(key + "=")) + .map(param -> param.split("=")[1]) + .findFirst() + .orElse(""); + } + + private boolean findUserByInfo(String account, String password) { + return InMemoryUserRepository.findByAccount(account) + .filter(user -> user.checkPassword(password)) + .map(user -> { + log.info(user.toString()); + return true; + }) + .orElse(false); + } + + private String loadResourceContent(String resource) throws IOException { + String resourcePath = Objects.requireNonNull(getClass().getClassLoader() + .getResource(RESOURCE_BASE_PATH + resource)) + .getPath(); + + try (FileInputStream file = new FileInputStream(resourcePath)) { + return new String(file.readAllBytes(), StandardCharsets.UTF_8); + } + } + + private void buildOkResponse(String responseBody, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("200 OK") + .withResponseBody(responseBody) + .addHeader("Content-Type", "text/html") + .addHeader("Content-Length", String.valueOf(responseBody.getBytes().length)); + } + + private void buildRedirectResponse(String location, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("302 Found") + .addHeader("Location", location); + } + + private void buildRedirectWithCookieResponse(String location, String sessionId, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("302 Found") + .addHeader("Location", location) + .addHeader("Set-Cookie", "JSESSIONID=" + sessionId); + } + + private String ensureHtmlExtension(String path) { + if (!path.contains(".")) { + path += ".html"; + } + + return path; + } +} diff --git a/tomcat/src/main/java/com/techcourse/controller/NotFoundController.java b/tomcat/src/main/java/com/techcourse/controller/NotFoundController.java new file mode 100644 index 0000000000..9a8e51f815 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/NotFoundController.java @@ -0,0 +1,22 @@ +package com.techcourse.controller; + +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; + +public class NotFoundController extends AbstractController { + + @Override + protected void doGet(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception { + buildRedirectResponse("/401.html", response); + } + + @Override + protected void doPost(HttpRequest request, HttpResponse.HttpResponseBuilder response) { + throw new RuntimeException(); + } + + private void buildRedirectResponse(String location, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("302 Found") + .addHeader("Location", location); + } +} diff --git a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java new file mode 100644 index 0000000000..d467f3b3cc --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java @@ -0,0 +1,63 @@ +package com.techcourse.controller; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.model.User; +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; + +public class RegisterController extends AbstractController { + + private static final String RESOURCE_BASE_PATH = "static"; + + @Override + protected void doGet(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception { + String resource = ensureHtmlExtension(request.getPath()); + String responseBody = loadResourceContent(resource); + buildOkResponse(responseBody, response); + } + + @Override + protected void doPost(HttpRequest request, HttpResponse.HttpResponseBuilder response) { + String requestBody = request.getRequestBody(); + String account = requestBody.split("&")[0].split("=")[1]; + String email = requestBody.split("&")[1].split("=")[1]; + String password = requestBody.split("&")[2].split("=")[1]; + User user = new User(account, password, email); + InMemoryUserRepository.save(user); + buildRedirectResponse("/index.html", response); + } + + private String ensureHtmlExtension(String path) { + if (!path.contains(".")) { + path += ".html"; + } + + return path; + } + + private String loadResourceContent(String resource) throws IOException { + String resourcePath = Objects.requireNonNull(getClass().getClassLoader() + .getResource(RESOURCE_BASE_PATH + resource)) + .getPath(); + + try (FileInputStream file = new FileInputStream(resourcePath)) { + return new String(file.readAllBytes(), StandardCharsets.UTF_8); + } + } + + private void buildOkResponse(String responseBody, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("200 OK") + .withResponseBody(responseBody) + .addHeader("Content-Type", "text/html") + .addHeader("Content-Length", String.valueOf(responseBody.getBytes().length)); + } + + private void buildRedirectResponse(String location, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("302 Found") + .addHeader("Location", location); + } +} diff --git a/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java new file mode 100644 index 0000000000..7f0ed04c33 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java @@ -0,0 +1,41 @@ +package com.techcourse.controller; + +import java.util.HashMap; +import java.util.Map; +import org.apache.coyote.http11.HttpRequest; + +public class RequestMapping { + + private final Map controllers = new HashMap<>(); + private final Controller staticResourceController = new StaticResourceController(); + + public RequestMapping() { + initializeControllers(); + } + + private void initializeControllers() { + controllers.put("/", new HomeController()); + controllers.put("/login", new LoginController()); + controllers.put("/register", new RegisterController()); + } + + public Controller getController(HttpRequest request) { + String path = request.getPath(); + + if (isStaticResource(path)) { + return staticResourceController; + } + + for (Map.Entry entry : controllers.entrySet()) { + if (path.contains(entry.getKey())) { + return entry.getValue(); + } + } + + return new NotFoundController(); + } + + private boolean isStaticResource(String path) { + return path.matches(".+\\.(css|js|ico|html)$"); + } +} diff --git a/tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java b/tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java new file mode 100644 index 0000000000..5267ffbd74 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java @@ -0,0 +1,67 @@ +package com.techcourse.controller; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; + +public class StaticResourceController extends AbstractController { + + private static final String RESOURCE_BASE_PATH = "static"; + + @Override + protected void doGet(HttpRequest request, HttpResponse.HttpResponseBuilder response) { + String resource = request.getPath(); + + try { + String responseBody = loadResourceContent(resource); + String contentType = getContentType(resource); + buildOkResponse(responseBody, contentType, response); + } catch (Exception e) { + buildRedirectResponse("/404.html", response); + } + } + + @Override + protected void doPost(HttpRequest request, HttpResponse.HttpResponseBuilder response) { + throw new RuntimeException(); + } + + private String loadResourceContent(String resource) throws IOException { + String resourcePath = Objects.requireNonNull(getClass().getClassLoader() + .getResource(RESOURCE_BASE_PATH + resource)) + .getPath(); + + try (FileInputStream file = new FileInputStream(resourcePath)) { + return new String(file.readAllBytes(), StandardCharsets.UTF_8); + } + } + + private void buildOkResponse(String responseBody, String contentType, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("200 OK") + .withResponseBody(responseBody) + .addHeader("Content-Type", contentType) + .addHeader("Content-Length", String.valueOf(responseBody.getBytes().length)); + } + + private void buildRedirectResponse(String location, HttpResponse.HttpResponseBuilder response) { + response.withStatusCode("302 Found") + .addHeader("Location", location); + } + + private String getContentType(String resource) { + if (resource.endsWith(".css")) { + return "text/css"; + } else if (resource.endsWith(".js")) { + return "application/javascript"; + } else if (resource.endsWith(".ico")) { + return "image/x-icon"; + } else if (resource.endsWith(".html")) { + return "text/html"; + } else { + return "text/plain"; + } + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e69410f6a9..a6fd70ff4b 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,8 +1,7 @@ package org.apache.catalina; -import jakarta.servlet.http.HttpSession; - import java.io.IOException; +import org.apache.coyote.session.Session; /** * A Manager manages the pool of Sessions that are associated with a @@ -25,14 +24,15 @@ public interface Manager { /** - * Add this Session to the set of active Sessions for this Manager. + * Add this SessionTemp to the set of active Sessions for this Manager. * - * @param session Session to be added + * @param session SessionTemp to be added */ - void add(HttpSession session); + + void add(Session session); /** - * Return the active Session, associated with this Manager, with the + * Return the active SessionTemp, 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 @@ -45,12 +45,13 @@ public interface Manager { * @return the request session or {@code null} if a session with the * requested ID could not be found */ - HttpSession findSession(String id) throws IOException; + Session findSession(String id) throws IOException; /** - * Remove this Session from the active Sessions for this Manager. + * Remove this SessionTemp from the active Sessions for this Manager. * - * @param session Session to be removed + * @param session SessionTemp to be removed */ - void remove(HttpSession session); + + void remove(Session session); } diff --git a/tomcat/src/main/java/org/apache/coyote/Session.java b/tomcat/src/main/java/org/apache/coyote/Session.java deleted file mode 100644 index fc15d250ed..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/Session.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.apache.coyote; - -import java.util.HashMap; -import java.util.Map; -import com.techcourse.model.User; - -public class Session { - - private static final Map session = new HashMap<>(); - - public static void save(String uuid, User user) { - session.put(uuid, user); - } - - public static boolean containsSession(String uuid) { - return session.containsKey(uuid); - } -} 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 3ac8e7aea9..6a06dc0a38 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,46 +1,21 @@ package org.apache.coyote.http11; import java.io.BufferedReader; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.controller.RequestMapping; import com.techcourse.exception.UncheckedServletException; -import com.techcourse.model.User; import org.apache.coyote.Processor; -import org.apache.coyote.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final int REQUEST_RESOURCE_POSITION = 1; - private static final String DEFAULT_RESPONSE_BODY = "Hello world!"; - private static final String DEFAULT_EXTENSION = "html"; - private static final String QUERY_PARAM_DELIMITER = "?"; - private static final String ROOT_PATH = "/"; - private static final String PARAMETER_DELIMITER = "&"; - private static final String KEY_VALUE_DELIMITER = "="; - private static final String STATIC_RESOURCE_PATH = "static"; - private static final String EXTENSION_DELIMITER_REGEX = "\\."; - private static final String HTTP_VERSION = "HTTP/1.1"; - private static final String STATUS_CODE_200 = "200 OK"; - private static final String STATUS_CODE_302 = "302 Found"; - private static final String STATUS_CODE_401 = "401 Unauthorized"; - private static final String CONTENT_TYPE_HEADER = "Content-Type: "; - private static final String TEXT_TYPE_PREFIX = "text/"; - private static final String CHARSET_UTF_8 = ";charset=utf-8"; - private static final String CONTENT_LENGTH_HEADER = "Content-Length: "; - private static final String LOCATION_HEADER = "Location: "; private final Socket connection; + private final RequestMapping requestMapping = new RequestMapping(); public Http11Processor(Socket connection) { this.connection = connection; @@ -57,229 +32,20 @@ public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { BufferedReader request = new BufferedReader(new InputStreamReader(inputStream)); - String firstLine = request.readLine(); - System.out.println(firstLine); - String resource = ""; - String response = ""; - if (firstLine.split(" ")[0].equals("GET") && !parseRequestResource(firstLine).contains("/login")) { - resource = parseRequestResource(firstLine); - response = getResponse(resource); - } - if (firstLine.split(" ")[0].equals("GET") && parseRequestResource(firstLine).contains("/login")) { - String line; - while (!(line = request.readLine()).isEmpty()) { - if (line.contains("Cookie")) { - String uuid = ""; - for (String v : line.split(";")) { - if (v.contains("JSESSIONID")) { - uuid = v.split("=")[1]; - } - } - if (Session.containsSession(uuid)) { - response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: " + "/index.html"); - } else { - resource = parseRequestResource(firstLine); - response = getResponse(resource); - } - } else { - resource = parseRequestResource(firstLine); - response = getResponse(resource); - } - } - } + HttpRequest httpRequest = HttpRequest.from(request); + HttpResponse.HttpResponseBuilder responseBuilder = HttpResponse.builder(); - if (firstLine.split(" ")[0].equals("POST")) { - String line; - int contentLength = 0; - while (!(line = request.readLine()).isEmpty()) { - if (line.contains("Content-Length")) { - contentLength = Integer.parseInt(line.split(" ")[1]); - } - } + requestMapping.getController(httpRequest) + .service(httpRequest, responseBuilder); + HttpResponse response = responseBuilder.build(); - char[] buffer = new char[contentLength]; - request.read(buffer, 0, contentLength); - String requestBody = new String(buffer); - if (parseRequestResource(firstLine).contains("login")) { - String account = requestBody.split("&")[0].split("=")[1]; - String password = requestBody.split("&")[1].split("=")[1]; - if (findUserByInfo(account, password)) { - UUID uuid = UUID.randomUUID(); - response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: " + "/index.html", - "Set-Cookie: JSESSIONID=" + uuid); - Session.save(uuid.toString(), InMemoryUserRepository.findByAccount(account).get()); - } - - if (!findUserByInfo(account, password)) { - response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: " + "/401.html"); - } - } - - if (parseRequestResource(firstLine).contains("register")) { - String account = requestBody.split("&")[0].split("=")[1]; - String email = requestBody.split("&")[1].split("=")[1]; - String password = requestBody.split("&")[2].split("=")[1]; - User user = new User(account, password, email); - InMemoryUserRepository.save(user); - response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: " + "/index.html"); - } - - } outputStream.write(response.getBytes()); outputStream.flush(); - } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException(e); } } - - private String parseRequestResource(String firstLine) throws IOException { - return firstLine.split(" ")[REQUEST_RESOURCE_POSITION]; - } - - private String getResponse(String resource) throws IOException { - String statusCode = STATUS_CODE_200; - String responseBody = DEFAULT_RESPONSE_BODY; - String extension = DEFAULT_EXTENSION; - - if (resource.contains(QUERY_PARAM_DELIMITER)) { - Result result = handleQueryParameters(resource); - resource = result.resourceWithExtension; - statusCode = result.statusCode; - } - - if (!resource.equals(ROOT_PATH)) { - resource = addExtensionWhenDoesNotExists(resource); - responseBody = loadResourceContent(resource); - extension = getExtension(resource); - } - - return joinResponse(statusCode, responseBody, extension); - } - - private Result handleQueryParameters(String resource) { - int index = resource.indexOf(QUERY_PARAM_DELIMITER); - String resourcePage = resource.substring(0, index); - String queryString = resource.substring(index + 1); - String statusCode = STATUS_CODE_200; - - if (isLoginPage(resourcePage)) { - statusCode = getStatusCodeByLoginPageResponse(queryString, statusCode); - } - - String resourceWithExtension = resourcePage + "." + DEFAULT_EXTENSION; - - return new Result(resourceWithExtension, statusCode); - } - - private String getStatusCodeByLoginPageResponse(String queryString, String statusCode) { - if (loginPageResponse(queryString)) { - statusCode = STATUS_CODE_302; - } - - if (!loginPageResponse(queryString)) { - statusCode = STATUS_CODE_401; - } - - return statusCode; - } - - private record Result(String resourceWithExtension, String statusCode) { - } - - private boolean isLoginPage(String resourcePage) { - return resourcePage.contains("/login"); - } - - private boolean loginPageResponse(String queryString) { - String account = extractQueryParameter(queryString, "account"); - String password = extractQueryParameter(queryString, "password"); - return findUserByInfo(account, password); - } - - private String extractQueryParameter(String queryString, String parameter) { - for (String pair : queryString.split(PARAMETER_DELIMITER)) { - String[] keyValue = pair.split(KEY_VALUE_DELIMITER); - if (keyValue[0].equals(parameter) && keyValue.length > 1) { - return keyValue[1]; - } - } - return ""; - } - - private boolean findUserByInfo(String account, String password) { - return InMemoryUserRepository.findByAccount(account) - .filter(user -> user.checkPassword(password)) - .map(user -> { - log.info(user.toString()); - return true; - }) - .orElse(false); - } - - private String addExtensionWhenDoesNotExists(String resource) { - if (hasNotExtension(resource)) { - resource += "." + DEFAULT_EXTENSION; - } - - return resource; - } - - private String loadResourceContent(String resource) throws IOException { - String resourcePath = Objects.requireNonNull(getClass().getClassLoader() - .getResource(STATIC_RESOURCE_PATH + resource)) - .getPath(); - - try (FileInputStream file = new FileInputStream(resourcePath)) { - return new String(file.readAllBytes(), StandardCharsets.UTF_8); - } - } - - private boolean hasNotExtension(String resource) { - return !resource.contains("."); - } - - private String getExtension(String resource) { - String[] parts = resource.split(EXTENSION_DELIMITER_REGEX); - return parts[parts.length - 1]; - } - - private String joinResponse(String statusCode, String responseBody, String extension) { - String redirectHeader = getRedirectHeader(statusCode); - - List headers = new ArrayList<>(); - headers.add(HTTP_VERSION + " " + statusCode + " "); - headers.add(CONTENT_TYPE_HEADER + TEXT_TYPE_PREFIX + extension + CHARSET_UTF_8 + " "); - headers.add(CONTENT_LENGTH_HEADER + responseBody.getBytes().length + " "); - - if (!redirectHeader.isBlank()) { - headers.set(0, HTTP_VERSION + " " + "302" + " "); - headers.add(redirectHeader + " "); - } - - return String.join("\r\n", headers) + "\r\n\r\n" + responseBody; - } - - private String getRedirectHeader(String statusCode) { - String redirectHeader = ""; - - if (STATUS_CODE_302.equals(statusCode)) { - redirectHeader = LOCATION_HEADER + "/index.html"; - } - - if (STATUS_CODE_401.equals(statusCode)) { - redirectHeader = LOCATION_HEADER + "/401.html"; - } - - return redirectHeader; - } } 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..32cef08181 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -0,0 +1,32 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class HttpCookie { + + private static final String JSESSIONID = "JSESSIONID"; + + private final Map cookies; + + public HttpCookie(String cookie) { + this.cookies = new HashMap<>(); + add(cookie); + } + + private void add(String cookie) { + String[] parts = cookie.split("; "); + for (String part : parts) { + String[] parse = part.split("="); + cookies.put(parse[0], parse[1]); + } + } + + public boolean containsJSessionId() { + return cookies.containsKey(JSESSIONID); + } + + public String getJSessionId() { + return cookies.get(JSESSIONID); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java new file mode 100644 index 0000000000..5b39e41b39 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -0,0 +1,68 @@ +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class HttpRequest { + + private final RequestLine requestLine; + private final Map headers; + + private HttpRequest(RequestLine requestLine, Map headers) { + this.requestLine = requestLine; + this.headers = headers; + } + + public static HttpRequest from(BufferedReader request) throws IOException { + RequestLine requestLine = RequestLine.from(request.readLine()); + Map headers = parseHeaders(request); + parseRequestBody(request, headers); + + return new HttpRequest(requestLine, headers); + } + + private static Map parseHeaders(BufferedReader request) throws IOException { + String line; + Map headers = new HashMap<>(); + while (!(line = request.readLine()).isEmpty()) { + String[] headerParts = line.split(": ", 2); + if (headerParts.length == 2) { + headers.put(headerParts[0], headerParts[1]); + } + } + return headers; + } + + private static void parseRequestBody(BufferedReader request, Map headers) throws IOException { + if (headers.containsKey("Content-Length")) { + int contentLength = Integer.parseInt(headers.get("Content-Length")); + if (contentLength > 0) { + char[] buffer = new char[contentLength]; + request.read(buffer, 0, contentLength); + headers.put("requestBody", new String(buffer)); + } + } + } + + public boolean containsCookie() { + return headers.containsKey("Cookie"); + } + + public String getMethod() { + return requestLine.getMethod(); + } + + public String getPath() { + return requestLine.getPath(); + } + + public String getCookie() { + return headers.get("Cookie"); + } + + public String getRequestBody() { + return headers.get("requestBody"); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java new file mode 100644 index 0000000000..a75dc5d68c --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -0,0 +1,73 @@ +package org.apache.coyote.http11; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class HttpResponse { + + private final String httpVersion; + private final String statusCode; + private final Map headers; + private final String responseBody; + + private HttpResponse(String httpVersion, String statusCode, Map headers, String responseBody) { + this.httpVersion = httpVersion; + this.statusCode = statusCode; + this.headers = headers; + this.responseBody = responseBody; + } + + public static HttpResponseBuilder builder() { + return new HttpResponseBuilder().withHttpVersion("HTTP/1.1"); + } + + public static class HttpResponseBuilder { + private String httpVersion; + private String statusCode; + private Map headers = new HashMap<>(); + private String responseBody; + + public HttpResponseBuilder withHttpVersion(String httpVersion) { + this.httpVersion = httpVersion; + return this; + } + + public HttpResponseBuilder withStatusCode(String statusCode) { + this.statusCode = statusCode; + return this; + } + + public HttpResponseBuilder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public HttpResponseBuilder withResponseBody(String responseBody) { + this.responseBody = responseBody; + return this; + } + + public HttpResponse build() { + return new HttpResponse(httpVersion, statusCode, headers, responseBody); + } + } + + public byte[] getBytes() { + StringBuilder responseBuilder = new StringBuilder(); + + responseBuilder.append(httpVersion).append(" ").append(statusCode).append(" ").append("\r\n"); + + for (Map.Entry header : headers.entrySet()) { + responseBuilder.append(header.getKey()).append(": ").append(header.getValue()).append(" ").append("\r\n"); + } + + responseBuilder.append("\r\n"); + + if (responseBody != null && !responseBody.isEmpty()) { + responseBuilder.append(responseBody); + } + + return responseBuilder.toString().getBytes(StandardCharsets.UTF_8); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java new file mode 100644 index 0000000000..709b892422 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java @@ -0,0 +1,34 @@ +package org.apache.coyote.http11; + +public class RequestLine { + + private final String method; + private final String path; + private final String version; + + private RequestLine(String method, String path, String version) { + this.method = method; + this.path = path; + this.version = version; + } + + public static RequestLine from(String requestLine) { + String[] parts = splitRequestLine(requestLine); + String method = parts[0]; + String path = parts[1]; + String version = parts[2]; + return new RequestLine(method, path, version); + } + + private static String[] splitRequestLine(String requestLine) { + return requestLine.split(" "); + } + + public String getMethod() { + return method; + } + + public String getPath() { + return path; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/session/Session.java b/tomcat/src/main/java/org/apache/coyote/session/Session.java new file mode 100644 index 0000000000..ffab41cd78 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/session/Session.java @@ -0,0 +1,22 @@ +package org.apache.coyote.session; + +import java.util.HashMap; +import java.util.Map; + +public class Session { + + private final String id; + private final Map values = new HashMap<>(); + + public Session(final String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setAttribute(final String name, final Object value) { + values.put(name, value); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/session/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/session/SessionManager.java new file mode 100644 index 0000000000..894bd18acb --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/session/SessionManager.java @@ -0,0 +1,33 @@ +package org.apache.coyote.session; + +import java.util.HashMap; +import java.util.Map; +import org.apache.catalina.Manager; + +public class SessionManager implements Manager { + + private static final Map SESSIONS = new HashMap<>(); + private static final SessionManager INSTANCE = new SessionManager(); + + private SessionManager() { + } + + public static SessionManager getInstance() { + return INSTANCE; + } + + @Override + public void add(Session session) { + SESSIONS.put(session.getId(), session); + } + + @Override + public Session findSession(final String id) { + return SESSIONS.get(id); + } + + @Override + public void remove(Session session) { + SESSIONS.remove(session.getId()); + } +} 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..8ea51e7c0a 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,13 @@ 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.Test; +import support.StubSocket; class Http11ProcessorTest { @@ -24,8 +23,8 @@ void process() { // then var expected = String.join("\r\n", "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", "Content-Length: 12 ", + "Content-Type: text/html ", "", "Hello world!"); @@ -51,8 +50,8 @@ 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" + + "Content-Type: text/html \r\n" + "\r\n"+ new String(Files.readAllBytes(new File(resource.getFile()).toPath())); From a86389de93dbacff349e959cfa6b53c8887e6cfb Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Fri, 6 Sep 2024 16:09:55 +0900 Subject: [PATCH 06/14] =?UTF-8?q?test:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 19 ++++--- study/src/test/java/study/IOStreamTest.java | 63 +++++++++++++-------- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..1133130026 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,13 +1,14 @@ 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.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 파일을 제공 할 수 있어야 한다. @@ -28,7 +29,7 @@ class FileTest { final String fileName = "nextstep.txt"; // todo - final String actual = ""; + final String actual = getClass().getClassLoader().getResource("nextstep.txt").getPath(); assertThat(actual).endsWith(fileName); } @@ -40,14 +41,14 @@ class FileTest { * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws IOException { final String fileName = "nextstep.txt"; // todo - final Path path = null; + final Path path = Paths.get("/Users/lily/java-http/study/src/test/resources/", fileName); // todo - final List actual = Collections.emptyList(); + final List actual = Files.readAllLines(path); assertThat(actual).containsOnly("nextstep"); } diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..bb579123ff 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -1,22 +1,33 @@ package study; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - /** * 자바는 스트림(Stream)으로부터 I/O를 사용한다. * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. - * + *

* InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. * FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. * FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환) - * + *

* Stream은 데이터를 바이트로 읽고 쓴다. * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다. @@ -26,7 +37,7 @@ class IOStreamTest { /** * OutputStream 학습하기 - * + *

* 자바의 기본 출력 클래스는 java.io.OutputStream이다. * OutputStream의 write(int b) 메서드는 기반 메서드이다. * public abstract void write(int b) throws IOException; @@ -39,7 +50,7 @@ class OutputStream_학습_테스트 { * OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다. * 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때, * 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다. - * + *

* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다. * write(byte[] data)write(byte b[], int off, int len) 메서드는 * 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다. @@ -53,7 +64,7 @@ class OutputStream_학습_테스트 { * todo * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 */ - + outputStream.write(bytes); final String actual = outputStream.toString(); assertThat(actual).isEqualTo("nextstep"); @@ -63,7 +74,7 @@ class OutputStream_학습_테스트 { /** * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. - * + *

* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 @@ -78,7 +89,7 @@ class OutputStream_학습_테스트 { * flush를 사용해서 테스트를 통과시킨다. * ByteArrayOutputStream과 어떤 차이가 있을까? */ - + outputStream.flush(); verify(outputStream, atLeastOnce()).flush(); outputStream.close(); } @@ -96,19 +107,19 @@ class OutputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ - + outputStream.close(); verify(outputStream, atLeastOnce()).close(); } } /** * InputStream 학습하기 - * + *

* 자바의 기본 입력 클래스는 java.io.InputStream이다. * InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다. * InputStream의 read() 메서드는 기반 메서드이다. * public abstract int read() throws IOException; - * + *

* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다. */ @Nested @@ -128,7 +139,7 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + final String actual = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -148,14 +159,14 @@ class InputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ - + inputStream.close(); verify(inputStream, atLeastOnce()).close(); } } /** * FilterStream 학습하기 - * + *

* 필터는 필터 스트림, reader, writer로 나뉜다. * 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. * reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다. @@ -169,12 +180,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,17 +208,23 @@ class InputStreamReader_학습_테스트 { * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. */ @Test - void BufferedReader를_사용하여_문자열을_읽어온다() { + void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException { final String emoji = String.join("\r\n", "😀😃😄😁😆😅😂🤣🥲☺️😊", "😇🙂🙃😉😌😍🥰😘😗😙😚", "😋😛😝😜🤪🤨🧐🤓😎🥸🤩", ""); final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); - + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); final StringBuilder actual = new StringBuilder(); + while (bufferedReader.ready()) { + actual.append(bufferedReader.readLine() + "\r\n"); + } + assertThat(actual).hasToString(emoji); + } } } From 568e37cdaed48b8c303d85c06166629d22bdd629 Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Fri, 6 Sep 2024 17:48:16 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=ED=95=99=EC=8A=B5=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/build.gradle | 1 + .../cache/com/example/GreetingController.java | 7 +++--- .../example/cachecontrol/CacheWebConfig.java | 15 ++++++++++++ .../example/etag/EtagFilterConfiguration.java | 23 +++++++++++++++---- .../version/CacheBustingWebConfig.java | 5 +++- study/src/main/resources/application.yml | 3 +++ 6 files changed, 45 insertions(+), 9 deletions(-) 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' diff --git a/study/src/main/java/cache/com/example/GreetingController.java b/study/src/main/java/cache/com/example/GreetingController.java index c0053cda42..4ed8036e67 100644 --- a/study/src/main/java/cache/com/example/GreetingController.java +++ b/study/src/main/java/cache/com/example/GreetingController.java @@ -1,18 +1,17 @@ package cache.com.example; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; -import jakarta.servlet.http.HttpServletResponse; - @Controller public class GreetingController { @GetMapping("/") public String index() { - return "index"; + return "index.html"; } /** @@ -30,7 +29,7 @@ public String cacheControl(final HttpServletResponse response) { @GetMapping("/etag") public String etag() { - return "index"; + return "index.html"; } @GetMapping("/resource-versioning") 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..aa0149b29e 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -1,13 +1,28 @@ package cache.com.example.cachecontrol; +import cache.com.example.version.ResourceVersion; 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 { + public static final String PREFIX_STATIC_RESOURCES = "/resources"; + + private final ResourceVersion version; + + public CacheWebConfig(final ResourceVersion version) { + this.version = version; + } + @Override public void addInterceptors(final InterceptorRegistry registry) { + WebContentInterceptor interceptor = new WebContentInterceptor(); + interceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/*"); + registry.addInterceptor(interceptor) + .excludePathPatterns(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**"); } } 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..c4e6fb2422 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,27 @@ package cache.com.example.etag; +import cache.com.example.version.ResourceVersion; +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; -// } + public static final String PREFIX_STATIC_RESOURCES = "/resources"; + + private final ResourceVersion version; + + public EtagFilterConfiguration(final ResourceVersion version) { + this.version = version; + } + + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + final FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new ShallowEtagHeaderFilter()); + registration.addUrlPatterns("/etag", PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/*"); + return registration; + } } diff --git a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java index 6da6d2c795..c7addcdab4 100644 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java @@ -1,7 +1,9 @@ package cache.com.example.version; +import java.time.Duration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -20,6 +22,7 @@ public CacheBustingWebConfig(ResourceVersion version) { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .addResourceLocations("classpath:/static/") + .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic()); } } 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 1afb92c75e012406e803b0e3ed226b8b4b7f364d Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Mon, 9 Sep 2024 16:37:33 +0900 Subject: [PATCH 08/14] =?UTF-8?q?refactor:=20http=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/controller/AbstractController.java | 4 ++-- .../src/main/java/com/techcourse/controller/Controller.java | 4 ++-- .../main/java/com/techcourse/controller/HomeController.java | 4 ++-- .../java/com/techcourse/controller/LoginController.java | 6 +++--- .../java/com/techcourse/controller/NotFoundController.java | 4 ++-- .../java/com/techcourse/controller/RegisterController.java | 4 ++-- .../main/java/com/techcourse/controller/RequestMapping.java | 2 +- .../com/techcourse/controller/StaticResourceController.java | 4 ++-- .../java/org/apache/coyote/{http11 => http}/HttpCookie.java | 2 +- .../org/apache/coyote/{http11 => http}/HttpRequest.java | 2 +- .../org/apache/coyote/{http11 => http}/HttpResponse.java | 2 +- .../org/apache/coyote/{http11 => http}/RequestLine.java | 2 +- .../main/java/org/apache/coyote/http11/Http11Processor.java | 2 ++ 13 files changed, 22 insertions(+), 20 deletions(-) rename tomcat/src/main/java/org/apache/coyote/{http11 => http}/HttpCookie.java (95%) rename tomcat/src/main/java/org/apache/coyote/{http11 => http}/HttpRequest.java (98%) rename tomcat/src/main/java/org/apache/coyote/{http11 => http}/HttpResponse.java (98%) rename tomcat/src/main/java/org/apache/coyote/{http11 => http}/RequestLine.java (95%) diff --git a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java index 5ce675d570..a859839bfd 100644 --- a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java +++ b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java @@ -1,7 +1,7 @@ package com.techcourse.controller; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; public abstract class AbstractController implements Controller { diff --git a/tomcat/src/main/java/com/techcourse/controller/Controller.java b/tomcat/src/main/java/com/techcourse/controller/Controller.java index dba9c97dec..c45fa3e0df 100644 --- a/tomcat/src/main/java/com/techcourse/controller/Controller.java +++ b/tomcat/src/main/java/com/techcourse/controller/Controller.java @@ -1,7 +1,7 @@ package com.techcourse.controller; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; public interface Controller { diff --git a/tomcat/src/main/java/com/techcourse/controller/HomeController.java b/tomcat/src/main/java/com/techcourse/controller/HomeController.java index c8e0c83ff3..4021be71cc 100644 --- a/tomcat/src/main/java/com/techcourse/controller/HomeController.java +++ b/tomcat/src/main/java/com/techcourse/controller/HomeController.java @@ -1,7 +1,7 @@ package com.techcourse.controller; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; public class HomeController extends AbstractController { diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 49e81ea5df..d681b1e93b 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -8,9 +8,9 @@ import java.util.UUID; import com.techcourse.db.InMemoryUserRepository; import org.apache.coyote.http11.Http11Processor; -import org.apache.coyote.http11.HttpCookie; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http.HttpCookie; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; import org.apache.coyote.session.Session; import org.apache.coyote.session.SessionManager; import org.slf4j.Logger; diff --git a/tomcat/src/main/java/com/techcourse/controller/NotFoundController.java b/tomcat/src/main/java/com/techcourse/controller/NotFoundController.java index 9a8e51f815..7688b1f391 100644 --- a/tomcat/src/main/java/com/techcourse/controller/NotFoundController.java +++ b/tomcat/src/main/java/com/techcourse/controller/NotFoundController.java @@ -1,7 +1,7 @@ package com.techcourse.controller; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; public class NotFoundController extends AbstractController { diff --git a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java index d467f3b3cc..725ba27e9e 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java +++ b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java @@ -6,8 +6,8 @@ import java.util.Objects; import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; public class RegisterController extends AbstractController { diff --git a/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java index 7f0ed04c33..24aef72d30 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java +++ b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java @@ -2,7 +2,7 @@ import java.util.HashMap; import java.util.Map; -import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http.HttpRequest; public class RequestMapping { diff --git a/tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java b/tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java index 5267ffbd74..09498ca69a 100644 --- a/tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java +++ b/tomcat/src/main/java/com/techcourse/controller/StaticResourceController.java @@ -4,8 +4,8 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Objects; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; public class StaticResourceController extends AbstractController { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http/HttpCookie.java similarity index 95% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java rename to tomcat/src/main/java/org/apache/coyote/http/HttpCookie.java index 32cef08181..aabfe4538e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpCookie.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http; import java.util.HashMap; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java similarity index 98% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java rename to tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java index 5b39e41b39..3b7e81bf25 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http; import java.io.BufferedReader; import java.io.IOException; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http/HttpResponse.java similarity index 98% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java rename to tomcat/src/main/java/org/apache/coyote/http/HttpResponse.java index a75dc5d68c..8087e1b09d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpResponse.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http; import java.nio.charset.StandardCharsets; import java.util.HashMap; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http/RequestLine.java similarity index 95% rename from tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java rename to tomcat/src/main/java/org/apache/coyote/http/RequestLine.java index 709b892422..57479055b9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http/RequestLine.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http; public class RequestLine { 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 6a06dc0a38..7cc3b986b7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -7,6 +7,8 @@ import com.techcourse.controller.RequestMapping; import com.techcourse.exception.UncheckedServletException; import org.apache.coyote.Processor; +import org.apache.coyote.http.HttpRequest; +import org.apache.coyote.http.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From eae383d760f376d0d291a7cd3794602006ff7ebd Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Mon, 9 Sep 2024 16:48:27 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20requestBody=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http/HttpRequest.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java index 3b7e81bf25..02e3db54e7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java @@ -9,18 +9,20 @@ public class HttpRequest { private final RequestLine requestLine; private final Map headers; + private final String requestBody; - private HttpRequest(RequestLine requestLine, Map headers) { + private HttpRequest(RequestLine requestLine, Map headers, String requestBody) { this.requestLine = requestLine; this.headers = headers; + this.requestBody = requestBody; } public static HttpRequest from(BufferedReader request) throws IOException { RequestLine requestLine = RequestLine.from(request.readLine()); Map headers = parseHeaders(request); - parseRequestBody(request, headers); + String requestBody = parseRequestBody(request, headers); - return new HttpRequest(requestLine, headers); + return new HttpRequest(requestLine, headers, requestBody); } private static Map parseHeaders(BufferedReader request) throws IOException { @@ -35,15 +37,16 @@ private static Map parseHeaders(BufferedReader request) throws I return headers; } - private static void parseRequestBody(BufferedReader request, Map headers) throws IOException { + private static String parseRequestBody(BufferedReader request, Map headers) throws IOException { if (headers.containsKey("Content-Length")) { int contentLength = Integer.parseInt(headers.get("Content-Length")); if (contentLength > 0) { char[] buffer = new char[contentLength]; request.read(buffer, 0, contentLength); - headers.put("requestBody", new String(buffer)); + return new String(buffer); } } + return ""; } public boolean containsCookie() { @@ -63,6 +66,6 @@ public String getCookie() { } public String getRequestBody() { - return headers.get("requestBody"); + return requestBody; } } From f6f29c66f4c2b51cac89ce21fb0ee17df0168729 Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Tue, 10 Sep 2024 11:48:14 +0900 Subject: [PATCH 10/14] =?UTF-8?q?refactor:=20httpVersion=20=EB=B0=8F=20sta?= =?UTF-8?q?tusCode=20=EA=B0=9D=EC=B2=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http/HttpResponse.java | 11 ++++++----- .../main/java/org/apache/coyote/http/HttpVersion.java | 4 ++++ .../main/java/org/apache/coyote/http/StatusCode.java | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http/HttpVersion.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http/StatusCode.java diff --git a/tomcat/src/main/java/org/apache/coyote/http/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http/HttpResponse.java index 8087e1b09d..32f6d292f3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpResponse.java @@ -6,14 +6,14 @@ public class HttpResponse { - private final String httpVersion; - private final String statusCode; + private final HttpVersion httpVersion; + private final StatusCode statusCode; private final Map headers; private final String responseBody; private HttpResponse(String httpVersion, String statusCode, Map headers, String responseBody) { - this.httpVersion = httpVersion; - this.statusCode = statusCode; + this.httpVersion = new HttpVersion(httpVersion); + this.statusCode = new StatusCode(statusCode); this.headers = headers; this.responseBody = responseBody; } @@ -56,7 +56,8 @@ public HttpResponse build() { public byte[] getBytes() { StringBuilder responseBuilder = new StringBuilder(); - responseBuilder.append(httpVersion).append(" ").append(statusCode).append(" ").append("\r\n"); + responseBuilder.append(httpVersion.version()).append(" ") + .append(statusCode.statusCode()).append(" ").append("\r\n"); for (Map.Entry header : headers.entrySet()) { responseBuilder.append(header.getKey()).append(": ").append(header.getValue()).append(" ").append("\r\n"); diff --git a/tomcat/src/main/java/org/apache/coyote/http/HttpVersion.java b/tomcat/src/main/java/org/apache/coyote/http/HttpVersion.java new file mode 100644 index 0000000000..23f5acb8c8 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpVersion.java @@ -0,0 +1,4 @@ +package org.apache.coyote.http; + +public record HttpVersion(String version) { +} diff --git a/tomcat/src/main/java/org/apache/coyote/http/StatusCode.java b/tomcat/src/main/java/org/apache/coyote/http/StatusCode.java new file mode 100644 index 0000000000..d02ca3558d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http/StatusCode.java @@ -0,0 +1,4 @@ +package org.apache.coyote.http; + +public record StatusCode(String statusCode) { +} From 605778ac4a1770a283d4a9373d8b6ee406124404 Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Tue, 10 Sep 2024 12:05:43 +0900 Subject: [PATCH 11/14] =?UTF-8?q?refactor:=20requestMethod=20enum=EC=9C=BC?= =?UTF-8?q?=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 --- .../controller/AbstractController.java | 7 ++--- .../org/apache/coyote/http/HttpRequest.java | 2 +- .../org/apache/coyote/http/RequestLine.java | 6 ++--- .../org/apache/coyote/http/RequestMethod.java | 27 +++++++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http/RequestMethod.java diff --git a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java index a859839bfd..00a4f90cd4 100644 --- a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java +++ b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java @@ -2,17 +2,18 @@ import org.apache.coyote.http.HttpRequest; import org.apache.coyote.http.HttpResponse; +import org.apache.coyote.http.RequestMethod; public abstract class AbstractController implements Controller { @Override public void service(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception { - String method = request.getMethod(); - if ("GET".equals(method)) { + RequestMethod method = request.getMethod(); + if (method.isGetMethod()) { doGet(request, response); } - if ("POST".equals(method)) { + if (method.isPostMethod()) { doPost(request, response); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java index 02e3db54e7..fa4a404bb2 100644 --- a/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java @@ -53,7 +53,7 @@ public boolean containsCookie() { return headers.containsKey("Cookie"); } - public String getMethod() { + public RequestMethod getMethod() { return requestLine.getMethod(); } diff --git a/tomcat/src/main/java/org/apache/coyote/http/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http/RequestLine.java index 57479055b9..7544414ed6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http/RequestLine.java @@ -2,12 +2,12 @@ public class RequestLine { - private final String method; + private final RequestMethod method; private final String path; private final String version; private RequestLine(String method, String path, String version) { - this.method = method; + this.method = RequestMethod.fromString(method); this.path = path; this.version = version; } @@ -24,7 +24,7 @@ private static String[] splitRequestLine(String requestLine) { return requestLine.split(" "); } - public String getMethod() { + public RequestMethod getMethod() { return method; } diff --git a/tomcat/src/main/java/org/apache/coyote/http/RequestMethod.java b/tomcat/src/main/java/org/apache/coyote/http/RequestMethod.java new file mode 100644 index 0000000000..4e6888d8ef --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http/RequestMethod.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http; + +import java.util.Arrays; + +public enum RequestMethod { + + GET, + POST; + + RequestMethod() { + } + + public static RequestMethod fromString(String method) { + return Arrays.stream(RequestMethod.values()) + .filter(requestMethod -> requestMethod.name().equalsIgnoreCase(method)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + } + + public boolean isGetMethod() { + return this.equals(GET); + } + + public boolean isPostMethod() { + return this.equals(POST); + } +} From b7d96b340a7a19d32773ae13580e4d7ba009b15a Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Tue, 10 Sep 2024 12:08:28 +0900 Subject: [PATCH 12/14] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20if=20=EB=AC=B8=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20e?= =?UTF-8?q?arly=20return?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/controller/LoginController.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index d681b1e93b..16dc3b17c1 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -7,10 +7,10 @@ import java.util.Objects; import java.util.UUID; import com.techcourse.db.InMemoryUserRepository; -import org.apache.coyote.http11.Http11Processor; import org.apache.coyote.http.HttpCookie; import org.apache.coyote.http.HttpRequest; import org.apache.coyote.http.HttpResponse; +import org.apache.coyote.http11.Http11Processor; import org.apache.coyote.session.Session; import org.apache.coyote.session.SessionManager; import org.slf4j.Logger; @@ -59,11 +59,10 @@ private void handleCookieRequest(HttpCookie httpCookie, String responseBody, Htt return; } buildRedirectResponse("/index.html", response); + return; } - if (!httpCookie.containsJSessionId()) { - buildOkResponse(responseBody, response); - } + buildOkResponse(responseBody, response); } private void handleSuccessfulLogin(HttpResponse.HttpResponseBuilder response, String account) { From 6ffe1924048a66958b234e7769c8627a25618570 Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Tue, 10 Sep 2024 12:14:16 +0900 Subject: [PATCH 13/14] =?UTF-8?q?refactor:=20stream=EC=9C=BC=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 --- .../com/techcourse/controller/RequestMapping.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java index 24aef72d30..0db2821561 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java +++ b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java @@ -26,13 +26,11 @@ public Controller getController(HttpRequest request) { return staticResourceController; } - for (Map.Entry entry : controllers.entrySet()) { - if (path.contains(entry.getKey())) { - return entry.getValue(); - } - } - - return new NotFoundController(); + return controllers.entrySet().stream() + .filter(entry -> path.contains(entry.getKey())) + .map(Map.Entry::getValue) + .findFirst() + .orElse(new NotFoundController()); } private boolean isStaticResource(String path) { From 2a4fb9fd62e9e4eee6ef86a7f52f00547f08cd25 Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Tue, 10 Sep 2024 14:17:24 +0900 Subject: [PATCH 14/14] =?UTF-8?q?refactor:=20header=EC=97=90=EC=84=9C=20co?= =?UTF-8?q?okie=20=EC=9D=B8=EC=9E=90=EB=A5=BC=20=EB=B0=9B=EC=95=84=20?= =?UTF-8?q?=EA=BA=BC=EB=82=B4=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/controller/LoginController.java | 7 ++++--- .../src/main/java/org/apache/coyote/http/HttpRequest.java | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 16dc3b17c1..e9bc87036c 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -25,12 +25,13 @@ public class LoginController extends AbstractController { protected void doGet(HttpRequest request, HttpResponse.HttpResponseBuilder response) throws Exception { String resource = ensureHtmlExtension(request.getPath()); String responseBody = loadResourceContent(resource); - if (request.containsCookie()) { - HttpCookie httpCookie = new HttpCookie(request.getCookie()); + boolean containsCookie = request.containsHeaders("Cookie"); + if (containsCookie) { + HttpCookie httpCookie = new HttpCookie(request.getHeader("Cookie")); handleCookieRequest(httpCookie, responseBody, response); } - if (!request.containsCookie()) { + if (!containsCookie) { buildOkResponse(responseBody, response); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java index fa4a404bb2..91b4107a1f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http/HttpRequest.java @@ -49,8 +49,8 @@ private static String parseRequestBody(BufferedReader request, Map