From fed02f6f5f4308400e55c160d9495cad010f5bfb Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 11:11:09 +0900 Subject: [PATCH 01/11] fix: remove implementation logback-classic on gradle (#501) --- study/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/study/build.gradle b/study/build.gradle index 5c69542f84..87a1f0313c 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -19,7 +19,6 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'ch.qos.logback:logback-classic:1.5.7' implementation 'org.apache.commons:commons-lang3:3.14.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1' From 7e9135698878932274ddc1f523ba817ed9c56c70 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 13:51:07 +0900 Subject: [PATCH 02/11] 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 21fcd4599e3065d0deccb3b0865ec7abd9de0ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 3 Sep 2024 14:25:54 +0900 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20javax=20->=20jakarta=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=98=EC=97=AC=20=EC=BB=B4=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cache/com/example/GreetingController.java | 3 ++- .../java/cache/com/example/version/ResourceVersion.java | 3 ++- study/src/test/java/study/FileTest.java | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/study/src/main/java/cache/com/example/GreetingController.java b/study/src/main/java/cache/com/example/GreetingController.java index c0053cda42..22a24c36e1 100644 --- a/study/src/main/java/cache/com/example/GreetingController.java +++ b/study/src/main/java/cache/com/example/GreetingController.java @@ -1,11 +1,12 @@ 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 { diff --git a/study/src/main/java/cache/com/example/version/ResourceVersion.java b/study/src/main/java/cache/com/example/version/ResourceVersion.java index 27a2f22813..8c531e3efa 100644 --- a/study/src/main/java/cache/com/example/version/ResourceVersion.java +++ b/study/src/main/java/cache/com/example/version/ResourceVersion.java @@ -2,10 +2,11 @@ import org.springframework.stereotype.Component; -import jakarta.annotation.PostConstruct; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import jakarta.annotation.PostConstruct; + @Component public class ResourceVersion { diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..9639548b31 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,13 +1,13 @@ package study; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.nio.file.Path; import java.util.Collections; import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; /** * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. From 36e4a157cb9f3f74a86f54665564111aae96834b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 3 Sep 2024 14:50:56 +0900 Subject: [PATCH 04/11] =?UTF-8?q?test:=20FileTest=20=ED=95=99=EC=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index 9639548b31..e3f1b0bb88 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -2,8 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -27,8 +28,9 @@ class FileTest { void resource_디렉터리에_있는_파일의_경로를_찾는다() { final String fileName = "nextstep.txt"; - // todo - final String actual = ""; + final var classLoader = getClass().getClassLoader(); + final var url = classLoader.getResource(fileName); + final String actual = url.toString(); assertThat(actual).endsWith(fileName); } @@ -40,14 +42,14 @@ class FileTest { * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws IOException { final String fileName = "nextstep.txt"; - // todo - final Path path = null; + final var classLoader = getClass().getClassLoader(); + final var url = classLoader.getResource(fileName); + final Path path = Path.of(url.getPath()); - // todo - final List actual = Collections.emptyList(); + final List actual = Files.readAllLines(path); assertThat(actual).containsOnly("nextstep"); } From 2088b7af2e9aa7c5f747857225c2fd0035a9ac7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 3 Sep 2024 15:58:44 +0900 Subject: [PATCH 05/11] =?UTF-8?q?test:=20IOStreamTest=20=ED=95=99=EC=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/IOStreamTest.java | 44 ++++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..9c15ad01d1 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -1,14 +1,26 @@ package study; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - /** * 자바는 스트림(Stream)으로부터 I/O를 사용한다. * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. @@ -54,6 +66,7 @@ class OutputStream_학습_테스트 { * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 */ + outputStream.write(bytes); final String actual = outputStream.toString(); assertThat(actual).isEqualTo("nextstep"); @@ -78,6 +91,7 @@ class OutputStream_학습_테스트 { * flush를 사용해서 테스트를 통과시킨다. * ByteArrayOutputStream과 어떤 차이가 있을까? */ + outputStream.flush(); verify(outputStream, atLeastOnce()).flush(); outputStream.close(); @@ -96,6 +110,7 @@ class OutputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (outputStream) {} verify(outputStream, atLeastOnce()).close(); } @@ -128,7 +143,8 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + byte[] stream = inputStream.readAllBytes(); + final String actual = new String(stream, StandardCharsets.UTF_8); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -148,6 +164,7 @@ class InputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (inputStream) {} verify(inputStream, atLeastOnce()).close(); } @@ -169,12 +186,12 @@ class FilterStream_학습_테스트 { * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? */ @Test - void 필터인_BufferedInputStream를_사용해보자() { + void 필터인_BufferedInputStream를_사용해보자() throws IOException { final String text = "필터에 연결해보자."; final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; + final InputStream bufferedInputStream = new BufferedInputStream(inputStream); - final byte[] actual = new byte[0]; + final byte[] actual = bufferedInputStream.readAllBytes(); assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); @@ -197,16 +214,23 @@ class InputStreamReader_학습_테스트 { * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. */ @Test - void BufferedReader를_사용하여_문자열을_읽어온다() { + void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException { final String emoji = String.join("\r\n", "😀😃😄😁😆😅😂🤣🥲☺️😊", "😇🙂🙃😉😌😍🥰😘😗😙😚", "😋😛😝😜🤪🤨🧐🤓😎🥸🤩", ""); final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final BufferedReader bufferedReader = new BufferedReader(inputStreamReader); final StringBuilder actual = new StringBuilder(); + while (bufferedReader.ready()) { + actual.append(bufferedReader.readLine()) + .append("\r\n"); + } + assertThat(actual).hasToString(emoji); } } From a0161f2ae8e95df1bff0c82e9a51619515d51ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 3 Sep 2024 21:44:42 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20GET=20`index.html`=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements.md | 5 +++ .../apache/coyote/http11/Http11Processor.java | 38 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 docs/requirements.md diff --git a/docs/requirements.md b/docs/requirements.md new file mode 100644 index 0000000000..430a1eebb0 --- /dev/null +++ b/docs/requirements.md @@ -0,0 +1,5 @@ +# 기능 요구 사항 + +- [x] 1\. GET `index.html` 응답하기 +- [ ] 2\. CSS 지원하기 +- [ ] 3\. 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..ef56f6c452 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,12 +1,17 @@ package org.apache.coyote.http11; -import com.techcourse.exception.UncheckedServletException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.nio.file.Files; +import java.nio.file.Path; + import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.Socket; +import com.techcourse.exception.UncheckedServletException; public class Http11Processor implements Runnable, Processor { @@ -29,8 +34,33 @@ public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - final var responseBody = "Hello world!"; + // 1. Request + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + + // 1.1. Request Line + final var requestLine = bufferedReader.readLine(); + final var requestLineSplit = requestLine.split(" "); + final var _method = requestLineSplit[0]; + final var path = requestLineSplit[1]; + final var _protocolVersion = requestLineSplit[2]; + + final var filename = path.replace("/", ""); + + // 1.1.1. Find Static Resource + String responseBody; + if (!filename.isEmpty()) { + final var classLoader = getClass().getClassLoader(); + final var url = classLoader.getResource("static/" + filename); + final var resourcePath = Path.of(url.getPath()); + + responseBody = Files.readString(resourcePath); + + } else { + responseBody = "Hello world!"; + } + // 2. Response final var response = String.join("\r\n", "HTTP/1.1 200 OK ", "Content-Type: text/html;charset=utf-8 ", From 0a13b88dcb611939505449e71028db76babb8c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 6 Sep 2024 00:38:39 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20http=20request=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 35 ++-------- .../apache/coyote/http11/http/Headers.java | 22 ++++++ .../coyote/http11/http/HttpRequest.java | 68 +++++++++++++++++++ .../http11/resource/ResourceReader.java | 25 +++++++ .../coyote/http11/Http11ProcessorTest.java | 7 +- 5 files changed, 126 insertions(+), 31 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index ef56f6c452..d79ec7a09a 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,11 @@ package org.apache.coyote.http11; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.net.Socket; -import java.nio.file.Files; -import java.nio.file.Path; import org.apache.coyote.Processor; +import org.apache.coyote.http11.http.HttpRequest; +import org.apache.coyote.http11.resource.ResourceReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,38 +33,19 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream()) { // 1. Request - final var inputStreamReader = new InputStreamReader(inputStream); - final var bufferedReader = new BufferedReader(inputStreamReader); - - // 1.1. Request Line - final var requestLine = bufferedReader.readLine(); - final var requestLineSplit = requestLine.split(" "); - final var _method = requestLineSplit[0]; - final var path = requestLineSplit[1]; - final var _protocolVersion = requestLineSplit[2]; - - final var filename = path.replace("/", ""); + final var request = new HttpRequest(inputStream); // 1.1.1. Find Static Resource - String responseBody; - if (!filename.isEmpty()) { - final var classLoader = getClass().getClassLoader(); - final var url = classLoader.getResource("static/" + filename); - final var resourcePath = Path.of(url.getPath()); - - responseBody = Files.readString(resourcePath); - - } else { - responseBody = "Hello world!"; - } + final var body = ResourceReader.readString(request.getPath()); // 2. Response final var response = String.join("\r\n", "HTTP/1.1 200 OK ", "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", + "Content-Length: " + body.getBytes().length + " ", "", - responseBody); + body + ); outputStream.write(response.getBytes()); outputStream.flush(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java b/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java new file mode 100644 index 0000000000..a0cfbe96a9 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java @@ -0,0 +1,22 @@ +package org.apache.coyote.http11.http; + +import java.util.HashMap; +import java.util.Map; + +public record Headers(Map headers) { + + private static final String DELIMITER = ": "; + + public Headers() { + this(new HashMap<>()); + } + + public void put(final String header) { + final var split = header.split(DELIMITER); + put(split[0], split[1]); + } + + public void put(final String name, final String value) { + headers.put(name, value); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java new file mode 100644 index 0000000000..e5899ef041 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java @@ -0,0 +1,68 @@ +package org.apache.coyote.http11.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.StringJoiner; + +public class HttpRequest { + + private final String method; + private final String path; + private final String protocol; + private final Headers headers; + private final String body; + + public HttpRequest(final InputStream inputStream) throws IOException { + final var reader = new InputStreamReader(inputStream); + final var buffer = new BufferedReader(reader); + final var requestLine = buffer.readLine(); + final var requestLineSplit = requestLine.split(" "); + this.method = requestLineSplit[0]; + this.path = requestLineSplit[1]; + this.protocol = requestLineSplit[2]; + this.headers = createHeaders(buffer); + this.body = createBody(buffer); + } + + private Headers createHeaders(final BufferedReader reader) throws IOException { + final var headers = new Headers(); + var line = reader.readLine(); + while (line != null && !line.isEmpty()) { + headers.put(line); + line = reader.readLine(); + } + return headers; + } + + private String createBody(final BufferedReader reader) throws IOException { + final var body = new StringJoiner("\r\n"); + var line = reader.readLine(); + while (line != null) { + body.add(line); + line = reader.readLine(); + } + return body.toString(); + } + + public String getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public String getProtocol() { + return protocol; + } + + public Headers getHeaders() { + return headers; + } + + public String getBody() { + return body; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java b/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java new file mode 100644 index 0000000000..232033b1d8 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java @@ -0,0 +1,25 @@ +package org.apache.coyote.http11.resource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ResourceReader { + + private static final ClassLoader CLASS_LOADER = ClassLoader.getSystemClassLoader(); + + private static final String ROOT_PATH = "/"; + + private static final String DEFAULT_PATH = "static"; + + private static final String DEFAULT_RESOURCE = "Hello world!"; + + public static String readString(final String resourcePath) throws IOException { + if (resourcePath.equals(ROOT_PATH)) { + return DEFAULT_RESOURCE; + } + final var url = CLASS_LOADER.getResource(DEFAULT_PATH + resourcePath); + final var path = Path.of(url.getPath()); + return Files.readString(path); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java index 2aba8c56e0..3489da1325 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java @@ -1,14 +1,15 @@ package org.apache.coyote.http11; -import org.junit.jupiter.api.Test; -import support.StubSocket; +import static org.assertj.core.api.Assertions.assertThat; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +import support.StubSocket; class Http11ProcessorTest { From 60847894611a1a192c69e4070cf02aeece6aa38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 6 Sep 2024 14:13:23 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20CSS=20=EC=A7=80=EC=9B=90=ED=95=98?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements.md | 3 +- .../apache/coyote/http11/Http11Processor.java | 40 ++++++++++++++----- .../apache/coyote/http11/http/Headers.java | 24 +++++++++-- .../coyote/http11/http/HttpRequest.java | 31 +++++++------- .../coyote/http11/http/HttpResponse.java | 36 +++++++++++++++++ .../coyote/http11/http/HttpStatusCode.java | 23 +++++++++++ .../apache/coyote/http11/http/MediaType.java | 7 ++++ .../http/ResourceNotFoundException.java | 7 ++++ .../http11/resource/ResourceReader.java | 5 +++ .../coyote/http11/Http11ProcessorTest.java | 34 ++++++++++++++-- 10 files changed, 178 insertions(+), 32 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/HttpResponse.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/HttpStatusCode.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java diff --git a/docs/requirements.md b/docs/requirements.md index 430a1eebb0..2e1de907b3 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -1,5 +1,6 @@ # 기능 요구 사항 +## 1단계 - HTTP 서버 구현하기 - [x] 1\. GET `index.html` 응답하기 -- [ ] 2\. CSS 지원하기 +- [x] 2\. CSS 지원하기 - [ ] 3\. 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 d79ec7a09a..8ca0671df4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,10 +1,16 @@ package org.apache.coyote.http11; +import static org.apache.coyote.http11.http.MediaType.TEXT_CSS; +import static org.apache.coyote.http11.http.MediaType.TEXT_HTML; + import java.io.IOException; import java.net.Socket; import org.apache.coyote.Processor; +import org.apache.coyote.http11.http.Headers; import org.apache.coyote.http11.http.HttpRequest; +import org.apache.coyote.http11.http.HttpResponse; +import org.apache.coyote.http11.http.HttpStatusCode; import org.apache.coyote.http11.resource.ResourceReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,25 +38,41 @@ public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - // 1. Request final var request = new HttpRequest(inputStream); + log.info("request: \n{}", request); - // 1.1.1. Find Static Resource final var body = ResourceReader.readString(request.getPath()); - // 2. Response - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + body.getBytes().length + " ", - "", + final var headers = new Headers(); + final var extension = extension(request.getPath()); + final var mediaType = mediaType(extension); + headers.put("Content-Type", mediaType); + headers.put("Content-Type", "charset=utf-8"); + headers.put("Content-Length", String.valueOf(body.getBytes().length)); + + final var httpResponse = new HttpResponse( + request.getHttpVersion(), + HttpStatusCode.OK, + headers, body ); - outputStream.write(response.getBytes()); + outputStream.write(httpResponse.getBytes()); outputStream.flush(); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } + + private String extension(final String path) { + final var index = path.lastIndexOf("."); + return path.substring(index + 1); + } + + private String mediaType(final String extension) { + if (extension.equals("css")) { + return TEXT_CSS; + } + return TEXT_HTML; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java b/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java index a0cfbe96a9..7ccdbd7fa1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java @@ -1,6 +1,6 @@ package org.apache.coyote.http11.http; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; public record Headers(Map headers) { @@ -8,15 +8,31 @@ public record Headers(Map headers) { private static final String DELIMITER = ": "; public Headers() { - this(new HashMap<>()); + this(new LinkedHashMap<>()); } - public void put(final String header) { - final var split = header.split(DELIMITER); + public void put(final String headerLine) { + final var split = headerLine.split(DELIMITER); put(split[0], split[1]); } public void put(final String name, final String value) { + if (headers.containsKey(name)) { + headers.merge(name, value, (v1, v2) -> v1 + ";" + v2); + return; + } headers.put(name, value); } + + @Override + public String toString() { + final var result = new StringBuilder(); + for (String key : headers.keySet()) { + result.append(key) + .append(": ") + .append(headers.get(key)) + .append(" \r\n"); + } + return result.toString(); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java index e5899ef041..f368588041 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java @@ -10,7 +10,7 @@ public class HttpRequest { private final String method; private final String path; - private final String protocol; + private final String httpVersion; private final Headers headers; private final String body; @@ -21,7 +21,7 @@ public HttpRequest(final InputStream inputStream) throws IOException { final var requestLineSplit = requestLine.split(" "); this.method = requestLineSplit[0]; this.path = requestLineSplit[1]; - this.protocol = requestLineSplit[2]; + this.httpVersion = requestLineSplit[2]; this.headers = createHeaders(buffer); this.body = createBody(buffer); } @@ -38,31 +38,32 @@ private Headers createHeaders(final BufferedReader reader) throws IOException { private String createBody(final BufferedReader reader) throws IOException { final var body = new StringJoiner("\r\n"); - var line = reader.readLine(); - while (line != null) { + while (reader.ready()) { + var line = reader.readLine(); body.add(line); - line = reader.readLine(); } return body.toString(); } - public String getMethod() { - return method; - } - public String getPath() { return path; } - public String getProtocol() { - return protocol; - } - - public Headers getHeaders() { - return headers; + public String getHttpVersion() { + return httpVersion; } public String getBody() { return body; } + + @Override + public String toString() { + final var result = new StringJoiner("\r\n"); + final var requestLine = method + " " + path + " " + httpVersion + " "; + return result.add(requestLine) + .add(headers.toString()) + .add(body) + .toString(); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpResponse.java new file mode 100644 index 0000000000..a52048074b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpResponse.java @@ -0,0 +1,36 @@ +package org.apache.coyote.http11.http; + +import java.util.StringJoiner; + +public class HttpResponse { + + private final String httpVersion; + private final HttpStatusCode httpStatusCode; + private final Headers headers; + private final String body; + + public HttpResponse(String httpVersion, HttpStatusCode httpStatusCode, Headers headers, String body) { + this.httpVersion = httpVersion; + this.httpStatusCode = httpStatusCode; + this.headers = headers; + this.body = body; + } + + public String getBody() { + return body; + } + + public byte[] getBytes() { + return toString().getBytes(); + } + + @Override + public String toString() { + final var result = new StringJoiner("\r\n"); + final var statusLine = httpVersion + " " + httpStatusCode + " "; + result.add(statusLine) + .add(headers.toString()) + .add(body); + return result.toString(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpStatusCode.java new file mode 100644 index 0000000000..6ed1b100aa --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpStatusCode.java @@ -0,0 +1,23 @@ +package org.apache.coyote.http11.http; + +public enum HttpStatusCode { + + OK(200, "OK"); + + private final int value; + private final String reasonPhrase; + + HttpStatusCode(int value, String reasonPhrase) { + this.value = value; + this.reasonPhrase = reasonPhrase; + } + + public int value() { + return value; + } + + @Override + public String toString() { + return "%s %s".formatted(value, reasonPhrase); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java b/tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java new file mode 100644 index 0000000000..0f7f02b29a --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java @@ -0,0 +1,7 @@ +package org.apache.coyote.http11.http; + +public class MediaType { + + public static final String TEXT_HTML = "text/html"; + public static final String TEXT_CSS = "text/css"; +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java b/tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java new file mode 100644 index 0000000000..f485ff8aa7 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package org.apache.coyote.http11.http; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java b/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java index 232033b1d8..11e53d7b0a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java @@ -4,6 +4,8 @@ import java.nio.file.Files; import java.nio.file.Path; +import org.apache.coyote.http11.http.ResourceNotFoundException; + public class ResourceReader { private static final ClassLoader CLASS_LOADER = ClassLoader.getSystemClassLoader(); @@ -19,6 +21,9 @@ public static String readString(final String resourcePath) throws IOException { return DEFAULT_RESOURCE; } final var url = CLASS_LOADER.getResource(DEFAULT_PATH + resourcePath); + if (url == null) { + throw new ResourceNotFoundException(resourcePath); + } final var path = Path.of(url.getPath()); return Files.readString(path); } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java index 3489da1325..936768c5b0 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java @@ -36,7 +36,7 @@ void process() { @Test void index() throws IOException { // given - final String httpRequest= String.join("\r\n", + final String httpRequest = String.join("\r\n", "GET /index.html HTTP/1.1 ", "Host: localhost:8080 ", "Connection: keep-alive ", @@ -44,7 +44,7 @@ void index() throws IOException { ""); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket); + final var processor = new Http11Processor(socket); // when processor.process(socket); @@ -54,7 +54,35 @@ void index() throws IOException { var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html;charset=utf-8 \r\n" + "Content-Length: 5564 \r\n" + - "\r\n"+ + "\r\n" + + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + assertThat(socket.output()).isEqualTo(expected); + } + + @Test + void css() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /css/styles.css HTTP/1.1 ", + "Host: localhost:8080 ", + "Accept: text/css,*/*;q=0.1 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final var processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/css/styles.css"); + var expected = "HTTP/1.1 200 OK \r\n" + + "Content-Type: text/css;charset=utf-8 \r\n" + + "Content-Length: 211991 \r\n" + + "\r\n" + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); assertThat(socket.output()).isEqualTo(expected); From b38813e3864be3efc08f2132a1e40bca9d3b2014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 6 Sep 2024 16:12:27 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20Query=20String=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements.md | 2 +- .../apache/coyote/http11/Http11Processor.java | 56 +++++++++++++++---- .../apache/coyote/http11/http/Headers.java | 7 +++ .../coyote/http11/http/HttpRequest.java | 24 ++++++-- .../apache/coyote/http11/http/MediaType.java | 1 + .../http11/resource/ResourceReader.java | 30 ---------- 6 files changed, 73 insertions(+), 47 deletions(-) delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java diff --git a/docs/requirements.md b/docs/requirements.md index 2e1de907b3..d1724225f3 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -3,4 +3,4 @@ ## 1단계 - HTTP 서버 구현하기 - [x] 1\. GET `index.html` 응답하기 - [x] 2\. CSS 지원하기 -- [ ] 3\. Query String 파싱 +- [x] 3\. 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 8ca0671df4..783bc45a4d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,26 +1,34 @@ package org.apache.coyote.http11; +import static org.apache.coyote.http11.http.MediaType.DEFAULT_CHARSET; import static org.apache.coyote.http11.http.MediaType.TEXT_CSS; import static org.apache.coyote.http11.http.MediaType.TEXT_HTML; import java.io.IOException; import java.net.Socket; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import org.apache.coyote.Processor; import org.apache.coyote.http11.http.Headers; import org.apache.coyote.http11.http.HttpRequest; import org.apache.coyote.http11.http.HttpResponse; import org.apache.coyote.http11.http.HttpStatusCode; -import org.apache.coyote.http11.resource.ResourceReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.techcourse.db.InMemoryUserRepository; import com.techcourse.exception.UncheckedServletException; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + private static final ClassLoader CLASS_LOADER = ClassLoader.getSystemClassLoader(); + private final Socket connection; public Http11Processor(final Socket connection) { @@ -39,15 +47,12 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream()) { final var request = new HttpRequest(inputStream); - log.info("request: \n{}", request); - final var body = ResourceReader.readString(request.getPath()); + String body = mapResource(request); final var headers = new Headers(); - final var extension = extension(request.getPath()); - final var mediaType = mediaType(extension); - headers.put("Content-Type", mediaType); - headers.put("Content-Type", "charset=utf-8"); + headers.put("Content-Type", contentType(request.getAccept())); + headers.put("Content-Type", DEFAULT_CHARSET); headers.put("Content-Length", String.valueOf(body.getBytes().length)); final var httpResponse = new HttpResponse( @@ -64,13 +69,40 @@ public void process(final Socket connection) { } } - private String extension(final String path) { - final var index = path.lastIndexOf("."); - return path.substring(index + 1); + private String mapResource(final HttpRequest request) throws IOException { + String body = ""; + if (Objects.equals(request.getPath(), "/")) { + body = "Hello world!"; + } + if (Objects.equals(request.getPath(), "/index.html")) { + var url = CLASS_LOADER.getResource("static" + request.getPath()); + body = Files.readString(Path.of(url.getPath())); + } + if (Objects.equals(request.getPath(), "/css/styles.css")) { + var url = CLASS_LOADER.getResource("static" + request.getPath()); + body = Files.readString(Path.of(url.getPath())); + } + if (Objects.equals(request.getPath(), "/login")) { + var url = CLASS_LOADER.getResource("static" + request.getPath() + ".html"); + body = Files.readString(Path.of(url.getPath())); + String queryString = request.getQueryString(); + if (!queryString.isBlank()) { + Map queryParams = new ConcurrentHashMap<>(); + var params = queryString.split("&"); + for (String param : params) { + String[] keyValue = param.split("="); + queryParams.put(keyValue[0], keyValue[1]); + } + var account = queryParams.get("username"); + var user = InMemoryUserRepository.findByAccount(account).orElse(null); + log.info("user: {}", user); + } + } + return body; } - private String mediaType(final String extension) { - if (extension.equals("css")) { + private String contentType(final String accept) { + if (accept != null && accept.contains(TEXT_CSS)) { return TEXT_CSS; } return TEXT_HTML; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java b/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java index 7ccdbd7fa1..2d36ec60eb 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/Headers.java @@ -11,7 +11,14 @@ public Headers() { this(new LinkedHashMap<>()); } + public String get(final String name) { + return headers.get(name); + } + public void put(final String headerLine) { + if (!headerLine.contains(DELIMITER)) { + throw new IllegalArgumentException(headerLine); + } final var split = headerLine.split(DELIMITER); put(split[0], split[1]); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java index f368588041..ed036d49ca 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpRequest.java @@ -9,7 +9,7 @@ public class HttpRequest { private final String method; - private final String path; + private final String requestURI; private final String httpVersion; private final Headers headers; private final String body; @@ -20,7 +20,7 @@ public HttpRequest(final InputStream inputStream) throws IOException { final var requestLine = buffer.readLine(); final var requestLineSplit = requestLine.split(" "); this.method = requestLineSplit[0]; - this.path = requestLineSplit[1]; + this.requestURI = requestLineSplit[1]; this.httpVersion = requestLineSplit[2]; this.headers = createHeaders(buffer); this.body = createBody(buffer); @@ -46,13 +46,29 @@ private String createBody(final BufferedReader reader) throws IOException { } public String getPath() { - return path; + int index = requestURI.lastIndexOf("?"); + if (index == -1) { + return requestURI; + } + return requestURI.substring(0, index); + } + + public String getQueryString() { + int index = requestURI.lastIndexOf("?"); + if (index == -1) { + return ""; + } + return requestURI.substring(index + 1); } public String getHttpVersion() { return httpVersion; } + public String getAccept() { + return headers.get("Accept"); + } + public String getBody() { return body; } @@ -60,7 +76,7 @@ public String getBody() { @Override public String toString() { final var result = new StringJoiner("\r\n"); - final var requestLine = method + " " + path + " " + httpVersion + " "; + final var requestLine = method + " " + requestURI + " " + httpVersion + " "; return result.add(requestLine) .add(headers.toString()) .add(body) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java b/tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java index 0f7f02b29a..500ccb301a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/MediaType.java @@ -2,6 +2,7 @@ public class MediaType { + public static final String DEFAULT_CHARSET = "charset=utf-8"; public static final String TEXT_HTML = "text/html"; public static final String TEXT_CSS = "text/css"; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java b/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java deleted file mode 100644 index 11e53d7b0a..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/resource/ResourceReader.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.apache.coyote.http11.resource; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.apache.coyote.http11.http.ResourceNotFoundException; - -public class ResourceReader { - - private static final ClassLoader CLASS_LOADER = ClassLoader.getSystemClassLoader(); - - private static final String ROOT_PATH = "/"; - - private static final String DEFAULT_PATH = "static"; - - private static final String DEFAULT_RESOURCE = "Hello world!"; - - public static String readString(final String resourcePath) throws IOException { - if (resourcePath.equals(ROOT_PATH)) { - return DEFAULT_RESOURCE; - } - final var url = CLASS_LOADER.getResource(DEFAULT_PATH + resourcePath); - if (url == null) { - throw new ResourceNotFoundException(resourcePath); - } - final var path = Path.of(url.getPath()); - return Files.readString(path); - } -} From 9db8fe976e0a06bbbec92e9874b2dbe4d7402871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 6 Sep 2024 17:07:29 +0900 Subject: [PATCH 10/11] =?UTF-8?q?feat:=20HTTP=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=ED=95=99=EC=8A=B5=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 --- study/build.gradle | 1 + .../cachecontrol/CacheControlInterceptor.java | 21 +++++++++++++++++++ .../example/cachecontrol/CacheWebConfig.java | 2 ++ .../example/etag/EtagFilterConfiguration.java | 15 +++++++++---- .../version/CacheBustingWebConfig.java | 7 ++++++- study/src/main/resources/application.yml | 3 +++ 6 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 study/src/main/java/cache/com/example/cachecontrol/CacheControlInterceptor.java diff --git a/study/build.gradle b/study/build.gradle index 87a1f0313c..7bdce4f751 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -19,6 +19,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 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' 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..1f3c4d4e0d --- /dev/null +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheControlInterceptor.java @@ -0,0 +1,21 @@ +package cache.com.example.cachecontrol; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.lang.Nullable; +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, + @Nullable ModelAndView modelAndView + ) { + response.setHeader("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..7959008433 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,7 @@ public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(new CacheControlInterceptor()) + .addPathPatterns("/**"); } } 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..9c0f47b0ac 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,19 @@ package cache.com.example.etag; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ShallowEtagHeaderFilter; @Configuration public class EtagFilterConfiguration { -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(); + filterRegistrationBean.setFilter(new ShallowEtagHeaderFilter()); + filterRegistrationBean.addUrlPatterns("/etag"); + filterRegistrationBean.addInitParameter("tag", "etag"); + 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..1ea48b7848 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,10 @@ 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 +23,8 @@ public CacheBustingWebConfig(ResourceVersion version) { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .addResourceLocations("classpath:/static/") + .setEtagGenerator(__ -> version.getVersion()) + .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..4c735aea11 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -2,6 +2,9 @@ handlebars: suffix: .html server: + compression: + enabled: true + min-response-size: 10 tomcat: accept-count: 1 max-connections: 1 From 3c7765984d1c806a2af83dba58246eca45a48c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 6 Sep 2024 17:17:10 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/http/ResourceNotFoundException.java | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java b/tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java deleted file mode 100644 index f485ff8aa7..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/ResourceNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.apache.coyote.http11.http; - -public class ResourceNotFoundException extends RuntimeException { - public ResourceNotFoundException(String message) { - super(message); - } -}