From 8094af4654f8733319272d3e8b84675d598bb660 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 10:03:52 +0900 Subject: [PATCH 01/35] =?UTF-8?q?docs:=20readme=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index b24f542e33..fd88325f4a 100644 --- a/README.md +++ b/README.md @@ -1 +1,14 @@ # 톰캣 구현하기 + +## CheckList + +### 1단계 - HTTP 서버 구현하기 +- [ ] GET /index.html 응답하기 +- [ ] CSS 지원하기 +- [ ] Query String 파싱 + +### 2단계 - 로그인 구현하기 +- [ ] HTTP Status Code 302 +- [ ] POST 방식으로 회원가입 +- [ ] Cookie에 JSESSIONID 값 저장하기 +- [ ] Session 구현하기 From 9b07f65326909637da186f0a3744ede311cfd331 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 10:20:57 +0900 Subject: [PATCH 02/35] =?UTF-8?q?feat:=20FileTest=20=ED=95=99=EC=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..ffa7d95c77 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -3,9 +3,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; @@ -18,7 +20,7 @@ class FileTest { /** * resource 디렉터리 경로 찾기 - * + *

* File 객체를 생성하려면 파일의 경로를 알아야 한다. * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? @@ -28,26 +30,26 @@ class FileTest { final String fileName = "nextstep.txt"; // todo - final String actual = ""; + final String actual = Objects.requireNonNull(getClass().getClassLoader().getResource(fileName)).getPath(); assertThat(actual).endsWith(fileName); } /** * 파일 내용 읽기 - * + *

* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws IOException { final String fileName = "nextstep.txt"; // todo - final Path path = null; + final Path path = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource(fileName)).getPath()); // todo - final List actual = Collections.emptyList(); + final List actual = Files.readAllLines(path); assertThat(actual).containsOnly("nextstep"); } From 28790859167626154985609cd32ccce992a76840 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 10:57:41 +0900 Subject: [PATCH 03/35] =?UTF-8?q?feat:=20IOStreamTest=20=ED=95=99=EC=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/IOStreamTest.java | 43 ++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..1e889a239b 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import java.io.*; +import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; @@ -12,11 +13,11 @@ /** * 자바는 스트림(Stream)으로부터 I/O를 사용한다. * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. - * + *

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

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

* 자바의 기본 출력 클래스는 java.io.OutputStream이다. * OutputStream의 write(int b) 메서드는 기반 메서드이다. * public abstract void write(int b) throws IOException; @@ -39,7 +40,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바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다. @@ -54,6 +55,8 @@ class OutputStream_학습_테스트 { * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 */ + outputStream.write(bytes); + final String actual = outputStream.toString(); assertThat(actual).isEqualTo("nextstep"); @@ -63,7 +66,7 @@ class OutputStream_학습_테스트 { /** * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. - * + *

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

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

* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다. */ @Nested @@ -128,7 +134,8 @@ 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); @@ -149,13 +156,17 @@ class InputStream_학습_테스트 { * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (inputStream) { + + } + 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,7 +208,7 @@ class InputStreamReader_학습_테스트 { * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. */ @Test - void BufferedReader를_사용하여_문자열을_읽어온다() { + void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException { final String emoji = String.join("\r\n", "😀😃😄😁😆😅😂🤣🥲☺️😊", "😇🙂🙃😉😌😍🥰😘😗😙😚", @@ -206,6 +217,12 @@ class InputStreamReader_학습_테스트 { final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); final StringBuilder actual = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + actual.append(line).append("\r\n"); + } + } assertThat(actual).hasToString(emoji); } From 0b1d77897093ff16481fbb158c7281d104d030fa Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 12:07:21 +0900 Subject: [PATCH 04/35] =?UTF-8?q?feat:=20/index.html=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../apache/coyote/http11/Http11Processor.java | 37 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd88325f4a..052208c94c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## CheckList ### 1단계 - HTTP 서버 구현하기 -- [ ] GET /index.html 응답하기 +- [x] GET /index.html 응답하기 - [ ] CSS 지원하기 - [ ] Query String 파싱 diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 7f1b2c7e96..870051ce41 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -5,8 +5,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; public class Http11Processor implements Runnable, Processor { @@ -29,7 +35,24 @@ public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - final var responseBody = "Hello world!"; + final StringBuilder request = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + request.append(line).append("\r\n"); + } + } + + String fileName = extractResourcePathFromRequest(request); + var responseBody = ""; + + if (fileName.length() == 0) { + responseBody = "Hello world!"; + } else { + final Path path = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/" + fileName)).getPath()); + + responseBody = new String(Files.readAllBytes(path)); + } final var response = String.join("\r\n", "HTTP/1.1 200 OK ", @@ -40,8 +63,20 @@ public void process(final Socket connection) { outputStream.write(response.getBytes()); outputStream.flush(); + } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } + + private String extractResourcePathFromRequest(StringBuilder request) { + int startIndex = request.indexOf("GET ") + 4; + int endIndex = request.indexOf(" HTTP/1.1"); + + if (startIndex != -1 && endIndex != -1) { + return request.substring(startIndex + 1, endIndex); + } + + return ""; + } } From a935129151dc06ec05534d1831295462ac9fc2c6 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 12:48:42 +0900 Subject: [PATCH 05/35] =?UTF-8?q?feat:=20=EC=9B=B9=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=86=8C=EC=BC=93=20close=20err=20=EB=A1=9C=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 870051ce41..f514fd4e63 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -33,17 +33,23 @@ public void run() { @Override public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { - - final StringBuilder request = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - request.append(line).append("\r\n"); - } + final var outputStream = connection.getOutputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + + String firstLine = reader.readLine(); + if (firstLine == null) { + return; + } + final StringBuilder request = new StringBuilder(firstLine); + + String line; + while (!Objects.equals(line = reader.readLine(), "")) { + request.append(line).append("\r\n"); } String fileName = extractResourcePathFromRequest(request); + + var responseBody = ""; if (fileName.length() == 0) { From d872e10ea726bf92bdb462ba11fa5b82f6b39cf0 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 13:28:40 +0900 Subject: [PATCH 06/35] =?UTF-8?q?feat:=20CSS=20=EC=A7=80=EC=9B=90=ED=95=98?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../java/org/apache/coyote/http11/Http11Processor.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 052208c94c..5274e95937 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### 1단계 - HTTP 서버 구현하기 - [x] GET /index.html 응답하기 -- [ ] CSS 지원하기 +- [x] CSS 지원하기 - [ ] Query String 파싱 ### 2단계 - 로그인 구현하기 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 f514fd4e63..4b1a1a5382 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -49,6 +49,11 @@ public void process(final Socket connection) { String fileName = extractResourcePathFromRequest(request); + String contentType = "html"; + if (fileName.length() != 0) { + String[] splitedFileName = fileName.split("\\."); + contentType = splitedFileName[1]; + } var responseBody = ""; @@ -62,7 +67,7 @@ public void process(final Socket connection) { final var response = String.join("\r\n", "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", + "Content-Type: text/" + contentType + ";charset=utf-8 ", "Content-Length: " + responseBody.getBytes().length + " ", "", responseBody); From 6da0d8526d02423b2015153a2f6006979ec02f0c Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 14:05:15 +0900 Subject: [PATCH 07/35] =?UTF-8?q?feat:=20QueryString=20=ED=8C=8C=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../apache/coyote/http11/Http11Processor.java | 67 ++++++++++++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5274e95937..d0431c3fbb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ### 1단계 - HTTP 서버 구현하기 - [x] GET /index.html 응답하기 - [x] CSS 지원하기 -- [ ] Query String 파싱 +- [x] Query String 파싱 ### 2단계 - 로그인 구현하기 - [ ] HTTP Status Code 302 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 4b1a1a5382..1a4f51ae08 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,6 +1,8 @@ package org.apache.coyote.http11; +import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; +import nextstep.jwp.model.User; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,7 +14,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.Optional; public class Http11Processor implements Runnable, Processor { @@ -47,22 +52,52 @@ public void process(final Socket connection) { request.append(line).append("\r\n"); } - String fileName = extractResourcePathFromRequest(request); + String uri = extractRequestUriFromRequest(request); + + int index = uri.indexOf("?"); + String path; + String queryString = ""; + + if (index != -1) { + path = uri.substring(0, index); + queryString = uri.substring(index + 1); + } else { + path = uri; + } + + if (path.equals("login") && !queryString.equals("")) { + Map queryParams = parseQueryParams(queryString); + String account = queryParams.get("account"); + String password = queryParams.get("password"); + + Optional user = InMemoryUserRepository.findByAccount(account); + if (user.isPresent()) { + log.info(user.toString()); + } else { + log.warn("미가입회원입니다"); + } + } + String contentType = "html"; - if (fileName.length() != 0) { - String[] splitedFileName = fileName.split("\\."); - contentType = splitedFileName[1]; + if (path.length() != 0) { + String[] splitedFileName = path.split("\\."); + if (splitedFileName.length != 1) { + contentType = splitedFileName[1]; + } + if (splitedFileName.length == 1) { + path += ".html"; + } } var responseBody = ""; - if (fileName.length() == 0) { + if (uri.length() == 0) { responseBody = "Hello world!"; } else { - final Path path = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/" + fileName)).getPath()); + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/" + path)).getPath()); - responseBody = new String(Files.readAllBytes(path)); + responseBody = new String(Files.readAllBytes(filePath)); } final var response = String.join("\r\n", @@ -80,7 +115,7 @@ public void process(final Socket connection) { } } - private String extractResourcePathFromRequest(StringBuilder request) { + private String extractRequestUriFromRequest(StringBuilder request) { int startIndex = request.indexOf("GET ") + 4; int endIndex = request.indexOf(" HTTP/1.1"); @@ -90,4 +125,20 @@ private String extractResourcePathFromRequest(StringBuilder request) { return ""; } + + private Map parseQueryParams(String input) { + Map params = new HashMap<>(); + + String[] keyValuePairs = input.split("&"); + for (String pair : keyValuePairs) { + String[] keyValue = pair.split("="); + if (keyValue.length == 2) { + String key = keyValue[0]; + String value = keyValue[1]; + params.put(key, value); + } + } + + return params; + } } From f44f9fb456fd7138bd9bf3ab2ebb27a6f55a532a Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 14:12:25 +0900 Subject: [PATCH 08/35] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20HTTP=20StatusCode=20302?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../org/apache/coyote/http11/Http11Processor.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d0431c3fbb..d87b08973e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - [x] Query String 파싱 ### 2단계 - 로그인 구현하기 -- [ ] HTTP Status Code 302 +- [x] HTTP Status Code 302 - [ ] POST 방식으로 회원가입 - [ ] Cookie에 JSESSIONID 값 저장하기 - [ ] Session 구현하기 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 1a4f51ae08..815c3a5e91 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -58,6 +58,9 @@ public void process(final Socket connection) { String path; String queryString = ""; + int statusCode = 200; + String statusMessage = "OK"; + if (index != -1) { path = uri.substring(0, index); queryString = uri.substring(index + 1); @@ -66,6 +69,9 @@ public void process(final Socket connection) { } if (path.equals("login") && !queryString.equals("")) { + statusCode = 302; + statusMessage = "Found"; + Map queryParams = parseQueryParams(queryString); String account = queryParams.get("account"); String password = queryParams.get("password"); @@ -73,8 +79,12 @@ public void process(final Socket connection) { Optional user = InMemoryUserRepository.findByAccount(account); if (user.isPresent()) { log.info(user.toString()); + + path = "/index.html"; } else { log.warn("미가입회원입니다"); + + path = "/401.html"; } } @@ -101,7 +111,7 @@ public void process(final Socket connection) { } final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", + "HTTP/1.1 " + statusCode + " " + statusMessage + " ", "Content-Type: text/" + contentType + ";charset=utf-8 ", "Content-Length: " + responseBody.getBytes().length + " ", "", From 3f015b7232a488ea325253fb438cb0cb5b406a9c Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 15:05:00 +0900 Subject: [PATCH 09/35] =?UTF-8?q?feat:=20POST=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- .../apache/coyote/http11/Http11Processor.java | 53 +++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d87b08973e..104c364a7b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ ### 2단계 - 로그인 구현하기 - [x] HTTP Status Code 302 -- [ ] POST 방식으로 회원가입 +- [x] POST 방식으로 회원가입 +- [ ] Login 도 POST 로 변경 - [ ] Cookie에 JSESSIONID 값 저장하기 - [ ] Session 구현하기 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 815c3a5e91..e5e1cbd806 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -45,14 +45,25 @@ public void process(final Socket connection) { if (firstLine == null) { return; } - final StringBuilder request = new StringBuilder(firstLine); + final StringBuilder requestHeader = new StringBuilder(firstLine); String line; while (!Objects.equals(line = reader.readLine(), "")) { - request.append(line).append("\r\n"); + requestHeader.append(line).append("\r\n"); } - String uri = extractRequestUriFromRequest(request); + StringBuilder requestBody = new StringBuilder(); + int contentLength = getContentLength(requestHeader.toString()); + + if (contentLength > 0) { + char[] buffer = new char[contentLength]; + int bytesRead = reader.read(buffer, 0, contentLength); + if (bytesRead > 0) { + requestBody.append(buffer, 0, bytesRead); + } + } + + String uri = extractRequestUriFromRequest(requestHeader); int index = uri.indexOf("?"); String path; @@ -68,6 +79,8 @@ public void process(final Socket connection) { path = uri; } + System.out.println("path = " + path); + if (path.equals("login") && !queryString.equals("")) { statusCode = 302; statusMessage = "Found"; @@ -88,6 +101,16 @@ public void process(final Socket connection) { } } + Map requestBodyMap = parseQueryParams(requestBody.toString()); + + if (path.equals("register") && requestHeader.indexOf("POST") != -1) { + String account = requestBodyMap.get("account"); + String password = requestBodyMap.get("password"); + String email = requestBodyMap.get("email"); + + InMemoryUserRepository.save(new User(account, password, email)); + path = "/index.html"; + } String contentType = "html"; if (path.length() != 0) { @@ -126,7 +149,13 @@ public void process(final Socket connection) { } private String extractRequestUriFromRequest(StringBuilder request) { - int startIndex = request.indexOf("GET ") + 4; + int startIndex = 0; + if (request.indexOf("GET") != -1) { + startIndex = request.indexOf("GET ") + 4; + } + if (request.indexOf("POST") != -1) { + startIndex = request.indexOf("POST ") + 5; + } int endIndex = request.indexOf(" HTTP/1.1"); if (startIndex != -1 && endIndex != -1) { @@ -151,4 +180,20 @@ private Map parseQueryParams(String input) { return params; } + + private int getContentLength(String requestHeader) { + String[] headerLines = requestHeader.split("\r\n"); + for (String line : headerLines) { + if (line.startsWith("Content-Length:")) { + String[] parts = line.split(" "); + if (parts.length >= 2) { + try { + return Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + } + } + } + } + return 0; + } } From 2cb2540e9cb0de8c4fd335fd5f38a08289d210be Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 16:23:47 +0900 Subject: [PATCH 10/35] =?UTF-8?q?feat:=20Login=20=EB=8F=84=20POST=20?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- tomcat/src/main/resources/static/login.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 104c364a7b..d46e7095a2 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,6 @@ ### 2단계 - 로그인 구현하기 - [x] HTTP Status Code 302 - [x] POST 방식으로 회원가입 -- [ ] Login 도 POST 로 변경 -- [ ] Cookie에 JSESSIONID 값 저장하기 +- [x] Login 도 POST 로 변경 +- [] Cookie에 JSESSIONID 값 저장하기 - [ ] Session 구현하기 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 2590d48c513b185da39739cd3a36dddf199764c7 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 16:24:20 +0900 Subject: [PATCH 11/35] =?UTF-8?q?feat:=20Cookie=20=EC=97=90=20JSESSIONID?= =?UTF-8?q?=20=EA=B0=92=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../java/org/apache/coyote/HttpCookie.java | 48 +++++++++++++++++++ .../apache/coyote/http11/Http11Processor.java | 47 +++++++++++++----- 3 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/HttpCookie.java diff --git a/README.md b/README.md index d46e7095a2..5014dd79c2 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,5 @@ - [x] HTTP Status Code 302 - [x] POST 방식으로 회원가입 - [x] Login 도 POST 로 변경 -- [] Cookie에 JSESSIONID 값 저장하기 +- [x] Cookie에 JSESSIONID 값 저장하기 - [ ] Session 구현하기 diff --git a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java new file mode 100644 index 0000000000..55ce0f8b33 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java @@ -0,0 +1,48 @@ +package org.apache.coyote; + +import java.util.HashMap; +import java.util.Map; + +public class HttpCookie { + + private final static String BASE_COOKIE = "yummy_cookie=choco; tasty_cookie=strawberry; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46"; + + private final Map cookie; + + public HttpCookie(Map cookie) { + this.cookie = cookie; + } + + public static HttpCookie of() { + return parseCookieString(BASE_COOKIE); + } + + public static HttpCookie parseCookieString(String cookieString) { + Map cookies = new HashMap<>(); + + if (cookieString != null && !cookieString.isEmpty()) { + String[] cookiePairs = cookieString.split("; "); + for (String cookiePair : cookiePairs) { + String[] parts = cookiePair.split("="); + if (parts.length == 2) { + String name = parts[0]; + String value = parts[1]; + cookies.put(name, value); + } + } + } + + return new HttpCookie(cookies); + } + + public boolean existJSESSIONID() { + return cookie.containsKey("JSESSIONID"); + } + + @Override + public String toString() { + return "HttpCookie{" + + "cookie=" + cookie + + '}'; + } +} 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 e5e1cbd806..83445ec2e2 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -3,6 +3,7 @@ import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; +import org.apache.coyote.HttpCookie; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,10 +15,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; public class Http11Processor implements Runnable, Processor { @@ -81,17 +79,22 @@ public void process(final Socket connection) { System.out.println("path = " + path); - if (path.equals("login") && !queryString.equals("")) { + Map requestBodyMap = parseQueryParams(requestBody.toString()); + + String setCookie = ""; + + if (path.equals("login") && requestHeader.indexOf("POST") != -1) { statusCode = 302; statusMessage = "Found"; - Map queryParams = parseQueryParams(queryString); - String account = queryParams.get("account"); - String password = queryParams.get("password"); + String account = requestBodyMap.get("account"); + String password = requestBodyMap.get("password"); Optional user = InMemoryUserRepository.findByAccount(account); if (user.isPresent()) { log.info(user.toString()); + UUID uuid = UUID.randomUUID(); + setCookie = "Set-Cookie: JSESSIONID=" + uuid; path = "/index.html"; } else { @@ -101,8 +104,6 @@ public void process(final Socket connection) { } } - Map requestBodyMap = parseQueryParams(requestBody.toString()); - if (path.equals("register") && requestHeader.indexOf("POST") != -1) { String account = requestBodyMap.get("account"); String password = requestBodyMap.get("password"); @@ -123,6 +124,8 @@ public void process(final Socket connection) { } } + HttpCookie requestCookie = getCookie(requestHeader.toString()); + var responseBody = ""; if (uri.length() == 0) { @@ -133,13 +136,24 @@ public void process(final Socket connection) { responseBody = new String(Files.readAllBytes(filePath)); } - final var response = String.join("\r\n", + + String response = String.join("\r\n", "HTTP/1.1 " + statusCode + " " + statusMessage + " ", "Content-Type: text/" + contentType + ";charset=utf-8 ", "Content-Length: " + responseBody.getBytes().length + " ", "", responseBody); + if (setCookie.length() != 0) { + response = String.join("\r\n", + "HTTP/1.1 " + statusCode + " " + statusMessage + " ", + setCookie, + "Content-Type: text/" + contentType + ";charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + } + outputStream.write(response.getBytes()); outputStream.flush(); @@ -196,4 +210,15 @@ private int getContentLength(String requestHeader) { } return 0; } + + private HttpCookie getCookie(String requestHeader) { + String[] headerLines = requestHeader.split("\r\n"); + for (String line : headerLines) { + if (line.startsWith("Cookie:")) { + String[] parts = line.split(" "); + return HttpCookie.parseCookieString(parts[1]); + } + } + return null; + } } From 5fe897ec7806f44b3094b3113687e3cc1078a7bf Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 16:39:25 +0900 Subject: [PATCH 12/35] =?UTF-8?q?feat:=20Session=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EA=B0=9D=EC=B2=B4=EB=93=A4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/catalina/Session.java | 34 +++++++++++++++++++ .../catalina/manager/SessionManager.java | 26 ++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/catalina/Session.java create mode 100644 tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java diff --git a/tomcat/src/main/java/org/apache/catalina/Session.java b/tomcat/src/main/java/org/apache/catalina/Session.java new file mode 100644 index 0000000000..b0d2d62e3e --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/Session.java @@ -0,0 +1,34 @@ +package org.apache.catalina; + +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 Object getAttribute(final String name) { + return values.get(name); + } + + public void setAttribute(final String name, final Object value) { + values.put(name, value); + } + + public void removeAttribute(final String name) { + values.remove(name); + } + + public void invalidate() { + values.clear(); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java new file mode 100644 index 0000000000..dfe6f1e2f8 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -0,0 +1,26 @@ +package org.apache.catalina.manager; + +import org.apache.catalina.Session; + +import java.util.HashMap; +import java.util.Map; + +public class SessionManager { + + private static final Map SESSIONS = new HashMap<>(); + + public void add(final Session session) { + SESSIONS.put(session.getId(), session); + } + + public Session findSession(final String id) { + return SESSIONS.get(id); + } + + public void remove(final String id) { + SESSIONS.remove(id); + } + + private SessionManager() { + } +} From 54de2b344eebaa12dc6be45764981cdda227f591 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 20:52:07 +0900 Subject: [PATCH 13/35] =?UTF-8?q?feat:=20Session=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++-- .../catalina/manager/SessionManager.java | 10 +++--- .../java/org/apache/coyote/HttpCookie.java | 4 +++ .../apache/coyote/http11/Http11Processor.java | 31 +++++++++++++++++-- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5014dd79c2..9d01d7c0d1 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,15 @@ ## CheckList ### 1단계 - HTTP 서버 구현하기 + - [x] GET /index.html 응답하기 - [x] CSS 지원하기 - [x] Query String 파싱 -### 2단계 - 로그인 구현하기 +### 2단계 - 로그인 구현하기 + - [x] HTTP Status Code 302 - [x] POST 방식으로 회원가입 - [x] Login 도 POST 로 변경 - [x] Cookie에 JSESSIONID 값 저장하기 -- [ ] Session 구현하기 +- [x] Session 구현하기 diff --git a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java index dfe6f1e2f8..62b99a32a2 100644 --- a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -9,18 +9,18 @@ public class SessionManager { private static final Map SESSIONS = new HashMap<>(); - public void add(final Session session) { + private SessionManager() { + } + + public static void add(final Session session) { SESSIONS.put(session.getId(), session); } - public Session findSession(final String id) { + public static Session findSession(final String id) { return SESSIONS.get(id); } public void remove(final String id) { SESSIONS.remove(id); } - - private SessionManager() { - } } diff --git a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java index 55ce0f8b33..bad4e36640 100644 --- a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java @@ -39,6 +39,10 @@ public boolean existJSESSIONID() { return cookie.containsKey("JSESSIONID"); } + public String getJSessionId() { + return cookie.get("JSESSIONID"); + } + @Override public String toString() { return "HttpCookie{" + 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 83445ec2e2..1a9291bc5c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -3,6 +3,8 @@ import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; +import org.apache.catalina.Session; +import org.apache.catalina.manager.SessionManager; import org.apache.coyote.HttpCookie; import org.apache.coyote.Processor; import org.slf4j.Logger; @@ -77,12 +79,33 @@ public void process(final Socket connection) { path = uri; } - System.out.println("path = " + path); - Map requestBodyMap = parseQueryParams(requestBody.toString()); String setCookie = ""; + if (path.equals("login") && requestHeader.indexOf("GET") != -1) { + statusCode = 302; + statusMessage = "Found"; + + HttpCookie cookie = getCookie(requestHeader.toString()); + + if (cookie == null || !cookie.existJSESSIONID()) { + path = "/login.html"; + } else { + String jSessionId = cookie.getJSessionId(); + Session session = SessionManager.findSession(jSessionId); + + Optional user = (Optional) session.getAttribute("user"); + + if (user.isPresent()) { + path = "/index.html"; + } else { + path = "/login.html"; + } + } + } + + if (path.equals("login") && requestHeader.indexOf("POST") != -1) { statusCode = 302; statusMessage = "Found"; @@ -94,6 +117,10 @@ public void process(final Socket connection) { if (user.isPresent()) { log.info(user.toString()); UUID uuid = UUID.randomUUID(); + Session session = new Session(uuid.toString()); + session.setAttribute("user", user); + SessionManager.add(session); + setCookie = "Set-Cookie: JSESSIONID=" + uuid; path = "/index.html"; From a09895797374332149bdec86854b4a67a0fdae6a Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 21:56:36 +0900 Subject: [PATCH 14/35] =?UTF-8?q?refactor:=20session=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/catalina/manager/SessionManager.java | 2 +- .../src/main/java/org/apache/{catalina => coyote}/Session.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename tomcat/src/main/java/org/apache/{catalina => coyote}/Session.java (95%) diff --git a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java index 62b99a32a2..e98c02440f 100644 --- a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -1,6 +1,6 @@ package org.apache.catalina.manager; -import org.apache.catalina.Session; +import org.apache.coyote.Session; import java.util.HashMap; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/catalina/Session.java b/tomcat/src/main/java/org/apache/coyote/Session.java similarity index 95% rename from tomcat/src/main/java/org/apache/catalina/Session.java rename to tomcat/src/main/java/org/apache/coyote/Session.java index b0d2d62e3e..89cdd01dd7 100644 --- a/tomcat/src/main/java/org/apache/catalina/Session.java +++ b/tomcat/src/main/java/org/apache/coyote/Session.java @@ -1,4 +1,4 @@ -package org.apache.catalina; +package org.apache.coyote; import java.util.HashMap; import java.util.Map; From 6760207ddca90ee17ca1b09c58d807086c125203 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 22:14:57 +0900 Subject: [PATCH 15/35] =?UTF-8?q?refactor:=20HttpRequest=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 81 +++++++------------ .../coyote/request/HttpRequestBody.java | 34 ++++++++ .../coyote/request/HttpRequestHeader.java | 43 ++++++++++ .../coyote/request/HttpRequestLine.java | 36 +++++++++ 4 files changed, 143 insertions(+), 51 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java create mode 100644 tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 1a9291bc5c..45a7c356d0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -3,10 +3,13 @@ import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; -import org.apache.catalina.Session; import org.apache.catalina.manager.SessionManager; import org.apache.coyote.HttpCookie; import org.apache.coyote.Processor; +import org.apache.coyote.Session; +import org.apache.coyote.request.HttpRequestBody; +import org.apache.coyote.request.HttpRequestHeader; +import org.apache.coyote.request.HttpRequestLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +20,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; public class Http11Processor implements Runnable, Processor { @@ -39,22 +44,28 @@ public void run() { public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - String firstLine = reader.readLine(); - if (firstLine == null) { + String requestLine = reader.readLine(); + if (requestLine == null) { return; } - final StringBuilder requestHeader = new StringBuilder(firstLine); + + HttpRequestLine httpRequestLine = HttpRequestLine.from(requestLine); + + final StringBuilder requestHeader = new StringBuilder(); String line; while (!Objects.equals(line = reader.readLine(), "")) { requestHeader.append(line).append("\r\n"); } - StringBuilder requestBody = new StringBuilder(); - int contentLength = getContentLength(requestHeader.toString()); + HttpRequestHeader httpRequestHeader = HttpRequestHeader.from(requestHeader.toString()); + + int contentLength = httpRequestHeader.getContentLength(); + + StringBuilder requestBody = new StringBuilder(); if (contentLength > 0) { char[] buffer = new char[contentLength]; int bytesRead = reader.read(buffer, 0, contentLength); @@ -63,7 +74,9 @@ public void process(final Socket connection) { } } - String uri = extractRequestUriFromRequest(requestHeader); + HttpRequestBody httpRequestBody = HttpRequestBody.from(requestBody.toString()); + + String uri = extractRequestUriFromRequest(requestLine); int index = uri.indexOf("?"); String path; @@ -79,11 +92,9 @@ public void process(final Socket connection) { path = uri; } - Map requestBodyMap = parseQueryParams(requestBody.toString()); - String setCookie = ""; - if (path.equals("login") && requestHeader.indexOf("GET") != -1) { + if (path.equals("login") && httpRequestLine.isGetMethod()) { statusCode = 302; statusMessage = "Found"; @@ -106,12 +117,12 @@ public void process(final Socket connection) { } - if (path.equals("login") && requestHeader.indexOf("POST") != -1) { + if (path.equals("login") && httpRequestLine.isPostMethod()) { statusCode = 302; statusMessage = "Found"; - String account = requestBodyMap.get("account"); - String password = requestBodyMap.get("password"); + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); Optional user = InMemoryUserRepository.findByAccount(account); if (user.isPresent()) { @@ -132,9 +143,9 @@ public void process(final Socket connection) { } if (path.equals("register") && requestHeader.indexOf("POST") != -1) { - String account = requestBodyMap.get("account"); - String password = requestBodyMap.get("password"); - String email = requestBodyMap.get("email"); + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); + String email = httpRequestBody.getValue("email"); InMemoryUserRepository.save(new User(account, password, email)); path = "/index.html"; @@ -189,7 +200,7 @@ public void process(final Socket connection) { } } - private String extractRequestUriFromRequest(StringBuilder request) { + private String extractRequestUriFromRequest(String request) { int startIndex = 0; if (request.indexOf("GET") != -1) { startIndex = request.indexOf("GET ") + 4; @@ -206,38 +217,6 @@ private String extractRequestUriFromRequest(StringBuilder request) { return ""; } - private Map parseQueryParams(String input) { - Map params = new HashMap<>(); - - String[] keyValuePairs = input.split("&"); - for (String pair : keyValuePairs) { - String[] keyValue = pair.split("="); - if (keyValue.length == 2) { - String key = keyValue[0]; - String value = keyValue[1]; - params.put(key, value); - } - } - - return params; - } - - private int getContentLength(String requestHeader) { - String[] headerLines = requestHeader.split("\r\n"); - for (String line : headerLines) { - if (line.startsWith("Content-Length:")) { - String[] parts = line.split(" "); - if (parts.length >= 2) { - try { - return Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - } - } - } - } - return 0; - } - private HttpCookie getCookie(String requestHeader) { String[] headerLines = requestHeader.split("\r\n"); for (String line : headerLines) { diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java new file mode 100644 index 0000000000..0e43892888 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java @@ -0,0 +1,34 @@ +package org.apache.coyote.request; + +import java.util.HashMap; +import java.util.Map; + +public class HttpRequestBody { + + public static final String BODY_SEPERATOR = "="; + private static final String SEPERATOR = "&"; + private final Map body; + + private HttpRequestBody(Map body) { + this.body = body; + } + + public static HttpRequestBody from(String requestBody) { + Map body = new HashMap<>(); + + String[] keyValuePairs = requestBody.split("&"); + for (String pair : keyValuePairs) { + String[] keyValue = pair.split(BODY_SEPERATOR); + if (keyValue.length == 2) { + String key = keyValue[0]; + String value = keyValue[1]; + body.put(key, value); + } + } + return new HttpRequestBody(body); + } + + public String getValue(String name) { + return body.get(name); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java new file mode 100644 index 0000000000..e15a7ef82b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java @@ -0,0 +1,43 @@ +package org.apache.coyote.request; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HttpRequestHeader { + + private static final String SEPERATOR = "\r\n"; + private static final String HEADER_SEPERATOR = ": "; + + private final Map headers; + + private HttpRequestHeader(Map headers) { + this.headers = headers; + } + + public static HttpRequestHeader from(String requestHeader) { + Map headers = new HashMap<>(); + String[] splitedLines = requestHeader.split(SEPERATOR); + + List splited = Arrays.asList(splitedLines); + for (String line : splited) { + String[] parts = line.split(HEADER_SEPERATOR); + if (parts.length == 2) { + String key = parts[0].trim(); + String value = parts[1].trim(); + headers.put(key, value); + } + } + return new HttpRequestHeader(headers); + } + + public int getContentLength() { + String contentLength = headers.get("Content-Length"); + try { + return Integer.parseInt(contentLength); + } catch (NumberFormatException e) { + } + return 0; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java new file mode 100644 index 0000000000..484a9b113f --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java @@ -0,0 +1,36 @@ +package org.apache.coyote.request; + +public class HttpRequestLine { + + private static final String SEPERATOR = " "; + private final String method; + private final String requestUri; + private final String version; + + private HttpRequestLine(String method, String requestUri, String version) { + this.method = method; + this.requestUri = requestUri; + this.version = version; + } + + public static HttpRequestLine from(String requestLine) { + String[] splitedLines = requestLine.split(SEPERATOR); + return new HttpRequestLine(splitedLines[0], splitedLines[1], splitedLines[2]); + } + + public String getMethod() { + return method; + } + + public String getRequestUri() { + return requestUri; + } + + public boolean isGetMethod() { + return method.equals("GET"); + } + + public boolean isPostMethod() { + return method.equals("POST"); + } +} From ea5aa94f572b53ab77c0019936ae39091952f412 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 23:17:53 +0900 Subject: [PATCH 16/35] =?UTF-8?q?refactor:=20HttpResponse=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 31 +++++++------------ .../coyote/request/HttpRequestHeader.java | 7 +++++ .../apache/coyote/response/HttpResponse.java | 18 +++++++++++ .../coyote/response/HttpResponseHeader.java | 28 +++++++++++++++++ .../apache/coyote/response/StatusLine.java | 25 +++++++++++++++ 5 files changed, 89 insertions(+), 20 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java create mode 100644 tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/response/StatusLine.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 45a7c356d0..bd09a0a36e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -10,6 +10,9 @@ import org.apache.coyote.request.HttpRequestBody; import org.apache.coyote.request.HttpRequestHeader; import org.apache.coyote.request.HttpRequestLine; +import org.apache.coyote.response.HttpResponse; +import org.apache.coyote.response.HttpResponseHeader; +import org.apache.coyote.response.StatusLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +23,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -98,7 +102,8 @@ public void process(final Socket connection) { statusCode = 302; statusMessage = "Found"; - HttpCookie cookie = getCookie(requestHeader.toString()); + HttpCookie cookie = httpRequestHeader.getCookie(); + if (cookie == null || !cookie.existJSESSIONID()) { path = "/login.html"; @@ -162,8 +167,6 @@ public void process(final Socket connection) { } } - HttpCookie requestCookie = getCookie(requestHeader.toString()); - var responseBody = ""; if (uri.length() == 0) { @@ -174,13 +177,12 @@ public void process(final Socket connection) { responseBody = new String(Files.readAllBytes(filePath)); } + StatusLine statusLine = StatusLine.from(statusCode, statusMessage); + HttpResponseHeader responseHeader = new HttpResponseHeader(new HashMap<>()); + responseHeader.add("Content-Type", "text/" + contentType + ";charset=utf-8"); + responseHeader.add("Content-Length", responseBody.getBytes().length); - String response = String.join("\r\n", - "HTTP/1.1 " + statusCode + " " + statusMessage + " ", - "Content-Type: text/" + contentType + ";charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); + String response = new HttpResponse(statusLine, responseHeader, responseBody).getResponse(); if (setCookie.length() != 0) { response = String.join("\r\n", @@ -216,15 +218,4 @@ private String extractRequestUriFromRequest(String request) { return ""; } - - private HttpCookie getCookie(String requestHeader) { - String[] headerLines = requestHeader.split("\r\n"); - for (String line : headerLines) { - if (line.startsWith("Cookie:")) { - String[] parts = line.split(" "); - return HttpCookie.parseCookieString(parts[1]); - } - } - return null; - } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java index e15a7ef82b..ff34fa714c 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java @@ -1,5 +1,7 @@ package org.apache.coyote.request; +import org.apache.coyote.HttpCookie; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -40,4 +42,9 @@ public int getContentLength() { } return 0; } + + public HttpCookie getCookie() { + String cookie = headers.get("Cookie"); + return HttpCookie.parseCookieString(cookie); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java new file mode 100644 index 0000000000..75ae27541c --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -0,0 +1,18 @@ +package org.apache.coyote.response; + +public class HttpResponse { + + private final StatusLine statusLine; + private final HttpResponseHeader headers; + private final String body; + + public HttpResponse(StatusLine statusLine, HttpResponseHeader headers, String body) { + this.statusLine = statusLine; + this.headers = headers; + this.body = body; + } + + public String getResponse() { + return statusLine.getStatusLine() + "\r\n" + headers.getResponseHeader() + "\r\n" + body; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java new file mode 100644 index 0000000000..f0130cdb02 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java @@ -0,0 +1,28 @@ +package org.apache.coyote.response; + +import java.util.Map; + +public class HttpResponseHeader { + + private final Map headers; + + public HttpResponseHeader(Map headers) { + this.headers = headers; + } + + public void add(String key, Object value) { + headers.put(key, value); + } + + public String getResponseHeader() { + StringBuilder headerBuilder = new StringBuilder(); + + for (Map.Entry entry : headers.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + headerBuilder.append(key).append(": ").append(value).append(" \r\n"); + } + return headerBuilder.toString(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java b/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java new file mode 100644 index 0000000000..494db71db8 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java @@ -0,0 +1,25 @@ +package org.apache.coyote.response; + +public class StatusLine { + + private static final String SEPERATOR = " "; + private static final String BASIC_HTTP_VERSION = "HTTP/1.1"; + + private final String version; + private final int statusCode; + private final String statusMessage; + + private StatusLine(String version, int statusCode, String statusMessage) { + this.version = version; + this.statusCode = statusCode; + this.statusMessage = statusMessage; + } + + public static StatusLine from(int statusCode, String statusMessage) { + return new StatusLine(BASIC_HTTP_VERSION, statusCode, statusMessage); + } + + public String getStatusLine() { + return BASIC_HTTP_VERSION + SEPERATOR + statusCode + SEPERATOR + statusMessage + SEPERATOR; + } +} From 79228ee427680463e76f1c68b4375f003c902c35 Mon Sep 17 00:00:00 2001 From: yenawee Date: Mon, 4 Sep 2023 23:24:49 +0900 Subject: [PATCH 17/35] =?UTF-8?q?fix:=20test=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index bd09a0a36e..f5c7595453 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -178,7 +178,7 @@ public void process(final Socket connection) { } StatusLine statusLine = StatusLine.from(statusCode, statusMessage); - HttpResponseHeader responseHeader = new HttpResponseHeader(new HashMap<>()); + HttpResponseHeader responseHeader = new HttpResponseHeader(new LinkedHashMap<>()); responseHeader.add("Content-Type", "text/" + contentType + ";charset=utf-8"); responseHeader.add("Content-Length", responseBody.getBytes().length); From d92c8e515c1a203599bf76eb8baecd04a9d9fb11 Mon Sep 17 00:00:00 2001 From: yenawee Date: Tue, 5 Sep 2023 15:56:59 +0900 Subject: [PATCH 18/35] =?UTF-8?q?feat:=20cache=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/cachecontrol/CacheWebConfig.java | 8 ++++++++ .../example/etag/EtagFilterConfiguration.java | 17 +++++++++++++---- .../example/version/CacheBustingWebConfig.java | 8 +++++++- .../version/VersionHandlebarsHelper.java | 4 +++- study/src/main/resources/application.yml | 3 +++ 5 files changed, 34 insertions(+), 6 deletions(-) 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..b29cdb1aec 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,21 @@ package cache.com.example.cachecontrol; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.WebContentInterceptor; @Configuration public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + CacheControl cacheControl = CacheControl.noCache().cachePrivate(); + + WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); + webContentInterceptor.addCacheMapping(cacheControl, "/**"); + + registry.addInterceptor(webContentInterceptor); } } 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..1495474449 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,21 @@ package cache.com.example.etag; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ShallowEtagHeaderFilter; + +import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES; @Configuration public class EtagFilterConfiguration { -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + FilterRegistrationBean filterFilterRegistrationBean = + new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); + + filterFilterRegistrationBean.addUrlPatterns("/etag", PREFIX_STATIC_RESOURCES + "/*"); + return filterFilterRegistrationBean; + } } 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..377533c316 100644 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java @@ -2,9 +2,12 @@ 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; +import java.time.Duration; + @Configuration public class CacheBustingWebConfig implements WebMvcConfigurer { @@ -20,6 +23,9 @@ public CacheBustingWebConfig(ResourceVersion version) { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .setUseLastModified(true) + .addResourceLocations("classpath:/static/") + .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic()); + ; } } diff --git a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java b/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java index a8e004466a..cec312374e 100644 --- a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java +++ b/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java @@ -6,6 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired; import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper; +import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES; + @HandlebarsHelper public class VersionHandlebarsHelper { @@ -20,6 +22,6 @@ public VersionHandlebarsHelper(ResourceVersion version) { public String staticUrls(String path, Options options) { log.debug("static url : {}", path); - return String.format("/resources/%s%s", version.getVersion(), path); + return String.format(PREFIX_STATIC_RESOURCES + "/%s%s", version.getVersion(), path); } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..385c11d5f1 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -7,3 +7,6 @@ server: max-connections: 1 threads: max: 2 + compression: + enabled: true + min-response-size: 10 From ceb3a9c71c648738b4eea31805af82ee8d593a81 Mon Sep 17 00:00:00 2001 From: yenawee Date: Fri, 8 Sep 2023 16:54:49 +0900 Subject: [PATCH 19/35] =?UTF-8?q?feat:=20Thread=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=ED=85=8C=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/thread/stage0/SynchronizationTest.java | 2 +- study/src/test/java/thread/stage0/ThreadPoolsTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java index 0333c18e3b..b463c2b984 100644 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ b/study/src/test/java/thread/stage0/SynchronizationTest.java @@ -41,7 +41,7 @@ private static final class SynchronizedMethods { private int sum = 0; - public void calculate() { + public synchronized void calculate() { setSum(getSum() + 1); } diff --git a/study/src/test/java/thread/stage0/ThreadPoolsTest.java b/study/src/test/java/thread/stage0/ThreadPoolsTest.java index 238611ebfe..03efdabc8d 100644 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ b/study/src/test/java/thread/stage0/ThreadPoolsTest.java @@ -31,8 +31,8 @@ void testNewFixedThreadPool() { executor.submit(logWithSleep("hello fixed thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; - final int expectedQueueSize = 0; + final int expectedPoolSize = 2; + final int expectedQueueSize = 1; assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size()); @@ -46,7 +46,7 @@ void testNewCachedThreadPool() { executor.submit(logWithSleep("hello cached thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; + final int expectedPoolSize = 3; final int expectedQueueSize = 0; assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); From e01ff4f35aa16811b2a23b16d28d3d7c539c2eb2 Mon Sep 17 00:00:00 2001 From: yenawee Date: Fri, 8 Sep 2023 22:33:28 +0900 Subject: [PATCH 20/35] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/Http11Processor.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index f5c7595453..8ac7296a3d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -82,6 +82,7 @@ public void process(final Socket connection) { String uri = extractRequestUriFromRequest(requestLine); + // requestUri 에서 path 와 queryString 분리 int index = uri.indexOf("?"); String path; String queryString = ""; @@ -98,6 +99,7 @@ public void process(final Socket connection) { String setCookie = ""; + // path 가 login 일때 분기 if (path.equals("login") && httpRequestLine.isGetMethod()) { statusCode = 302; statusMessage = "Found"; @@ -147,15 +149,20 @@ public void process(final Socket connection) { } } - if (path.equals("register") && requestHeader.indexOf("POST") != -1) { + // pass 가 register 일때 분기 + if (path.equals("register") && httpRequestLine.isPostMethod()) { String account = httpRequestBody.getValue("account"); String password = httpRequestBody.getValue("password"); String email = httpRequestBody.getValue("email"); InMemoryUserRepository.save(new User(account, password, email)); + + statusCode = 302; + statusMessage = "Found"; path = "/index.html"; } + // content-Type 처리 String contentType = "html"; if (path.length() != 0) { String[] splitedFileName = path.split("\\."); @@ -169,6 +176,7 @@ public void process(final Socket connection) { var responseBody = ""; + // / 일때 분기 if (uri.length() == 0) { responseBody = "Hello world!"; } else { From 7ae92a8d1c572e8bc531773c98c703dedaea9946 Mon Sep 17 00:00:00 2001 From: yenawee Date: Fri, 8 Sep 2023 23:52:54 +0900 Subject: [PATCH 21/35] =?UTF-8?q?fix:=20ContentType=20enum=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/ContentType.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/ContentType.java diff --git a/tomcat/src/main/java/org/apache/coyote/ContentType.java b/tomcat/src/main/java/org/apache/coyote/ContentType.java new file mode 100644 index 0000000000..1b47d4ec83 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/ContentType.java @@ -0,0 +1,29 @@ +package org.apache.coyote; + +import java.util.Arrays; + +public enum ContentType { + HTML("html", "text/html"), + CSS("css", "text/css"), + JAVASCRIPT("js", "text/javascript"), + FAVICON("ico", "image/x-icon"); + + private final String extension; + private final String type; + + ContentType(String extension, String type) { + this.extension = extension; + this.type = type; + } + + public static ContentType from(String extension) { + return Arrays.stream(values()) + .filter(it -> it.getExtension().equals(extension)) + .findFirst() + .orElse(HTML); + } + + public String getExtension() { + return extension; + } +} From 4cb4253b5371bad831c15980a28e59de18aa6d4a Mon Sep 17 00:00:00 2001 From: yenawee Date: Sat, 9 Sep 2023 15:58:49 +0900 Subject: [PATCH 22/35] =?UTF-8?q?feat:=20HttpResponse=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/ContentType.java | 4 ++ .../java/org/apache/coyote/HttpCookie.java | 8 +++ .../apache/coyote/response/HttpResponse.java | 58 +++++++++++++++++-- .../coyote/response/HttpResponseHeader.java | 8 +-- .../apache/coyote/response/HttpStatus.java | 18 ++++++ .../apache/coyote/response/StatusLine.java | 15 ++--- 6 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java diff --git a/tomcat/src/main/java/org/apache/coyote/ContentType.java b/tomcat/src/main/java/org/apache/coyote/ContentType.java index 1b47d4ec83..4b114dc3f5 100644 --- a/tomcat/src/main/java/org/apache/coyote/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/ContentType.java @@ -26,4 +26,8 @@ public static ContentType from(String extension) { public String getExtension() { return extension; } + + public String getType() { + return type; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java index bad4e36640..9c89be609a 100644 --- a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; public class HttpCookie { @@ -43,6 +44,13 @@ public String getJSessionId() { return cookie.get("JSESSIONID"); } + public String convertToHeader() { + return cookie.entrySet() + .stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining("; ")); + } + @Override public String toString() { return "HttpCookie{" + diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 75ae27541c..f7ef186ea2 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -1,18 +1,68 @@ package org.apache.coyote.response; +import org.apache.coyote.ContentType; +import org.apache.coyote.HttpCookie; + +import java.util.LinkedHashMap; +import java.util.Map; + public class HttpResponse { private final StatusLine statusLine; private final HttpResponseHeader headers; private final String body; - public HttpResponse(StatusLine statusLine, HttpResponseHeader headers, String body) { - this.statusLine = statusLine; - this.headers = headers; - this.body = body; + private HttpResponse(Builder builder) { + this.statusLine = new StatusLine(builder.version, builder.httpStatus); + this.headers = new HttpResponseHeader(builder.headers); + this.body = builder.body; } public String getResponse() { return statusLine.getStatusLine() + "\r\n" + headers.getResponseHeader() + "\r\n" + body; } + + public static class Builder { + private final String version; + private final Map headers; + private HttpStatus httpStatus; + private String body; + + public Builder() { + this.version = "HTTP/1.1"; + this.httpStatus = HttpStatus.OK; + this.headers = new LinkedHashMap<>(); + this.body = ""; + } + + public Builder header(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder status(HttpStatus httpStatus) { + this.httpStatus = httpStatus; + return this; + } + + public Builder contentType(ContentType contentType) { + this.headers.put("Content-Type", contentType.getType() + ";charset=utf-8"); + return this; + } + + public Builder setCookie(HttpCookie cookie) { + this.headers.put("Set-Cookie", cookie.convertToHeader()); + return this; + } + + public Builder body(String body) { + this.headers.put("Content-Length", String.valueOf(body.getBytes().length)); + this.body = body; + return this; + } + + public HttpResponse build() { + return new HttpResponse(this); + } + } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java index f0130cdb02..75742181d2 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponseHeader.java @@ -4,20 +4,20 @@ public class HttpResponseHeader { - private final Map headers; + private final Map headers; - public HttpResponseHeader(Map headers) { + public HttpResponseHeader(Map headers) { this.headers = headers; } - public void add(String key, Object value) { + public void add(String key, String value) { headers.put(key, value); } public String getResponseHeader() { StringBuilder headerBuilder = new StringBuilder(); - for (Map.Entry entry : headers.entrySet()) { + for (Map.Entry entry : headers.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java new file mode 100644 index 0000000000..38ab9c26e1 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java @@ -0,0 +1,18 @@ +package org.apache.coyote.response; + +public enum HttpStatus { + + OK(200), + FOUND(302), + UNAUTHORIZED(401); + + private final int code; + + HttpStatus(int code) { + this.code = code; + } + + public String getHttpStatus() { + return code + " " + name(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java b/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java index 494db71db8..dfa2528ca0 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java +++ b/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java @@ -3,23 +3,16 @@ public class StatusLine { private static final String SEPERATOR = " "; - private static final String BASIC_HTTP_VERSION = "HTTP/1.1"; private final String version; - private final int statusCode; - private final String statusMessage; + private final HttpStatus httpStatus; - private StatusLine(String version, int statusCode, String statusMessage) { + public StatusLine(String version, HttpStatus httpStatus) { this.version = version; - this.statusCode = statusCode; - this.statusMessage = statusMessage; - } - - public static StatusLine from(int statusCode, String statusMessage) { - return new StatusLine(BASIC_HTTP_VERSION, statusCode, statusMessage); + this.httpStatus = httpStatus; } public String getStatusLine() { - return BASIC_HTTP_VERSION + SEPERATOR + statusCode + SEPERATOR + statusMessage + SEPERATOR; + return version + SEPERATOR + httpStatus.getHttpStatus() + SEPERATOR; } } From d11169cce6ef7866f17a61152c6ae001ba17a7f8 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sat, 9 Sep 2023 16:26:05 +0900 Subject: [PATCH 23/35] =?UTF-8?q?refactor=20:=20/=20=EC=9D=BC=20=EB=95=8C?= =?UTF-8?q?=20hello=20world=20=EB=B0=98=ED=99=98=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 299 ++++++++---------- .../apache/coyote/request/HttpRequest.java | 18 ++ .../coyote/request/HttpRequestLine.java | 12 +- .../org/apache/coyote/request/RequestUri.java | 69 ++++ 4 files changed, 220 insertions(+), 178 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/request/RequestUri.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 8ac7296a3d..721235559f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,18 +1,10 @@ package org.apache.coyote.http11; -import nextstep.jwp.db.InMemoryUserRepository; + import nextstep.jwp.exception.UncheckedServletException; -import nextstep.jwp.model.User; -import org.apache.catalina.manager.SessionManager; -import org.apache.coyote.HttpCookie; import org.apache.coyote.Processor; -import org.apache.coyote.Session; -import org.apache.coyote.request.HttpRequestBody; -import org.apache.coyote.request.HttpRequestHeader; -import org.apache.coyote.request.HttpRequestLine; +import org.apache.coyote.request.*; import org.apache.coyote.response.HttpResponse; -import org.apache.coyote.response.HttpResponseHeader; -import org.apache.coyote.response.StatusLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,12 +13,9 @@ import java.io.InputStreamReader; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.LinkedHashMap; import java.util.Objects; -import java.util.Optional; -import java.util.UUID; + +import static org.apache.coyote.ContentType.HTML; public class Http11Processor implements Runnable, Processor { @@ -50,180 +39,148 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream(); final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - String requestLine = reader.readLine(); - if (requestLine == null) { - return; - } - - HttpRequestLine httpRequestLine = HttpRequestLine.from(requestLine); - - final StringBuilder requestHeader = new StringBuilder(); - - String line; - while (!Objects.equals(line = reader.readLine(), "")) { - requestHeader.append(line).append("\r\n"); - } - - HttpRequestHeader httpRequestHeader = HttpRequestHeader.from(requestHeader.toString()); - - - int contentLength = httpRequestHeader.getContentLength(); - - StringBuilder requestBody = new StringBuilder(); - if (contentLength > 0) { - char[] buffer = new char[contentLength]; - int bytesRead = reader.read(buffer, 0, contentLength); - if (bytesRead > 0) { - requestBody.append(buffer, 0, bytesRead); - } - } - - HttpRequestBody httpRequestBody = HttpRequestBody.from(requestBody.toString()); - - String uri = extractRequestUriFromRequest(requestLine); - - // requestUri 에서 path 와 queryString 분리 - int index = uri.indexOf("?"); - String path; - String queryString = ""; - - int statusCode = 200; - String statusMessage = "OK"; - - if (index != -1) { - path = uri.substring(0, index); - queryString = uri.substring(index + 1); - } else { - path = uri; - } - - String setCookie = ""; - - // path 가 login 일때 분기 - if (path.equals("login") && httpRequestLine.isGetMethod()) { - statusCode = 302; - statusMessage = "Found"; - - HttpCookie cookie = httpRequestHeader.getCookie(); - - - if (cookie == null || !cookie.existJSESSIONID()) { - path = "/login.html"; - } else { - String jSessionId = cookie.getJSessionId(); - Session session = SessionManager.findSession(jSessionId); - - Optional user = (Optional) session.getAttribute("user"); + HttpRequest httpRequest = generateHttpRequest(reader); - if (user.isPresent()) { - path = "/index.html"; - } else { - path = "/login.html"; - } - } - } - - - if (path.equals("login") && httpRequestLine.isPostMethod()) { - statusCode = 302; - statusMessage = "Found"; + RequestUri requestUri = httpRequest.getRequestLine().getRequestUri(); - String account = httpRequestBody.getValue("account"); - String password = httpRequestBody.getValue("password"); + String path = requestUri.getPath(); - Optional user = InMemoryUserRepository.findByAccount(account); - if (user.isPresent()) { - log.info(user.toString()); - UUID uuid = UUID.randomUUID(); - Session session = new Session(uuid.toString()); - session.setAttribute("user", user); - SessionManager.add(session); + HttpResponse response = null; - setCookie = "Set-Cookie: JSESSIONID=" + uuid; - - path = "/index.html"; - } else { - log.warn("미가입회원입니다"); - - path = "/401.html"; - } + // / 일때 분기 + if (path.equals("/")) { + response = new HttpResponse.Builder() + .contentType(HTML) + .body("hello world") + .build(); } - // pass 가 register 일때 분기 - if (path.equals("register") && httpRequestLine.isPostMethod()) { - String account = httpRequestBody.getValue("account"); - String password = httpRequestBody.getValue("password"); - String email = httpRequestBody.getValue("email"); - - InMemoryUserRepository.save(new User(account, password, email)); - - statusCode = 302; - statusMessage = "Found"; - path = "/index.html"; - } +// // path 가 index 일때 분기 +// if (path.equals("/index")) { +// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); +// responseBody = new String(Files.readAllBytes(filePath)); +// } +// +// // path 가 login 일때 분기 +// if (path.equals("/login") && httpRequestLine.isGetMethod()) { +// statusCode = 302; +// statusMessage = "Found"; +// +// HttpCookie cookie = httpRequestHeader.getCookie(); +// +// if (cookie == null || !cookie.existJSESSIONID()) { +// path = "/login.html"; +// } else { +// String jSessionId = cookie.getJSessionId(); +// Session session = SessionManager.findSession(jSessionId); +// +// Optional user = (Optional) session.getAttribute("user"); +// +// if (user.isPresent()) { +// path = "/index.html"; +// } else { +// path = "/login.html"; +// } +// } +// +// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); +// responseBody = new String(Files.readAllBytes(filePath)); +// } +// +// +// if (path.equals("/login") && httpRequestLine.isPostMethod()) { +// statusCode = 302; +// statusMessage = "Found"; +// +// String account = httpRequestBody.getValue("account"); +// String password = httpRequestBody.getValue("password"); +// +// Optional user = InMemoryUserRepository.findByAccount(account); +// if (user.isPresent() && user.get().checkPassword(password)) { +// log.info(user.toString()); +// UUID uuid = UUID.randomUUID(); +// Session session = new Session(uuid.toString()); +// session.setAttribute("user", user); +// SessionManager.add(session); +// +// setCookie = "Set-Cookie: JSESSIONID=" + uuid; +// +// path = "/index.html"; +// } else if (user.isPresent()) { +// log.warn("비밀번호가 틀렸습니다"); +// +// path = "/401.html"; +// +// } else { +// log.warn("미가입회원입니다"); +// +// path = "/401.html"; +// } +// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); +// +// responseBody = new String(Files.readAllBytes(filePath)); +// } +// +// // pass 가 register 일때 분기 +// if (path.equals("/register") && httpRequestLine.isPostMethod()) { +// String account = httpRequestBody.getValue("account"); +// String password = httpRequestBody.getValue("password"); +// String email = httpRequestBody.getValue("email"); +// +// InMemoryUserRepository.save(new User(account, password, email)); +// +// statusCode = 302; +// statusMessage = "Found"; +// path = "/index.html"; +// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); +// +// responseBody = new String(Files.readAllBytes(filePath)); +// } + + outputStream.write(response.getResponse().getBytes()); + outputStream.flush(); - // content-Type 처리 - String contentType = "html"; - if (path.length() != 0) { - String[] splitedFileName = path.split("\\."); - if (splitedFileName.length != 1) { - contentType = splitedFileName[1]; - } - if (splitedFileName.length == 1) { - path += ".html"; - } - } + } catch (IOException | + UncheckedServletException e) { + log.error(e.getMessage(), e); + } + } - var responseBody = ""; + private HttpRequest generateHttpRequest(BufferedReader reader) throws IOException { + String requestLine = reader.readLine(); - // / 일때 분기 - if (uri.length() == 0) { - responseBody = "Hello world!"; - } else { - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/" + path)).getPath()); + if (requestLine == null) { + return null; + } - responseBody = new String(Files.readAllBytes(filePath)); - } + HttpRequestLine httpRequestLine = HttpRequestLine.from(requestLine); + HttpRequestHeader httpRequestHeader = getRequestHeader(reader); - StatusLine statusLine = StatusLine.from(statusCode, statusMessage); - HttpResponseHeader responseHeader = new HttpResponseHeader(new LinkedHashMap<>()); - responseHeader.add("Content-Type", "text/" + contentType + ";charset=utf-8"); - responseHeader.add("Content-Length", responseBody.getBytes().length); - - String response = new HttpResponse(statusLine, responseHeader, responseBody).getResponse(); - - if (setCookie.length() != 0) { - response = String.join("\r\n", - "HTTP/1.1 " + statusCode + " " + statusMessage + " ", - setCookie, - "Content-Type: text/" + contentType + ";charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - } + int contentLength = httpRequestHeader.getContentLength(); + HttpRequestBody httpRequestBody = getRequestBody(reader, contentLength); - outputStream.write(response.getBytes()); - outputStream.flush(); + return new HttpRequest(httpRequestLine, httpRequestHeader, httpRequestBody); + } - } catch (IOException | UncheckedServletException e) { - log.error(e.getMessage(), e); + private HttpRequestBody getRequestBody(BufferedReader reader, int contentLength) throws IOException { + StringBuilder requestBody = new StringBuilder(); + if (contentLength > 0) { + char[] buffer = new char[contentLength]; + int bytesRead = reader.read(buffer, 0, contentLength); + requestBody.append(buffer, 0, bytesRead); } + + return HttpRequestBody.from(requestBody.toString()); } - private String extractRequestUriFromRequest(String request) { - int startIndex = 0; - if (request.indexOf("GET") != -1) { - startIndex = request.indexOf("GET ") + 4; - } - if (request.indexOf("POST") != -1) { - startIndex = request.indexOf("POST ") + 5; - } - int endIndex = request.indexOf(" HTTP/1.1"); + private HttpRequestHeader getRequestHeader(BufferedReader reader) throws IOException { + StringBuilder requestHeader = new StringBuilder(); - if (startIndex != -1 && endIndex != -1) { - return request.substring(startIndex + 1, endIndex); + String line; + while (!Objects.equals(line = reader.readLine(), "")) { + requestHeader.append(line).append("\r\n"); } - return ""; + return HttpRequestHeader.from(requestHeader.toString()); } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java new file mode 100644 index 0000000000..b53fa45dd9 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -0,0 +1,18 @@ +package org.apache.coyote.request; + +public class HttpRequest { + + private final HttpRequestLine requestLine; + private final HttpRequestHeader requestHeader; + private final HttpRequestBody requestBody; + + public HttpRequest(HttpRequestLine requestLine, HttpRequestHeader requestHeader, HttpRequestBody requestBody) { + this.requestLine = requestLine; + this.requestHeader = requestHeader; + this.requestBody = requestBody; + } + + public HttpRequestLine getRequestLine() { + return requestLine; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java index 484a9b113f..cbe710ee20 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java @@ -1,13 +1,14 @@ package org.apache.coyote.request; + public class HttpRequestLine { private static final String SEPERATOR = " "; private final String method; - private final String requestUri; + private final RequestUri requestUri; private final String version; - private HttpRequestLine(String method, String requestUri, String version) { + public HttpRequestLine(String method, RequestUri requestUri, String version) { this.method = method; this.requestUri = requestUri; this.version = version; @@ -15,14 +16,11 @@ private HttpRequestLine(String method, String requestUri, String version) { public static HttpRequestLine from(String requestLine) { String[] splitedLines = requestLine.split(SEPERATOR); - return new HttpRequestLine(splitedLines[0], splitedLines[1], splitedLines[2]); - } - public String getMethod() { - return method; + return new HttpRequestLine(splitedLines[0], RequestUri.from(splitedLines[1]), splitedLines[2]); } - public String getRequestUri() { + public RequestUri getRequestUri() { return requestUri; } diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java b/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java new file mode 100644 index 0000000000..d8cdb42fb4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java @@ -0,0 +1,69 @@ +package org.apache.coyote.request; + +import java.net.URI; +import java.util.LinkedHashMap; +import java.util.Map; + +public class RequestUri { + + private static final String SEPERATOR = "\\?"; + private static final String QUERY_SEPARATOR = "&"; + private static final String KEY_VALUE_SEPEARTOR = "="; + private static final String DOT = "."; + + private final String path; + private final String extension; + private final Map queryParams; + + public RequestUri(String path, String extension, Map queryParams) { + this.path = path; + this.extension = extension; + this.queryParams = queryParams; + } + + public static RequestUri from(String requestUri) { + URI uri = URI.create(requestUri); + + + String path = uri.getPath(); + String query = uri.getQuery(); + String extension = getExtension(path); + + return new RequestUri(path, extension, parseQueryStrings(query)); + } + + private static String getExtension(String path) { + int dotIndex = path.lastIndexOf(DOT); + + if (dotIndex < 0) { + return ""; + } + return path.substring(dotIndex + 1); + } + + private static Map parseQueryStrings(String queryStrings) { + Map queryParams = new LinkedHashMap<>(); + + if (queryStrings == null) { + return queryParams; + } + + String[] splitedQueryParams = queryStrings.split(QUERY_SEPARATOR); + + for (String line : splitedQueryParams) { + String[] keyValue = line.split(KEY_VALUE_SEPEARTOR); + if (keyValue.length == 2) { + queryParams.put(keyValue[0], keyValue[1]); + } + } + return queryParams; + } + + public String getPath() { + return path; + } + + public String getExtension() { + return extension; + } +} From 77aad2e52246c4e49063a54a4258f491d52a7888 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sat, 9 Sep 2023 16:36:25 +0900 Subject: [PATCH 24/35] =?UTF-8?q?refactor=20:=20/index.html=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 721235559f..590e4300da 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -2,6 +2,7 @@ import nextstep.jwp.exception.UncheckedServletException; +import org.apache.coyote.ContentType; import org.apache.coyote.Processor; import org.apache.coyote.request.*; import org.apache.coyote.response.HttpResponse; @@ -13,6 +14,8 @@ import java.io.InputStreamReader; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Objects; import static org.apache.coyote.ContentType.HTML; @@ -45,6 +48,8 @@ public void process(final Socket connection) { String path = requestUri.getPath(); + System.out.println("path = " + path); + HttpResponse response = null; // / 일때 분기 @@ -53,13 +58,15 @@ public void process(final Socket connection) { .contentType(HTML) .body("hello world") .build(); - } + } else { + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + requestUri.getPath())).getPath()); + var responseBody = new String(Files.readAllBytes(filePath)); -// // path 가 index 일때 분기 -// if (path.equals("/index")) { -// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); -// responseBody = new String(Files.readAllBytes(filePath)); -// } + response = new HttpResponse.Builder() + .contentType(ContentType.from(requestUri.getExtension())) + .body(responseBody) + .build(); + } // // // path 가 login 일때 분기 // if (path.equals("/login") && httpRequestLine.isGetMethod()) { From 4e53e9549669796fbcd95724f7f2ac555be5dbcf Mon Sep 17 00:00:00 2001 From: yenawee Date: Sat, 9 Sep 2023 23:28:16 +0900 Subject: [PATCH 25/35] =?UTF-8?q?refactor=20:=20/login=20GET=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/HttpCookie.java | 20 +-- .../main/java/org/apache/coyote/Session.java | 5 +- .../apache/coyote/http11/Http11Processor.java | 135 ++++++++++-------- .../apache/coyote/request/HttpRequest.java | 8 ++ .../coyote/request/HttpRequestHeader.java | 6 +- tomcat/src/main/resources/static/favicon.ico | Bin 0 -> 1150 bytes 6 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 tomcat/src/main/resources/static/favicon.ico diff --git a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java index 9c89be609a..91bae92be3 100644 --- a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/HttpCookie.java @@ -1,25 +1,19 @@ package org.apache.coyote; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; public class HttpCookie { - private final static String BASE_COOKIE = "yummy_cookie=choco; tasty_cookie=strawberry; JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46"; - private final Map cookie; public HttpCookie(Map cookie) { this.cookie = cookie; } - public static HttpCookie of() { - return parseCookieString(BASE_COOKIE); - } - - public static HttpCookie parseCookieString(String cookieString) { - Map cookies = new HashMap<>(); + public static HttpCookie of(String cookieString) { + Map cookies = new LinkedHashMap<>(); if (cookieString != null && !cookieString.isEmpty()) { String[] cookiePairs = cookieString.split("; "); @@ -36,12 +30,8 @@ public static HttpCookie parseCookieString(String cookieString) { return new HttpCookie(cookies); } - public boolean existJSESSIONID() { - return cookie.containsKey("JSESSIONID"); - } - - public String getJSessionId() { - return cookie.get("JSESSIONID"); + public String getValue(String key) { + return cookie.get(key); } public String convertToHeader() { diff --git a/tomcat/src/main/java/org/apache/coyote/Session.java b/tomcat/src/main/java/org/apache/coyote/Session.java index 89cdd01dd7..05773629c8 100644 --- a/tomcat/src/main/java/org/apache/coyote/Session.java +++ b/tomcat/src/main/java/org/apache/coyote/Session.java @@ -2,14 +2,15 @@ import java.util.HashMap; import java.util.Map; +import java.util.UUID; public class Session { private final String id; private final Map values = new HashMap<>(); - public Session(final String id) { - this.id = id; + public Session() { + this.id = String.valueOf(UUID.randomUUID()); } public String getId() { 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 590e4300da..9609a2d94d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,9 +1,14 @@ package org.apache.coyote.http11; +import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; +import nextstep.jwp.model.User; +import org.apache.catalina.manager.SessionManager; import org.apache.coyote.ContentType; +import org.apache.coyote.HttpCookie; import org.apache.coyote.Processor; +import org.apache.coyote.Session; import org.apache.coyote.request.*; import org.apache.coyote.response.HttpResponse; import org.slf4j.Logger; @@ -17,8 +22,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; +import java.util.Optional; import static org.apache.coyote.ContentType.HTML; +import static org.apache.coyote.response.HttpStatus.FOUND; +import static org.apache.coyote.response.HttpStatus.OK; public class Http11Processor implements Runnable, Processor { @@ -46,18 +54,55 @@ public void process(final Socket connection) { RequestUri requestUri = httpRequest.getRequestLine().getRequestUri(); + String path = requestUri.getPath(); System.out.println("path = " + path); HttpResponse response = null; - // / 일때 분기 if (path.equals("/")) { response = new HttpResponse.Builder() .contentType(HTML) .body("hello world") .build(); + } else if (path.equals("/login") && httpRequest.getRequestLine().isGetMethod()) { + response = getLoginHttpResponse(httpRequest); + } else if (path.equals("/login") && httpRequest.getRequestLine().isPostMethod()) { + + HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); + + Optional user = InMemoryUserRepository.findByAccount(account); + if (user.isPresent() && user.get().checkPassword(password)) { + log.info(user.toString()); + + Session session = new Session(); + session.setAttribute("user", user); + SessionManager.add(session); + + path = "/index.html"; + + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); + +// response = new HttpResponse.Builder() +// .status(FOUND) +// .contentType(HTML) +// .setCookie() +// .build(); + + } else if (user.isPresent()) { + log.warn("비밀번호가 틀렸습니다"); + + path = "/401.html"; + + } else { + log.warn("미가입회원입니다"); + + path = "/401.html"; + } + } else { final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + requestUri.getPath())).getPath()); var responseBody = new String(Files.readAllBytes(filePath)); @@ -67,67 +112,8 @@ public void process(final Socket connection) { .body(responseBody) .build(); } -// -// // path 가 login 일때 분기 -// if (path.equals("/login") && httpRequestLine.isGetMethod()) { -// statusCode = 302; -// statusMessage = "Found"; -// -// HttpCookie cookie = httpRequestHeader.getCookie(); -// -// if (cookie == null || !cookie.existJSESSIONID()) { -// path = "/login.html"; -// } else { -// String jSessionId = cookie.getJSessionId(); -// Session session = SessionManager.findSession(jSessionId); -// -// Optional user = (Optional) session.getAttribute("user"); -// -// if (user.isPresent()) { -// path = "/index.html"; -// } else { -// path = "/login.html"; -// } -// } -// -// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); -// responseBody = new String(Files.readAllBytes(filePath)); -// } -// -// -// if (path.equals("/login") && httpRequestLine.isPostMethod()) { -// statusCode = 302; -// statusMessage = "Found"; -// -// String account = httpRequestBody.getValue("account"); -// String password = httpRequestBody.getValue("password"); -// -// Optional user = InMemoryUserRepository.findByAccount(account); -// if (user.isPresent() && user.get().checkPassword(password)) { -// log.info(user.toString()); -// UUID uuid = UUID.randomUUID(); -// Session session = new Session(uuid.toString()); -// session.setAttribute("user", user); -// SessionManager.add(session); -// -// setCookie = "Set-Cookie: JSESSIONID=" + uuid; -// -// path = "/index.html"; -// } else if (user.isPresent()) { -// log.warn("비밀번호가 틀렸습니다"); -// -// path = "/401.html"; -// -// } else { -// log.warn("미가입회원입니다"); -// -// path = "/401.html"; -// } -// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); -// -// responseBody = new String(Files.readAllBytes(filePath)); -// } -// + + // // pass 가 register 일때 분기 // if (path.equals("/register") && httpRequestLine.isPostMethod()) { // String account = httpRequestBody.getValue("account"); @@ -153,6 +139,31 @@ public void process(final Socket connection) { } } + private HttpResponse getLoginHttpResponse(HttpRequest httpRequest) throws IOException { + HttpCookie cookie = httpRequest.getRequestHeader().getCookie(); + + String jsessionid = cookie.getValue("JSESSIONID"); + Session session = SessionManager.findSession(jsessionid); + + if (session != null) { + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); + return new HttpResponse.Builder() + .status(FOUND) + .header("Location", "/index.html") + .contentType(HTML) + .body(new String(Files.readAllBytes(filePath))) + .build(); + } + + + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/login.html")).getPath()); + return new HttpResponse.Builder() + .status(OK) + .contentType(HTML) + .body(new String(Files.readAllBytes(filePath))) + .build(); + } + private HttpRequest generateHttpRequest(BufferedReader reader) throws IOException { String requestLine = reader.readLine(); diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java index b53fa45dd9..d0da0c698f 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequest.java @@ -15,4 +15,12 @@ public HttpRequest(HttpRequestLine requestLine, HttpRequestHeader requestHeader, public HttpRequestLine getRequestLine() { return requestLine; } + + public HttpRequestHeader getRequestHeader() { + return requestHeader; + } + + public HttpRequestBody getRequestBody() { + return requestBody; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java index ff34fa714c..29354598e4 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java @@ -3,7 +3,7 @@ import org.apache.coyote.HttpCookie; import java.util.Arrays; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -19,7 +19,7 @@ private HttpRequestHeader(Map headers) { } public static HttpRequestHeader from(String requestHeader) { - Map headers = new HashMap<>(); + Map headers = new LinkedHashMap<>(); String[] splitedLines = requestHeader.split(SEPERATOR); List splited = Arrays.asList(splitedLines); @@ -45,6 +45,6 @@ public int getContentLength() { public HttpCookie getCookie() { String cookie = headers.get("Cookie"); - return HttpCookie.parseCookieString(cookie); + return HttpCookie.of(cookie); } } diff --git a/tomcat/src/main/resources/static/favicon.ico b/tomcat/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..33c4f2294119228bcf43b58bb0e530ee9a0c8dd1 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x#lFaYI*xFHzK2NH(G^9LRN&tUT( z8z#lA==w>~3sO6h^`rY8#wRrl2U0!E%`lo&x4`tlXnN}>)jo3712Y-}2>+00VEAK? Ni9zBpHOTTXF#tS&hWY>i literal 0 HcmV?d00001 From 47d0f4c083919e7d803fe2cf3996169ce8d21f41 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sat, 9 Sep 2023 23:46:25 +0900 Subject: [PATCH 26/35] =?UTF-8?q?refactor=20:=20/login=20POST=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 86 +++++++++++-------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 9609a2d94d..e41e2bc142 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -69,40 +69,7 @@ public void process(final Socket connection) { } else if (path.equals("/login") && httpRequest.getRequestLine().isGetMethod()) { response = getLoginHttpResponse(httpRequest); } else if (path.equals("/login") && httpRequest.getRequestLine().isPostMethod()) { - - HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); - String account = httpRequestBody.getValue("account"); - String password = httpRequestBody.getValue("password"); - - Optional user = InMemoryUserRepository.findByAccount(account); - if (user.isPresent() && user.get().checkPassword(password)) { - log.info(user.toString()); - - Session session = new Session(); - session.setAttribute("user", user); - SessionManager.add(session); - - path = "/index.html"; - - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); - -// response = new HttpResponse.Builder() -// .status(FOUND) -// .contentType(HTML) -// .setCookie() -// .build(); - - } else if (user.isPresent()) { - log.warn("비밀번호가 틀렸습니다"); - - path = "/401.html"; - - } else { - log.warn("미가입회원입니다"); - - path = "/401.html"; - } - + response = postLoginHttpResponse(httpRequest); } else { final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + requestUri.getPath())).getPath()); var responseBody = new String(Files.readAllBytes(filePath)); @@ -139,6 +106,57 @@ public void process(final Socket connection) { } } + private HttpResponse postLoginHttpResponse(HttpRequest httpRequest) { + HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); + + Optional user = InMemoryUserRepository.findByAccount(account); + + if (user.isPresent() && user.get().checkPassword(password)) { + log.info(user.toString()); + + return loginSuccess(user); + + } else if (user.isPresent()) { + log.warn("비밀번호가 틀렸습니다"); + + return loginFail(); + + } else { + log.warn("미가입회원입니다"); + + return loginFail(); + } + } + + private HttpResponse loginSuccess(Optional user) { + Session session = new Session(); + session.setAttribute("user", user); + SessionManager.add(session); + + HttpCookie cookie = HttpCookie.of("JSESSIONID=" + session.getId()); + + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); + + return new HttpResponse.Builder() + .status(FOUND) + .contentType(HTML) + .header("Location", "/index.html") + .setCookie(cookie) + .build(); + } + + private HttpResponse loginFail() { + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/401.html")).getPath()); + + return new HttpResponse.Builder() + .status(FOUND) + .contentType(HTML) + .header("Location", "/401.html") + .build(); + } + private HttpResponse getLoginHttpResponse(HttpRequest httpRequest) throws IOException { HttpCookie cookie = httpRequest.getRequestHeader().getCookie(); From 00e13b73cd086031817e279b15a26711ade84069 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 00:03:18 +0900 Subject: [PATCH 27/35] =?UTF-8?q?refactor=20:=20/register=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index e41e2bc142..c40c5a2786 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -57,8 +57,6 @@ public void process(final Socket connection) { String path = requestUri.getPath(); - System.out.println("path = " + path); - HttpResponse response = null; if (path.equals("/")) { @@ -70,6 +68,30 @@ public void process(final Socket connection) { response = getLoginHttpResponse(httpRequest); } else if (path.equals("/login") && httpRequest.getRequestLine().isPostMethod()) { response = postLoginHttpResponse(httpRequest); + } else if (path.equals("/register") && httpRequest.getRequestLine().isGetMethod()) { + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/register.html")).getPath()); + var responseBody = new String(Files.readAllBytes(filePath)); + + response = new HttpResponse.Builder() + .contentType(ContentType.from(requestUri.getExtension())) + .body(responseBody) + .build(); + } else if (path.equals("/register") && httpRequest.getRequestLine().isPostMethod()) { + HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); + + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); + String email = httpRequestBody.getValue("email"); + + InMemoryUserRepository.save(new User(account, password, email)); + + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); + + response = new HttpResponse.Builder() + .status(FOUND) + .contentType(HTML) + .header("Location", "/index.html") + .build(); } else { final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + requestUri.getPath())).getPath()); var responseBody = new String(Files.readAllBytes(filePath)); @@ -81,22 +103,6 @@ public void process(final Socket connection) { } -// // pass 가 register 일때 분기 -// if (path.equals("/register") && httpRequestLine.isPostMethod()) { -// String account = httpRequestBody.getValue("account"); -// String password = httpRequestBody.getValue("password"); -// String email = httpRequestBody.getValue("email"); -// -// InMemoryUserRepository.save(new User(account, password, email)); -// -// statusCode = 302; -// statusMessage = "Found"; -// path = "/index.html"; -// final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + path)).getPath()); -// -// responseBody = new String(Files.readAllBytes(filePath)); -// } - outputStream.write(response.getResponse().getBytes()); outputStream.flush(); From e1445aa03b28befc0b476bce1931fccf6fec9beb Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 00:06:19 +0900 Subject: [PATCH 28/35] =?UTF-8?q?refactor=20:=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 130 +++++++++--------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index c40c5a2786..3b21f1c3c9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -54,7 +54,6 @@ public void process(final Socket connection) { RequestUri requestUri = httpRequest.getRequestLine().getRequestUri(); - String path = requestUri.getPath(); HttpResponse response = null; @@ -62,7 +61,7 @@ public void process(final Socket connection) { if (path.equals("/")) { response = new HttpResponse.Builder() .contentType(HTML) - .body("hello world") + .body("Hello world!") .build(); } else if (path.equals("/login") && httpRequest.getRequestLine().isGetMethod()) { response = getLoginHttpResponse(httpRequest); @@ -102,7 +101,6 @@ public void process(final Socket connection) { .build(); } - outputStream.write(response.getResponse().getBytes()); outputStream.flush(); @@ -112,6 +110,69 @@ public void process(final Socket connection) { } } + private HttpRequest generateHttpRequest(BufferedReader reader) throws IOException { + String requestLine = reader.readLine(); + + if (requestLine == null) { + return null; + } + + HttpRequestLine httpRequestLine = HttpRequestLine.from(requestLine); + HttpRequestHeader httpRequestHeader = getRequestHeader(reader); + + int contentLength = httpRequestHeader.getContentLength(); + HttpRequestBody httpRequestBody = getRequestBody(reader, contentLength); + + return new HttpRequest(httpRequestLine, httpRequestHeader, httpRequestBody); + } + + private HttpRequestBody getRequestBody(BufferedReader reader, int contentLength) throws IOException { + StringBuilder requestBody = new StringBuilder(); + if (contentLength > 0) { + char[] buffer = new char[contentLength]; + int bytesRead = reader.read(buffer, 0, contentLength); + requestBody.append(buffer, 0, bytesRead); + } + + return HttpRequestBody.from(requestBody.toString()); + } + + private HttpRequestHeader getRequestHeader(BufferedReader reader) throws IOException { + StringBuilder requestHeader = new StringBuilder(); + + String line; + while (!Objects.equals(line = reader.readLine(), "")) { + requestHeader.append(line).append("\r\n"); + } + + return HttpRequestHeader.from(requestHeader.toString()); + } + + private HttpResponse getLoginHttpResponse(HttpRequest httpRequest) throws IOException { + HttpCookie cookie = httpRequest.getRequestHeader().getCookie(); + + String jsessionid = cookie.getValue("JSESSIONID"); + Session session = SessionManager.findSession(jsessionid); + + if (session != null) { + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); + return new HttpResponse.Builder() + .status(FOUND) + .header("Location", "/index.html") + .contentType(HTML) + .body(new String(Files.readAllBytes(filePath))) + .build(); + } + + + final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/login.html")).getPath()); + return new HttpResponse.Builder() + .status(OK) + .contentType(HTML) + .body(new String(Files.readAllBytes(filePath))) + .build(); + } + private HttpResponse postLoginHttpResponse(HttpRequest httpRequest) { HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); String account = httpRequestBody.getValue("account"); @@ -162,67 +223,4 @@ private HttpResponse loginFail() { .header("Location", "/401.html") .build(); } - - private HttpResponse getLoginHttpResponse(HttpRequest httpRequest) throws IOException { - HttpCookie cookie = httpRequest.getRequestHeader().getCookie(); - - String jsessionid = cookie.getValue("JSESSIONID"); - Session session = SessionManager.findSession(jsessionid); - - if (session != null) { - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); - return new HttpResponse.Builder() - .status(FOUND) - .header("Location", "/index.html") - .contentType(HTML) - .body(new String(Files.readAllBytes(filePath))) - .build(); - } - - - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/login.html")).getPath()); - return new HttpResponse.Builder() - .status(OK) - .contentType(HTML) - .body(new String(Files.readAllBytes(filePath))) - .build(); - } - - private HttpRequest generateHttpRequest(BufferedReader reader) throws IOException { - String requestLine = reader.readLine(); - - if (requestLine == null) { - return null; - } - - HttpRequestLine httpRequestLine = HttpRequestLine.from(requestLine); - HttpRequestHeader httpRequestHeader = getRequestHeader(reader); - - int contentLength = httpRequestHeader.getContentLength(); - HttpRequestBody httpRequestBody = getRequestBody(reader, contentLength); - - return new HttpRequest(httpRequestLine, httpRequestHeader, httpRequestBody); - } - - private HttpRequestBody getRequestBody(BufferedReader reader, int contentLength) throws IOException { - StringBuilder requestBody = new StringBuilder(); - if (contentLength > 0) { - char[] buffer = new char[contentLength]; - int bytesRead = reader.read(buffer, 0, contentLength); - requestBody.append(buffer, 0, bytesRead); - } - - return HttpRequestBody.from(requestBody.toString()); - } - - private HttpRequestHeader getRequestHeader(BufferedReader reader) throws IOException { - StringBuilder requestHeader = new StringBuilder(); - - String line; - while (!Objects.equals(line = reader.readLine(), "")) { - requestHeader.append(line).append("\r\n"); - } - - return HttpRequestHeader.from(requestHeader.toString()); - } } From a2a9e1bf9965a09cde551b39e3c19408d2720269 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 00:07:45 +0900 Subject: [PATCH 29/35] =?UTF-8?q?refactor=20:=20package=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/catalina/manager/SessionManager.java | 2 +- .../java/org/apache/coyote/{ => common}/ContentType.java | 2 +- .../java/org/apache/coyote/{ => common}/HttpCookie.java | 2 +- .../main/java/org/apache/coyote/{ => common}/Session.java | 2 +- .../java/org/apache/coyote/http11/Http11Processor.java | 8 ++++---- .../java/org/apache/coyote/request/HttpRequestHeader.java | 2 +- .../java/org/apache/coyote/response/HttpResponse.java | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) rename tomcat/src/main/java/org/apache/coyote/{ => common}/ContentType.java (95%) rename tomcat/src/main/java/org/apache/coyote/{ => common}/HttpCookie.java (97%) rename tomcat/src/main/java/org/apache/coyote/{ => common}/Session.java (95%) diff --git a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java index e98c02440f..894d6f93e4 100644 --- a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -1,6 +1,6 @@ package org.apache.catalina.manager; -import org.apache.coyote.Session; +import org.apache.coyote.common.Session; import java.util.HashMap; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/coyote/ContentType.java b/tomcat/src/main/java/org/apache/coyote/common/ContentType.java similarity index 95% rename from tomcat/src/main/java/org/apache/coyote/ContentType.java rename to tomcat/src/main/java/org/apache/coyote/common/ContentType.java index 4b114dc3f5..0745b2c857 100644 --- a/tomcat/src/main/java/org/apache/coyote/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/common/ContentType.java @@ -1,4 +1,4 @@ -package org.apache.coyote; +package org.apache.coyote.common; import java.util.Arrays; diff --git a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java similarity index 97% rename from tomcat/src/main/java/org/apache/coyote/HttpCookie.java rename to tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java index 91bae92be3..cf91a35917 100644 --- a/tomcat/src/main/java/org/apache/coyote/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java @@ -1,4 +1,4 @@ -package org.apache.coyote; +package org.apache.coyote.common; import java.util.LinkedHashMap; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/coyote/Session.java b/tomcat/src/main/java/org/apache/coyote/common/Session.java similarity index 95% rename from tomcat/src/main/java/org/apache/coyote/Session.java rename to tomcat/src/main/java/org/apache/coyote/common/Session.java index 05773629c8..b2b0218269 100644 --- a/tomcat/src/main/java/org/apache/coyote/Session.java +++ b/tomcat/src/main/java/org/apache/coyote/common/Session.java @@ -1,4 +1,4 @@ -package org.apache.coyote; +package org.apache.coyote.common; import java.util.HashMap; import java.util.Map; 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 3b21f1c3c9..09ee0e794e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -5,10 +5,10 @@ import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; import org.apache.catalina.manager.SessionManager; -import org.apache.coyote.ContentType; -import org.apache.coyote.HttpCookie; import org.apache.coyote.Processor; -import org.apache.coyote.Session; +import org.apache.coyote.common.ContentType; +import org.apache.coyote.common.HttpCookie; +import org.apache.coyote.common.Session; import org.apache.coyote.request.*; import org.apache.coyote.response.HttpResponse; import org.slf4j.Logger; @@ -24,7 +24,7 @@ import java.util.Objects; import java.util.Optional; -import static org.apache.coyote.ContentType.HTML; +import static org.apache.coyote.common.ContentType.HTML; import static org.apache.coyote.response.HttpStatus.FOUND; import static org.apache.coyote.response.HttpStatus.OK; diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java index 29354598e4..941c64500a 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java @@ -1,6 +1,6 @@ package org.apache.coyote.request; -import org.apache.coyote.HttpCookie; +import org.apache.coyote.common.HttpCookie; import java.util.Arrays; import java.util.LinkedHashMap; diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index f7ef186ea2..416617a2f7 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -1,7 +1,7 @@ package org.apache.coyote.response; -import org.apache.coyote.ContentType; -import org.apache.coyote.HttpCookie; +import org.apache.coyote.common.ContentType; +import org.apache.coyote.common.HttpCookie; import java.util.LinkedHashMap; import java.util.Map; From 0928ec86596e4a7140c15787335cbe813c1212ef Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 00:22:46 +0900 Subject: [PATCH 30/35] =?UTF-8?q?refactor=20:=20FileUtils=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 26 ++++--------------- .../org/apache/coyote/utils/FileUtils.java | 17 ++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 09ee0e794e..04f97b83e2 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -5,6 +5,8 @@ import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; import org.apache.catalina.manager.SessionManager; + +import org.apache.coyote.utils.FileUtils; import org.apache.coyote.Processor; import org.apache.coyote.common.ContentType; import org.apache.coyote.common.HttpCookie; @@ -19,8 +21,6 @@ import java.io.InputStreamReader; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Objects; import java.util.Optional; @@ -68,12 +68,9 @@ public void process(final Socket connection) { } else if (path.equals("/login") && httpRequest.getRequestLine().isPostMethod()) { response = postLoginHttpResponse(httpRequest); } else if (path.equals("/register") && httpRequest.getRequestLine().isGetMethod()) { - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/register.html")).getPath()); - var responseBody = new String(Files.readAllBytes(filePath)); - response = new HttpResponse.Builder() .contentType(ContentType.from(requestUri.getExtension())) - .body(responseBody) + .body(FileUtils.readFile("/register.html")) .build(); } else if (path.equals("/register") && httpRequest.getRequestLine().isPostMethod()) { HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); @@ -84,20 +81,15 @@ public void process(final Socket connection) { InMemoryUserRepository.save(new User(account, password, email)); - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); - response = new HttpResponse.Builder() .status(FOUND) .contentType(HTML) .header("Location", "/index.html") .build(); } else { - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static" + requestUri.getPath())).getPath()); - var responseBody = new String(Files.readAllBytes(filePath)); - response = new HttpResponse.Builder() .contentType(ContentType.from(requestUri.getExtension())) - .body(responseBody) + .body(FileUtils.readFile(requestUri.getPath())) .build(); } @@ -155,21 +147,17 @@ private HttpResponse getLoginHttpResponse(HttpRequest httpRequest) throws IOExce Session session = SessionManager.findSession(jsessionid); if (session != null) { - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); return new HttpResponse.Builder() .status(FOUND) .header("Location", "/index.html") .contentType(HTML) - .body(new String(Files.readAllBytes(filePath))) .build(); } - - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/login.html")).getPath()); return new HttpResponse.Builder() .status(OK) .contentType(HTML) - .body(new String(Files.readAllBytes(filePath))) + .body(FileUtils.readFile("/login.html")) .build(); } @@ -204,8 +192,6 @@ private HttpResponse loginSuccess(Optional user) { HttpCookie cookie = HttpCookie.of("JSESSIONID=" + session.getId()); - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/index.html")).getPath()); - return new HttpResponse.Builder() .status(FOUND) .contentType(HTML) @@ -215,8 +201,6 @@ private HttpResponse loginSuccess(Optional user) { } private HttpResponse loginFail() { - final Path filePath = Path.of(Objects.requireNonNull(getClass().getClassLoader().getResource("static/401.html")).getPath()); - return new HttpResponse.Builder() .status(FOUND) .contentType(HTML) diff --git a/tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java b/tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java new file mode 100644 index 0000000000..377fee426d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java @@ -0,0 +1,17 @@ +package org.apache.coyote.utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +public class FileUtils { + + private static final String STATIC_DIRECTORY = "static"; + + public static String readFile(String path) throws IOException { + final Path filePath = Path.of(Objects.requireNonNull(FileUtils.class.getClassLoader().getResource(STATIC_DIRECTORY + path)).getPath()); + + return new String(Files.readAllBytes(filePath)); + } +} From 096f77c1f0f4fab5e39394848e11388f3bf56875 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 00:27:06 +0900 Subject: [PATCH 31/35] refactor : java convention --- .../main/java/org/apache/coyote/request/HttpRequestBody.java | 4 ++-- .../main/java/org/apache/coyote/request/HttpRequestLine.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java index 0e43892888..52f6c109c3 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java @@ -5,7 +5,7 @@ public class HttpRequestBody { - public static final String BODY_SEPERATOR = "="; + private static final String BODY_SEPERATOR = "="; private static final String SEPERATOR = "&"; private final Map body; @@ -16,7 +16,7 @@ private HttpRequestBody(Map body) { public static HttpRequestBody from(String requestBody) { Map body = new HashMap<>(); - String[] keyValuePairs = requestBody.split("&"); + String[] keyValuePairs = requestBody.split(SEPERATOR); for (String pair : keyValuePairs) { String[] keyValue = pair.split(BODY_SEPERATOR); if (keyValue.length == 2) { diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java index cbe710ee20..73e65d503f 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java @@ -4,6 +4,7 @@ public class HttpRequestLine { private static final String SEPERATOR = " "; + private final String method; private final RequestUri requestUri; private final String version; From 14cfde1de2a60b75f347995744ae85408323de51 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 00:55:48 +0900 Subject: [PATCH 32/35] =?UTF-8?q?test:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20Get?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/Http11ProcessorTest.java | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java index 512b919f09..adb4135adb 100644 --- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java @@ -1,14 +1,17 @@ package nextstep.org.apache.coyote.http11; -import support.StubSocket; import org.apache.coyote.http11.Http11Processor; +import org.apache.coyote.response.HttpResponse; import org.junit.jupiter.api.Test; +import support.StubSocket; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; +import static org.apache.coyote.common.ContentType.HTML; +import static org.apache.coyote.response.HttpStatus.OK; import static org.assertj.core.api.Assertions.assertThat; class Http11ProcessorTest { @@ -36,7 +39,7 @@ void process() { @Test void index() throws IOException { // given - final String httpRequest= String.join("\r\n", + final String httpRequest = String.join("\r\n", "GET /index.html HTTP/1.1 ", "Host: localhost:8080 ", "Connection: keep-alive ", @@ -54,9 +57,40 @@ void index() throws IOException { var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html;charset=utf-8 \r\n" + "Content-Length: 5564 \r\n" + - "\r\n"+ + "\r\n" + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); assertThat(socket.output()).isEqualTo(expected); } + + @Test + void 로그인_GET_요청시_쿠키에_JSESSION_ID_없으면_로그인_페이지() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /login HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/login.html"); + String responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + HttpResponse expected = new HttpResponse.Builder() + .status(OK) + .contentType(HTML) + .header("Content-Length", String.valueOf(responseBody.length())) + .body(responseBody) + .build(); + + + assertThat(socket.output()).isEqualTo(expected.getResponse()); + } } From 6fe6cd8130694cc06847a02fe6a1a04a389ddee8 Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 01:02:05 +0900 Subject: [PATCH 33/35] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/Http11Processor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 04f97b83e2..af9311cd77 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -5,14 +5,13 @@ import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; import org.apache.catalina.manager.SessionManager; - -import org.apache.coyote.utils.FileUtils; import org.apache.coyote.Processor; import org.apache.coyote.common.ContentType; import org.apache.coyote.common.HttpCookie; import org.apache.coyote.common.Session; import org.apache.coyote.request.*; import org.apache.coyote.response.HttpResponse; +import org.apache.coyote.utils.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,6 +78,10 @@ public void process(final Socket connection) { String password = httpRequestBody.getValue("password"); String email = httpRequestBody.getValue("email"); + if (InMemoryUserRepository.findByAccount(account).isPresent()) { + throw new IllegalArgumentException("중복 ID 입니다"); + } + InMemoryUserRepository.save(new User(account, password, email)); response = new HttpResponse.Builder() From 2b0effcda4d90c10aa1d3d2c2171f7493f77769f Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 01:14:16 +0900 Subject: [PATCH 34/35] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/common/HttpCookie.java | 4 +- .../apache/coyote/http11/Http11Processor.java | 38 ++++++++++--------- .../org/apache/coyote/request/RequestUri.java | 1 - 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java index cf91a35917..9910d8f67f 100644 --- a/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java @@ -1,5 +1,7 @@ package org.apache.coyote.common; +import org.apache.commons.lang3.StringUtils; + import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; @@ -15,7 +17,7 @@ public HttpCookie(Map cookie) { public static HttpCookie of(String cookieString) { Map cookies = new LinkedHashMap<>(); - if (cookieString != null && !cookieString.isEmpty()) { + if (StringUtils.isNotBlank(cookieString)) { String[] cookiePairs = cookieString.split("; "); for (String cookiePair : cookiePairs) { String[] parts = cookiePair.split("="); 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 af9311cd77..f142e944e5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -72,23 +72,7 @@ public void process(final Socket connection) { .body(FileUtils.readFile("/register.html")) .build(); } else if (path.equals("/register") && httpRequest.getRequestLine().isPostMethod()) { - HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); - - String account = httpRequestBody.getValue("account"); - String password = httpRequestBody.getValue("password"); - String email = httpRequestBody.getValue("email"); - - if (InMemoryUserRepository.findByAccount(account).isPresent()) { - throw new IllegalArgumentException("중복 ID 입니다"); - } - - InMemoryUserRepository.save(new User(account, password, email)); - - response = new HttpResponse.Builder() - .status(FOUND) - .contentType(HTML) - .header("Location", "/index.html") - .build(); + response = postRegisterHttpResponse(httpRequest); } else { response = new HttpResponse.Builder() .contentType(ContentType.from(requestUri.getExtension())) @@ -210,4 +194,24 @@ private HttpResponse loginFail() { .header("Location", "/401.html") .build(); } + + private HttpResponse postRegisterHttpResponse(HttpRequest httpRequest) { + HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); + + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); + String email = httpRequestBody.getValue("email"); + + if (InMemoryUserRepository.findByAccount(account).isPresent()) { + throw new IllegalArgumentException("중복 ID 입니다"); + } + + InMemoryUserRepository.save(new User(account, password, email)); + + return new HttpResponse.Builder() + .status(FOUND) + .contentType(HTML) + .header("Location", "/index.html") + .build(); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java b/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java index d8cdb42fb4..a39949709c 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java @@ -24,7 +24,6 @@ public RequestUri(String path, String extension, Map queryParams public static RequestUri from(String requestUri) { URI uri = URI.create(requestUri); - String path = uri.getPath(); String query = uri.getQuery(); String extension = getExtension(path); From 20bbf6d332fd2b791029bebeaae3e5d80248cc8f Mon Sep 17 00:00:00 2001 From: yenawee Date: Sun, 10 Sep 2023 01:22:31 +0900 Subject: [PATCH 35/35] =?UTF-8?q?fix:=20null=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index f142e944e5..7bd72c524d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -92,10 +92,6 @@ public void process(final Socket connection) { private HttpRequest generateHttpRequest(BufferedReader reader) throws IOException { String requestLine = reader.readLine(); - if (requestLine == null) { - return null; - } - HttpRequestLine httpRequestLine = HttpRequestLine.from(requestLine); HttpRequestHeader httpRequestHeader = getRequestHeader(reader);