From 4b9de96acf3334c05b9ed0a5e30bfcaeb6145573 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 4 Sep 2024 13:31:27 +0900 Subject: [PATCH 01/22] =?UTF-8?q?test:=20=ED=8C=8C=EC=9D=BC,=20=EC=9E=85?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EC=8A=A4=ED=8A=B8=EB=A6=BC=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5=20=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/study/FileTest.java | 49 ++++---- study/src/test/java/study/IOStreamTest.java | 131 +++++++++++--------- 2 files changed, 96 insertions(+), 84 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..7f2c05f7c1 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,54 +1,59 @@ package study; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import java.io.BufferedReader; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import java.util.Objects; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; /** - * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. - * File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. + * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. */ @DisplayName("File 클래스 학습 테스트") class FileTest { /** * resource 디렉터리 경로 찾기 - * - * File 객체를 생성하려면 파일의 경로를 알아야 한다. - * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. - * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? + *

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

+ * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws URISyntaxException { final String fileName = "nextstep.txt"; - // todo - final Path path = null; + URL resource = getClass().getClassLoader().getResource(fileName); + Path path = Path.of(resource.toURI()); + + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List actual = bufferedReader.lines().toList(); + assertThat(actual).containsOnly("nextstep"); + } catch (Exception e) { + } - // todo - final List actual = Collections.emptyList(); +// final List actual = Files.readLines(file, Charset.defaultCharset()); <- 나중에 해결 안 됨 - assertThat(actual).containsOnly("nextstep"); +// assertThat(actual).containsOnly("nextstep"); } } diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..442c308240 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -1,45 +1,50 @@ package study; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - /** - * 자바는 스트림(Stream)으로부터 I/O를 사용한다. - * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. - * - * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. - * FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. - * FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환) - * - * Stream은 데이터를 바이트로 읽고 쓴다. - * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. - * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다. + * 자바는 스트림(Stream)으로부터 I/O를 사용한다. 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. + *

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

+ * Stream은 데이터를 바이트로 읽고 쓴다. 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 + * 처리할 수 있다. */ @DisplayName("Java I/O Stream 클래스 학습 테스트") class IOStreamTest { /** * OutputStream 학습하기 - * - * 자바의 기본 출력 클래스는 java.io.OutputStream이다. - * OutputStream의 write(int b) 메서드는 기반 메서드이다. + *

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

* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다. * write(byte[] data)write(byte b[], int off, int len) 메서드는 * 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다. @@ -54,6 +59,8 @@ class OutputStream_학습_테스트 { * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 */ + outputStream.write(bytes); + final String actual = outputStream.toString(); assertThat(actual).isEqualTo("nextstep"); @@ -61,13 +68,10 @@ class OutputStream_학습_테스트 { } /** - * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. - * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. - * - * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. - * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. - * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 - * 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다. + * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. + *

+ * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. Stream은 + * 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다. */ @Test void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException { @@ -78,14 +82,14 @@ class OutputStream_학습_테스트 { * flush를 사용해서 테스트를 통과시킨다. * ByteArrayOutputStream과 어떤 차이가 있을까? */ + outputStream.flush(); verify(outputStream, atLeastOnce()).flush(); outputStream.close(); } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. */ @Test void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -96,27 +100,26 @@ class OutputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ - + try (outputStream) { + } verify(outputStream, atLeastOnce()).close(); } } /** * InputStream 학습하기 - * - * 자바의 기본 입력 클래스는 java.io.InputStream이다. - * InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다. - * InputStream의 read() 메서드는 기반 메서드이다. + *

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

* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다. */ @Nested class InputStream_학습_테스트 { /** - * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. - * int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다. + * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다. * 그리고 Stream 끝에 도달하면 -1을 반환한다. */ @Test @@ -128,7 +131,9 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + inputStream.readAllBytes(); + + final String actual = new String(bytes); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -136,8 +141,7 @@ class InputStream_학습_테스트 { } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. */ @Test void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -148,6 +152,9 @@ class InputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (inputStream) { + + } verify(inputStream, atLeastOnce()).close(); } @@ -155,26 +162,24 @@ class InputStream_학습_테스트 { /** * FilterStream 학습하기 - * - * 필터는 필터 스트림, reader, writer로 나뉜다. - * 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. - * reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다. + *

+ * 필터는 필터 스트림, reader, writer로 나뉜다. 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 + * 텍스트를 처리하는 데 사용된다. */ @Nested class FilterStream_학습_테스트 { /** - * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다. - * InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다. - * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? + * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다. InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다. 버퍼 크기를 지정하지 + * 않으면 버퍼의 기본 사이즈는 얼마일까? */ @Test - void 필터인_BufferedInputStream를_사용해보자() { + void 필터인_BufferedInputStream를_사용해보자() throws IOException { final String text = "필터에 연결해보자."; final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; + final InputStream bufferedInputStream = new BufferedInputStream(inputStream); // buffer size = 8192 bytes - final byte[] actual = new byte[0]; + final byte[] actual = bufferedInputStream.readAllBytes(); assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); @@ -182,30 +187,32 @@ class FilterStream_학습_테스트 { } /** - * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. - * 문자열이 아닌 바이트 단위로 처리하려니 불편하다. - * 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. - * reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. - * 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다. + * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. 문자열이 아닌 바이트 단위로 처리하려니 불편하다. 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. reader, + * writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다. */ @Nested class InputStreamReader_학습_테스트 { /** - * InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다. - * 읽어온 문자(char)를 문자열(String)로 처리하자. - * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. + * InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다. 읽어온 문자(char)를 문자열(String)로 처리하자. 필터인 BufferedReader를 사용하면 + * readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. */ @Test - void BufferedReader를_사용하여_문자열을_읽어온다() { + void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException { final String emoji = String.join("\r\n", "😀😃😄😁😆😅😂🤣🥲☺️😊", "😇🙂🙃😉😌😍🥰😘😗😙😚", "😋😛😝😜🤪🤨🧐🤓😎🥸🤩", ""); final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String line; final StringBuilder actual = new StringBuilder(); + while ((line = bufferedReader.readLine()) != null) { + actual.append(line); + actual.append("\r\n"); + } assertThat(actual).hasToString(emoji); } From b051a204919e8b3870b178888608281dcaa0bbb9 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 4 Sep 2024 13:32:00 +0900 Subject: [PATCH 02/22] =?UTF-8?q?docs:=201=EB=8B=A8=EA=B3=84=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tomcat/README.md diff --git a/tomcat/README.md b/tomcat/README.md new file mode 100644 index 0000000000..c28a551ce3 --- /dev/null +++ b/tomcat/README.md @@ -0,0 +1,5 @@ +## 1단계 - HTTP 서버 구현하기 + +- [ ] GET /index.html 응답하기 +- [ ] CSS 지원하기 +- [ ] Query String 파싱 From 670827ab1a94b0801994582cca3e56f3773c2193 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 4 Sep 2024 13:32:54 +0900 Subject: [PATCH 03/22] =?UTF-8?q?feat:=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=91=EB=8B=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 2 +- .../apache/coyote/http11/Http11Processor.java | 82 ++++++++++++++++--- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/tomcat/README.md b/tomcat/README.md index c28a551ce3..a68b377430 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -1,5 +1,5 @@ ## 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 bb14184757..462fe988c4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,13 +1,20 @@ package org.apache.coyote.http11; import com.techcourse.exception.UncheckedServletException; +import java.io.BufferedReader; +import java.io.IOException; +import java.net.Socket; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.Socket; - public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); @@ -29,19 +36,70 @@ public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - final var responseBody = "Hello world!"; + // inputstream read + byte[] bytes = new byte[18000]; + int readByteCount = inputStream.read(bytes); + String data = new String(bytes, 0, readByteCount, StandardCharsets.UTF_8); + + String referer = extractReferer(data); + + // default page + if (referer.equals("/")) { + final var responseBody = "Hello world!"; + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + + outputStream.write(response.getBytes()); + outputStream.flush(); + return; + } - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); + // index.html file read + String fileName = referer.substring(1); - outputStream.write(response.getBytes()); - outputStream.flush(); + System.out.println(fileName); + URL resource = getClass().getResource("/static/" + fileName); + + Path path = null; + try { + path = Path.of(resource.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List actual = bufferedReader.lines().toList(); + + String collect = actual.stream() + .collect(Collectors.joining("\n")) + "\n"; + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + collect.getBytes().length + " ", + "", + collect); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { + } } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } + + private static String extractReferer(String httpRequest) { + String firstLine = httpRequest.split("\n")[0]; + String[] split = firstLine.split(" "); + if (split[0].equals("GET")) { + return split[1]; + } + throw new IllegalArgumentException("GET 요청만 처리 가능.."); + } } From fed02f6f5f4308400e55c160d9495cad010f5bfb Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 11:11:09 +0900 Subject: [PATCH 04/22] fix: remove implementation logback-classic on gradle (#501) --- study/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/study/build.gradle b/study/build.gradle index 5c69542f84..87a1f0313c 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -19,7 +19,6 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'ch.qos.logback:logback-classic:1.5.7' implementation 'org.apache.commons:commons-lang3:3.14.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1' From 7e9135698878932274ddc1f523ba817ed9c56c70 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 13:51:07 +0900 Subject: [PATCH 05/22] fix: add threads min-spare configuration on properties (#502) --- study/src/main/resources/application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..e3503a5fb9 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -6,4 +6,5 @@ server: accept-count: 1 max-connections: 1 threads: + min-spare: 2 max: 2 From d158e08149cad1e9014c5392fa721cb8190b9234 Mon Sep 17 00:00:00 2001 From: mzeong Date: Thu, 5 Sep 2024 10:17:12 +0900 Subject: [PATCH 06/22] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=91=EB=8B=B5=20=EB=B0=8F=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 4 +- .../apache/coyote/http11/Http11Processor.java | 117 +++++++++++++----- 2 files changed, 85 insertions(+), 36 deletions(-) diff --git a/tomcat/README.md b/tomcat/README.md index a68b377430..0ad7b67bf2 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -1,5 +1,5 @@ ## 1단계 - HTTP 서버 구현하기 - [x] GET /index.html 응답하기 -- [ ] CSS 지원하기 -- [ ] Query String 파싱 +- [x] CSS 지원하기 +- [x] 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 462fe988c4..183ff88092 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,5 +1,6 @@ package org.apache.coyote.http11; +import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; import java.io.BufferedReader; import java.io.IOException; @@ -41,10 +42,10 @@ public void process(final Socket connection) { int readByteCount = inputStream.read(bytes); String data = new String(bytes, 0, readByteCount, StandardCharsets.UTF_8); - String referer = extractReferer(data); + String uri = extractReferer(data); // default page - if (referer.equals("/")) { + if (uri.equals("/")) { final var responseBody = "Hello world!"; final var response = String.join("\r\n", @@ -59,47 +60,95 @@ public void process(final Socket connection) { return; } - // index.html file read - String fileName = referer.substring(1); + // static file page + if (uri.endsWith(".html")) { + String fileName = uri.substring(1); - System.out.println(fileName); - URL resource = getClass().getResource("/static/" + fileName); + URL resource = getClass().getResource("/static/" + fileName); - Path path = null; - try { - path = Path.of(resource.toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); + Path pt = null; + try { + pt = Path.of(resource.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { + List actual = bufferedReader.lines().toList(); + + String collect = actual.stream() + .collect(Collectors.joining("\n")) + "\n"; + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + collect.getBytes().length + " ", + "", + collect); + + outputStream.write(response.getBytes()); + outputStream.flush(); + + return; + } catch (Exception e) { + } } - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List actual = bufferedReader.lines().toList(); + // query string parse + if (uri.startsWith("/login")) { + int index = uri.indexOf("?"); + String fileName = uri.substring(1, index) + ".html"; + String queryString = uri.substring(index + 1); - String collect = actual.stream() - .collect(Collectors.joining("\n")) + "\n"; + URL resource = getClass().getResource("/static/" + fileName); - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + collect.getBytes().length + " ", - "", - collect); + Path pt = null; + try { + pt = Path.of(resource.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { + try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { + List actual = bufferedReader.lines().toList(); + + String collect = actual.stream() + .collect(Collectors.joining("\n")) + "\n"; + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + collect.getBytes().length + " ", + "", + collect); + + outputStream.write(response.getBytes()); + outputStream.flush(); + + } catch (Exception e) { + } + + int index2 = queryString.indexOf("&"); + String accountValue = queryString.substring("account=".length(), index2); + String passwordValue = queryString.substring(index2 + 1 + "password=".length()); + + InMemoryUserRepository.findByAccount(accountValue) + .ifPresent(savedUser -> { + log.info("user : {}", savedUser); + }); } - } catch (IOException | UncheckedServletException e) { - log.error(e.getMessage(), e); - } + } catch(IOException |UncheckedServletException e) + { + log.error(e.getMessage(), e); } +} - private static String extractReferer(String httpRequest) { - String firstLine = httpRequest.split("\n")[0]; - String[] split = firstLine.split(" "); - if (split[0].equals("GET")) { - return split[1]; - } - throw new IllegalArgumentException("GET 요청만 처리 가능.."); +private static String extractReferer(String httpRequest) { + String firstLine = httpRequest.split("\n")[0]; + String[] split = firstLine.split(" "); + if (split[0].equals("GET")) { + return split[1]; } + throw new IllegalArgumentException("GET 요청만 처리 가능.."); +} } From 0bce03c5ac8378d5e1907bc1dfdd0249a5b39f77 Mon Sep 17 00:00:00 2001 From: mzeong Date: Thu, 5 Sep 2024 10:36:24 +0900 Subject: [PATCH 07/22] =?UTF-8?q?docs:=202=EB=8B=A8=EA=B3=84=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tomcat/README.md b/tomcat/README.md index 0ad7b67bf2..cfbca773fe 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -3,3 +3,20 @@ - [x] GET /index.html 응답하기 - [x] CSS 지원하기 - [x] Query String 파싱 + +## 2단계 - 로그인 구현하기 + +- [ ] HTTP Status Code 302 + - [ ] 로그인 성공 시 http status code 302 반환 + - [ ] 로그인 성공 시 `/index.html`로 리다이렉트, 실패 시 `/401.html`로 리다이렉트 +- [ ] POST 방식으로 회원가입 + - [ ] `/register` 접속하면 회원가입 페이지(register.html) 응답 + - [ ] 회원가입 페이지를 보여줄 때는 GET 사용, 회원가입 버튼을 누르면 POST 사용 + - [ ] 회원가입 완료 시 `/index.html`로 리다이렉트 + - [ ] 로그인 페이지에서도 로그인 버튼을 누르면 POST 사용 +- [ ] Cookie에 JSESSIONID 값 저장하기 + - [ ] Cookie 클래스 추가 + - [ ] HTTP Request Header의 Cookie에 JSESSIONID가 없으면 Set-Cookie 반환 +- [ ] Session 구현하기 + - [ ] 로그인 성공 시 Session 객체의 값으로 User 객체 저장 + - [ ] 로그인된 상태에서 `/login` 접속하면 `index.html`로 리다이렉트 From 10a580c1c26a2d59aaebe6f7281dda4a7cb0d33a Mon Sep 17 00:00:00 2001 From: mzeong Date: Thu, 5 Sep 2024 13:09:48 +0900 Subject: [PATCH 08/22] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20302=20=EB=B0=98=ED=99=98=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=97=AC=EB=B6=80=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=EB=8B=A4=EB=A5=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 6 +-- .../apache/coyote/http11/Http11Processor.java | 53 +++++++++++-------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/tomcat/README.md b/tomcat/README.md index cfbca773fe..57533c9299 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -6,9 +6,9 @@ ## 2단계 - 로그인 구현하기 -- [ ] HTTP Status Code 302 - - [ ] 로그인 성공 시 http status code 302 반환 - - [ ] 로그인 성공 시 `/index.html`로 리다이렉트, 실패 시 `/401.html`로 리다이렉트 +- [x] HTTP Status Code 302 + - [x] 로그인 성공 시 http status code 302 반환 + - [x] 로그인 성공 시 `/index.html`로 리다이렉트, 실패 시 `/401.html`로 리다이렉트 - [ ] POST 방식으로 회원가입 - [ ] `/register` 접속하면 회원가입 페이지(register.html) 응답 - [ ] 회원가입 페이지를 보여줄 때는 GET 사용, 회원가입 버튼을 누르면 POST 사용 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 183ff88092..8e57623f40 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -2,6 +2,7 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; +import com.techcourse.model.User; import java.io.BufferedReader; import java.io.IOException; import java.net.Socket; @@ -11,6 +12,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.coyote.Processor; import org.slf4j.Logger; @@ -109,14 +111,33 @@ public void process(final Socket connection) { throw new RuntimeException(e); } + int index2 = queryString.indexOf("&"); + String accountValue = queryString.substring("account=".length(), index2); + String passwordValue = queryString.substring(index2 + 1 + "password=".length()); + + boolean isValidPassword = false; + Optional savedUser = InMemoryUserRepository.findByAccount(accountValue); + if (savedUser.isPresent()) { + log.info("user : {}", savedUser); + isValidPassword = savedUser.get().checkPassword(passwordValue); + } + try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { List actual = bufferedReader.lines().toList(); String collect = actual.stream() .collect(Collectors.joining("\n")) + "\n"; +// final var response = String.join("\r\n", +// "HTTP/1.1 200 OK ", +// "Content-Type: text/html;charset=utf-8 ", +// "Content-Length: " + collect.getBytes().length + " ", +// "", +// collect); + final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", + "HTTP/1.1 302 Found ", + "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), "Content-Type: text/html;charset=utf-8 ", "Content-Length: " + collect.getBytes().length + " ", "", @@ -127,28 +148,18 @@ public void process(final Socket connection) { } catch (Exception e) { } - - int index2 = queryString.indexOf("&"); - String accountValue = queryString.substring("account=".length(), index2); - String passwordValue = queryString.substring(index2 + 1 + "password=".length()); - - InMemoryUserRepository.findByAccount(accountValue) - .ifPresent(savedUser -> { - log.info("user : {}", savedUser); - }); } - } catch(IOException |UncheckedServletException e) - { - log.error(e.getMessage(), e); + } catch (IOException | UncheckedServletException e) { + log.error(e.getMessage(), e); + } } -} -private static String extractReferer(String httpRequest) { - String firstLine = httpRequest.split("\n")[0]; - String[] split = firstLine.split(" "); - if (split[0].equals("GET")) { - return split[1]; + private static String extractReferer(String httpRequest) { + String firstLine = httpRequest.split("\n")[0]; + String[] split = firstLine.split(" "); + if (split[0].equals("GET")) { + return split[1]; + } + throw new IllegalArgumentException("GET 요청만 처리 가능.."); } - throw new IllegalArgumentException("GET 요청만 처리 가능.."); -} } From 52331b9006d740c22d903664c81b62faccc80fbc Mon Sep 17 00:00:00 2001 From: mzeong Date: Fri, 6 Sep 2024 00:03:43 +0900 Subject: [PATCH 09/22] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=B0=8F=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 6 +- .../apache/coyote/http11/Http11Processor.java | 244 ++++++++++++------ .../org/apache/coyote/http11/HttpHeaders.java | 32 +++ .../org/apache/coyote/http11/HttpRequest.java | 77 ++++++ .../coyote/http11/HttpRequestMethod.java | 16 ++ .../coyote/http11/HttpRequestParser.java | 30 +++ 6 files changed, 327 insertions(+), 78 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMethod.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java diff --git a/tomcat/README.md b/tomcat/README.md index 57533c9299..8687a2be0e 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -10,9 +10,9 @@ - [x] 로그인 성공 시 http status code 302 반환 - [x] 로그인 성공 시 `/index.html`로 리다이렉트, 실패 시 `/401.html`로 리다이렉트 - [ ] POST 방식으로 회원가입 - - [ ] `/register` 접속하면 회원가입 페이지(register.html) 응답 - - [ ] 회원가입 페이지를 보여줄 때는 GET 사용, 회원가입 버튼을 누르면 POST 사용 - - [ ] 회원가입 완료 시 `/index.html`로 리다이렉트 + - [x] `/register` 접속하면 회원가입 페이지(register.html) 응답 + - [x] 회원가입 페이지를 보여줄 때는 GET 사용, 회원가입 버튼을 누르면 POST 사용 + - [x] 회원가입 완료 시 `/index.html`로 리다이렉트 - [ ] 로그인 페이지에서도 로그인 버튼을 누르면 POST 사용 - [ ] Cookie에 JSESSIONID 값 저장하기 - [ ] 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 8e57623f40..9ec2b71056 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -8,11 +8,9 @@ import java.net.Socket; import java.net.URISyntaxException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import org.apache.coyote.Processor; import org.slf4j.Logger; @@ -39,118 +37,214 @@ public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { + HttpRequest request = HttpRequestParser.parse(inputStream); + + log.info("request: {}", request); + // inputstream read - byte[] bytes = new byte[18000]; - int readByteCount = inputStream.read(bytes); - String data = new String(bytes, 0, readByteCount, StandardCharsets.UTF_8); +// byte[] bytes = new byte[18000]; +// int readByteCount = inputStream.read(bytes); +// String data = new String(bytes, 0, readByteCount, StandardCharsets.UTF_8); +// +// String uri = extractReferer(data); + URL resource = getUrl(request); - String uri = extractReferer(data); + if (request.isGetMethod() && resource != null) { // html, js, css 인 경우 - // default page - if (uri.equals("/")) { - final var responseBody = "Hello world!"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - - outputStream.write(response.getBytes()); - outputStream.flush(); - return; - } + Path path = Path.of(resource.toURI()); + String contentType = Files.probeContentType(path); - // static file page - if (uri.endsWith(".html")) { - String fileName = uri.substring(1); + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); - URL resource = getClass().getResource("/static/" + fileName); + String body = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; - Path pt = null; - try { - pt = Path.of(resource.toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + body.getBytes().length + " ", + "", + body); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { } + } + + if (request.isGetMethod() && request.getPath().equals("/register")) { + URL fakeResource = getClass().getResource("/static/register.html"); + Path path = Path.of(fakeResource.toURI()); + String contentType = Files.probeContentType(path); - try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { - List actual = bufferedReader.lines().toList(); + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); - String collect = actual.stream() + String body = rawBody.stream() .collect(Collectors.joining("\n")) + "\n"; final var response = String.join("\r\n", "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + collect.getBytes().length + " ", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + body.getBytes().length + " ", "", - collect); + body); outputStream.write(response.getBytes()); outputStream.flush(); - - return; } catch (Exception e) { } } - // query string parse - if (uri.startsWith("/login")) { - int index = uri.indexOf("?"); - String fileName = uri.substring(1, index) + ".html"; - String queryString = uri.substring(index + 1); - - URL resource = getClass().getResource("/static/" + fileName); + if (request.isPostMethod() && request.getPath().equals("/register")) { + String body = request.getBody(); - Path pt = null; - try { - pt = Path.of(resource.toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } + String[] bodyParts = body.split("&"); + String account = bodyParts[0].substring("account=".length()); + String email = bodyParts[1].substring("email=".length()); + String password = bodyParts[2].substring("password=".length()); - int index2 = queryString.indexOf("&"); - String accountValue = queryString.substring("account=".length(), index2); - String passwordValue = queryString.substring(index2 + 1 + "password=".length()); + InMemoryUserRepository.save(new User(account, email, password)); - boolean isValidPassword = false; - Optional savedUser = InMemoryUserRepository.findByAccount(accountValue); - if (savedUser.isPresent()) { - log.info("user : {}", savedUser); - isValidPassword = savedUser.get().checkPassword(passwordValue); - } + URL fakeResource = getClass().getResource("/static/index.html"); + Path path = Path.of(fakeResource.toURI()); + String contentType = Files.probeContentType(path); - try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { - List actual = bufferedReader.lines().toList(); + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); - String collect = actual.stream() + String newBody = rawBody.stream() .collect(Collectors.joining("\n")) + "\n"; -// final var response = String.join("\r\n", -// "HTTP/1.1 200 OK ", -// "Content-Type: text/html;charset=utf-8 ", -// "Content-Length: " + collect.getBytes().length + " ", -// "", -// collect); - final var response = String.join("\r\n", "HTTP/1.1 302 Found ", - "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + collect.getBytes().length + " ", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + newBody.getBytes().length + " ", "", - collect); + newBody); outputStream.write(response.getBytes()); outputStream.flush(); - } catch (Exception e) { } } + + // default page +// if (uri.equals("/")) { +// final var responseBody = "Hello world!"; +// +// final var response = String.join("\r\n", +// "HTTP/1.1 200 OK ", +// "Content-Type: text/html;charset=utf-8 ", +// "Content-Length: " + responseBody.getBytes().length + " ", +// "", +// responseBody); +// +// outputStream.write(response.getBytes()); +// outputStream.flush(); +// return; +// } +// +// // static file page +// if (uri.endsWith(".html")) { +// String fileName = uri.substring(1); +// +// URL resource = getClass().getResource("/static/" + fileName); +// +// Path pt = null; +// try { +// pt = Path.of(resource.toURI()); +// } catch (URISyntaxException e) { +// throw new RuntimeException(e); +// } +// +// try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { +// List actual = bufferedReader.lines().toList(); +// +// String collect = actual.stream() +// .collect(Collectors.joining("\n")) + "\n"; +// +// final var response = String.join("\r\n", +// "HTTP/1.1 200 OK ", +// "Content-Type: text/html;charset=utf-8 ", +// "Content-Length: " + collect.getBytes().length + " ", +// "", +// collect); +// +// outputStream.write(response.getBytes()); +// outputStream.flush(); +// +// return; +// } catch (Exception e) { +// } +// } +// +// // query string parse +// if (uri.startsWith("/login")) { +// int index = uri.indexOf("?"); +// String fileName = uri.substring(1, index) + ".html"; +// String queryString = uri.substring(index + 1); +// +// URL resource = getClass().getResource("/static/" + fileName); +// +// Path pt = null; +// try { +// pt = Path.of(resource.toURI()); +// } catch (URISyntaxException e) { +// throw new RuntimeException(e); +// } +// +// int index2 = queryString.indexOf("&"); +// String accountValue = queryString.substring("account=".length(), index2); +// String passwordValue = queryString.substring(index2 + 1 + "password=".length()); +// +// boolean isValidPassword = false; +// Optional savedUser = InMemoryUserRepository.findByAccount(accountValue); +// if (savedUser.isPresent()) { +// log.info("user : {}", savedUser); +// isValidPassword = savedUser.get().checkPassword(passwordValue); +// } +// +// try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { +// List actual = bufferedReader.lines().toList(); +// +// String collect = actual.stream() +// .collect(Collectors.joining("\n")) + "\n"; +// +//// final var response = String.join("\r\n", +//// "HTTP/1.1 200 OK ", +//// "Content-Type: text/html;charset=utf-8 ", +//// "Content-Length: " + collect.getBytes().length + " ", +//// "", +//// collect); +// +// final var response = String.join("\r\n", +// "HTTP/1.1 302 Found ", +// "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), +// "Content-Type: text/html;charset=utf-8 ", +// "Content-Length: " + collect.getBytes().length + " ", +// "", +// collect); +// +// outputStream.write(response.getBytes()); +// outputStream.flush(); +// +// } catch (Exception e) { +// } +// } } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private URL getUrl(HttpRequest request) { + try { + return getClass().getResource("/static" + request.getPath()); + } catch (Exception e) { + return null; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java new file mode 100644 index 0000000000..ef51b9f238 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -0,0 +1,32 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class HttpHeaders { + + private final Map fields; + + public HttpHeaders() { + this.fields = new HashMap<>(); + } + + public void add(String headerLine) { + String[] headerParts = headerLine.split(": ", 2); + fields.put(headerParts[0], headerParts[1]); + } + + public int findContentLength() { + if (fields.containsKey("Content-Length")) { + return Integer.parseInt(fields.get("Content-Length")); + } + return 0; + } + + @Override + public String toString() { + return "HttpHeaders{" + + "fields=" + fields + + '}'; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java new file mode 100644 index 0000000000..004c90bdb6 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -0,0 +1,77 @@ +package org.apache.coyote.http11; + + +public class HttpRequest { + + private final HttpRequestMethod method; + private final String path; + private final String protocolVersion; + private final HttpHeaders headers; + private String body; + + public HttpRequest(String requestLine) { + String[] startLineParts = requestLine.split(" ", 3); + + this.method = HttpRequestMethod.valueOf(startLineParts[0]); + this.path = startLineParts[1]; + this.protocolVersion = startLineParts[2]; + this.headers = new HttpHeaders(); + this.body = null; + } + + public void addHeader(String headerLine) { + headers.add(headerLine); + } + + public void addBody(String body) { + this.body = body; + } + + public HttpRequestMethod getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public String getProtocolVersion() { + return protocolVersion; + } + + public HttpHeaders getHeaders() { + return headers; + } + + public String getBody() { + return body; + } + + public int getContentLength() { + return headers.findContentLength(); + } + + + public boolean isGetMethod() { + return method.isGet(); + } + + public boolean isPostMethod() { + return method.isPost(); + } + + public boolean isHtmlPath() { + return path.endsWith(".html"); + } + + @Override + public String toString() { + return "HttpRequest{" + + "method=" + method + + ", path='" + path + '\'' + + ", protocolVersion='" + protocolVersion + '\'' + + ", headers=" + headers + + ", body='" + body + '\'' + + '}'; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMethod.java new file mode 100644 index 0000000000..cf3dd64ecf --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMethod.java @@ -0,0 +1,16 @@ +package org.apache.coyote.http11; + +public enum HttpRequestMethod { + + GET, + POST, + ; + + public boolean isGet() { + return this == GET; + } + + public boolean isPost() { + return this == POST; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java new file mode 100644 index 0000000000..82aa001080 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class HttpRequestParser { + + public static HttpRequest parse(InputStream inputStream) throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + + String requestLine = bufferedReader.readLine(); + HttpRequest request = new HttpRequest(requestLine); + + String headerLine; + while (!(headerLine = bufferedReader.readLine()).isEmpty()) { + request.addHeader(headerLine); + } + + if (request.isPostMethod()) { + int contentLength = request.getContentLength(); + char[] rawBody = new char[contentLength]; + bufferedReader.read(rawBody, 0, contentLength); + request.addBody(new String(rawBody)); + } + + return request; + } +} From 1abe95182ceedb3b30f259a80175bb7b5a5660cc Mon Sep 17 00:00:00 2001 From: mzeong Date: Fri, 6 Sep 2024 00:14:47 +0900 Subject: [PATCH 10/22] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=A9=94=EC=84=9C=EB=93=9C=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 --- tomcat/README.md | 4 +- .../apache/coyote/http11/Http11Processor.java | 125 +++++++++++++----- tomcat/src/main/resources/static/login.html | 2 +- 3 files changed, 92 insertions(+), 39 deletions(-) diff --git a/tomcat/README.md b/tomcat/README.md index 8687a2be0e..9643c48eee 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -9,11 +9,11 @@ - [x] HTTP Status Code 302 - [x] 로그인 성공 시 http status code 302 반환 - [x] 로그인 성공 시 `/index.html`로 리다이렉트, 실패 시 `/401.html`로 리다이렉트 -- [ ] POST 방식으로 회원가입 +- [x] POST 방식으로 회원가입 - [x] `/register` 접속하면 회원가입 페이지(register.html) 응답 - [x] 회원가입 페이지를 보여줄 때는 GET 사용, 회원가입 버튼을 누르면 POST 사용 - [x] 회원가입 완료 시 `/index.html`로 리다이렉트 - - [ ] 로그인 페이지에서도 로그인 버튼을 누르면 POST 사용 + - [x] 로그인 페이지에서도 로그인 버튼을 누르면 POST 사용 - [ ] Cookie에 JSESSIONID 값 저장하기 - [ ] Cookie 클래스 추가 - [ ] HTTP Request Header의 Cookie에 JSESSIONID가 없으면 Set-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 9ec2b71056..60e28ebecb 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -11,6 +11,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.coyote.Processor; import org.slf4j.Logger; @@ -73,6 +74,30 @@ public void process(final Socket connection) { } } + if (request.isGetMethod() && request.getPath().equals("/")) { + URL fakeResource = getClass().getResource("/static/index.html"); + Path path = Path.of(fakeResource.toURI()); + String contentType = Files.probeContentType(path); + + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); + + String body = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + body.getBytes().length + " ", + "", + body); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { + } + } + if (request.isGetMethod() && request.getPath().equals("/register")) { URL fakeResource = getClass().getResource("/static/register.html"); Path path = Path.of(fakeResource.toURI()); @@ -119,6 +144,7 @@ public void process(final Socket connection) { final var response = String.join("\r\n", "HTTP/1.1 302 Found ", + "Location: http://localhost:8080/index.html", "Content-Type: " + contentType + ";charset=utf-8 ", "Content-Length: " + newBody.getBytes().length + " ", "", @@ -181,11 +207,33 @@ public void process(final Socket connection) { // } // // // query string parse -// if (uri.startsWith("/login")) { + if (request.isGetMethod() && request.getPath().equals("/login")) { + URL fakeResource = getClass().getResource("/static/login.html"); + Path path = Path.of(fakeResource.toURI()); + String contentType = Files.probeContentType(path); + + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); + + String body = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + body.getBytes().length + " ", + "", + body); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { + } + // int index = uri.indexOf("?"); // String fileName = uri.substring(1, index) + ".html"; // String queryString = uri.substring(index + 1); -// + // URL resource = getClass().getResource("/static/" + fileName); // // Path pt = null; @@ -199,40 +247,45 @@ public void process(final Socket connection) { // String accountValue = queryString.substring("account=".length(), index2); // String passwordValue = queryString.substring(index2 + 1 + "password=".length()); // -// boolean isValidPassword = false; -// Optional savedUser = InMemoryUserRepository.findByAccount(accountValue); -// if (savedUser.isPresent()) { -// log.info("user : {}", savedUser); -// isValidPassword = savedUser.get().checkPassword(passwordValue); -// } -// -// try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { -// List actual = bufferedReader.lines().toList(); -// -// String collect = actual.stream() -// .collect(Collectors.joining("\n")) + "\n"; -// -//// final var response = String.join("\r\n", -//// "HTTP/1.1 200 OK ", -//// "Content-Type: text/html;charset=utf-8 ", -//// "Content-Length: " + collect.getBytes().length + " ", -//// "", -//// collect); -// -// final var response = String.join("\r\n", -// "HTTP/1.1 302 Found ", -// "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), -// "Content-Type: text/html;charset=utf-8 ", -// "Content-Length: " + collect.getBytes().length + " ", -// "", -// collect); -// -// outputStream.write(response.getBytes()); -// outputStream.flush(); -// -// } catch (Exception e) { -// } -// } + } + + if (request.isPostMethod() && request.getPath().equals("/login")) { + String body = request.getBody(); + + String[] bodyParts = body.split("&"); + String account = bodyParts[0].substring("account=".length()); + String password = bodyParts[1].substring("password=".length()); + + boolean isValidPassword = false; + Optional savedUser = InMemoryUserRepository.findByAccount(account); + if (savedUser.isPresent()) { + log.info("user : {}", savedUser); + isValidPassword = savedUser.get().checkPassword(password); + } + + URL fakeResource = getClass().getResource("/static/index.html"); + Path path = Path.of(fakeResource.toURI()); + String contentType = Files.probeContentType(path); + + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); + + String newBody = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; + + final var response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + newBody.getBytes().length + " ", + "", + newBody); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { + } + } } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } catch (URISyntaxException e) { 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 a68de82501920f72c67b7747ed29edcac75e1595 Mon Sep 17 00:00:00 2001 From: mzeong Date: Fri, 6 Sep 2024 01:26:30 +0900 Subject: [PATCH 11/22] =?UTF-8?q?feat:=20Cookie=EC=97=90=20JSESSIONID=20?= =?UTF-8?q?=EA=B0=92=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 2 +- .../org/apache/coyote/http11/Http11Processor.java | 11 +++++++++++ .../java/org/apache/coyote/http11/HttpHeaders.java | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/tomcat/README.md b/tomcat/README.md index 9643c48eee..c6ec56dbb9 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -16,7 +16,7 @@ - [x] 로그인 페이지에서도 로그인 버튼을 누르면 POST 사용 - [ ] Cookie에 JSESSIONID 값 저장하기 - [ ] Cookie 클래스 추가 - - [ ] HTTP Request Header의 Cookie에 JSESSIONID가 없으면 Set-Cookie 반환 + - [x] HTTP Request Header의 Cookie에 JSESSIONID가 없으면 Set-Cookie 반환 - [ ] Session 구현하기 - [ ] 로그인 성공 시 Session 객체의 값으로 User 객체 저장 - [ ] 로그인된 상태에서 `/login` 접속하면 `index.html`로 리다이렉트 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 60e28ebecb..feb2f07201 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -12,6 +12,8 @@ import java.nio.file.Path; import java.util.List; import java.util.Optional; +import java.util.SplittableRandom; +import java.util.UUID; import java.util.stream.Collectors; import org.apache.coyote.Processor; import org.slf4j.Logger; @@ -263,6 +265,13 @@ public void process(final Socket connection) { isValidPassword = savedUser.get().checkPassword(password); } + boolean jsessionid = request.getHeaders().findInCookie("JSESSIONID"); + String setCookie = null; + if (!jsessionid) { + UUID idValue = UUID.randomUUID(); + setCookie = "Set-Cookie: JSESSIONID=" + idValue; + } + URL fakeResource = getClass().getResource("/static/index.html"); Path path = Path.of(fakeResource.toURI()); String contentType = Files.probeContentType(path); @@ -275,12 +284,14 @@ public void process(final Socket connection) { final var response = String.join("\r\n", "HTTP/1.1 302 Found ", + setCookie, "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), "Content-Type: " + contentType + ";charset=utf-8 ", "Content-Length: " + newBody.getBytes().length + " ", "", newBody); + System.out.println("response = " + response); outputStream.write(response.getBytes()); outputStream.flush(); } catch (Exception e) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index ef51b9f238..e08a32c061 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -23,6 +23,20 @@ public int findContentLength() { return 0; } + public boolean findInCookie(String key) { + if (fields.containsKey("Cookie")) { + String value = fields.get("Cookie"); + + String[] valueParts = value.split("; "); + for (String valuePart : valueParts) { + if (valuePart.startsWith(key + "=")) { + return true; + } + } + } + return false; + } + @Override public String toString() { return "HttpHeaders{" + From ef8e7901a760b76861d61e4d5554ab480f44dd3d Mon Sep 17 00:00:00 2001 From: mzeong Date: Fri, 6 Sep 2024 02:14:49 +0900 Subject: [PATCH 12/22] =?UTF-8?q?feat:=20Session=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=A0=91=EC=86=8D=20=EC=8B=9C=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 6 +-- .../apache/coyote/http11/Http11Processor.java | 43 ++++++++++++++++++- .../org/apache/coyote/http11/HttpHeaders.java | 14 ++++++ .../org/apache/coyote/http11/Session.java | 37 ++++++++++++++++ .../apache/coyote/http11/SessionManager.java | 23 ++++++++++ 5 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Session.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java diff --git a/tomcat/README.md b/tomcat/README.md index c6ec56dbb9..9129a79a6a 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -17,6 +17,6 @@ - [ ] Cookie에 JSESSIONID 값 저장하기 - [ ] Cookie 클래스 추가 - [x] HTTP Request Header의 Cookie에 JSESSIONID가 없으면 Set-Cookie 반환 -- [ ] Session 구현하기 - - [ ] 로그인 성공 시 Session 객체의 값으로 User 객체 저장 - - [ ] 로그인된 상태에서 `/login` 접속하면 `index.html`로 리다이렉트 +- [x] Session 구현하기 + - [x] 로그인 성공 시 Session 객체의 값으로 User 객체 저장 + - [x] 로그인된 상태에서 `/login` 접속하면 `index.html`로 리다이렉트 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 feb2f07201..6f6d14a3b4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -22,6 +22,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + private static final SessionManager sessionManager = new SessionManager(); private final Socket connection; @@ -210,6 +211,37 @@ public void process(final Socket connection) { // // // query string parse if (request.isGetMethod() && request.getPath().equals("/login")) { + boolean isLogined = request.getHeaders().findInCookie("JSESSIONID"); + if (isLogined) { + String jsessionId = request.getHeaders().findJsessionId(); + Session session = sessionManager.findSession(jsessionId); + + URL fakeResource = getClass().getResource("/static/index.html"); + Path path = Path.of(fakeResource.toURI()); + String contentType = Files.probeContentType(path); + + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); + + String newBody = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; + + final var response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Location: http://localhost:8080/index.html", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + newBody.getBytes().length + " ", + "", + newBody); + + System.out.println("response = " + response); + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { + } + } + + URL fakeResource = getClass().getResource("/static/login.html"); Path path = Path.of(fakeResource.toURI()); String contentType = Files.probeContentType(path); @@ -269,6 +301,15 @@ public void process(final Socket connection) { String setCookie = null; if (!jsessionid) { UUID idValue = UUID.randomUUID(); + + Session session = new Session(idValue.toString()); + session.setAttribute("user", savedUser.get()); + sessionManager.add(session); + + Session session1 = sessionManager.findSession(idValue.toString()); + Object user = session1.getAttribute("user"); + System.out.println("user = " + user);; + setCookie = "Set-Cookie: JSESSIONID=" + idValue; } @@ -284,7 +325,7 @@ public void process(final Socket connection) { final var response = String.join("\r\n", "HTTP/1.1 302 Found ", - setCookie, + setCookie, // TODO: null이 그대로 응답에 찍힘 "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), "Content-Type: " + contentType + ";charset=utf-8 ", "Content-Length: " + newBody.getBytes().length + " ", diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index e08a32c061..a9b3ef2548 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -43,4 +43,18 @@ public String toString() { "fields=" + fields + '}'; } + + public String findJsessionId() { + if (fields.containsKey("Cookie")) { + String value = fields.get("Cookie"); + + String[] valueParts = value.split("; "); + for (String valuePart : valueParts) { + if (valuePart.startsWith("JSESSION=")) { + return valuePart; + } + } + } + return null; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Session.java b/tomcat/src/main/java/org/apache/coyote/http11/Session.java new file mode 100644 index 0000000000..cb0e029ca4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Session.java @@ -0,0 +1,37 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class Session { + + private final String id; + private final Map values = new HashMap<>(); + + public Session(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public Object getAttribute(String name) { + if (values.containsKey(name)) { + return values.get(name); + } + throw new IllegalArgumentException(name + "인 attribute 가 없습니다."); + } + + 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/coyote/http11/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java new file mode 100644 index 0000000000..947e4d3970 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java @@ -0,0 +1,23 @@ +package org.apache.coyote.http11; + +import jakarta.servlet.http.HttpSession; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class SessionManager { + + private static final Map SESSIONS = new HashMap<>(); + + public void add(Session session) { + SESSIONS.put(session.getId(), session); + } + + public Session findSession(String id) throws IOException { + return SESSIONS.get(id); + } + + public void remove(HttpSession session) { + SESSIONS.remove(session.getId()); + } +} From e8b17c1014600ac418c1fd401830713e6a6780b0 Mon Sep 17 00:00:00 2001 From: mzeong Date: Fri, 6 Sep 2024 16:38:02 +0900 Subject: [PATCH 13/22] =?UTF-8?q?test:=20HTTP=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=ED=95=99=EC=8A=B5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cache/com/example/GreetingController.java | 8 +++---- .../cachecontrol/CacheControlInterceptor.java | 24 +++++++++++++++++++ .../example/cachecontrol/CacheWebConfig.java | 1 + .../example/etag/EtagFilterConfiguration.java | 17 +++++++++---- .../version/CacheBustingWebConfig.java | 5 +++- study/src/main/resources/application.yml | 3 +++ .../{templates => static}/index.html | 0 .../resource-versioning.html | 0 8 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 study/src/main/java/cache/com/example/cachecontrol/CacheControlInterceptor.java rename study/src/main/resources/{templates => static}/index.html (100%) rename study/src/main/resources/{templates => static}/resource-versioning.html (100%) diff --git a/study/src/main/java/cache/com/example/GreetingController.java b/study/src/main/java/cache/com/example/GreetingController.java index c0053cda42..e95565ba1c 100644 --- a/study/src/main/java/cache/com/example/GreetingController.java +++ b/study/src/main/java/cache/com/example/GreetingController.java @@ -1,18 +1,17 @@ package cache.com.example; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; -import jakarta.servlet.http.HttpServletResponse; - @Controller public class GreetingController { @GetMapping("/") public String index() { - return "index"; + return "index.html"; } /** @@ -30,7 +29,8 @@ public String cacheControl(final HttpServletResponse response) { @GetMapping("/etag") public String etag() { - return "index"; + + return "index.html"; } @GetMapping("/resource-versioning") diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheControlInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/CacheControlInterceptor.java new file mode 100644 index 0000000000..02a02a12ae --- /dev/null +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheControlInterceptor.java @@ -0,0 +1,24 @@ +package cache.com.example.cachecontrol; + +import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +public class CacheControlInterceptor implements HandlerInterceptor { + + @Override + public void postHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler, + ModelAndView modelAndView + ) throws Exception { + if (!request.getRequestURI().startsWith(PREFIX_STATIC_RESOURCES)) { + response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, private"); + } + } +} 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..1c8197d1e2 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -9,5 +9,6 @@ public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(new CacheControlInterceptor()); } } 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..9bcea82659 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 static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ShallowEtagHeaderFilter; @Configuration public class EtagFilterConfiguration { -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + FilterRegistrationBean filterRegistrationBean + = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); + filterRegistrationBean.addUrlPatterns("/etag"); + filterRegistrationBean.addUrlPatterns(PREFIX_STATIC_RESOURCES + "/*"); + return filterRegistrationBean; + } } diff --git a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java index 6da6d2c795..c7addcdab4 100644 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java @@ -1,7 +1,9 @@ package cache.com.example.version; +import java.time.Duration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -20,6 +22,7 @@ public CacheBustingWebConfig(ResourceVersion version) { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .addResourceLocations("classpath:/static/") + .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic()); } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index e3503a5fb9..8b74bdfd88 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -8,3 +8,6 @@ server: threads: min-spare: 2 max: 2 + compression: + enabled: true + min-response-size: 10 diff --git a/study/src/main/resources/templates/index.html b/study/src/main/resources/static/index.html similarity index 100% rename from study/src/main/resources/templates/index.html rename to study/src/main/resources/static/index.html diff --git a/study/src/main/resources/templates/resource-versioning.html b/study/src/main/resources/static/resource-versioning.html similarity index 100% rename from study/src/main/resources/templates/resource-versioning.html rename to study/src/main/resources/static/resource-versioning.html From 24a75cee6deebb88af93e7b3da0222925676bbe5 Mon Sep 17 00:00:00 2001 From: mzeong Date: Fri, 6 Sep 2024 17:42:29 +0900 Subject: [PATCH 14/22] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20response=20=EC=B2=98=EB=A6=AC=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 381 ++++++------------ .../org/apache/coyote/http11/HttpHeaders.java | 4 +- 2 files changed, 123 insertions(+), 262 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 6f6d14a3b4..626e748b69 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,7 @@ import com.techcourse.model.User; import java.io.BufferedReader; import java.io.IOException; +import java.io.OutputStream; import java.net.Socket; import java.net.URISyntaxException; import java.net.URL; @@ -12,7 +13,6 @@ import java.nio.file.Path; import java.util.List; import java.util.Optional; -import java.util.SplittableRandom; import java.util.UUID; import java.util.stream.Collectors; import org.apache.coyote.Processor; @@ -43,86 +43,54 @@ public void process(final Socket connection) { HttpRequest request = HttpRequestParser.parse(inputStream); - log.info("request: {}", request); - - // inputstream read -// byte[] bytes = new byte[18000]; -// int readByteCount = inputStream.read(bytes); -// String data = new String(bytes, 0, readByteCount, StandardCharsets.UTF_8); -// -// String uri = extractReferer(data); - URL resource = getUrl(request); - - if (request.isGetMethod() && resource != null) { // html, js, css 인 경우 - + if (request.isGetMethod() && resourceExists(request)) { + URL resource = getClass().getResource("/static" + request.getPath()); Path path = Path.of(resource.toURI()); - String contentType = Files.probeContentType(path); - - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - - String body = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", - body); - - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { - } + serveFile(path, outputStream); } if (request.isGetMethod() && request.getPath().equals("/")) { - URL fakeResource = getClass().getResource("/static/index.html"); - Path path = Path.of(fakeResource.toURI()); - String contentType = Files.probeContentType(path); - - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - - String body = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", - body); - - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { - } + URL resource = getClass().getResource("/static/index.html"); + Path path = Path.of(resource.toURI()); + serveFile(path, outputStream); } - if (request.isGetMethod() && request.getPath().equals("/register")) { - URL fakeResource = getClass().getResource("/static/register.html"); - Path path = Path.of(fakeResource.toURI()); - String contentType = Files.probeContentType(path); - - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - - String body = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", - body); + URL resource = getClass().getResource("/static/register.html"); + Path path = Path.of(resource.toURI()); + serveFile(path, outputStream); + } + if (request.isGetMethod() && request.getPath().startsWith("/login")) { + boolean hasJsessionId = request.getHeaders().findInCookie("JSESSIONID"); + + if (request.getPath().contains("?")) { + int index = request.getPath().indexOf("?"); + String queryString = request.getPath().substring(index + 1); + + index = queryString.indexOf("&"); + String account = queryString.substring("account=".length(), index); + String password = queryString.substring(index + 1 + "password=".length()); + + InMemoryUserRepository.findByAccount(account) + .ifPresent(user -> { + if (user.checkPassword(password)) { + log.info("user : {}", InMemoryUserRepository.findByAccount(account).get()); + } + }); + } - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { + if (hasJsessionId) { + String jsessionId = request.getHeaders().findJsessionId(); + Session session = sessionManager.findSession(jsessionId); + if (session != null) { + URL resource = getClass().getResource("/static/index.html"); + Path path = Path.of(resource.toURI()); + serveFile(path, outputStream, "index.html"); + return; + } } + URL resource = getClass().getResource("/static/login.html"); + Path path = Path.of(resource.toURI()); + serveFile(path, outputStream); } if (request.isPostMethod() && request.getPath().equals("/register")) { @@ -137,163 +105,16 @@ public void process(final Socket connection) { URL fakeResource = getClass().getResource("/static/index.html"); Path path = Path.of(fakeResource.toURI()); - String contentType = Files.probeContentType(path); - - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - - String newBody = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; - - final var response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: http://localhost:8080/index.html", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + newBody.getBytes().length + " ", - "", - newBody); - - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { - } + serveFile(path, outputStream, "index.html"); } - - // default page -// if (uri.equals("/")) { -// final var responseBody = "Hello world!"; -// -// final var response = String.join("\r\n", -// "HTTP/1.1 200 OK ", -// "Content-Type: text/html;charset=utf-8 ", -// "Content-Length: " + responseBody.getBytes().length + " ", -// "", -// responseBody); -// -// outputStream.write(response.getBytes()); -// outputStream.flush(); -// return; -// } -// -// // static file page -// if (uri.endsWith(".html")) { -// String fileName = uri.substring(1); -// -// URL resource = getClass().getResource("/static/" + fileName); -// -// Path pt = null; -// try { -// pt = Path.of(resource.toURI()); -// } catch (URISyntaxException e) { -// throw new RuntimeException(e); -// } -// -// try (BufferedReader bufferedReader = Files.newBufferedReader(pt)) { -// List actual = bufferedReader.lines().toList(); -// -// String collect = actual.stream() -// .collect(Collectors.joining("\n")) + "\n"; -// -// final var response = String.join("\r\n", -// "HTTP/1.1 200 OK ", -// "Content-Type: text/html;charset=utf-8 ", -// "Content-Length: " + collect.getBytes().length + " ", -// "", -// collect); -// -// outputStream.write(response.getBytes()); -// outputStream.flush(); -// -// return; -// } catch (Exception e) { -// } -// } -// -// // query string parse - if (request.isGetMethod() && request.getPath().equals("/login")) { - boolean isLogined = request.getHeaders().findInCookie("JSESSIONID"); - if (isLogined) { - String jsessionId = request.getHeaders().findJsessionId(); - Session session = sessionManager.findSession(jsessionId); - - URL fakeResource = getClass().getResource("/static/index.html"); - Path path = Path.of(fakeResource.toURI()); - String contentType = Files.probeContentType(path); - - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - - String newBody = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; - - final var response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: http://localhost:8080/index.html", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + newBody.getBytes().length + " ", - "", - newBody); - - System.out.println("response = " + response); - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { - } - } - - - URL fakeResource = getClass().getResource("/static/login.html"); - Path path = Path.of(fakeResource.toURI()); - String contentType = Files.probeContentType(path); - - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - - String body = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", - body); - - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { - } - -// int index = uri.indexOf("?"); -// String fileName = uri.substring(1, index) + ".html"; -// String queryString = uri.substring(index + 1); - -// URL resource = getClass().getResource("/static/" + fileName); -// -// Path pt = null; -// try { -// pt = Path.of(resource.toURI()); -// } catch (URISyntaxException e) { -// throw new RuntimeException(e); -// } -// -// int index2 = queryString.indexOf("&"); -// String accountValue = queryString.substring("account=".length(), index2); -// String passwordValue = queryString.substring(index2 + 1 + "password=".length()); -// - } - if (request.isPostMethod() && request.getPath().equals("/login")) { - String body = request.getBody(); - - String[] bodyParts = body.split("&"); + String[] bodyParts = request.getBody().split("&"); String account = bodyParts[0].substring("account=".length()); String password = bodyParts[1].substring("password=".length()); boolean isValidPassword = false; Optional savedUser = InMemoryUserRepository.findByAccount(account); if (savedUser.isPresent()) { - log.info("user : {}", savedUser); isValidPassword = savedUser.get().checkPassword(password); } @@ -301,41 +122,21 @@ public void process(final Socket connection) { String setCookie = null; if (!jsessionid) { UUID idValue = UUID.randomUUID(); - Session session = new Session(idValue.toString()); session.setAttribute("user", savedUser.get()); sessionManager.add(session); - Session session1 = sessionManager.findSession(idValue.toString()); - Object user = session1.getAttribute("user"); - System.out.println("user = " + user);; - + Session session1 = sessionManager.findSession(session.getId()); + System.out.println("session1 = " + session1); setCookie = "Set-Cookie: JSESSIONID=" + idValue; } - URL fakeResource = getClass().getResource("/static/index.html"); - Path path = Path.of(fakeResource.toURI()); - String contentType = Files.probeContentType(path); - - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - - String newBody = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; - - final var response = String.join("\r\n", - "HTTP/1.1 302 Found ", - setCookie, // TODO: null이 그대로 응답에 찍힘 - "Location: http://localhost:8080/" + (isValidPassword ? "index.html" : "401.html"), - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + newBody.getBytes().length + " ", - "", - newBody); - - System.out.println("response = " + response); - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { + URL resource = getClass().getResource("/static/index.html"); + Path path = Path.of(resource.toURI()); + if (isValidPassword) { + serveFile(path, outputStream, "index.html", setCookie); + } else { + serveFile(path, outputStream, "401.html", setCookie); } } } catch (IOException | UncheckedServletException e) { @@ -345,20 +146,80 @@ public void process(final Socket connection) { } } - private URL getUrl(HttpRequest request) { - try { - return getClass().getResource("/static" + request.getPath()); + private void serveFile(Path path, OutputStream outputStream) { + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); + String body = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; + + String contentType = Files.probeContentType(path); + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + body.getBytes().length + " ", + "", + body); + + outputStream.write(response.getBytes()); + outputStream.flush(); } catch (Exception e) { - return null; } } - private static String extractReferer(String httpRequest) { - String firstLine = httpRequest.split("\n")[0]; - String[] split = firstLine.split(" "); - if (split[0].equals("GET")) { - return split[1]; + private void serveFile(Path path, OutputStream outputStream, String locationPath) { + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); + String body = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; + + String contentType = Files.probeContentType(path); + + final var response = String.join("\r\n", + "HTTP/1.1 302 FOUND ", + "Location: http://localhost:8080/" + locationPath, + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + body.getBytes().length + " ", + "", + body); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { + } + } + + private void serveFile(Path path, OutputStream outputStream, String locationPath, String setCookie) + throws IOException { + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + List rawBody = bufferedReader.lines().toList(); + String body = rawBody.stream() + .collect(Collectors.joining("\n")) + "\n"; + + String contentType = Files.probeContentType(path); + + final var response = String.join("\r\n", + "HTTP/1.1 302 FOUND ", + (setCookie != null ? setCookie : ""), + "Location: http://localhost:8080/" + locationPath, + "Content-Type: " + contentType + ";charset=utf-8 ", + "Content-Length: " + body.getBytes().length + " ", + "", + body); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (Exception e) { + } + } + + private boolean resourceExists(HttpRequest request) { + try { + URL resource = getClass().getResource("/static" + request.getPath()); + Path.of(resource.toURI()); + return true; + } catch (Exception e) { + return false; } - throw new IllegalArgumentException("GET 요청만 처리 가능.."); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index a9b3ef2548..d3c10c2ddd 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -50,8 +50,8 @@ public String findJsessionId() { String[] valueParts = value.split("; "); for (String valuePart : valueParts) { - if (valuePart.startsWith("JSESSION=")) { - return valuePart; + if (valuePart.startsWith("JSESSIONID=")) { + return valuePart.substring("JSESSIONID=".length()); } } } From c19a6153241a709483549a070ce10e5e76210d39 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 00:13:34 +0900 Subject: [PATCH 15/22] refactor: Session, SessionManager, Manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 매개변수 타입 수정, final 제거 - SessionManager 싱글톤 적용 --- .../java/org/apache/catalina/Manager.java | 36 ++++++++----------- .../org/apache/coyote/http11/Session.java | 6 ++-- .../apache/coyote/http11/SessionManager.java | 21 +++++++++-- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e69410f6a9..2fef449bfb 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,18 +1,15 @@ package org.apache.catalina; -import jakarta.servlet.http.HttpSession; - import java.io.IOException; +import org.apache.coyote.http11.Session; /** - * A Manager manages the pool of Sessions that are associated with a - * particular Container. Different Manager implementations may support - * value-added features such as the persistent storage of session data, - * as well as migrating sessions for distributable web applications. + * A Manager manages the pool of Sessions that are associated with a particular Container. Different Manager + * implementations may support value-added features such as the persistent storage of session data, as well as migrating + * sessions for distributable web applications. *

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

    *
  • Must implement Lifecycle so that the Context can indicate * that a restart is required. @@ -29,28 +26,23 @@ public interface Manager { * * @param session Session to be added */ - void add(HttpSession session); + void add(Session session); /** - * Return the active Session, associated with this Manager, with the - * specified session id (if any); otherwise return null. + * Return the active Session, associated with this Manager, with the specified session id (if any); otherwise return + * null. * * @param id The session id for the session to be returned - * - * @exception IllegalStateException if a new session cannot be - * instantiated for any reason - * @exception IOException if an input/output error occurs while - * processing this request - * - * @return the request session or {@code null} if a session with the - * requested ID could not be found + * @return the request session or {@code null} if a session with the requested ID could not be found + * @throws IllegalStateException if a new session cannot be instantiated for any reason + * @throws IOException if an input/output error occurs while processing this request */ - HttpSession findSession(String id) throws IOException; + Session findSession(String id) throws IOException; /** * Remove this Session from the active Sessions for this Manager. * * @param session Session to be removed */ - void remove(HttpSession session); + void remove(Session session); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Session.java b/tomcat/src/main/java/org/apache/coyote/http11/Session.java index cb0e029ca4..f45490de3a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Session.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Session.java @@ -20,14 +20,14 @@ public Object getAttribute(String name) { if (values.containsKey(name)) { return values.get(name); } - throw new IllegalArgumentException(name + "인 attribute 가 없습니다."); + throw new IllegalArgumentException("세션 속성을 찾을 수 없습니다. : " + name); } - public void setAttribute(final String name, final Object value) { + public void setAttribute(String name, Object value) { values.put(name, value); } - public void removeAttribute(final String name) { + public void removeAttribute(String name) { values.remove(name); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java index 947e4d3970..7d2849ee20 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java @@ -1,23 +1,38 @@ package org.apache.coyote.http11; -import jakarta.servlet.http.HttpSession; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import org.apache.catalina.Manager; -public class SessionManager { +public class SessionManager implements Manager { private static final Map SESSIONS = new HashMap<>(); + private static SessionManager INSTANCE; + + private SessionManager() { + } + + public static SessionManager getInstance() { + if (INSTANCE == null) { + INSTANCE = new SessionManager(); + } + return INSTANCE; + } + + @Override public void add(Session session) { SESSIONS.put(session.getId(), session); } + @Override public Session findSession(String id) throws IOException { return SESSIONS.get(id); } - public void remove(HttpSession session) { + @Override + public void remove(Session session) { SESSIONS.remove(session.getId()); } } From 40862696bb10aaa1555b2f35a5ec376a7b7e84c3 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 01:05:40 +0900 Subject: [PATCH 16/22] =?UTF-8?q?refactor:=20Request=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HeaderType, HttpCookies, RequestLine 생성 --- .../org/apache/coyote/http11/HeaderType.java | 26 ++++++ .../org/apache/coyote/http11/HttpCookies.java | 29 +++++++ .../org/apache/coyote/http11/HttpHeaders.java | 65 ++++++++------- .../org/apache/coyote/http11/HttpRequest.java | 79 ++++++------------- .../coyote/http11/HttpRequestParser.java | 21 +++-- .../org/apache/coyote/http11/RequestLine.java | 27 +++++++ 6 files changed, 148 insertions(+), 99 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpCookies.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java b/tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java new file mode 100644 index 0000000000..4cac7d340d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java @@ -0,0 +1,26 @@ +package org.apache.coyote.http11; + +import java.util.Arrays; + +public enum HeaderType { + + COOKIE("Cookie"), + CONTENT_LENGTH("Content-Length"), + CONTENT_TYPE("Content-Type"), + SET_COOKIE("Set-Cookie"), + LOCATION("Location"), + ; + + private final String name; + + HeaderType(String name) { + this.name = name; + } + + public static HeaderType findByName(String name) { + return Arrays.stream(values()) + .filter(headerType -> headerType.name.equals(name)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("헤더 타입을 찾을 수 없습니다. : " + name)); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookies.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookies.java new file mode 100644 index 0000000000..48bfcd326d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookies.java @@ -0,0 +1,29 @@ +package org.apache.coyote.http11; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +public class HttpCookies { + + private final Map fields; + + public HttpCookies() { + this.fields = new HashMap<>(); + } + + public HttpCookies(String value) { + this.fields = Arrays.stream(value.split("; ")) + .map(part -> part.split("=", 2)) + .collect(Collectors.toMap(part -> part[0], part -> part[1])); + } + + public Optional findByName(String name) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().equals(name)) + .map(Map.Entry::getValue) + .findAny(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index d3c10c2ddd..cc64e9e061 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -2,59 +2,56 @@ import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Collectors; public class HttpHeaders { - private final Map fields; + private final Map fields; public HttpHeaders() { this.fields = new HashMap<>(); } public void add(String headerLine) { - String[] headerParts = headerLine.split(": ", 2); - fields.put(headerParts[0], headerParts[1]); + String[] parts = headerLine.split(": ", 2); + add(HeaderType.findByName(parts[0]), parts[1]); } - public int findContentLength() { - if (fields.containsKey("Content-Length")) { - return Integer.parseInt(fields.get("Content-Length")); - } - return 0; + public void add(HeaderType headerType, String value) { + fields.put(headerType, value); } - public boolean findInCookie(String key) { - if (fields.containsKey("Cookie")) { - String value = fields.get("Cookie"); + public Optional findByName(HeaderType headerType) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().equals(headerType)) + .map(Entry::getValue) + .findAny(); + } - String[] valueParts = value.split("; "); - for (String valuePart : valueParts) { - if (valuePart.startsWith(key + "=")) { - return true; - } - } + public Optional findCookieByName(String name) { + Optional rawCookies = findByName(HeaderType.COOKIE); + if (rawCookies.isEmpty()) { + return Optional.empty(); } - return false; + + HttpCookies cookies = new HttpCookies(rawCookies.get()); + return cookies.findByName(name); } - @Override - public String toString() { - return "HttpHeaders{" + - "fields=" + fields + - '}'; + public int getContentLength() { + return Integer.parseInt(findByName(HeaderType.CONTENT_LENGTH).orElse("0")); } - public String findJsessionId() { - if (fields.containsKey("Cookie")) { - String value = fields.get("Cookie"); + public String build() { + return fields.entrySet().stream() + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.joining("\r\n")); + } - String[] valueParts = value.split("; "); - for (String valuePart : valueParts) { - if (valuePart.startsWith("JSESSIONID=")) { - return valuePart.substring("JSESSIONID=".length()); - } - } - } - return null; + @Override + public String toString() { + return build(); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index 004c90bdb6..0e23a58e51 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -1,77 +1,50 @@ package org.apache.coyote.http11; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; public class HttpRequest { - private final HttpRequestMethod method; - private final String path; - private final String protocolVersion; + private final RequestLine requestLine; private final HttpHeaders headers; private String body; - public HttpRequest(String requestLine) { - String[] startLineParts = requestLine.split(" ", 3); - - this.method = HttpRequestMethod.valueOf(startLineParts[0]); - this.path = startLineParts[1]; - this.protocolVersion = startLineParts[2]; - this.headers = new HttpHeaders(); - this.body = null; - } - - public void addHeader(String headerLine) { - headers.add(headerLine); - } - - public void addBody(String body) { + public HttpRequest(String rawRequestLine, HttpHeaders headers, String body) { + this.requestLine = new RequestLine(rawRequestLine); + this.headers = headers; this.body = body; } - public HttpRequestMethod getMethod() { - return method; - } - - public String getPath() { - return path; - } - - public String getProtocolVersion() { - return protocolVersion; - } - - public HttpHeaders getHeaders() { - return headers; + public boolean isGetMethod() { + return requestLine.getMethod().isGet(); } - public String getBody() { - return body; + public boolean isPostMethod() { + return requestLine.getMethod().isPost(); } - public int getContentLength() { - return headers.findContentLength(); + public Optional findCookieByName(String name) { + return headers.findCookieByName(name); } - - public boolean isGetMethod() { - return method.isGet(); + public String getPath() { + return requestLine.getPath(); } - public boolean isPostMethod() { - return method.isPost(); + public String getPathWithoutQueryString() { + return getPath().split("\\?")[0]; } - public boolean isHtmlPath() { - return path.endsWith(".html"); - } + public Map getQueryString() { + if (getPath().contains("?")) { + String queryString = getPath().substring(getPathWithoutQueryString().length()); - @Override - public String toString() { - return "HttpRequest{" + - "method=" + method + - ", path='" + path + '\'' + - ", protocolVersion='" + protocolVersion + '\'' + - ", headers=" + headers + - ", body='" + body + '\'' + - '}'; + return Arrays.stream(queryString.split("&")) + .map(query -> query.split("=", 2)) + .collect(Collectors.toMap(parts -> parts[0], parts -> parts[1])); + } + return Map.of(); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java index 82aa001080..4b76239854 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java @@ -11,20 +11,17 @@ public static HttpRequest parse(InputStream inputStream) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String requestLine = bufferedReader.readLine(); - HttpRequest request = new HttpRequest(requestLine); - String headerLine; - while (!(headerLine = bufferedReader.readLine()).isEmpty()) { - request.addHeader(headerLine); - } + HttpHeaders headers = new HttpHeaders(); + bufferedReader.lines() + .takeWhile(line -> !line.isEmpty()) + .forEach(headers::add); - if (request.isPostMethod()) { - int contentLength = request.getContentLength(); - char[] rawBody = new char[contentLength]; - bufferedReader.read(rawBody, 0, contentLength); - request.addBody(new String(rawBody)); - } + int contentLength = headers.getContentLength(); + char[] rawBody = new char[contentLength]; + bufferedReader.read(rawBody, 0, contentLength); + String body = new String(rawBody); - return request; + return new HttpRequest(requestLine, headers, body); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java new file mode 100644 index 0000000000..9b7b3f8af3 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http11; + +public class RequestLine { + + private final HttpRequestMethod method; + private final String path; + private final String protocolVersion; + + public RequestLine(String rawRequestLine) { + String[] parts = rawRequestLine.split(" ", 3); + this.method = HttpRequestMethod.valueOf(parts[0]); + this.path = parts[1]; + this.protocolVersion = parts[2]; + } + + public HttpRequestMethod getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public String getProtocolVersion() { + return protocolVersion; + } +} From bad7ae9ef4c6c9a18959b2e628e507ea4a380e90 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 01:47:48 +0900 Subject: [PATCH 17/22] =?UTF-8?q?refactor:=20Response=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StatusLine, Status 생성 --- .../apache/coyote/http11/HttpResponse.java | 28 ++++++++++++++++++ .../java/org/apache/coyote/http11/Status.java | 23 +++++++++++++++ .../org/apache/coyote/http11/StatusLine.java | 29 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Status.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/StatusLine.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java new file mode 100644 index 0000000000..4179d3b70e --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -0,0 +1,28 @@ +package org.apache.coyote.http11; + +public class HttpResponse { + + private final StatusLine statusLine; + private final HttpHeaders headers; + private String body; + + public HttpResponse(StatusLine statusLine, HttpHeaders headers, String body) { + this.statusLine = statusLine; + this.headers = headers; + this.body = body; + } + + public HttpResponse(StatusLine statusLine, HttpHeaders headers) { + this.statusLine = statusLine; + this.headers = headers; + } + + public String build() { + return statusLine + "\r\n" + headers + "\r\n" + body; + } + + @Override + public String toString() { + return build(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Status.java b/tomcat/src/main/java/org/apache/coyote/http11/Status.java new file mode 100644 index 0000000000..3e7d35fdf5 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Status.java @@ -0,0 +1,23 @@ +package org.apache.coyote.http11; + +public enum Status { + + OK(200, "OK"), + FOUND(302, "Found"); + + private final int code; + private final String message; + + Status(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/StatusLine.java b/tomcat/src/main/java/org/apache/coyote/http11/StatusLine.java new file mode 100644 index 0000000000..79da160d7b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/StatusLine.java @@ -0,0 +1,29 @@ +package org.apache.coyote.http11; + +public class StatusLine { + + private final String protocolVersion; + private final Status status; + + public StatusLine(String protocolVersion, Status status) { + this.protocolVersion = protocolVersion; + this.status = status; + } + + public int getStatusCode() { + return status.getCode(); + } + + public String getStatusMessage() { + return status.getMessage(); + } + + public String build() { + return protocolVersion + " " + getStatusCode() + " " + getStatusMessage(); + } + + @Override + public String toString() { + return build(); + } +} From d910225a551b62e579eb0303c25d4766b9e2e096 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 01:50:03 +0900 Subject: [PATCH 18/22] =?UTF-8?q?refactor:=20Request=20=EB=82=B4=20protoco?= =?UTF-8?q?lVersion,=20body=20=EA=B2=8C=ED=84=B0=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/http11/HttpRequest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index 0e23a58e51..a3483ff1e3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -47,4 +47,14 @@ public Map getQueryString() { } return Map.of(); } + + public String getProtocolVersion() { + return requestLine.getProtocolVersion(); + } + + public Map getBody() { + return Arrays.stream(body.split("&")) + .map(query -> query.split("=", 2)) + .collect(Collectors.toMap(parts -> parts[0], parts -> parts[1])); + } } From 4fbb64d5cbbf1d44c23924d0c1c5094a12d6ebcf Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 01:50:58 +0900 Subject: [PATCH 19/22] =?UTF-8?q?refactor:=20Session=20=EC=A0=95=EC=A0=81?= =?UTF-8?q?=20=ED=8C=A9=ED=84=B0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20SessionManager=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EC=9D=98=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=A0=84=ED=8C=8C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/src/main/java/org/apache/coyote/http11/Session.java | 5 +++++ .../main/java/org/apache/coyote/http11/SessionManager.java | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Session.java b/tomcat/src/main/java/org/apache/coyote/http11/Session.java index f45490de3a..25824d72a7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Session.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Session.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.UUID; public class Session { @@ -12,6 +13,10 @@ public Session(String id) { this.id = id; } + public static Session create() { + return new Session(UUID.randomUUID().toString()); + } + public String getId() { return id; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java index 7d2849ee20..c5b4260015 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java @@ -1,6 +1,5 @@ package org.apache.coyote.http11; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.catalina.Manager; @@ -27,7 +26,7 @@ public void add(Session session) { } @Override - public Session findSession(String id) throws IOException { + public Session findSession(String id) { return SESSIONS.get(id); } From 9f711832da070dd31ebe00329d6aebd6e293bf69 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 01:52:13 +0900 Subject: [PATCH 20/22] =?UTF-8?q?refactor:=20Http11Processor=20=EB=82=B4?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 320 +++++++++--------- 1 file changed, 159 insertions(+), 161 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 626e748b69..ca99b1a38c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -5,15 +5,15 @@ import com.techcourse.model.User; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.UUID; import java.util.stream.Collectors; import org.apache.coyote.Processor; import org.slf4j.Logger; @@ -22,7 +22,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final SessionManager sessionManager = new SessionManager(); + private static final SessionManager sessionManager = SessionManager.getInstance(); private final Socket connection; @@ -37,189 +37,187 @@ public void run() { } @Override - public void process(final Socket connection) { - try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { - + public void process(Socket connection) { + try (InputStream inputStream = connection.getInputStream(); + OutputStream outputStream = connection.getOutputStream()) { HttpRequest request = HttpRequestParser.parse(inputStream); + HttpResponse response = handleRequest(request); + outputStream.write(response.build().getBytes()); + outputStream.flush(); + } catch (IOException | UncheckedServletException exception) { + log.error(exception.getMessage(), exception); + } + } - if (request.isGetMethod() && resourceExists(request)) { - URL resource = getClass().getResource("/static" + request.getPath()); - Path path = Path.of(resource.toURI()); - serveFile(path, outputStream); - } + private HttpResponse handleRequest(HttpRequest request) { + if (request.isGetMethod()) { + return handleGetRequest(request); + } else if (request.isPostMethod()) { + return handlePostRequest(request); + } + throw new IllegalArgumentException("처리할 수 없는 요청입니다."); + } - if (request.isGetMethod() && request.getPath().equals("/")) { - URL resource = getClass().getResource("/static/index.html"); - Path path = Path.of(resource.toURI()); - serveFile(path, outputStream); - } - if (request.isGetMethod() && request.getPath().equals("/register")) { - URL resource = getClass().getResource("/static/register.html"); - Path path = Path.of(resource.toURI()); - serveFile(path, outputStream); - } - if (request.isGetMethod() && request.getPath().startsWith("/login")) { - boolean hasJsessionId = request.getHeaders().findInCookie("JSESSIONID"); - - if (request.getPath().contains("?")) { - int index = request.getPath().indexOf("?"); - String queryString = request.getPath().substring(index + 1); - - index = queryString.indexOf("&"); - String account = queryString.substring("account=".length(), index); - String password = queryString.substring(index + 1 + "password=".length()); - - InMemoryUserRepository.findByAccount(account) - .ifPresent(user -> { - if (user.checkPassword(password)) { - log.info("user : {}", InMemoryUserRepository.findByAccount(account).get()); - } - }); - } - - if (hasJsessionId) { - String jsessionId = request.getHeaders().findJsessionId(); - Session session = sessionManager.findSession(jsessionId); - if (session != null) { - URL resource = getClass().getResource("/static/index.html"); - Path path = Path.of(resource.toURI()); - serveFile(path, outputStream, "index.html"); - return; - } - } - URL resource = getClass().getResource("/static/login.html"); - Path path = Path.of(resource.toURI()); - serveFile(path, outputStream); - } + private HttpResponse handleGetRequest(HttpRequest request) { + if (hasPath(request)) { + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); + String body = buildResponseBody(request.getPathWithoutQueryString()); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue(request.getPathWithoutQueryString())); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + return new HttpResponse(statusLine, headers, body); + } - if (request.isPostMethod() && request.getPath().equals("/register")) { - String body = request.getBody(); + if (request.getPath().equals("/")) { + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); + String body = buildResponseBody("/index.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + return new HttpResponse(statusLine, headers, body); + } + if (request.getPath().equals("/register")) { + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); + String body = buildResponseBody("/register.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/register.html")); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + return new HttpResponse(statusLine, headers, body); + } + if (request.getPathWithoutQueryString().equals("/login")) { + parseQueryString(request); + + if (isLoggedIn(request)) { + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); + String body = buildResponseBody("/index.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + return new HttpResponse(statusLine, headers, body); + } - String[] bodyParts = body.split("&"); - String account = bodyParts[0].substring("account=".length()); - String email = bodyParts[1].substring("email=".length()); - String password = bodyParts[2].substring("password=".length()); + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); + String body = buildResponseBody("/login.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/login.html")); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + return new HttpResponse(statusLine, headers, body); + } - InMemoryUserRepository.save(new User(account, email, password)); + throw new IllegalArgumentException("처리할 수 없는 GET 요청입니다."); + } - URL fakeResource = getClass().getResource("/static/index.html"); - Path path = Path.of(fakeResource.toURI()); - serveFile(path, outputStream, "index.html"); - } - if (request.isPostMethod() && request.getPath().equals("/login")) { - String[] bodyParts = request.getBody().split("&"); - String account = bodyParts[0].substring("account=".length()); - String password = bodyParts[1].substring("password=".length()); - - boolean isValidPassword = false; - Optional savedUser = InMemoryUserRepository.findByAccount(account); - if (savedUser.isPresent()) { - isValidPassword = savedUser.get().checkPassword(password); - } - - boolean jsessionid = request.getHeaders().findInCookie("JSESSIONID"); - String setCookie = null; - if (!jsessionid) { - UUID idValue = UUID.randomUUID(); - Session session = new Session(idValue.toString()); - session.setAttribute("user", savedUser.get()); - sessionManager.add(session); - - Session session1 = sessionManager.findSession(session.getId()); - System.out.println("session1 = " + session1); - setCookie = "Set-Cookie: JSESSIONID=" + idValue; - } - - URL resource = getClass().getResource("/static/index.html"); - Path path = Path.of(resource.toURI()); - if (isValidPassword) { - serveFile(path, outputStream, "index.html", setCookie); - } else { - serveFile(path, outputStream, "401.html", setCookie); - } - } - } catch (IOException | UncheckedServletException e) { - log.error(e.getMessage(), e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); + private boolean hasPath(HttpRequest request) { + try { + buildPath(request.getPath()); + return true; + } catch (IllegalArgumentException exception) { + return false; } } - private void serveFile(Path path, OutputStream outputStream) { - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - String body = rawBody.stream() + private String buildResponseBody(String path) { + try (BufferedReader bufferedReader = Files.newBufferedReader(buildPath(path))) { + return bufferedReader.lines() .collect(Collectors.joining("\n")) + "\n"; + } catch (IOException e) { + return ""; + } + } - String contentType = Files.probeContentType(path); - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", - body); - - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { + private String buildContentTypeValue(String path) { + try { + return Files.probeContentType(buildPath(path)) + ";charset=utf-8;"; + } catch (IOException exception) { + throw new IllegalArgumentException(exception); } } - private void serveFile(Path path, OutputStream outputStream, String locationPath) { - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - String body = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; + private Path buildPath(String path) { + URL resource = getClass().getResource("/static" + path); + try { + return Path.of(resource.toURI()); + } catch (URISyntaxException exception) { + throw new IllegalArgumentException(exception); + } + } - String contentType = Files.probeContentType(path); + private String buildContentLengthValue(String body) { + return String.valueOf(body.getBytes().length); + } - final var response = String.join("\r\n", - "HTTP/1.1 302 FOUND ", - "Location: http://localhost:8080/" + locationPath, - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", - body); + private void parseQueryString(HttpRequest request) { + Map queryString = request.getQueryString(); + Optional user = InMemoryUserRepository.findByAccount(queryString.get("account")); + if (user.isPresent() && user.get().checkPassword(queryString.get("password"))) { + log.info("user : {}", user.get()); + } + } - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { + private boolean isLoggedIn(HttpRequest request) { + Optional jsessionid = request.findCookieByName("JSESSIONID"); + if (jsessionid.isEmpty()) { + return false; } + Session session = sessionManager.findSession(jsessionid.get()); + log.info("user : {}", session.getAttribute("user")); + return true; } - private void serveFile(Path path, OutputStream outputStream, String locationPath, String setCookie) - throws IOException { - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - List rawBody = bufferedReader.lines().toList(); - String body = rawBody.stream() - .collect(Collectors.joining("\n")) + "\n"; + private HttpResponse handlePostRequest(HttpRequest request) { + if (request.getPath().equals("/register")) { + registerUser(request); + + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); + String body = buildResponseBody("/index.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderType.LOCATION, buildLocationValue("/index.html")); + return new HttpResponse(statusLine, headers, body); + } + if (request.getPath().equals("/login")) { + Map requestBody = request.getBody(); + Optional user = InMemoryUserRepository.findByAccount(requestBody.get("account")); + + if (user.isPresent() && user.get().checkPassword(requestBody.get("password"))) { + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); + String body = buildResponseBody("/index.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderType.LOCATION, buildLocationValue("/index.html")); + headers.add(HeaderType.SET_COOKIE, buildSetCookieValue(user.get())); + return new HttpResponse(statusLine, headers, body); + } + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); + String body = buildResponseBody("/401.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/401.html")); + headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderType.LOCATION, buildLocationValue("/401.html")); + return new HttpResponse(statusLine, headers, body); + } - String contentType = Files.probeContentType(path); + throw new IllegalArgumentException("처리할 수 없는 POST 요청입니다."); + } - final var response = String.join("\r\n", - "HTTP/1.1 302 FOUND ", - (setCookie != null ? setCookie : ""), - "Location: http://localhost:8080/" + locationPath, - "Content-Type: " + contentType + ";charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", - body); + private void registerUser(HttpRequest request) { + Map requestBody = request.getBody(); + String account = requestBody.get("account"); + String password = requestBody.get("password"); + String email = requestBody.get("email"); + InMemoryUserRepository.save(new User(account, password, email)); + } - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (Exception e) { - } + private String buildLocationValue(String path) { + return "http://localhost:8080" + path; } - private boolean resourceExists(HttpRequest request) { - try { - URL resource = getClass().getResource("/static" + request.getPath()); - Path.of(resource.toURI()); - return true; - } catch (Exception e) { - return false; - } + private String buildSetCookieValue(User user) { + Session session = Session.create(); + session.setAttribute("user", user); + sessionManager.add(session); + return "JSESSIONID=" + session.getId(); } } From 543af1a05ec07d22feb6d062fad6194eb8c18bfb Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 14:47:29 +0900 Subject: [PATCH 21/22] =?UTF-8?q?fix:=20=ED=97=A4=EB=8D=94=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=98=A4=EB=8F=99?= =?UTF-8?q?=EC=9E=91,=20=EB=B9=88=20request=20body=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/HeaderKey.java | 21 +++++ .../org/apache/coyote/http11/HeaderType.java | 26 ------ .../apache/coyote/http11/Http11Processor.java | 82 ++++++++++--------- .../org/apache/coyote/http11/HttpHeaders.java | 20 +++-- .../org/apache/coyote/http11/HttpRequest.java | 13 +++ .../coyote/http11/HttpRequestParser.java | 2 +- .../apache/coyote/http11/HttpResponse.java | 2 +- .../org/apache/coyote/http11/RequestLine.java | 4 + 8 files changed, 95 insertions(+), 75 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HeaderKey.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HeaderKey.java b/tomcat/src/main/java/org/apache/coyote/http11/HeaderKey.java new file mode 100644 index 0000000000..a98fd1f9fd --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HeaderKey.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11; + +public enum HeaderKey { + + COOKIE("Cookie"), + CONTENT_LENGTH("Content-Length"), + CONTENT_TYPE("Content-Type"), + SET_COOKIE("Set-Cookie"), + LOCATION("Location"), + ; + + private final String value; + + HeaderKey(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java b/tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java deleted file mode 100644 index 4cac7d340d..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/HeaderType.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.apache.coyote.http11; - -import java.util.Arrays; - -public enum HeaderType { - - COOKIE("Cookie"), - CONTENT_LENGTH("Content-Length"), - CONTENT_TYPE("Content-Type"), - SET_COOKIE("Set-Cookie"), - LOCATION("Location"), - ; - - private final String name; - - HeaderType(String name) { - this.name = name; - } - - public static HeaderType findByName(String name) { - return Arrays.stream(values()) - .filter(headerType -> headerType.name.equals(name)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("헤더 타입을 찾을 수 없습니다. : " + name)); - } -} 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 ca99b1a38c..53c3258fbe 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -59,29 +59,20 @@ private HttpResponse handleRequest(HttpRequest request) { } private HttpResponse handleGetRequest(HttpRequest request) { - if (hasPath(request)) { - StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); - String body = buildResponseBody(request.getPathWithoutQueryString()); - HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue(request.getPathWithoutQueryString())); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); - return new HttpResponse(statusLine, headers, body); - } - if (request.getPath().equals("/")) { StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); String body = buildResponseBody("/index.html"); HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); return new HttpResponse(statusLine, headers, body); } if (request.getPath().equals("/register")) { StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); String body = buildResponseBody("/register.html"); HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/register.html")); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue("/register.html")); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); return new HttpResponse(statusLine, headers, body); } if (request.getPathWithoutQueryString().equals("/login")) { @@ -91,19 +82,27 @@ private HttpResponse handleGetRequest(HttpRequest request) { StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); String body = buildResponseBody("/index.html"); HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); return new HttpResponse(statusLine, headers, body); } StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); String body = buildResponseBody("/login.html"); HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/login.html")); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue("/login.html")); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); return new HttpResponse(statusLine, headers, body); } + if (hasPath(request)) { + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.OK); + String body = buildResponseBody(request.getPathWithoutQueryString()); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue(request.getPathWithoutQueryString())); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); + return new HttpResponse(statusLine, headers, body); + } throw new IllegalArgumentException("처리할 수 없는 GET 요청입니다."); } @@ -120,7 +119,7 @@ private String buildResponseBody(String path) { try (BufferedReader bufferedReader = Files.newBufferedReader(buildPath(path))) { return bufferedReader.lines() .collect(Collectors.joining("\n")) + "\n"; - } catch (IOException e) { + } catch (IOException exception) { return ""; } } @@ -137,7 +136,7 @@ private Path buildPath(String path) { URL resource = getClass().getResource("/static" + path); try { return Path.of(resource.toURI()); - } catch (URISyntaxException exception) { + } catch (NullPointerException | URISyntaxException exception) { throw new IllegalArgumentException(exception); } } @@ -148,6 +147,10 @@ private String buildContentLengthValue(String body) { private void parseQueryString(HttpRequest request) { Map queryString = request.getQueryString(); + if (!queryString.containsKey("account") || !queryString.containsKey("password")) { + return; + } + Optional user = InMemoryUserRepository.findByAccount(queryString.get("account")); if (user.isPresent() && user.get().checkPassword(queryString.get("password"))) { log.info("user : {}", user.get()); @@ -160,8 +163,7 @@ private boolean isLoggedIn(HttpRequest request) { return false; } Session session = sessionManager.findSession(jsessionid.get()); - log.info("user : {}", session.getAttribute("user")); - return true; + return session != null; } private HttpResponse handlePostRequest(HttpRequest request) { @@ -171,31 +173,33 @@ private HttpResponse handlePostRequest(HttpRequest request) { StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); String body = buildResponseBody("/index.html"); HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); - headers.add(HeaderType.LOCATION, buildLocationValue("/index.html")); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderKey.LOCATION, buildLocationValue("/index.html")); return new HttpResponse(statusLine, headers, body); } if (request.getPath().equals("/login")) { Map requestBody = request.getBody(); - Optional user = InMemoryUserRepository.findByAccount(requestBody.get("account")); - - if (user.isPresent() && user.get().checkPassword(requestBody.get("password"))) { - StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); - String body = buildResponseBody("/index.html"); - HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/index.html")); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); - headers.add(HeaderType.LOCATION, buildLocationValue("/index.html")); - headers.add(HeaderType.SET_COOKIE, buildSetCookieValue(user.get())); - return new HttpResponse(statusLine, headers, body); + if (requestBody.containsKey("account") && requestBody.containsKey("password")) { + Optional user = InMemoryUserRepository.findByAccount(requestBody.get("account")); + if (user.isPresent() && user.get().checkPassword(requestBody.get("password"))) { + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); + String body = buildResponseBody("/index.html"); + HttpHeaders headers = new HttpHeaders(); + headers.add(HeaderKey.SET_COOKIE, buildSetCookieValue(user.get())); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue("/index.html")); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderKey.LOCATION, buildLocationValue("/index.html")); + return new HttpResponse(statusLine, headers, ""); + } } + StatusLine statusLine = new StatusLine(request.getProtocolVersion(), Status.FOUND); String body = buildResponseBody("/401.html"); HttpHeaders headers = new HttpHeaders(); - headers.add(HeaderType.CONTENT_TYPE, buildContentTypeValue("/401.html")); - headers.add(HeaderType.CONTENT_LENGTH, buildContentLengthValue(body)); - headers.add(HeaderType.LOCATION, buildLocationValue("/401.html")); + headers.add(HeaderKey.CONTENT_TYPE, buildContentTypeValue("/401.html")); + headers.add(HeaderKey.CONTENT_LENGTH, buildContentLengthValue(body)); + headers.add(HeaderKey.LOCATION, buildLocationValue("/401.html")); return new HttpResponse(statusLine, headers, body); } @@ -218,6 +222,6 @@ private String buildSetCookieValue(User user) { Session session = Session.create(); session.setAttribute("user", user); sessionManager.add(session); - return "JSESSIONID=" + session.getId(); + return "new_id=" + session.getId() + "; Max-Age=2592000"; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index cc64e9e061..dcaf1c7566 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -8,7 +8,7 @@ public class HttpHeaders { - private final Map fields; + private final Map fields; public HttpHeaders() { this.fields = new HashMap<>(); @@ -16,22 +16,26 @@ public HttpHeaders() { public void add(String headerLine) { String[] parts = headerLine.split(": ", 2); - add(HeaderType.findByName(parts[0]), parts[1]); + add(parts[0], parts[1]); } - public void add(HeaderType headerType, String value) { - fields.put(headerType, value); + public void add(String name, String value) { + fields.put(name, value); } - public Optional findByName(HeaderType headerType) { + public void add(HeaderKey headerKey, String value) { + add(headerKey.getValue(), value); + } + + public Optional findByName(HeaderKey headerKey) { return fields.entrySet().stream() - .filter(entry -> entry.getKey().equals(headerType)) + .filter(entry -> entry.getKey().equals(headerKey.getValue())) .map(Entry::getValue) .findAny(); } public Optional findCookieByName(String name) { - Optional rawCookies = findByName(HeaderType.COOKIE); + Optional rawCookies = findByName(HeaderKey.COOKIE); if (rawCookies.isEmpty()) { return Optional.empty(); } @@ -41,7 +45,7 @@ public Optional findCookieByName(String name) { } public int getContentLength() { - return Integer.parseInt(findByName(HeaderType.CONTENT_LENGTH).orElse("0")); + return Integer.parseInt(findByName(HeaderKey.CONTENT_LENGTH).orElse("0")); } public String build() { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index a3483ff1e3..c861eec88e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -53,8 +53,21 @@ public String getProtocolVersion() { } public Map getBody() { + if (body.isEmpty()) { + return Map.of(); + } + return Arrays.stream(body.split("&")) .map(query -> query.split("=", 2)) .collect(Collectors.toMap(parts -> parts[0], parts -> parts[1])); } + + public String build() { + return requestLine.build() + "\r\n" + headers.build() + "\r\n\r\n" + body; + } + + @Override + public String toString() { + return build(); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java index 4b76239854..e2d6305824 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java @@ -14,7 +14,7 @@ public static HttpRequest parse(InputStream inputStream) throws IOException { HttpHeaders headers = new HttpHeaders(); bufferedReader.lines() - .takeWhile(line -> !line.isEmpty()) + .takeWhile(headerLine -> !headerLine.isEmpty()) .forEach(headers::add); int contentLength = headers.getContentLength(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java index 4179d3b70e..3ba7894c1c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -18,7 +18,7 @@ public HttpResponse(StatusLine statusLine, HttpHeaders headers) { } public String build() { - return statusLine + "\r\n" + headers + "\r\n" + body; + return statusLine + "\r\n" + headers + "\r\n\r\n" + body; } @Override diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java index 9b7b3f8af3..1c092013d8 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java @@ -24,4 +24,8 @@ public String getPath() { public String getProtocolVersion() { return protocolVersion; } + + public String build() { + return method + " " + path + " " + protocolVersion; + } } From 130a967a703f9f2f4db44e18140a53048494bb74 Mon Sep 17 00:00:00 2001 From: mzeong Date: Wed, 11 Sep 2024 14:52:38 +0900 Subject: [PATCH 22/22] =?UTF-8?q?docs:=20=EC=99=84=EB=A3=8C=ED=95=9C=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tomcat/README.md b/tomcat/README.md index 9129a79a6a..3a371aea70 100644 --- a/tomcat/README.md +++ b/tomcat/README.md @@ -14,8 +14,8 @@ - [x] 회원가입 페이지를 보여줄 때는 GET 사용, 회원가입 버튼을 누르면 POST 사용 - [x] 회원가입 완료 시 `/index.html`로 리다이렉트 - [x] 로그인 페이지에서도 로그인 버튼을 누르면 POST 사용 -- [ ] Cookie에 JSESSIONID 값 저장하기 - - [ ] Cookie 클래스 추가 +- [x] Cookie에 JSESSIONID 값 저장하기 + - [x] Cookie 클래스 추가 - [x] HTTP Request Header의 Cookie에 JSESSIONID가 없으면 Set-Cookie 반환 - [x] Session 구현하기 - [x] 로그인 성공 시 Session 객체의 값으로 User 객체 저장