From fed02f6f5f4308400e55c160d9495cad010f5bfb Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 11:11:09 +0900 Subject: [PATCH 01/17] 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/17] 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 a8707e91b4d8a45ed513088f170da7c22c05a155 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:23:42 +0900 Subject: [PATCH 03/17] =?UTF-8?q?feat:=20Http=20=EC=9A=94=EC=B2=AD/?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B0=9D=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/message/HttpHeaders.java | 47 +++++++++++++ .../http11/message/request/HttpMethod.java | 28 ++++++++ .../http11/message/request/HttpRequest.java | 56 +++++++++++++++ .../message/request/HttpRequestInfo.java | 30 ++++++++ .../http11/message/request/HttpUrl.java | 22 ++++++ .../http11/message/request/HttpUrlParser.java | 51 ++++++++++++++ .../http11/message/response/HttpResponse.java | 46 ++++++++++++ .../http11/message/response/HttpStatus.java | 70 +++++++++++++++++++ 8 files changed, 350 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/HttpHeaders.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpMethod.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpStatus.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/message/HttpHeaders.java new file mode 100644 index 0000000000..1b5c6cc167 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/HttpHeaders.java @@ -0,0 +1,47 @@ +package org.apache.coyote.http11.message; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +public final class HttpHeaders { + + private static final String HEADER_LINE_DELIMITER = ": "; + private static final int HEADER_NAME_INDEX = 0; + private static final int HEADER_FIELD_INDEX = 1; + + private final Map headers; + + public HttpHeaders(Map headers) { + this.headers = new HashMap<>(headers); + } + + public static HttpHeaders from(List headerLines) { + return new HttpHeaders(headerLines.stream() + .map(headerLine -> headerLine.split(HEADER_LINE_DELIMITER)) + .collect(Collectors.toMap( + headerLineElements -> headerLineElements[HEADER_NAME_INDEX], + headerLineElements -> headerLineElements[HEADER_FIELD_INDEX]))); + } + + public Optional getFieldByHeaderName(String headerName) { + return Optional.ofNullable(headers.get(headerName)); + } + + public List toHeaderLines() { + return headers.entrySet() + .stream() + .map(this::toHeaderLine) + .toList(); + } + + private String toHeaderLine(Map.Entry headersEntry) { + return String.join(HEADER_LINE_DELIMITER, headersEntry.getKey(), headersEntry.getValue()); + } + + public void setHeader(String name, String field) { + headers.put(name, field); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpMethod.java new file mode 100644 index 0000000000..4a12d01577 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpMethod.java @@ -0,0 +1,28 @@ +package org.apache.coyote.http11.message.request; + +import java.util.Arrays; + +public enum HttpMethod { + GET("GET"), + HEAD("HEAD"), + PUT("PUT"), + PATCH("PATCH"), + POST("POST"), + DELETE("DELETE"), + TRACE("TRACE"), + OPTIONS("OPTIONS"), + CONNECT("CONNECT"); + + private final String method; + + HttpMethod(String method) { + this.method = method; + } + + public static HttpMethod from(String method) { + return Arrays.stream(values()) + .filter(httpMethod -> method.equals(httpMethod.method)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 HTTP 메소드입니다.")); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java new file mode 100644 index 0000000000..3cffd634c5 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java @@ -0,0 +1,56 @@ +package org.apache.coyote.http11.message.request; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.coyote.http11.message.HttpHeaders; + +public class HttpRequest { + private static final String REQUEST_LINE_DELIMITER = " "; + private static final int HTTP_METHOD_INDEX = 0; + private static final int HTTP_URL_INDEX = 1; + + private final HttpMethod method; + private final HttpUrl url; + private final HttpHeaders headers; + private final String body; + + public HttpRequest(HttpMethod method, HttpUrl url, HttpHeaders headers, String body) { + this.method = method; + this.url = url; + this.headers = headers; + this.body = body; + } + + public static HttpRequest of(String requestLine, HttpHeaders headers, String body) { + String[] requestLineElements = requestLine.split(REQUEST_LINE_DELIMITER); + HttpMethod method = HttpMethod.from(requestLineElements[HTTP_METHOD_INDEX]); + String url = requestLineElements[HTTP_URL_INDEX]; + + return new HttpRequest(method, HttpUrlParser.parseUrl(url), headers, body); + } + + public HttpRequestInfo getRequestInfo() { + return new HttpRequestInfo(method, getUrlPath()); + } + + public HttpMethod getMethod() { + return method; + } + + public String getUrlPath() { + return url.getPath(); + } + + public Optional getHeaderFieldByName(String name) { + return headers.getFieldByHeaderName(name); + } + + public String getBody() { + return body; + } + + public Map> getQueryParameters() { + return url.getQueryParameters(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java new file mode 100644 index 0000000000..bc49996df6 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11.message.request; + +import java.util.Objects; + +public class HttpRequestInfo { + private final HttpMethod method; + private final String urlPath; + + public HttpRequestInfo(HttpMethod method, String urlPath) { + this.method = method; + this.urlPath = urlPath; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HttpRequestInfo that = (HttpRequestInfo) o; + return method == that.method && Objects.equals(urlPath, that.urlPath); + } + + @Override + public int hashCode() { + return Objects.hash(method, urlPath); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java new file mode 100644 index 0000000000..052c83f3f6 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java @@ -0,0 +1,22 @@ +package org.apache.coyote.http11.message.request; + +import java.util.List; +import java.util.Map; + +public class HttpUrl { + private final String path; + private final Map> queryParameters; + + public HttpUrl(String path, Map> queryParameters) { + this.path = path; + this.queryParameters = queryParameters; + } + + public String getPath() { + return path; + } + + public Map> getQueryParameters() { + return queryParameters; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java new file mode 100644 index 0000000000..b9651e166a --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java @@ -0,0 +1,51 @@ +package org.apache.coyote.http11.message.request; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +public class HttpUrlParser { + + private static final String URL_DELIMITER = "\\?"; + private static final String QUERY_PARAMETERS_DELIMITER = "&"; + private static final String QUERY_PARAMETER_DELIMITER = "="; + private static final String QUERY_PARAMETER_VALUES_DELIMITER = ","; + private static final int PATH_INDEX = 0; + private static final int QUERY_STRING_INDEX = 1; + private static final int QUERY_PARAMETER_KEY_INDEX = 0; + private static final int QUERY_PARAMETER_VALUE_INDEX = 1; + + public static HttpUrl parseUrl(String url) { + String[] urlElements = url.split(URL_DELIMITER); + String path = urlElements[PATH_INDEX]; + + if (urlElements.length == 1) { + return new HttpUrl(path, new HashMap<>()); + } + + String queryString = urlElements[QUERY_STRING_INDEX]; + + Map> queryParameters = Arrays.stream(queryString.split(QUERY_PARAMETERS_DELIMITER)) + .map(HttpUrlParser::parseQueryParameter) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue, HttpUrlParser::mergeValues)); + + return new HttpUrl(path, queryParameters); + } + + private static Map.Entry> parseQueryParameter(String queryParameter) { + String[] queryParameterElements = queryParameter.split(QUERY_PARAMETER_DELIMITER); + String key = queryParameterElements[QUERY_PARAMETER_KEY_INDEX]; + List values = Arrays.asList( + queryParameterElements[QUERY_PARAMETER_VALUE_INDEX].split(QUERY_PARAMETER_VALUES_DELIMITER)); + + return Map.entry(key, values); + } + + private static List mergeValues(List existingValues, List newValues) { + existingValues.addAll(newValues); + return existingValues; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java new file mode 100644 index 0000000000..34c7b864a4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java @@ -0,0 +1,46 @@ +package org.apache.coyote.http11.message.response; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.coyote.http11.message.HttpHeaders; + +public class HttpResponse { + private final HttpStatus status; + private final HttpHeaders headers; + private final String body; + + public HttpResponse(HttpStatus status, HttpHeaders headers, String body) { + this.status = status; + this.headers = headers; + this.body = body; + } + + public static HttpResponse of(HttpStatus status, String body) { + HttpHeaders headers = new HttpHeaders(Map.of( + "Content-Length", String.valueOf(body.getBytes().length) + )); + return new HttpResponse(status, headers, body); + } + + public static HttpResponse from(HttpStatus status) { + HttpHeaders headers = new HttpHeaders(new HashMap<>()); + return new HttpResponse(status, headers, ""); + } + + public HttpStatus getStatus() { + return status; + } + + public String getBody() { + return body; + } + + public List getHeaderLines() { + return headers.toHeaderLines(); + } + + public void setHeader(String name, String field) { + headers.setHeader(name, field); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpStatus.java new file mode 100644 index 0000000000..5b79acde68 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpStatus.java @@ -0,0 +1,70 @@ +package org.apache.coyote.http11.message.response; + +import java.util.Arrays; + +public enum HttpStatus { + CONTINUE(100, "Continue"), + SWITCHING_PROTOCOLS(101, "Switching Protocols"), + OK(200, "OK"), + CREATED(201, "Created"), + ACCEPTED(202, "Accepted"), + NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"), + NO_CONTENT(204, "No Content"), + RESET_CONTENT(205, "Reset Content"), + PARTIAL_CONTENT(206, "Partial Content"), + MULTIPLE_CHOICES(300, "Multiple Choices"), + MOVED_PERMANENTLY(301, "Moved Permanently"), + FOUND(302, "Found"), + SEE_OTHER(303, "See Other"), + NOT_MODIFIED(304, "Not Modified"), + USE_PROXY(305, "Use Proxy"), + TEMPORARY_REDIRECT(307, "Temporary Redirect"), + PERMANENT_REDIRECT(308, "Permanent Redirect"), + BAD_REQUEST(400, "Bad Request"), + UNAUTHORIZED(401, "Unauthorized"), + PAYMENT_REQUIRED(402, "Payment Required"), + FORBIDDEN(403, "Forbidden"), + NOT_FOUND(404, "Not Found"), + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), + NOT_ACCEPTABLE(406, "Not Acceptable"), + PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), + REQUEST_TIMEOUT(408, "Request Timeout"), + CONFLICT(409, "Conflict"), + GONE(410, "Gone"), + LENGTH_REQUIRED(411, "Length Required"), + PRECONDITION_FAILED(412, "Precondition Failed"), + PAYLOAD_TOO_LARGE(413, "Payload Too Large"), + URI_TOO_LONG(414, "URI Too Long"), + UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), + RANGE_NOT_SATISFIABLE(416, "Range Not Satisfiable"), + EXPECTATION_FAILED(417, "Expectation Failed"), + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + NOT_IMPLEMENTED(501, "Not Implemented"), + BAD_GATEWAY(502, "Bad Gateway"), + SERVICE_UNAVAILABLE(503, "Service Unavailable"), + GATEWAY_TIMEOUT(504, "Gateway Timeout"), + HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"); + + private final int code; + private final String reasonPhrase; + + HttpStatus(int code, String reasonPhrase) { + this.code = code; + this.reasonPhrase = reasonPhrase; + } + + public int getCode() { + return code; + } + + public String getReasonPhrase() { + return reasonPhrase; + } + + public static HttpStatus fromCode(int code) { + return Arrays.stream(HttpStatus.values()) + .filter(status -> status.code == code) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 code의 HTTP status는 존재하지 않습니다.")); + } +} From 5063922b0f2c3e2ebceeef5aabca0f270a3bd5cf Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:24:56 +0900 Subject: [PATCH 04/17] =?UTF-8?q?feat:=20Http=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=9D=84=20read=ED=95=98=EA=B3=A0=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EC=9D=84=20wirte=20=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../message/request/HttpRequestReader.java | 68 +++++++++++++++++++ .../message/response/HttpResponseWriter.java | 32 +++++++++ 2 files changed, 100 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponseWriter.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java new file mode 100644 index 0000000000..81b0883609 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java @@ -0,0 +1,68 @@ +package org.apache.coyote.http11.message.request; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.apache.coyote.http11.message.HttpHeaders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: BufferedReader와 BufferedInputStream 같이 사용했을 때 문제가 발생하지 않는지 확인하기 +public class HttpRequestReader { + + private final InputStream inputStream; + + public HttpRequestReader(InputStream inputStream) { + this.inputStream = inputStream; + } + + private static final Logger log = LoggerFactory.getLogger(HttpRequestReader.class); + + public HttpRequest read() throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String requestLine = bufferedReader.readLine(); + HttpHeaders headers = readHeaderLines(bufferedReader); + + return HttpRequest.of(requestLine, headers, readBody(headers)); + } + + private HttpHeaders readHeaderLines(BufferedReader bufferedReader) throws IOException { + List headerLines = new ArrayList<>(); + + String headerLine = bufferedReader.readLine(); + while (!headerLine.isEmpty()) { + headerLines.add(headerLine); + headerLine = bufferedReader.readLine(); + } + + return HttpHeaders.from(headerLines); + } + + private String readBody(HttpHeaders headers) { + return headers.getFieldByHeaderName("Content-Length") + .map(Integer::parseInt) + .map(contentLength -> readBody(contentLength)) + .orElse(""); + } + + private String readBody(int contentLength) { + BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); + byte[] bodyBytes; + + try { + bodyBytes = bufferedInputStream.readNBytes(contentLength); + } catch (IOException e) { + throw new IllegalArgumentException("HTTP body를 읽는데 실패하였습니다.", e); + } + + if (bodyBytes.length != contentLength) { + throw new IllegalArgumentException("HTTP body의 길이와 Content-Type 의 값이 일치하지 않습니다."); + } + return new String(bodyBytes, StandardCharsets.UTF_8); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponseWriter.java b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponseWriter.java new file mode 100644 index 0000000000..cbb966f9d3 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponseWriter.java @@ -0,0 +1,32 @@ +package org.apache.coyote.http11.message.response; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.StringJoiner; + +public class HttpResponseWriter { + + private static final String CRLF = "\r\n"; + + private final OutputStream outputStream; + + public HttpResponseWriter(OutputStream outputStream) { + this.outputStream = outputStream; + } + + // TODO: body는 바이너리 데이터 일 수 있기 때문에 따로 처리해야 한다. + public void write(HttpResponse response) throws IOException { + String statusLine = + String.format("HTTP/1.1 %d %s", response.getStatus().getCode(), response.getStatus().getReasonPhrase()); + String headerLines = String.join(CRLF, response.getHeaderLines()); + String responseMessage = new StringJoiner(CRLF) + .add(statusLine) + .add(headerLines) + .add("") + .add(response.getBody()) + .toString(); + + outputStream.write(responseMessage.getBytes()); + outputStream.flush(); + } +} From dcb726e01570aadecd57007965d6d9e06b8180af Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:26:33 +0900 Subject: [PATCH 05/17] =?UTF-8?q?feat:=20Http=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=EB=B6=80=ED=84=B0=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EC=9D=84=20=EB=82=B4=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http11/ResourceToHttpBodyConverter.java | 41 +++++++++++++++++++ .../coyote/http11/handler/HttpHandler.java | 9 ++++ .../http11/handler/HttpHandlerMapper.java | 20 +++++++++ .../handler/HttpHandlerMapperFactory.java | 20 +++++++++ .../handler/StaticResourceHttpHandler.java | 21 ++++++++++ .../http11/handler/StringHttpHandler.java | 22 ++++++++++ .../http11/handler/ViewHttpHandler.java | 26 ++++++++++++ 7 files changed, 159 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapper.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapperFactory.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/StringHttpHandler.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java b/tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java new file mode 100644 index 0000000000..ebf8757b0e --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java @@ -0,0 +1,41 @@ +package org.apache.coyote.http11; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.apache.coyote.http11.message.response.HttpResponse; +import org.apache.coyote.http11.message.response.HttpStatus; + +public class ResourceToHttpBodyConverter { + + private static final String LINE_FEED = "\n"; + + private ResourceToHttpBodyConverter() { + } + + public static HttpResponse covert(URL resource) throws IOException { + if (resource == null) { + throw new IllegalArgumentException("해당 파일이 존재하지 않습니다."); + } + + Path path = Paths.get(resource.getPath()); + String responseBody = String.join(LINE_FEED, Files.readAllLines(path)) + LINE_FEED; + + HttpResponse response = HttpResponse.of(HttpStatus.OK, responseBody); + String mimeType = probeContentType(path); + response.setHeader("Content-Type", mimeType); + + return response; + } + + public static String probeContentType(Path path) throws IOException { + String mimeType = Files.probeContentType(path); + if (mimeType.startsWith("text")) { + return mimeType + ";charset=utf-8"; + } + + return mimeType; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java new file mode 100644 index 0000000000..94959e3ec8 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java @@ -0,0 +1,9 @@ +package org.apache.coyote.http11.handler; + +import java.io.IOException; +import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.response.HttpResponse; + +public interface HttpHandler { + HttpResponse handle(HttpRequest request) throws IOException; +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapper.java new file mode 100644 index 0000000000..1fc52ae671 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapper.java @@ -0,0 +1,20 @@ +package org.apache.coyote.http11.handler; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.request.HttpRequestInfo; + +public class HttpHandlerMapper { + + private final Map mapper; + + public HttpHandlerMapper(Map mapper) { + this.mapper = new HashMap<>(mapper); + } + + public Optional findHandler(HttpRequest request) { + return Optional.ofNullable(mapper.get(request.getRequestInfo())); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapperFactory.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapperFactory.java new file mode 100644 index 0000000000..a717007198 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandlerMapperFactory.java @@ -0,0 +1,20 @@ +package org.apache.coyote.http11.handler; + +import com.techcourse.controller.LoginController; +import com.techcourse.service.UserService; +import java.util.Map; +import org.apache.coyote.http11.message.request.HttpMethod; +import org.apache.coyote.http11.message.request.HttpRequestInfo; + +public class HttpHandlerMapperFactory { + + private HttpHandlerMapperFactory() { + } + + public static HttpHandlerMapper create() { + return new HttpHandlerMapper(Map.of( + new HttpRequestInfo(HttpMethod.GET, "/"), new StringHttpHandler("Hello world!"), + new HttpRequestInfo(HttpMethod.GET, "/login"), new LoginController(new UserService()) + )); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java new file mode 100644 index 0000000000..c12f064b45 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11.handler; + +import java.io.IOException; +import java.net.URL; +import org.apache.coyote.http11.ResourceToHttpBodyConverter; +import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.response.HttpResponse; + +public class StaticResourceHttpHandler implements HttpHandler { + + private static final String LINE_FEED = "\n"; + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + String fileName = "static" + request.getUrlPath(); + URL resource = getClass().getClassLoader() + .getResource(fileName); + + return ResourceToHttpBodyConverter.covert(resource); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/StringHttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/StringHttpHandler.java new file mode 100644 index 0000000000..35a6ab1757 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/StringHttpHandler.java @@ -0,0 +1,22 @@ +package org.apache.coyote.http11.handler; + +import java.io.IOException; +import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.response.HttpResponse; +import org.apache.coyote.http11.message.response.HttpStatus; + +public class StringHttpHandler implements HttpHandler { + + private final String body; + + public StringHttpHandler(String body) { + this.body = body; + } + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + HttpResponse response = HttpResponse.of(HttpStatus.OK, body); + response.setHeader("Content-Type", "text/plain;charset=utf-8"); + return response; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java new file mode 100644 index 0000000000..ceaf7d3058 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java @@ -0,0 +1,26 @@ +package org.apache.coyote.http11.handler; + +import java.io.IOException; +import java.net.URL; +import org.apache.coyote.http11.ResourceToHttpBodyConverter; +import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.response.HttpResponse; + +public class ViewHttpHandler implements HttpHandler { + + private final String viewName; + + public ViewHttpHandler(String viewName) { + this.viewName = viewName; + } + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + String fileName = "static/" + viewName + ".html"; + URL resource = getClass().getClassLoader() + .getResource(fileName); + + HttpResponse response = ResourceToHttpBodyConverter.covert(resource); + return response; + } +} From cb8d8e4d3feae6968b8d67ed57fbe03c3f6f147d Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:28:18 +0900 Subject: [PATCH 06/17] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=84=20=ED=95=98=EB=A9=B4=20=EC=9C=A0=EC=A0=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EC=B0=BE=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LoginController.java | 39 +++++++++++++++++++ .../techcourse/db/InMemoryUserRepository.java | 11 +++++- .../com/techcourse/service/UserService.java | 12 ++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tomcat/src/main/java/com/techcourse/controller/LoginController.java create mode 100644 tomcat/src/main/java/com/techcourse/service/UserService.java diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java new file mode 100644 index 0000000000..81b4c1f1ae --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -0,0 +1,39 @@ +package com.techcourse.controller; + +import com.techcourse.model.User; +import com.techcourse.service.UserService; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.apache.coyote.http11.handler.HttpHandler; +import org.apache.coyote.http11.handler.ViewHttpHandler; +import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.response.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoginController implements HttpHandler { + + private static final Logger log = LoggerFactory.getLogger(LoginController.class); + + private final UserService service; + private final ViewHttpHandler viewHttpHandler; + + public LoginController(UserService service) { + this.service = service; + this.viewHttpHandler = new ViewHttpHandler("login"); + } + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + Map> queryParameters = request.getQueryParameters(); + + String account = queryParameters.get("account").get(0); + String password = queryParameters.get("password").get(0); + + User user = service.findUserByAccountAndPassword(account, password); + log.info("user: {}", user); + + return viewHttpHandler.handle(request); + } +} diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index d3fa57feeb..e6f7ea7102 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -1,7 +1,6 @@ package com.techcourse.db; import com.techcourse.model.User; - import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -19,9 +18,17 @@ public static void save(User user) { database.put(user.getAccount(), user); } + public static Optional findByAccountAndPassword(String account, String password) { + return findByAccount(account) + .filter(user -> user.checkPassword(password)) + .stream() + .findFirst(); + } + public static Optional findByAccount(String account) { return Optional.ofNullable(database.get(account)); } - private InMemoryUserRepository() {} + private InMemoryUserRepository() { + } } diff --git a/tomcat/src/main/java/com/techcourse/service/UserService.java b/tomcat/src/main/java/com/techcourse/service/UserService.java new file mode 100644 index 0000000000..89ab304432 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/service/UserService.java @@ -0,0 +1,12 @@ +package com.techcourse.service; + +import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.model.User; + +public class UserService { + + public User findUserByAccountAndPassword(String account, String password) { + return InMemoryUserRepository.findByAccountAndPassword(account, password) + .orElseThrow(() -> new IllegalArgumentException("id/pw 가 올바르지 않습니다.")); + } +} From 9275770ccda5b3b0e67e5c70e639e97e79bab260 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:29:22 +0900 Subject: [PATCH 07/17] =?UTF-8?q?feat:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=EB=A1=9C=EB=B6=80=ED=84=B0=20HTTP=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=EC=9D=84=20=EB=B0=9B=EC=95=84=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=9D=84=20=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 32 +++++++++++-------- .../coyote/http11/Http11ProcessorTest.java | 29 ++++++++--------- 2 files changed, 33 insertions(+), 28 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 bb14184757..f258bffc18 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.IOException; +import java.net.Socket; import org.apache.coyote.Processor; +import org.apache.coyote.http11.handler.HttpHandler; +import org.apache.coyote.http11.handler.HttpHandlerMapper; +import org.apache.coyote.http11.handler.HttpHandlerMapperFactory; +import org.apache.coyote.http11.handler.StaticResourceHttpHandler; +import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.request.HttpRequestReader; +import org.apache.coyote.http11.message.response.HttpResponse; +import org.apache.coyote.http11.message.response.HttpResponseWriter; 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); @@ -28,18 +35,17 @@ public void run() { public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { + HttpHandlerMapper httpHandlerMapper = HttpHandlerMapperFactory.create(); + StaticResourceHttpHandler staticResourceHttpHandler = new StaticResourceHttpHandler(); - 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); + HttpRequestReader reader = new HttpRequestReader(inputStream); + HttpResponseWriter writer = new HttpResponseWriter(outputStream); + HttpRequest request = reader.read(); + HttpHandler httpHandler = httpHandlerMapper.findHandler(request) + .orElse(staticResourceHttpHandler); - outputStream.write(response.getBytes()); - outputStream.flush(); + HttpResponse response = httpHandler.handle(request); + writer.write(response); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } 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..96b2b24462 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/Http11ProcessorTest.java @@ -1,14 +1,13 @@ package org.apache.coyote.http11; -import org.junit.jupiter.api.Test; -import support.StubSocket; +import static org.assertj.core.api.Assertions.assertThat; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; +import support.StubSocket; class Http11ProcessorTest { @@ -23,9 +22,9 @@ void process() { // then var expected = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 12 ", + "HTTP/1.1 200 OK", + "Content-Length: 12", + "Content-Type: text/plain;charset=utf-8", "", "Hello world!"); @@ -35,10 +34,10 @@ void process() { @Test void index() throws IOException { // given - final String httpRequest= String.join("\r\n", - "GET /index.html HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", + final String httpRequest = String.join("\r\n", + "GET /index.html HTTP/1.1", + "Host: localhost:8080", + "Connection: keep-alive", "", ""); @@ -50,10 +49,10 @@ void index() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/index.html"); - var expected = "HTTP/1.1 200 OK \r\n" + - "Content-Type: text/html;charset=utf-8 \r\n" + - "Content-Length: 5564 \r\n" + - "\r\n"+ + var expected = "HTTP/1.1 200 OK\r\n" + + "Content-Length: 5564\r\n" + + "Content-Type: text/html;charset=utf-8\r\n" + + "\r\n" + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); assertThat(socket.output()).isEqualTo(expected); From 1877ccd6bc0119b76dedcf4da483222e60073db7 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:32:23 +0900 Subject: [PATCH 08/17] =?UTF-8?q?refactor:=20=EA=B0=81=20=ED=81=B4?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=EC=9D=98=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=EC=9D=84=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=8A=A4=EB=A0=88=EB=93=9C=EB=A5=BC=20=EB=8D=B0=EB=AA=AC=20?= =?UTF-8?q?=EC=8A=A4=EB=A0=88=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/catalina/connector/Connector.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java index 3b2c4dda7c..ad09e4a775 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -1,13 +1,12 @@ package org.apache.catalina.connector; -import org.apache.coyote.http11.Http11Processor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.io.UncheckedIOException; import java.net.ServerSocket; import java.net.Socket; +import org.apache.coyote.http11.Http11Processor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Connector implements Runnable { @@ -67,7 +66,9 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + Thread thread = new Thread(processor); + thread.setDaemon(true); + thread.start(); } public void stop() { From 94a6f543b10d891afdee3f8100ba13e3478040cf Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:34:49 +0900 Subject: [PATCH 09/17] =?UTF-8?q?feat:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=201=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 18 +++++---- study/src/test/java/study/IOStreamTest.java | 43 +++++++++++---------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..1348eebccb 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,5 +1,10 @@ package study; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -26,9 +31,7 @@ class FileTest { @Test void resource_디렉터리에_있는_파일의_경로를_찾는다() { final String fileName = "nextstep.txt"; - - // todo - final String actual = ""; + final String actual = getClass().getClassLoader().getResource(fileName).getPath(); assertThat(actual).endsWith(fileName); } @@ -40,14 +43,13 @@ class FileTest { * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws IOException { final String fileName = "nextstep.txt"; - // todo - final Path path = null; + URL resource = getClass().getClassLoader().getResource(fileName); + File file = new File(resource.getPath()); - // todo - final List actual = Collections.emptyList(); + final List actual = Files.readAllLines(file.toPath()); assertThat(actual).containsOnly("nextstep"); } diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..c513663b74 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -1,5 +1,7 @@ package study; +import java.util.StringJoiner; +import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -7,6 +9,7 @@ import java.io.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.in; import static org.mockito.Mockito.*; /** @@ -49,10 +52,7 @@ class OutputStream_학습_테스트 { final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112}; final OutputStream outputStream = new ByteArrayOutputStream(bytes.length); - /** - * todo - * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 - */ + outputStream.write(bytes); final String actual = outputStream.toString(); @@ -73,11 +73,7 @@ class OutputStream_학습_테스트 { void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException { final OutputStream outputStream = mock(BufferedOutputStream.class); - /** - * todo - * flush를 사용해서 테스트를 통과시킨다. - * ByteArrayOutputStream과 어떤 차이가 있을까? - */ + outputStream.flush(); verify(outputStream, atLeastOnce()).flush(); outputStream.close(); @@ -90,14 +86,17 @@ class OutputStream_학습_테스트 { @Test void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException { final OutputStream outputStream = mock(OutputStream.class); + try (outputStream) { + } catch (IOException e) { + } + verify(outputStream, atLeastOnce()).close(); /** * todo * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ - verify(outputStream, atLeastOnce()).close(); } } @@ -128,7 +127,7 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + final String actual = new String(inputStream.readAllBytes()); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -143,11 +142,9 @@ class InputStream_학습_테스트 { void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException { final InputStream inputStream = mock(InputStream.class); - /** - * todo - * try-with-resources를 사용한다. - * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. - */ + try (inputStream) { + } catch (IOException e) { + } verify(inputStream, atLeastOnce()).close(); } @@ -169,12 +166,12 @@ class FilterStream_학습_테스트 { * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? */ @Test - void 필터인_BufferedInputStream를_사용해보자() { + void 필터인_BufferedInputStream를_사용해보자() throws IOException { final String text = "필터에 연결해보자."; final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; + final InputStream bufferedInputStream = new BufferedInputStream(inputStream); - final byte[] actual = new byte[0]; + final byte[] actual = bufferedInputStream.readAllBytes(); assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); @@ -197,17 +194,21 @@ 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 Reader reader = new InputStreamReader(inputStream); + final BufferedReader bufferedReader = new BufferedReader(reader); - final StringBuilder actual = new StringBuilder(); + String actual = bufferedReader.lines() + .collect(Collectors.joining("\r\n")) + "\r\n"; assertThat(actual).hasToString(emoji); + System.out.println(System.lineSeparator()); } } } From c9dd25153e779c2eddbbc295baa811a9f9dc9c0b Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 16:35:01 +0900 Subject: [PATCH 10/17] =?UTF-8?q?feat:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=202=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/build.gradle | 2 +- .../cache/com/example/GreetingController.java | 3 +-- .../example/cachecontrol/CacheWebConfig.java | 21 +++++++++++++++ .../example/etag/EtagFilterConfiguration.java | 26 ++++++++++++++++--- .../com/example/version/ResourceVersion.java | 5 ++-- study/src/main/resources/application.yml | 4 +++ 6 files changed, 51 insertions(+), 10 deletions(-) diff --git a/study/build.gradle b/study/build.gradle index 5c69542f84..bb8b420ca5 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -19,10 +19,10 @@ 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' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.assertj:assertj-core:3.26.0' diff --git a/study/src/main/java/cache/com/example/GreetingController.java b/study/src/main/java/cache/com/example/GreetingController.java index 978eefdc34..2416d9ac6e 100644 --- a/study/src/main/java/cache/com/example/GreetingController.java +++ b/study/src/main/java/cache/com/example/GreetingController.java @@ -1,12 +1,11 @@ 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 javax.servlet.http.HttpServletResponse; - @Controller public class GreetingController { 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..daf75914ef 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -1,13 +1,34 @@ package cache.com.example.cachecontrol; +import cache.com.example.version.ResourceVersion; +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.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.WebContentInterceptor; @Configuration public class CacheWebConfig implements WebMvcConfigurer { + private final ResourceVersion version; + + @Autowired + public CacheWebConfig(ResourceVersion version) { + this.version = version; + } + @Override public void addInterceptors(final InterceptorRegistry registry) { + WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); + webContentInterceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**"); + webContentInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)), "/etag"); +// webContentInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)).cachePublic(), +// "/resource-versioning"); + webContentInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)).cachePublic(), + "/resources/" + version.getVersion() + "/**"); + + registry.addInterceptor(webContentInterceptor); } } diff --git a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java index 41ef7a3d9a..ef6276e66d 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,30 @@ package cache.com.example.etag; +import cache.com.example.version.ResourceVersion; +import org.springframework.beans.factory.annotation.Autowired; +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; -// } + private final ResourceVersion version; + + @Autowired + public EtagFilterConfiguration(ResourceVersion version) { + this.version = version; + } + + + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new ShallowEtagHeaderFilter()); + registrationBean.addUrlPatterns("/etag"); + registrationBean.addUrlPatterns("/resource-versioning"); + registrationBean.addUrlPatterns("/resources/" + version.getVersion() + "/*"); + return registrationBean; + } } 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 7049b3d82a..215ba7a705 100644 --- a/study/src/main/java/cache/com/example/version/ResourceVersion.java +++ b/study/src/main/java/cache/com/example/version/ResourceVersion.java @@ -1,10 +1,9 @@ package cache.com.example.version; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import org.springframework.stereotype.Component; @Component public class ResourceVersion { diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..8b74bdfd88 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -6,4 +6,8 @@ server: accept-count: 1 max-connections: 1 threads: + min-spare: 2 max: 2 + compression: + enabled: true + min-response-size: 10 From 013d6a61489c955dc919863b896cb0720e1ba543 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 17:19:46 +0900 Subject: [PATCH 11/17] =?UTF-8?q?refactor:=20query=20parameter=20=EB=93=A4?= =?UTF-8?q?=EC=9D=84=20=EC=9D=BC=EA=B8=89=20=EC=BB=AC=EB=A0=89=EC=85=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=9E=98=ED=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LoginController.java | 9 ++++---- .../http11/message/request/HttpRequest.java | 4 +--- .../http11/message/request/HttpUrl.java | 9 +++----- .../http11/message/request/HttpUrlParser.java | 4 ++-- .../message/request/QueryParameters.java | 21 +++++++++++++++++++ 5 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 81b4c1f1ae..7bf09f91bc 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -3,11 +3,10 @@ import com.techcourse.model.User; import com.techcourse.service.UserService; import java.io.IOException; -import java.util.List; -import java.util.Map; import org.apache.coyote.http11.handler.HttpHandler; import org.apache.coyote.http11.handler.ViewHttpHandler; import org.apache.coyote.http11.message.request.HttpRequest; +import org.apache.coyote.http11.message.request.QueryParameters; import org.apache.coyote.http11.message.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,10 +25,10 @@ public LoginController(UserService service) { @Override public HttpResponse handle(HttpRequest request) throws IOException { - Map> queryParameters = request.getQueryParameters(); + QueryParameters queryParameters = request.getQueryParameters(); - String account = queryParameters.get("account").get(0); - String password = queryParameters.get("password").get(0); + String account = queryParameters.getSingleValueByKey("account"); + String password = queryParameters.getSingleValueByKey("password"); User user = service.findUserByAccountAndPassword(account, password); log.info("user: {}", user); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java index 3cffd634c5..dc66efa61c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java @@ -1,7 +1,5 @@ package org.apache.coyote.http11.message.request; -import java.util.List; -import java.util.Map; import java.util.Optional; import org.apache.coyote.http11.message.HttpHeaders; @@ -50,7 +48,7 @@ public String getBody() { return body; } - public Map> getQueryParameters() { + public QueryParameters getQueryParameters() { return url.getQueryParameters(); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java index 052c83f3f6..6c549c9e22 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java @@ -1,13 +1,10 @@ package org.apache.coyote.http11.message.request; -import java.util.List; -import java.util.Map; - public class HttpUrl { private final String path; - private final Map> queryParameters; + private final QueryParameters queryParameters; - public HttpUrl(String path, Map> queryParameters) { + public HttpUrl(String path, QueryParameters queryParameters) { this.path = path; this.queryParameters = queryParameters; } @@ -16,7 +13,7 @@ public String getPath() { return path; } - public Map> getQueryParameters() { + public QueryParameters getQueryParameters() { return queryParameters; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java index b9651e166a..1f356a963e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java @@ -23,7 +23,7 @@ public static HttpUrl parseUrl(String url) { String path = urlElements[PATH_INDEX]; if (urlElements.length == 1) { - return new HttpUrl(path, new HashMap<>()); + return new HttpUrl(path, new QueryParameters(new HashMap<>())); } String queryString = urlElements[QUERY_STRING_INDEX]; @@ -32,7 +32,7 @@ public static HttpUrl parseUrl(String url) { .map(HttpUrlParser::parseQueryParameter) .collect(Collectors.toMap(Entry::getKey, Entry::getValue, HttpUrlParser::mergeValues)); - return new HttpUrl(path, queryParameters); + return new HttpUrl(path, new QueryParameters(queryParameters)); } private static Map.Entry> parseQueryParameter(String queryParameter) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java new file mode 100644 index 0000000000..10f42f2846 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11.message.request; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class QueryParameters { + private final Map> parameters; + + public QueryParameters(Map> parameters) { + this.parameters = new HashMap<>(parameters); + } + + public String getSingleValueByKey(String key) { + if (!parameters.containsKey(key)) { + throw new IllegalArgumentException(String.format("key %s에 해당되는 쿼리 파라미터가 존재하지 않습니다.", key)); + } + + return parameters.get(key).getFirst(); + } +} From 4cfcf0860d47013fbfb29af0d9e29de53a5db598 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 17:22:33 +0900 Subject: [PATCH 12/17] =?UTF-8?q?refactor:=20static=20class=20=EC=9D=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=EB=A5=BC=20private=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/message/request/HttpUrlParser.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java index 1f356a963e..c8af3330d4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrlParser.java @@ -18,6 +18,9 @@ public class HttpUrlParser { private static final int QUERY_PARAMETER_KEY_INDEX = 0; private static final int QUERY_PARAMETER_VALUE_INDEX = 1; + private HttpUrlParser() { + } + public static HttpUrl parseUrl(String url) { String[] urlElements = url.split(URL_DELIMITER); String path = urlElements[PATH_INDEX]; From 064a916cc06b05274bfcb139215415ed6e5c617b Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 17:23:40 +0900 Subject: [PATCH 13/17] =?UTF-8?q?style:=20=EC=BD=94=EB=93=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=ED=86=B5=EC=9D=BC=EC=84=B1=EC=9D=B4=20?= =?UTF-8?q?=EB=A7=9E=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/handler/HttpHandler.java | 1 + .../coyote/http11/handler/StaticResourceHttpHandler.java | 2 -- .../org/apache/coyote/http11/message/request/HttpRequest.java | 1 + .../apache/coyote/http11/message/request/HttpRequestInfo.java | 1 + .../coyote/http11/message/request/HttpRequestReader.java | 4 ---- .../org/apache/coyote/http11/message/request/HttpUrl.java | 1 + .../apache/coyote/http11/message/request/QueryParameters.java | 1 + .../apache/coyote/http11/message/response/HttpResponse.java | 1 + 8 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java index 94959e3ec8..6121a049f9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java @@ -5,5 +5,6 @@ import org.apache.coyote.http11.message.response.HttpResponse; public interface HttpHandler { + HttpResponse handle(HttpRequest request) throws IOException; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java index c12f064b45..bbd9f3a8ed 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/StaticResourceHttpHandler.java @@ -8,8 +8,6 @@ public class StaticResourceHttpHandler implements HttpHandler { - private static final String LINE_FEED = "\n"; - @Override public HttpResponse handle(HttpRequest request) throws IOException { String fileName = "static" + request.getUrlPath(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java index dc66efa61c..c87012af03 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequest.java @@ -4,6 +4,7 @@ import org.apache.coyote.http11.message.HttpHeaders; public class HttpRequest { + private static final String REQUEST_LINE_DELIMITER = " "; private static final int HTTP_METHOD_INDEX = 0; private static final int HTTP_URL_INDEX = 1; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java index bc49996df6..b0a04b3fb6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestInfo.java @@ -3,6 +3,7 @@ import java.util.Objects; public class HttpRequestInfo { + private final HttpMethod method; private final String urlPath; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java index 81b0883609..4731da7269 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpRequestReader.java @@ -9,8 +9,6 @@ import java.util.ArrayList; import java.util.List; import org.apache.coyote.http11.message.HttpHeaders; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; // TODO: BufferedReader와 BufferedInputStream 같이 사용했을 때 문제가 발생하지 않는지 확인하기 public class HttpRequestReader { @@ -21,8 +19,6 @@ public HttpRequestReader(InputStream inputStream) { this.inputStream = inputStream; } - private static final Logger log = LoggerFactory.getLogger(HttpRequestReader.class); - public HttpRequest read() throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String requestLine = bufferedReader.readLine(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java index 6c549c9e22..85bec622b4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/HttpUrl.java @@ -1,6 +1,7 @@ package org.apache.coyote.http11.message.request; public class HttpUrl { + private final String path; private final QueryParameters queryParameters; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java index 10f42f2846..66db5f1ed5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameters.java @@ -5,6 +5,7 @@ import java.util.Map; public final class QueryParameters { + private final Map> parameters; public QueryParameters(Map> parameters) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java index 34c7b864a4..a11f2afb85 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/response/HttpResponse.java @@ -6,6 +6,7 @@ import org.apache.coyote.http11.message.HttpHeaders; public class HttpResponse { + private final HttpStatus status; private final HttpHeaders headers; private final String body; From 18211e11e75ff74c38e5665a7ac322bed57eb594 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 17:26:41 +0900 Subject: [PATCH 14/17] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/ResourceToHttpBodyConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java b/tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java index ebf8757b0e..709c9188d4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/ResourceToHttpBodyConverter.java @@ -24,8 +24,8 @@ public static HttpResponse covert(URL resource) throws IOException { String responseBody = String.join(LINE_FEED, Files.readAllLines(path)) + LINE_FEED; HttpResponse response = HttpResponse.of(HttpStatus.OK, responseBody); - String mimeType = probeContentType(path); - response.setHeader("Content-Type", mimeType); + String contentType = probeContentType(path); + response.setHeader("Content-Type", contentType); return response; } From 160869d899617f35c80a9451561869fdd935e818 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 17:26:55 +0900 Subject: [PATCH 15/17] =?UTF-8?q?refactor:=20=EC=A7=80=EC=97=AD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=B0=9B=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0=20=EB=B0=94=EB=A1=9C=20return=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/handler/ViewHttpHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java index ceaf7d3058..75eddcc769 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/ViewHttpHandler.java @@ -20,7 +20,6 @@ public HttpResponse handle(HttpRequest request) throws IOException { URL resource = getClass().getClassLoader() .getResource(fileName); - HttpResponse response = ResourceToHttpBodyConverter.covert(resource); - return response; + return ResourceToHttpBodyConverter.covert(resource); } } From cdec0289812b020c8485674b32762adfcf7ab1bd Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 20:14:45 +0900 Subject: [PATCH 16/17] =?UTF-8?q?fix:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=202=204=EB=B2=88=EC=97=90=EC=84=9C=20ETag=20?= =?UTF-8?q?=EA=B0=80=20=ED=8F=AC=ED=95=A8=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/cachecontrol/CacheWebConfig.java | 16 ++++++++-------- .../example/etag/EtagFilterConfiguration.java | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java index daf75914ef..8bbe28b9da 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -21,14 +21,14 @@ public CacheWebConfig(ResourceVersion version) { @Override public void addInterceptors(final InterceptorRegistry registry) { - WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); - webContentInterceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**"); - webContentInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)), "/etag"); -// webContentInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)).cachePublic(), -// "/resource-versioning"); - webContentInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)).cachePublic(), - "/resources/" + version.getVersion() + "/**"); + WebContentInterceptor noCacheInterceptor = new WebContentInterceptor(); + noCacheInterceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**"); + registry.addInterceptor(noCacheInterceptor); - registry.addInterceptor(webContentInterceptor); + WebContentInterceptor cacheInterceptor = new WebContentInterceptor(); + cacheInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)), "/etag"); + cacheInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)).cachePublic(), + "/resources/" + version.getVersion() + "/**"); + registry.addInterceptor(cacheInterceptor); } } 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 ef6276e66d..4b0b9ac4f6 100644 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java @@ -23,7 +23,6 @@ public FilterRegistrationBean shallowEtagHeaderFilter() FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new ShallowEtagHeaderFilter()); registrationBean.addUrlPatterns("/etag"); - registrationBean.addUrlPatterns("/resource-versioning"); registrationBean.addUrlPatterns("/resources/" + version.getVersion() + "/*"); return registrationBean; } From e37693bfea3f95e82801f0612bf37e7972064fa7 Mon Sep 17 00:00:00 2001 From: nhlee98 Date: Fri, 6 Sep 2024 20:31:39 +0900 Subject: [PATCH 17/17] =?UTF-8?q?fix:=20=EB=B8=8C=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=EC=A0=80=EC=97=90=EC=84=9C=20versoning=EC=9D=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/build.gradle | 3 +-- .../com/example/version/HandlebarsConfig.java | 26 +++++++++++++++++++ .../version/VersionHandlebarsHelper.java | 12 ++++++--- study/src/main/resources/application.yml | 3 --- 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 study/src/main/java/cache/com/example/version/HandlebarsConfig.java diff --git a/study/build.gradle b/study/build.gradle index bb8b420ca5..b8f82f0e57 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -21,8 +21,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.apache.commons:commons-lang3:3.14.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' - implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'com.github.jknack:handlebars-springmvc:4.4.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.assertj:assertj-core:3.26.0' diff --git a/study/src/main/java/cache/com/example/version/HandlebarsConfig.java b/study/src/main/java/cache/com/example/version/HandlebarsConfig.java new file mode 100644 index 0000000000..e509013696 --- /dev/null +++ b/study/src/main/java/cache/com/example/version/HandlebarsConfig.java @@ -0,0 +1,26 @@ +package cache.com.example.version; + +import com.github.jknack.handlebars.springmvc.HandlebarsViewResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.ViewResolver; + +@Configuration +public class HandlebarsConfig { + + private final VersionHandlebarsHelper versionHandlebarsHelper; + + public HandlebarsConfig(VersionHandlebarsHelper versionHandlebarsHelper) { + this.versionHandlebarsHelper = versionHandlebarsHelper; + } + + @Bean + public ViewResolver handlebarsViewResolver() { + HandlebarsViewResolver viewResolver = new HandlebarsViewResolver(); + viewResolver.registerHelper("staticUrls", versionHandlebarsHelper); + viewResolver.setPrefix("classpath:/templates/"); + viewResolver.setSuffix(".html"); + + return viewResolver; + } +} diff --git a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java b/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java index a8e004466a..8bf617189f 100644 --- a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java +++ b/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java @@ -1,13 +1,14 @@ package cache.com.example.version; +import com.github.jknack.handlebars.Helper; import com.github.jknack.handlebars.Options; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper; +import org.springframework.stereotype.Component; -@HandlebarsHelper -public class VersionHandlebarsHelper { +@Component +public class VersionHandlebarsHelper implements Helper { private static final Logger log = LoggerFactory.getLogger(VersionHandlebarsHelper.class); @@ -22,4 +23,9 @@ public String staticUrls(String path, Options options) { log.debug("static url : {}", path); return String.format("/resources/%s%s", version.getVersion(), path); } + + @Override + public Object apply(Object context, Options options) { + return staticUrls(context.toString(), options); + } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 8b74bdfd88..db798e1815 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -1,6 +1,3 @@ -handlebars: - suffix: .html - server: tomcat: accept-count: 1