From 32f26c2759f1838c2ad0580da8f21efe03a01021 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 11:11:09 +0900 Subject: [PATCH 01/41] 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 4fe78cc109453b29eaa06c5ff30ddb4e30587cce Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 5 Sep 2024 13:51:07 +0900 Subject: [PATCH 02/41] 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 aa8696ace57567e6cbb41235b4efeee644aac621 Mon Sep 17 00:00:00 2001 From: pricelees Date: Wed, 11 Sep 2024 12:52:55 +0900 Subject: [PATCH 03/41] =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/db/InMemoryUserRepository.java | 4 +- .../java/com/techcourse/web/HttpResponse.java | 179 ------------------ .../com/techcourse/web/HttpStatusCode.java | 30 --- .../com/techcourse/web/annotation/Param.java | 14 -- .../com/techcourse/web/annotation/Query.java | 13 -- .../techcourse/web/annotation/Request.java | 15 -- .../techcourse/web/handler/LoginHandler.java | 65 ------- .../techcourse/web/handler/PageHandler.java | 43 ----- .../web/handler/RequestHandler.java | 60 ------ .../web/handler/RequestHandlerMapper.java | 31 --- .../java/org/apache/catalina/Manager.java | 4 +- .../apache/catalina/connector/Connector.java | 8 +- .../org/apache/catalina/startup/Tomcat.java | 4 +- .../apache/coyote/http11/Http11Processor.java | 70 ++++--- .../org/apache/coyote/http11/HttpRequest.java | 114 ----------- .../coyote/http11/HttpRequestReader.java | 40 ---- .../web/handler/LoginHandlerTest.java | 42 ---- .../web/handler/PageHandlerTest.java | 65 ------- .../web/handler/RequestHandlerMapperTest.java | 38 ---- .../coyote/http11/Http11ProcessorTest.java | 7 +- .../coyote/http11/HttpRequestReaderTest.java | 37 ---- 21 files changed, 48 insertions(+), 835 deletions(-) delete mode 100644 tomcat/src/main/java/com/techcourse/web/HttpResponse.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/HttpStatusCode.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/annotation/Param.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/annotation/Query.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/annotation/Request.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/handler/PageHandler.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/handler/RequestHandler.java delete mode 100644 tomcat/src/main/java/com/techcourse/web/handler/RequestHandlerMapper.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequestReader.java delete mode 100644 tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java delete mode 100644 tomcat/src/test/java/com/techcourse/web/handler/PageHandlerTest.java delete mode 100644 tomcat/src/test/java/com/techcourse/web/handler/RequestHandlerMapperTest.java delete mode 100644 tomcat/src/test/java/org/apache/coyote/http11/HttpRequestReaderTest.java diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index d3fa57feeb..3be99c6e10 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -1,11 +1,11 @@ package com.techcourse.db; -import com.techcourse.model.User; - import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import com.techcourse.model.User; + public class InMemoryUserRepository { private static final Map database = new ConcurrentHashMap<>(); diff --git a/tomcat/src/main/java/com/techcourse/web/HttpResponse.java b/tomcat/src/main/java/com/techcourse/web/HttpResponse.java deleted file mode 100644 index 53665acdcb..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/HttpResponse.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.techcourse.web; - -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.LinkedHashMap; -import java.util.Map; - -public class HttpResponse { - - private final String protocol; - private final HttpStatusCode statusCode; - private final Map headers; - private final HttpResponse.Body body; - - private HttpResponse(String protocol, HttpStatusCode statusCode, Map headers, - HttpResponse.Body body) { - this.protocol = protocol; - this.statusCode = statusCode; - this.headers = headers; - this.body = body; - } - - public static HttpResponse.Builder builder() { - return new HttpResponse.Builder(); - } - - public String createResponseMessage() { - StringBuilder response = new StringBuilder(); - - // start line - response.append(protocol) - .append(" ") - .append(statusCode.getCode()) - .append(" ") - .append(statusCode.getMessage()) - .append(" ") - .append("\r\n"); - - // header - headers.forEach((key, value) -> response.append(key).append(": ").append(value).append(" ").append("\r\n")); - - // crlf - response.append("\r\n"); - - // body - if (body != null) { - response.append(body.getContent()); - } - - return response.toString(); - } - - public String getProtocol() { - return protocol; - } - - public HttpStatusCode getStatusCode() { - return statusCode; - } - - public Map getHeaders() { - return headers; - } - - public Body getBody() { - return body; - } - - @Override - public String toString() { - return "HttpResponse{" + - "protocol='" + protocol + '\'' + - ", statusCode=" + statusCode + - ", headers=" + headers + - ", body=" + body + - '}'; - } - - public static class Body { - - private static final String DEFAULT_CONTENT_CHARSET = ";charset=utf-8"; - - private final String contentType; - private final int contentLength; - private final String content; - - public static HttpResponse.Body fromPath(String path) throws IOException { - String fileName = path.contains(".") ? path : path + ".html"; - URL resource = HttpResponse.Body.class.getResource("/static" + fileName); - if (resource == null) { - throw new IllegalArgumentException("resource not found: " + "/static" + fileName); - } - - Path resourcePath = Paths.get(resource.getPath()); - - String contentType = Files.probeContentType(resourcePath) + DEFAULT_CONTENT_CHARSET; - String body = Files.readString(resourcePath); - int contentLength = body.getBytes(StandardCharsets.UTF_8).length; - - return new HttpResponse.Body(contentType, contentLength, body); - } - - public static HttpResponse.Body fromString(String body) { - String contentType = "text/html" + DEFAULT_CONTENT_CHARSET; - int contentLength = body.getBytes(StandardCharsets.UTF_8).length; - - return new HttpResponse.Body(contentType, contentLength, body); - } - - private Body(String contentType, int contentLength, String content) { - this.contentType = contentType; - this.contentLength = contentLength; - this.content = content; - } - - public String getContentType() { - return contentType; - } - - public int getContentLength() { - return contentLength; - } - - public String getContent() { - return content; - } - - @Override - public String toString() { - return "Body{" + - "contentType='" + contentType + '\'' + - ", contentLength=" + contentLength + - ", content='" + content + '\'' + - '}'; - } - } - - public static class Builder { - - private String protocol; - private HttpStatusCode statusCode; - private Map headers; - private HttpResponse.Body body; - - public Builder() { - headers = new LinkedHashMap<>(); - } - - public Builder protocol(String protocol) { - this.protocol = protocol; - return this; - } - - public Builder statusCode(HttpStatusCode statusCode) { - this.statusCode = statusCode; - return this; - } - - public Builder header(String key, String value) { - headers.put(key, value); - return this; - } - - public Builder body(HttpResponse.Body body) { - this.body = body; - headers.put("Content-Type", body.getContentType()); - headers.put("Content-Length", String.valueOf(body.getContentLength())); - return this; - } - - public HttpResponse build() { - return new HttpResponse(protocol, statusCode, headers, body); - } - } -} diff --git a/tomcat/src/main/java/com/techcourse/web/HttpStatusCode.java b/tomcat/src/main/java/com/techcourse/web/HttpStatusCode.java deleted file mode 100644 index d11033b1b6..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/HttpStatusCode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.techcourse.web; - -public enum HttpStatusCode { - - // success - OK(200, "OK"), - - // client error - BAD_REQUEST(400, "Bad Request"), - NOT_FOUND(404, "Not Found"), - - // server error, - INTERNAL_SERVER_ERROR(500, "Internal Server Error"); - - private final int code; - private final String message; - - HttpStatusCode(int code, String message) { - this.code = code; - this.message = message; - } - - public int getCode() { - return code; - } - - public String getMessage() { - return message; - } -} diff --git a/tomcat/src/main/java/com/techcourse/web/annotation/Param.java b/tomcat/src/main/java/com/techcourse/web/annotation/Param.java deleted file mode 100644 index 41e7ec2915..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/annotation/Param.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.techcourse.web.annotation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Param { - - String key(); - - boolean required(); -} diff --git a/tomcat/src/main/java/com/techcourse/web/annotation/Query.java b/tomcat/src/main/java/com/techcourse/web/annotation/Query.java deleted file mode 100644 index 9bcefa4ca6..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/annotation/Query.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.techcourse.web.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface Query { - - Param[] params(); -} diff --git a/tomcat/src/main/java/com/techcourse/web/annotation/Request.java b/tomcat/src/main/java/com/techcourse/web/annotation/Request.java deleted file mode 100644 index 92e57e4f77..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/annotation/Request.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.techcourse.web.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Request { - - String path(); - - String method(); -} diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java deleted file mode 100644 index dc67428115..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.techcourse.web.handler; - -import java.io.IOException; -import java.util.Map; -import java.util.Optional; - -import org.apache.coyote.http11.HttpRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.techcourse.db.InMemoryUserRepository; -import com.techcourse.model.User; -import com.techcourse.web.HttpResponse; -import com.techcourse.web.HttpStatusCode; -import com.techcourse.web.annotation.Param; -import com.techcourse.web.annotation.Query; -import com.techcourse.web.annotation.Request; - -public class LoginHandler extends RequestHandler { - - private static final Logger log = LoggerFactory.getLogger(LoginHandler.class); - private static final LoginHandler instance = new LoginHandler(); - - private LoginHandler() { - } - - public static LoginHandler getInstance() { - return instance; - } - - @Request(path = "/login", method = "GET") - @Query(params = { - @Param(key = "account", required = true), - @Param(key = "password", required = true) - }) - public HttpResponse loginWithQuery(HttpRequest request) throws IOException { - Map query = request.getRequestQuery(); - String account = query.get("account"); - String password = query.get("password"); - - logUser(account, password); - - return HttpResponse.builder() - .protocol(request.getProtocol()) - .statusCode(HttpStatusCode.OK) - .body(HttpResponse.Body.fromPath(request.getRequestPath())) - .build(); - } - - private void logUser(String account, String password) { - Optional userOptional = InMemoryUserRepository.findByAccount(account); - if (userOptional.isEmpty()) { - log.info("user not found. account: {}", account); - return; - } - - User user = userOptional.get(); - if (!user.checkPassword(password)) { - log.info("password not matched. account: {}", account); - return; - } - - log.info("user: {}", user); - } -} diff --git a/tomcat/src/main/java/com/techcourse/web/handler/PageHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/PageHandler.java deleted file mode 100644 index 6244cff645..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/handler/PageHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.techcourse.web.handler; - -import java.io.IOException; - -import org.apache.coyote.http11.HttpRequest; - -import com.techcourse.web.HttpResponse; -import com.techcourse.web.HttpStatusCode; -import com.techcourse.web.annotation.Request; - -public class PageHandler extends RequestHandler { - - private static final PageHandler instance = new PageHandler(); - - private PageHandler() { - } - - public static PageHandler getInstance() { - return instance; - } - - @Request(path = "*", method = "GET") - public HttpResponse getResource(HttpRequest request) throws IOException { - if (request.getRequestPath().contains("favicon")) { - return HttpResponse.builder() - .protocol(request.getProtocol()) - .statusCode(HttpStatusCode.NOT_FOUND) - .build(); - } - return HttpResponse.builder() - .protocol(request.getProtocol()) - .statusCode(HttpStatusCode.OK) - .body(getResponseBody(request.getRequestPath())) - .build(); - } - - private HttpResponse.Body getResponseBody(String requestTarget) throws IOException { - if (requestTarget.equals("/")) { - return HttpResponse.Body.fromString("Hello world!"); - } - return HttpResponse.Body.fromPath(requestTarget); - } -} diff --git a/tomcat/src/main/java/com/techcourse/web/handler/RequestHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/RequestHandler.java deleted file mode 100644 index c6ba4384e8..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/handler/RequestHandler.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.techcourse.web.handler; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; - -import org.apache.coyote.http11.HttpRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.techcourse.web.HttpResponse; -import com.techcourse.web.HttpStatusCode; -import com.techcourse.web.annotation.Query; -import com.techcourse.web.annotation.Request; - -public abstract class RequestHandler { - - private static final Logger log = LoggerFactory.getLogger(RequestHandler.class); - - public HttpResponse handle(HttpRequest request) { - Method handlerMethod = Arrays.stream(getClass().getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(Request.class)) - .filter(method -> { - Request requestAnnotation = method.getAnnotation(Request.class); - boolean isSameMethod = requestAnnotation.method().equals(request.getMethod()); - - String path = requestAnnotation.path(); - boolean isSamePath = path.equals("*") || path.equals(request.getRequestPath()); - - return isSameMethod && isSamePath; - }) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException( - "handler method not found: " + request.getMethod() + " " + request.getRequestPath())); - - if (handlerMethod.isAnnotationPresent(Query.class) && hasNotRequiredQueryParams(handlerMethod, request)) { - return HttpResponse.builder() - .protocol(request.getProtocol()) - .statusCode(HttpStatusCode.BAD_REQUEST) - .build(); - } - - try { - handlerMethod.setAccessible(true); - return (HttpResponse)handlerMethod.invoke(this, request); - } catch (IllegalAccessException | InvocationTargetException e) { - log.error(e.getMessage(), e); - return HttpResponse.builder() - .protocol(request.getProtocol()) - .statusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) - .build(); - } - } - - private boolean hasNotRequiredQueryParams(Method handlerMethod, HttpRequest request) { - Query annotation = handlerMethod.getAnnotation(Query.class); - return Arrays.stream(annotation.params()) - .anyMatch(param -> param.required() && !request.getRequestQuery().containsKey(param.key())); - } -} diff --git a/tomcat/src/main/java/com/techcourse/web/handler/RequestHandlerMapper.java b/tomcat/src/main/java/com/techcourse/web/handler/RequestHandlerMapper.java deleted file mode 100644 index bda9b829e8..0000000000 --- a/tomcat/src/main/java/com/techcourse/web/handler/RequestHandlerMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.techcourse.web.handler; - -import java.util.HashMap; -import java.util.Map; - -public class RequestHandlerMapper { - - private static final RequestHandlerMapper instance = new RequestHandlerMapper(); - private static final Map handlerMap = new HashMap<>(); - - static { - handlerMap.put("/", PageHandler.getInstance()); - handlerMap.put("^.*\\.(html|css|js|ico)$", PageHandler.getInstance()); - handlerMap.put("^(\\/login)(?!\\.).*", LoginHandler.getInstance()); - } - - private RequestHandlerMapper() { - } - - public static RequestHandlerMapper getInstance() { - return instance; - } - - public RequestHandler findHandler(String requestPath) { - return handlerMap.entrySet().stream() - .filter(entry -> requestPath.matches(entry.getKey())) - .map(Map.Entry::getValue) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("handler not found: " + requestPath)); - } -} diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e69410f6a9..e5e8108636 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,9 +1,9 @@ package org.apache.catalina; -import jakarta.servlet.http.HttpSession; - import java.io.IOException; +import jakarta.servlet.http.HttpSession; + /** * A Manager manages the pool of Sessions that are associated with a * particular Container. Different Manager implementations may support 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..9319ab163b 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -1,14 +1,14 @@ 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 { private static final Logger log = LoggerFactory.getLogger(Connector.class); diff --git a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java index 205159e95b..61afa8df71 100644 --- a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java +++ b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java @@ -1,11 +1,11 @@ package org.apache.catalina.startup; +import java.io.IOException; + import org.apache.catalina.connector.Connector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; - public class Tomcat { private static final Logger log = LoggerFactory.getLogger(Tomcat.class); 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 7c0429258d..6de2a7f3ab 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -2,49 +2,47 @@ import java.io.IOException; import java.net.Socket; -import java.nio.charset.StandardCharsets; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.techcourse.exception.UncheckedServletException; -import com.techcourse.web.HttpResponse; -import com.techcourse.web.handler.RequestHandler; -import com.techcourse.web.handler.RequestHandlerMapper; public class Http11Processor implements Runnable, Processor { - private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - - private final Socket connection; - private final RequestHandlerMapper handlerMapper; - - public Http11Processor(final Socket connection) { - this.connection = connection; - this.handlerMapper = RequestHandlerMapper.getInstance(); - } - - @Override - public void run() { - log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort()); - process(connection); - } - - @Override - public void process(final Socket connection) { - try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { - - HttpRequest request = HttpRequestReader.read(inputStream); - RequestHandler handler = handlerMapper.findHandler(request.getRequestPath()); - HttpResponse response = handler.handle(request); - String httpResponseMessage = response.createResponseMessage(); - - outputStream.write(httpResponseMessage.getBytes(StandardCharsets.UTF_8)); - outputStream.flush(); - } catch (IOException | UncheckedServletException e) { - log.error(e.getMessage(), e); - } - } + private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + + private final Socket connection; + + public Http11Processor(final Socket connection) { + this.connection = connection; + } + + @Override + public void run() { + log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort()); + process(connection); + } + + @Override + public void process(final Socket connection) { + try (final var inputStream = connection.getInputStream(); + final var outputStream = connection.getOutputStream()) { + + final var responseBody = "Hello world!"; + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (IOException | UncheckedServletException e) { + log.error(e.getMessage(), e); + } + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java deleted file mode 100644 index adf9646402..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.apache.coyote.http11; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class HttpRequest { - - private final String method; - private final HttpRequestTarget requestTarget; - private final String protocol; - private final Map headers; - private final String body; - - public HttpRequest(List httpRequest) { - String[] requestLine = httpRequest.getFirst().split(" "); - this.method = requestLine[0]; - this.requestTarget = new HttpRequestTarget(requestLine[1]); - this.protocol = requestLine[2]; - this.headers = createHeaders(httpRequest); - this.body = createBody(httpRequest); - } - - private Map createHeaders(List httpRequest) { - Map headers = new HashMap<>(); - httpRequest.stream() - .skip(1) - .takeWhile(header -> !header.isBlank()) - .forEach(header -> { - String[] keyValue = header.split(":", 2); - headers.put(keyValue[0], keyValue[1].strip()); - }); - return headers; - } - - private String createBody(List httpRequest) { - return httpRequest.stream() - .skip(1) - .dropWhile(header -> !header.isBlank()) - .collect(Collectors.joining("\r\n")); - } - - public String getMethod() { - return method; - } - - public String getRequestPath() { - return requestTarget.path; - } - - public Map getRequestQuery() { - return requestTarget.query; - } - - public String getProtocol() { - return protocol; - } - - public Map getHeaders() { - return headers; - } - - public String getBody() { - return body; - } - - @Override - public String toString() { - return "HttpRequest{" + - "method='" + method + '\'' + - ", requestTarget=" + requestTarget + - ", protocol='" + protocol + '\'' + - ", headers=" + headers + - ", body='" + body + '\'' + - '}'; - } - - static class HttpRequestTarget { - - private final String path; - private final Map query; - - public HttpRequestTarget(String requestTarget) { - String[] parts = requestTarget.split("\\?"); - String query = parts.length == 2 ? parts[1] : ""; - this.path = parts[0]; - this.query = createQuery(query); - } - - private Map createQuery(String query) { - Map queries = new HashMap<>(); - - if (query.isBlank()) { - return queries; - } - - String[] parts = query.split("&"); - for (String part : parts) { - String[] keyValue = part.split("="); - queries.put(keyValue[0], keyValue[1]); - } - return queries; - } - - @Override - public String toString() { - return "HttpRequestTarget{" + - "path='" + path + '\'' + - ", query=" + query + - '}'; - } - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestReader.java deleted file mode 100644 index 77a22a4812..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestReader.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.apache.coyote.http11; - -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; - -public class HttpRequestReader { - - public static HttpRequest read(InputStream inputStream) throws IOException { - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); - BufferedReader br = new BufferedReader(reader); - - List httpRequest = new ArrayList<>(); - - String line; - int contentLength = 0; - - while ((line = br.readLine()) != null && !line.isEmpty()) { - httpRequest.add(line); - - if (line.startsWith("Content-Length:")) { - contentLength = Integer.parseInt(line.split(":")[1].strip()); - } - } - - if (contentLength > 0) { - httpRequest.add(""); - char[] body = new char[contentLength]; - int numberOfCharacterRead = br.read(body, 0, contentLength); - String requestBody = new String(body, 0, numberOfCharacterRead); - httpRequest.add(requestBody); - } - - return new HttpRequest(httpRequest); - } -} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java deleted file mode 100644 index 0ed637f3ab..0000000000 --- a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.techcourse.web.handler; - -import static org.assertj.core.api.Assertions.*; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; - -import org.apache.coyote.http11.HttpRequest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import com.techcourse.web.HttpResponse; - -class LoginHandlerTest { - - - @DisplayName("/login?account={account}&password={password} 경로 요청에 대한 응답을 생성한다.") - @Test - void loginWithQuery() throws IOException { - HttpRequest httpRequest = new HttpRequest(Arrays.asList( - "GET /login?account=gugu&password=password HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "Accept: text/html " - )); - - - HttpResponse response = LoginHandler.getInstance().loginWithQuery(httpRequest); - Path path = Path.of("src/main/resources/static/login.html"); - String content = Files.readString(path); - - assertThat(response).satisfies(r -> { - assertThat(r.getProtocol()).isEqualTo("HTTP/1.1"); - assertThat(r.getStatusCode().getCode()).isEqualTo(200); - assertThat(r.getHeaders()).containsEntry("Content-Type", "text/html;charset=utf-8"); - assertThat(r.getHeaders()).containsEntry("Content-Length", String.valueOf(content.getBytes().length)); - assertThat(r.getBody().getContent()).isEqualTo(content); - }); - } -} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/PageHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/PageHandlerTest.java deleted file mode 100644 index 10e85ea2b2..0000000000 --- a/tomcat/src/test/java/com/techcourse/web/handler/PageHandlerTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.techcourse.web.handler; - -import static org.assertj.core.api.Assertions.*; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; - -import org.apache.coyote.http11.HttpRequest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import com.techcourse.web.HttpResponse; - -class PageHandlerTest { - - @DisplayName("/ 경로 요청에 대한 응답을 생성한다.") - @Test - void getResource_WhenRequestPathIsRoot() { - PageHandler handler = PageHandler.getInstance(); - - HttpRequest httpRequest = new HttpRequest(Arrays.asList( - "GET / HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "Accept: text/html " - )); - - HttpResponse response = handler.handle(httpRequest); - - assertThat(response).satisfies(r -> { - assertThat(r.getProtocol()).isEqualTo("HTTP/1.1"); - assertThat(r.getStatusCode().getCode()).isEqualTo(200); - assertThat(r.getHeaders()).containsEntry("Content-Type", "text/html;charset=utf-8"); - assertThat(r.getHeaders()).containsEntry("Content-Length", "12"); - assertThat(r.getBody().getContent()).isEqualTo("Hello world!"); - }); - } - - @DisplayName("/index.html 경로 요청에 대한 응답을 생성한다.") - @Test - void getResource_WhenRequestPathIsIndexHtml() throws IOException { - PageHandler handler = PageHandler.getInstance(); - - HttpRequest httpRequest = new HttpRequest(Arrays.asList( - "GET /index.html HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "Accept: text/html " - )); - - HttpResponse response = handler.handle(httpRequest); - Path path = Path.of("src/main/resources/static/index.html"); - String content = Files.readString(path); - - assertThat(response).satisfies(r -> { - assertThat(r.getProtocol()).isEqualTo("HTTP/1.1"); - assertThat(r.getStatusCode().getCode()).isEqualTo(200); - assertThat(r.getHeaders()).containsEntry("Content-Type", "text/html;charset=utf-8"); - assertThat(r.getHeaders()).containsEntry("Content-Length", String.valueOf(content.getBytes().length)); - assertThat(r.getBody().getContent()).isEqualTo(content); - }); - } -} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/RequestHandlerMapperTest.java b/tomcat/src/test/java/com/techcourse/web/handler/RequestHandlerMapperTest.java deleted file mode 100644 index 0c70913305..0000000000 --- a/tomcat/src/test/java/com/techcourse/web/handler/RequestHandlerMapperTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.techcourse.web.handler; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class RequestHandlerMapperTest { - - @DisplayName("/login 경로에 대한 핸들러를 찾는다.") - @ParameterizedTest - @ValueSource(strings = {"/login", "/login?account=account", "/login/1"}) - void findLoginHandler(String value) { - - RequestHandler handler = RequestHandlerMapper.getInstance().findHandler(value); - - assertThat(handler).isInstanceOf(LoginHandler.class); - } - - @DisplayName("/ 경로에 대한 핸들러를 찾는다.") - @Test - void findPageHandler() { - RequestHandler handler = RequestHandlerMapper.getInstance().findHandler("/"); - - assertThat(handler).isInstanceOf(PageHandler.class); - } - - @DisplayName("정적 파일에 대한 핸들러를 찾는다.") - @ParameterizedTest - @ValueSource(strings = {"/login.html", "/index.html", "/style.css", "/script.js", "/favicon.ico"}) - void findStaticFileHandler(String value) { - RequestHandler handler = RequestHandlerMapper.getInstance().findHandler(value); - - assertThat(handler).isInstanceOf(PageHandler.class); - } -} 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..ab4ac6e227 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.*; 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 { diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestReaderTest.java b/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestReaderTest.java deleted file mode 100644 index 71ef184688..0000000000 --- a/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestReaderTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.apache.coyote.http11; - -import static org.assertj.core.api.Assertions.*; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class HttpRequestReaderTest { - - @DisplayName("Http 요청 메시지를 읽는다.") - @Test - void read() throws IOException { - String httpRequest = String.join("\r\n", - "GET /index.html HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "", - ""); - - InputStream inputStream = new ByteArrayInputStream(httpRequest.getBytes()); - HttpRequest request = HttpRequestReader.read(inputStream); - - assertThat(request).satisfies(r -> { - assertThat(r.getMethod()).isEqualTo("GET"); - assertThat(r.getRequestPath()).isEqualTo("/index.html"); - assertThat(r.getProtocol()).isEqualTo("HTTP/1.1"); - assertThat(r.getHeaders()).satisfies(headers -> { - assertThat(headers).containsEntry("Host", "localhost:8080"); - assertThat(headers).containsEntry("Connection", "keep-alive"); - }); - }); - } -} \ No newline at end of file From a726a3cdbf4796a0f4fd53a88b21e0b86737e344 Mon Sep 17 00:00:00 2001 From: pricelees Date: Wed, 11 Sep 2024 13:17:03 +0900 Subject: [PATCH 04/41] =?UTF-8?q?test:=20http=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/build.gradle | 2 ++ .../example/cachecontrol/CacheWebConfig.java | 25 +++++++++++++++++++ .../example/etag/EtagFilterConfiguration.java | 14 ++++++++--- study/src/main/resources/application.yml | 3 +++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/study/build.gradle b/study/build.gradle index 87a1f0313c..f63452964a 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -22,6 +22,8 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.14.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.assertj:assertj-core:3.26.0' diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java index 305b1f1e1e..0141321ed1 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,38 @@ package cache.com.example.cachecontrol; +import java.time.Duration; + import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.WebContentInterceptor; @Configuration public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(getNoCacheInterceptor()); + registry.addInterceptor(getResourceCacheInterceptor()); + } + + // add no-cache, private cache + private WebContentInterceptor getNoCacheInterceptor() { + CacheControl cacheControl = CacheControl.noCache().cachePrivate(); + + return createWebContentInterceptorByCache(cacheControl, "/"); + } + + private WebContentInterceptor getResourceCacheInterceptor() { + CacheControl cacheControl = CacheControl.maxAge(Duration.ofDays(365)).cachePublic(); + + return createWebContentInterceptorByCache(cacheControl, "/resources/**"); + } + + private WebContentInterceptor createWebContentInterceptorByCache(CacheControl cacheControl, String path) { + WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); + webContentInterceptor.addCacheMapping(cacheControl, path); + return 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..ab48ff798e 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,18 @@ 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 registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new ShallowEtagHeaderFilter()); + registrationBean.addUrlPatterns("/etag", "/resources/*"); + return registrationBean; + } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index e3503a5fb9..8b74bdfd88 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -8,3 +8,6 @@ server: threads: min-spare: 2 max: 2 + compression: + enabled: true + min-response-size: 10 From 2537d06f4ac600cb997545df9b3e91e933c75f3d Mon Sep 17 00:00:00 2001 From: pricelees Date: Wed, 11 Sep 2024 15:12:42 +0900 Subject: [PATCH 05/41] =?UTF-8?q?feat:=20index.html=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=EB=B0=8F=20css=20=EC=A7=80=EC=9B=90=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 | 64 +++++++++++-------- .../apache/coyote/http11/ResourceLoader.java | 54 ++++++++++++++++ .../coyote/http11/response/ResponseBody.java | 48 ++++++++++++++ 3 files changed, 139 insertions(+), 27 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.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 6de2a7f3ab..a4719eccb0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,9 +1,12 @@ package org.apache.coyote.http11; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.net.Socket; import org.apache.coyote.Processor; +import org.apache.coyote.http11.response.ResponseBody; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,38 +14,45 @@ public class Http11Processor implements Runnable, Processor { - private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private final Socket connection; + private final Socket connection; - public Http11Processor(final Socket connection) { - this.connection = connection; - } + public Http11Processor(final Socket connection) { + this.connection = connection; + } - @Override - public void run() { - log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort()); - process(connection); - } + @Override + public void run() { + log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort()); + process(connection); + } - @Override - public void process(final Socket connection) { - try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { + @Override + public void process(final Socket connection) { + try (final var inputStream = connection.getInputStream(); + final var outputStream = connection.getOutputStream()) { - final var responseBody = "Hello world!"; + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String requestLine = reader.readLine(); + String requestUrl = requestLine.split(" ")[1]; - 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); + ResponseBody responseBody = ResourceLoader.getInstance().loadResource(requestUrl); - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (IOException | UncheckedServletException e) { - log.error(e.getMessage(), e); - } - } + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: " + responseBody.getContentType() + ";charset=utf-8" + " ", + "Content-Length: " + responseBody.getContentLength() + " ", + "", + new String(responseBody.getContent()) + ); + + // 바디(바이너리 또는 텍스트 데이터)를 전송 + outputStream.write(response.getBytes()); + outputStream.flush(); + + } catch (IOException | UncheckedServletException e) { + log.error(e.getMessage(), e); + } + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java b/tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java new file mode 100644 index 0000000000..c9111d48dd --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java @@ -0,0 +1,54 @@ +package org.apache.coyote.http11; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.coyote.http11.response.ResponseBody; + +public class ResourceLoader { + + private static final ResourceLoader instance = new ResourceLoader(); + private static final String RESOURCE_PATH = "/static"; + private static final String ROOT_PATH = "/"; + private static final String ROOT_PATH_CONTENT = "Hello world!"; + private static final String ROOT_PATH_CONTENT_TYPE = "text/html"; + + private ResourceLoader() { + } + + public static ResourceLoader getInstance() { + return instance; + } + + public ResponseBody loadResource(String requestUrl) throws IOException { + if (requestUrl.equals(ROOT_PATH)) { + return ResponseBody.builder() + .content(ROOT_PATH_CONTENT.getBytes()) + .contentType(ROOT_PATH_CONTENT_TYPE) + .build(); + } + + return readResource(requestUrl); + } + + private ResponseBody readResource(String requestUrl) throws IOException { + String fileName = RESOURCE_PATH + requestUrl; + Path filePath = getPath(fileName); + + return ResponseBody.builder() + .contentType(Files.probeContentType(filePath)) + .content(Files.readAllBytes(filePath)) + .build(); + } + + private Path getPath(String fileName) { + URL resource = getClass().getResource(fileName); + if (resource == null) { + throw new IllegalArgumentException("Resource not found. resource: " + fileName); + } + + return Path.of(resource.getPath()); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java new file mode 100644 index 0000000000..c5177e86b2 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java @@ -0,0 +1,48 @@ +package org.apache.coyote.http11.response; + +public class ResponseBody { + + private final String contentType; + private final byte[] content; + + public static ResponseBody.Builder builder() { + return new ResponseBody.Builder(); + } + + private ResponseBody(String contentType, byte[] content) { + this.contentType = contentType; + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public byte[] getContent() { + return content; + } + + public int getContentLength() { + return content.length; + } + + public static class Builder { + + private String contentType; + private byte[] content; + + public Builder contentType(String contentType) { + this.contentType = contentType; + return this; + } + + public Builder content(byte[] content) { + this.content = content; + return this; + } + + public ResponseBody build() { + return new ResponseBody(contentType, content); + } + } +} From a714e5a057d54beb3792c3222e5a6e076caf256d Mon Sep 17 00:00:00 2001 From: pricelees Date: Wed, 11 Sep 2024 15:12:50 +0900 Subject: [PATCH 06/41] =?UTF-8?q?feat:=20favicon=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/src/main/resources/static/favicon.ico | Bin 0 -> 15406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tomcat/src/main/resources/static/favicon.ico diff --git a/tomcat/src/main/resources/static/favicon.ico b/tomcat/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ceb052efc9887c2179fa2da0b0849abf492b8017 GIT binary patch literal 15406 zcmeHN3!IKs8vooEMQUq*+m;Fy#>NlvL7Phf3Hd|(q z)+$GNGi4)bB0oGS^`2cQ7jzvY&3liR7AUNSRze&j+KWeCCkv6OJ&Tw69%+kU-+t^1ML$7n{o@Z^l`h_&9H+GR+ z``Sji>Fu4;=AFG-SZ5pi8iU5dZ`R1$X8J;W>iSw~Yd<<(I&D5B*UiX~CR0~yVV!NY zA06kbM{UE(f6a^OQu~o{QWN9s@ct1ktk-^Iys*zoYwXI}#`p!Ki~c!zjgi82HbY*CqEEBZO+)R)qVr}2RU2|c0OLOyvg=T{p!;Xa|O2G(i{Nh3k|Em^f~rzEF;D3h10k@z=LWpwg#89isY)~768 zEA!TFmNi+QN}reC_F`z0@|7P3+O>5KxKDiTJbG)PT=2lN-Z?dMYn~PE>?M`%>aB%! z)9(AEjHs<=ieaFxu3H5jk^4{gx6j_bK6$Ikm7^)KA8RTukgQIQ<{ z@tEZ7{RTeFXR>1R4#h-Vtg|g=?|%6XG^FJpc{v}H?tD_JcN-~}KJ}v19W_s` znvfz*Ca;pF)7ENXoo(zRO^t4&gggdY1Z3IiK%4tr;R0E#n(H$gq`7 z9`aHKW!dq<1~So}XV-UL+V~E2zdvp9od*nOpo7Ixf31fTr0&Rh#rhjfTp`u}HbQ+* z`k$<`%_^tv==oCXq4;8L)_$#X>~258k**ee)(SXh+NK>)nhqO|O6BPOQmyMyJy&?P zuwFT;pRkW|+opZ1JjCv&v-ntz-{MGj!~E@P$9$(;_eO@)?mj_(H+%VgFAwXqb@nxf zPfl9?zF~fjm&Oi<$NrM+`mAjY6PKwxv!AXUIagvbE$@2N*=G7(*n8x$%5bPBdCq{K zw70&y55C3*xn;>7M_TrghO`d)inSYGF-+gCn2b>LIMC!9Kb7wS2b!|dsrshj-Z~&B0$D7=rrWW-UB^cv-H+PT0Gx-)(gIJ~0P1XeYjYX@I%) zTLLk+AO}J@tN{Gz60;zhoO(YjGpJ_ zTq}v5|2H1SNf4rSbw??SI0?k~{l$w^jQT|CL}g0-&Y99FCR-A*UTL^@F&1fpa))EESk?Q3A!m{P!SuFrF3B*)lA4 zhqUOGB~{89wu*;uQ5SlXF`oxeFL4aV>~YG#JRHD0%!%15^~z-oA{8QYT*G6S&b!To3w>}tb*)P&v&pfQwK^q{MWXn#vd;g@vi9sod;2Z+Bf6g zH!b*=GL!NBy;3c# zF>v0AJxgQB_VWe3H!b)?{y6Y;snn^jEmk}Hrj0!0y%FnG?9Svn24EnTaMpqA2JDXW z&bx8__{CDS^Ppn5Yjy6gal-KnRw72UO;%^_l5`Z-8S7#Uku+uud{RdJaZB{h?X=Fi z4zS~(Yk)n^eWSMLGY>}gT^mnmJcoC0)gSqkT=i(2Oj?|-F)j^o1^CBU*>j5!7XnXJ z@KOe4)g3ioDBI|3B66jpwxutvGVXt!Dsi$H`x84gemxJF}qVYikxd<`3vuDrB znZmOgOJod@b+&oJcS^bsF~uR^r3{XNGATRqqo19|z&*WM&sK+p0QMm$_GcvQy}`Jp zI#)Vph^;dAJLA2LGIoBd<}ah;UXj)VCrOK^kh^<)EOMh`v@}JXZEXfkmd;~em)&%{<9_g98)^ri?>_V&vspKtkDC{LDpk7-E@rzNhf`tfn7G`< zna-ELYUja1T~0XAew;plbqw@9ur_m?c(|rlj+i5ipSb7#Nx3k#zchO6S)K3LF|Q#9 zJx6*^Um$~E6C-CYlel^Bdh^k&vyHY!ng@}aj*fd(ZW-`A_Q5lV;r2Zj7ljV$3SdV< z|D_##Ff#`FPwIm>?AhDXf6!PMv0r@8Aep&xlkDFAo$UYNNBRE9aXEq*`H7RK>>~|nkNhWJdH;>L`>uVt@f~Cq~skxp_pv&S?!tQR;iK9| z8q$)7yylPN>l**MR}XbM;a8t8TZR)meQ>PD3MX2&bKe>}P07fXY~%x&ADEW1UPjDL zkskQofaB^gVw$ubFiBeWNtA1QB}lU;;kiB1 zgJ){q?c&EL$rZzrrQvhS<=R(2kQ?9HA-697TJyXeH~oYhD{^XfJ{j7We+0+rtFExCy`!t+Id(7|uwWlRcP-b&^p69^CILa2kgZ-Do*i3vTWc>BUERx^d zGfeL;zIZy}TZ+1)-;}CdhsgEG+uZvYV_}|}ehtq=yPW0C3wLTBHe$@eka2i)6F=7= zWIn#zdB*WPGc(@^Uh7Duk*fJm>UlPvzLZv z56_@*cy3$u9qebgj)6AOa?Unszb+5o8Lq_LzYBEAC%c{Wv&-nD3}yG-T|S|8XCTznoL>ky|XiCTLbS?<69F)Ys4cAy?M5N z=L_$+`M#}z?+(aI?7YkK+edx(g<;Ehe`zcg&!Dj#gM&}{=FFq|`V<_;&Hvc#`2J;| zjun`R+t^{l3CjZd`&Ql7I{gRwK=g&I?V;9Nr|i}9<}$>o=uglu2*%rfZJyFz*6$VN zrHo)acKxuOT#aGF2gThh)sE)Z+y?wBO#B^Z(C|R_mtWgBh?RFP-d}hJ(^x+4WWP3? zPI!3N<(-$<`Nz93*5kbA>YeZfZAYxZH{XFZsSCbxi|q%+6ymwb7*Cfi5}_0C^A9?P zqTfD#6{7sI!f7Xm*ieS}!1n%RYz}AXD)5H&KCshO4sHCFJUvkS(8c&Ib1m+CMTySk za=`7z!F&dDG9l!Zm~+8c9pmTahS9-B6$^7Qq5dI%f&JVcIQcF7FS-uE9(MkM!({#_ z+MR>>#nu65x;uXaETK+)F*>XRe2bn~gOKufWw=&6KMvB4!^s;J!*2NjIe+EeM=AHS zRdpawp}7_dhEi3r=Xq{HHuD>fIf62nf4)D5>KPfrIa87`@!iK<0ez6N@cAeMb8>+G z3w?l+U<#Lqz6bp${q_QWEOdu=f8c$IJn-A;`_g};4;C(t($dF!;_tjim{=;tVq$tG gwxlr*;QX9@z@2FBO^kn2Mrm>U#{b(4lynCE3%tE?YybcN literal 0 HcmV?d00001 From 27c9983c06d0d9c4b8532d5d7e951c38fa0c3d06 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 10:26:37 +0900 Subject: [PATCH 07/41] =?UTF-8?q?feat:=20HTTP=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20=EB=8B=B4=EB=8A=94=20?= =?UTF-8?q?=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/http/BaseHttpHeaders.java | 30 ++++++++++ .../http11/http/request/HttpRequest.java | 28 +++++++++ .../http/request/HttpRequestHeader.java | 42 ++++++++++++++ .../http11/http/request/HttpRequestLine.java | 57 ++++++++++++++++++ .../http11/http/request/HttpRequestQuery.java | 58 +++++++++++++++++++ .../http11/http/request/HttpRequestUrl.java | 36 ++++++++++++ 6 files changed, 251 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java new file mode 100644 index 0000000000..1d9ab19abf --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11.http; + +import java.util.List; +import java.util.Map; + +public abstract class BaseHttpHeaders { + + protected static final String HEADER_DELIMITER = ": "; + protected static final String HEADER_VALUE_DELIMITER = ", "; + + protected final Map> headers; + + public BaseHttpHeaders(Map> headers) { + this.headers = headers; + } + + public List getValue(String key) { + if (isNotExistKey(key)) { + throw new IllegalArgumentException("Key not found. key: " + key); + } + return headers.get(key); + } + + private boolean isNotExistKey(String key) { + if (headers == null) { + return false; + } + return !headers.containsKey(key); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequest.java new file mode 100644 index 0000000000..46ab9e4ace --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequest.java @@ -0,0 +1,28 @@ +package org.apache.coyote.http11.http.request; + +import java.util.List; + +public class HttpRequest { + + private final HttpRequestLine requestLine; + private final HttpRequestHeader headers; + private final String requestBody; + + public HttpRequest(String requestLine, List headers, String requestBody) { + this.requestLine = HttpRequestLine.from(requestLine); + this.headers = HttpRequestHeader.from(headers); + this.requestBody = requestBody; + } + + public HttpRequestLine getRequestLine() { + return requestLine; + } + + public HttpRequestHeader getHeaders() { + return headers; + } + + public String getRequestBody() { + return requestBody; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java new file mode 100644 index 0000000000..07d4d3fcf0 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java @@ -0,0 +1,42 @@ +package org.apache.coyote.http11.http.request; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.coyote.http11.http.BaseHttpHeaders; + +public class HttpRequestHeader extends BaseHttpHeaders { + + private static final int HEADER_KEY_INDEX = 0; + private static final int HEADER_VALUE_INDEX = 1; + + public HttpRequestHeader(Map> headers) { + super(headers); + } + + public static HttpRequestHeader from(List headers) { + return new HttpRequestHeader(initHeaders(headers)); + } + + private static Map> initHeaders(List headers) { + if (headers == null || headers.isEmpty()) { + return null; + } + + LinkedHashMap> result = new LinkedHashMap<>(); + headers.forEach(h -> { + String[] headerParts = h.split(HEADER_DELIMITER); + result.put(headerParts[HEADER_KEY_INDEX], parseHeaderValue(headerParts[HEADER_VALUE_INDEX])); + }); + + return result; + } + + private static List parseHeaderValue(String headerPart) { + return Arrays.stream(headerPart.split(HEADER_VALUE_DELIMITER)) + .map(String::strip) + .toList(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java new file mode 100644 index 0000000000..9729205b9b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java @@ -0,0 +1,57 @@ +package org.apache.coyote.http11.http.request; + +public class HttpRequestLine { + + private static final String PART_DELIMITER = " "; + private static final int METHOD_INDEX = 0; + private static final int REQUEST_URL_INDEX = 1; + private static final int HTTP_VERSION_INDEX = 2; + + private final HttpMethod method; + private final HttpRequestUrl httpRequestUrl; + private final String httpVersion; + + public HttpRequestLine(String method, HttpRequestUrl httpRequestUrl, String httpVersion) { + this.method = HttpMethod.valueOf(method); + this.httpRequestUrl = httpRequestUrl; + this.httpVersion = httpVersion; + } + + public static HttpRequestLine from(String requestLine) { + String[] parts = requestLine.split(PART_DELIMITER); + return new HttpRequestLine( + parts[METHOD_INDEX], + HttpRequestUrl.from(parts[REQUEST_URL_INDEX]), + parts[HTTP_VERSION_INDEX] + ); + } + + public boolean isSameMethod(HttpMethod method) { + return this.method == method; + } + + public HttpMethod getMethod() { + return method; + } + + public String getRequestPath() { + return httpRequestUrl.getPath(); + } + + public HttpRequestQuery getQuery() { + return httpRequestUrl.getQuery(); + } + + public String getHttpVersion() { + return httpVersion; + } + + @Override + public String toString() { + return "HttpRequestLine{" + + "method='" + method + '\'' + + ", requestUrl=" + httpRequestUrl + + ", httpVersion='" + httpVersion + '\'' + + '}'; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java new file mode 100644 index 0000000000..40002d95b8 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java @@ -0,0 +1,58 @@ +package org.apache.coyote.http11.http.request; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class HttpRequestQuery { + + private static final String QUERY_PARAM_DELIMITER = "&"; + private static final String QUERY_KEY_VALUE_DELIMITER = "="; + private static final int QUERY_KEY_INDEX = 0; + private static final int QUERY_VALUE_INDEX = 1; + + private final Map query; + + public HttpRequestQuery(Map query) { + this.query = query; + } + + public static HttpRequestQuery from(String query) { + return new HttpRequestQuery(initQuery(query)); + } + + private static Map initQuery(String query) { + if (query == null || query.isBlank()) { + return null; + } + + return Arrays.stream(query.split(QUERY_PARAM_DELIMITER)) + .collect(Collectors.toMap(HttpRequestQuery::parseKey, HttpRequestQuery::parseValue)); + } + + private static String parseKey(String queryPart) { + return queryPart.split(QUERY_KEY_VALUE_DELIMITER)[QUERY_KEY_INDEX]; + } + + private static String parseValue(String queryPart) { + String[] parts = queryPart.split(QUERY_KEY_VALUE_DELIMITER); + if (parts.length == 1) { + return ""; + } + return parts[QUERY_VALUE_INDEX]; + } + + public String getValue(String key) { + if (isNotExistKey(key)) { + throw new IllegalArgumentException("Key not found. key: " + key); + } + return query.get(key); + } + + private boolean isNotExistKey(String key) { + if (query == null) { + return false; + } + return query.containsKey(key); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java new file mode 100644 index 0000000000..9141b77cb4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java @@ -0,0 +1,36 @@ +package org.apache.coyote.http11.http.request; + +public class HttpRequestUrl { + + private final String path; + private final HttpRequestQuery httpRequestQuery; + + public static HttpRequestUrl from(String requestUrl) { + String[] urlParts = requestUrl.split("\\?"); + if (urlParts.length == 1) { + return new HttpRequestUrl(urlParts[0], null); + } + return new HttpRequestUrl(urlParts[0], urlParts[1]); + } + + public HttpRequestUrl(String path, String query) { + this.path = path; + this.httpRequestQuery = HttpRequestQuery.from(query); + } + + public String getPath() { + return path; + } + + public HttpRequestQuery getQuery() { + return httpRequestQuery; + } + + @Override + public String toString() { + return "RequestUrl{" + + "path='" + path + '\'' + + ", query=" + httpRequestQuery + + '}'; + } +} From 6afa63fa3777bd563bf75383f6131e0920467300 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 10:51:03 +0900 Subject: [PATCH 08/41] =?UTF-8?q?feat:=20HTTP=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20=EC=9D=BD=EB=8A=94=20?= =?UTF-8?q?=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 --- .../http11/HttpRequestMessageReader.java | 44 ++++++++++++++ .../http11/HttpRequestMessageReaderTest.java | 58 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java new file mode 100644 index 0000000000..5b3a6ddef1 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java @@ -0,0 +1,44 @@ +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.apache.coyote.http11.http.request.HttpRequest; + +public class HttpRequestMessageReader { + + public static HttpRequest read(InputStream inputStream) throws IOException { + + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String requestLine = reader.readLine(); + List headers = readHeaders(reader); + String body = readBody(reader); + + return new HttpRequest(requestLine, headers, body); + } + + private static String readBody(BufferedReader reader) throws IOException { + List result = new ArrayList<>(); + while (reader.ready()) { + result.add(reader.readLine()); + } + + return String.join("\r\n", result); + } + + private static List readHeaders(BufferedReader reader) throws IOException { + List headers = new ArrayList<>(); + + String line = reader.readLine(); + while (line != null && !line.isEmpty()) { + headers.add(line); + line = reader.readLine(); + } + + return headers; + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java b/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java new file mode 100644 index 0000000000..b0182181b6 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java @@ -0,0 +1,58 @@ +package org.apache.coyote.http11; + +import static org.assertj.core.api.Assertions.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.coyote.http11.http.request.HttpMethod; +import org.apache.coyote.http11.http.request.HttpRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HttpRequestMessageReaderTest { + + @DisplayName("Http 요청 메시지를 읽는다.") + @Test + void read() throws IOException { + String body = """ + { + "name": "John Doe", + "age": 30, + "email": "john.doe@example.com", + "isActive": true + }"""; + + String httpRequest = String.join("\r\n", + "POST /api/endpoint?hi=hello&bangga= HTTP/1.1 ", + "Host: example.com ", + "Content-Length: 39 ", + "Content-Type: application/json ", + "Accept: application/json, text/plain, */* ", + "", + body); + + InputStream inputStream = new ByteArrayInputStream(httpRequest.getBytes()); + HttpRequest request = HttpRequestMessageReader.read(inputStream); + + assertThat(request.getRequestLine()).satisfies( + requestLine -> { + assertThat(requestLine.getMethod()).isEqualTo(HttpMethod.POST); + assertThat(requestLine.getRequestPath()).isEqualTo("/api/endpoint"); + assertThat(requestLine.getHttpVersion()).isEqualTo("HTTP/1.1"); + } + ); + assertThat(request.getHeaders()).satisfies( + headers -> { + assertThat(headers.getValue("Host")).containsOnly("example.com"); + assertThat(headers.getValue("Content-Length")).containsOnly("39"); + assertThat(headers.getValue("Content-Type")).containsOnly("application/json"); + assertThat(headers.getValue("Accept")).containsExactly("application/json", "text/plain", "*/*"); + } + ); + assertThat(request.getRequestBody()).contains("name\": \"John Doe", "age\": 30", + "email\": \"john.doe@example.com", + "isActive\": true"); + } +} From 7a29f8aa47fb5743e4d94854c9cde6eaefa3b1b3 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 10:51:36 +0900 Subject: [PATCH 09/41] =?UTF-8?q?feat:=20HTTP=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20=EB=A7=8C=EB=93=A4=20?= =?UTF-8?q?=EB=95=8C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=EA=B0=9D?= =?UTF-8?q?=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 --- .../http11/http/request/HttpMethod.java | 9 +++++ .../http11/http/response/HttpResponse.java | 37 +++++++++++++++++++ .../http/response/HttpResponseBody.java | 30 +++++++++++++++ .../http/response/HttpResponseHeader.java | 32 ++++++++++++++++ .../http/response/HttpResponseStartLine.java | 16 ++++++++ .../http11/http/response/HttpStatusCode.java | 27 ++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpMethod.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseBody.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseStartLine.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpMethod.java new file mode 100644 index 0000000000..60d084f61a --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpMethod.java @@ -0,0 +1,9 @@ +package org.apache.coyote.http11.http.request; + +public enum HttpMethod { + GET, POST; + + public static HttpMethod from(String method) { + return HttpMethod.valueOf(method); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java new file mode 100644 index 0000000000..4c690a9b2f --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java @@ -0,0 +1,37 @@ +package org.apache.coyote.http11.http.response; + +public class HttpResponse { + + private static final String HTTP_VERSION = "HTTP/1.1"; + + private final HttpResponseStartLine startLine; + private final HttpResponseHeader header; + private final HttpResponseBody body; + + public static HttpResponse ok(HttpResponseHeader header, HttpResponseBody body) { + return new HttpResponse(new HttpResponseStartLine(HTTP_VERSION, HttpStatusCode.OK), header, body); + } + + public static HttpResponse notFound(HttpResponseHeader header, HttpResponseBody body) { + return new HttpResponse(new HttpResponseStartLine(HTTP_VERSION, HttpStatusCode.NOT_FOUND), header, body); + } + + public HttpResponse(HttpResponseStartLine startLine, HttpResponseHeader header, HttpResponseBody body) { + this.startLine = startLine; + this.header = header; + this.body = body; + } + + public String toResponseMessage() { + String contentCharset = ";charset=utf-8"; + header.addHeader("Content-Type", body.getContentType() + contentCharset); + header.addHeader("Content-Length", String.valueOf(body.getContentLength())); + + return String.join("\r\n" + , startLine.toResponseMessage() + , header.toResponseMessage() + , "" + , body.toResponseMessage() + ); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseBody.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseBody.java new file mode 100644 index 0000000000..cbd99fa774 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseBody.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11.http.response; + +import java.nio.charset.StandardCharsets; + +public class HttpResponseBody { + + private final String contentType; + private final byte[] content; + + public HttpResponseBody(String contentType, byte[] content) { + this.contentType = contentType; + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public byte[] getContent() { + return content; + } + + public int getContentLength() { + return content.length; + } + + public String toResponseMessage() { + return new String(content, StandardCharsets.UTF_8); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java new file mode 100644 index 0000000000..6fe720232d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java @@ -0,0 +1,32 @@ +package org.apache.coyote.http11.http.response; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import org.apache.coyote.http11.http.BaseHttpHeaders; + +public class HttpResponseHeader extends BaseHttpHeaders { + + public HttpResponseHeader() { + super(new LinkedHashMap<>()); + } + + public void addHeader(String key, String value) { + addHeader(key, List.of(value)); + } + + public void addHeader(String key, List values) { + headers.put(key, values); + } + + public String toResponseMessage() { + List lines = new ArrayList<>(); + headers.forEach((key, value) -> { + String values = String.join(HEADER_VALUE_DELIMITER, value); + lines.add(key + HEADER_DELIMITER + values + " "); + }); + + return String.join("\r\n", lines); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseStartLine.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseStartLine.java new file mode 100644 index 0000000000..15800e3441 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseStartLine.java @@ -0,0 +1,16 @@ +package org.apache.coyote.http11.http.response; + +public class HttpResponseStartLine { + + private final String httpVersion; + private final HttpStatusCode statusCode; + + public HttpResponseStartLine(String httpVersion, HttpStatusCode statusCode) { + this.httpVersion = httpVersion; + this.statusCode = statusCode; + } + + public String toResponseMessage() { + return httpVersion + " " + statusCode.getCode() + " " + statusCode.getMessage() + " "; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java new file mode 100644 index 0000000000..e95752a187 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http11.http.response; + +public enum HttpStatusCode { + + OK(200, "OK"), + + BAD_REQUEST(400, "Bad Request"), + NOT_FOUND(404, "Not Found"), + + INTERNAL_SERVER_ERROR(500, "Internal Server Error"); + + private final int code; + private final String message; + + HttpStatusCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} From d86ba666759e903af71e8f1ed3ed94a4dbee8db8 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 10:51:46 +0900 Subject: [PATCH 10/41] =?UTF-8?q?remove:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/ResourceLoader.java | 54 ------------------- .../coyote/http11/response/ResponseBody.java | 48 ----------------- 2 files changed, 102 deletions(-) delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java b/tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java deleted file mode 100644 index c9111d48dd..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/ResourceLoader.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.apache.coyote.http11; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.apache.coyote.http11.response.ResponseBody; - -public class ResourceLoader { - - private static final ResourceLoader instance = new ResourceLoader(); - private static final String RESOURCE_PATH = "/static"; - private static final String ROOT_PATH = "/"; - private static final String ROOT_PATH_CONTENT = "Hello world!"; - private static final String ROOT_PATH_CONTENT_TYPE = "text/html"; - - private ResourceLoader() { - } - - public static ResourceLoader getInstance() { - return instance; - } - - public ResponseBody loadResource(String requestUrl) throws IOException { - if (requestUrl.equals(ROOT_PATH)) { - return ResponseBody.builder() - .content(ROOT_PATH_CONTENT.getBytes()) - .contentType(ROOT_PATH_CONTENT_TYPE) - .build(); - } - - return readResource(requestUrl); - } - - private ResponseBody readResource(String requestUrl) throws IOException { - String fileName = RESOURCE_PATH + requestUrl; - Path filePath = getPath(fileName); - - return ResponseBody.builder() - .contentType(Files.probeContentType(filePath)) - .content(Files.readAllBytes(filePath)) - .build(); - } - - private Path getPath(String fileName) { - URL resource = getClass().getResource(fileName); - if (resource == null) { - throw new IllegalArgumentException("Resource not found. resource: " + fileName); - } - - return Path.of(resource.getPath()); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java deleted file mode 100644 index c5177e86b2..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.apache.coyote.http11.response; - -public class ResponseBody { - - private final String contentType; - private final byte[] content; - - public static ResponseBody.Builder builder() { - return new ResponseBody.Builder(); - } - - private ResponseBody(String contentType, byte[] content) { - this.contentType = contentType; - this.content = content; - } - - public String getContentType() { - return contentType; - } - - public byte[] getContent() { - return content; - } - - public int getContentLength() { - return content.length; - } - - public static class Builder { - - private String contentType; - private byte[] content; - - public Builder contentType(String contentType) { - this.contentType = contentType; - return this; - } - - public Builder content(byte[] content) { - this.content = content; - return this; - } - - public ResponseBody build() { - return new ResponseBody(contentType, content); - } - } -} From 2e2f23aaeab732a1b4c0c0e5b7f890f42f321220 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 11:00:19 +0900 Subject: [PATCH 11/41] =?UTF-8?q?fix:=20=EC=BF=BC=EB=A6=AC=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B0=92=EC=9D=84=20=EC=A1=B0=ED=9A=8C=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/http/request/HttpRequestQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java index 40002d95b8..c7f4aedb48 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java @@ -53,6 +53,6 @@ private boolean isNotExistKey(String key) { if (query == null) { return false; } - return query.containsKey(key); + return !query.containsKey(key); } } From d3af719a8d140091e88adc1cfb17808412af2a9e Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 11:04:44 +0900 Subject: [PATCH 12/41] =?UTF-8?q?feat:=20=EC=A0=95=EC=A0=81=20=EB=A6=AC?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/web/util/ResourceLoader.java | 36 ++++++ .../web/util/ResourceLoaderTest.java | 40 +++++++ tomcat/src/test/resources/index.html | 106 ++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 tomcat/src/main/java/com/techcourse/web/util/ResourceLoader.java create mode 100644 tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java create mode 100644 tomcat/src/test/resources/index.html diff --git a/tomcat/src/main/java/com/techcourse/web/util/ResourceLoader.java b/tomcat/src/main/java/com/techcourse/web/util/ResourceLoader.java new file mode 100644 index 0000000000..ed6de270d2 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/util/ResourceLoader.java @@ -0,0 +1,36 @@ +package com.techcourse.web.util; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.coyote.http11.http.response.HttpResponseBody; + +public class ResourceLoader { + + private static final ResourceLoader instance = new ResourceLoader(); + private static final String RESOURCE_PATH = "/static"; + + private ResourceLoader() { + } + + public static ResourceLoader getInstance() { + return instance; + } + + public HttpResponseBody loadResource(String filePath) throws IOException { + Path path = getPath(RESOURCE_PATH + filePath); + + return new HttpResponseBody(Files.probeContentType(path), Files.readAllBytes(path)); + } + + private Path getPath(String fileName) { + URL resource = getClass().getResource(fileName); + if (resource == null) { + throw new IllegalArgumentException("Resource not found. resource: " + fileName); + } + + return Path.of(resource.getPath()); + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java b/tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java new file mode 100644 index 0000000000..9e59dc33cb --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java @@ -0,0 +1,40 @@ +package com.techcourse.web.util; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.coyote.http11.http.response.HttpResponseBody; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ResourceLoaderTest { + + @DisplayName("정적 리소스를 로드한다.") + @Test + void loadResource() throws IOException { + ResourceLoader resourceLoader = ResourceLoader.getInstance(); + String filePath = "/index.html"; + + HttpResponseBody responseBody = resourceLoader.loadResource(filePath); + + byte[] expected = Files.readAllBytes(Path.of(getClass().getResource(filePath).getPath())); + assertThat(responseBody).satisfies(body -> { + assertThat(body.getContentType()).isEqualTo("text/html"); + assertThat(body.getContent()).isEqualTo(expected); + }); + } + + @DisplayName("파일이 존재하지 않을 경우 예외를 던진다.") + @Test + void loadResourceWithNonExistFile() { + ResourceLoader resourceLoader = ResourceLoader.getInstance(); + String filePath = "/non-exist.html"; + + assertThatThrownBy(() -> resourceLoader.loadResource(filePath)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Resource not found. resource: " + "/static" + filePath); + } +} diff --git a/tomcat/src/test/resources/index.html b/tomcat/src/test/resources/index.html new file mode 100644 index 0000000000..18ac924d4e --- /dev/null +++ b/tomcat/src/test/resources/index.html @@ -0,0 +1,106 @@ + + + + + + + + + 대시보드 + + + + + +
+
+ +
+
+
+
+

대시보드

+ +
+
+
+
+ + Bar Chart +
+
+
+
+
+
+
+ + Pie Chart +
+
+
+
+
+
+
+ +
+
+ + + + + + + + From cc45a5ebf95456512dd81ddbc3a5318eedf91feb Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 11:10:11 +0900 Subject: [PATCH 13/41] =?UTF-8?q?feat:=20/=20=EA=B2=BD=EB=A1=9C=EC=97=90?= =?UTF-8?q?=20=EB=8C=80=ED=95=9C=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/handler/RootPageHandler.java | 39 ++++++++++++ .../web/handler/RootPageHandlerTest.java | 62 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 tomcat/src/main/java/com/techcourse/web/handler/RootPageHandler.java create mode 100644 tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/RootPageHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/RootPageHandler.java new file mode 100644 index 0000000000..ca940a9330 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/handler/RootPageHandler.java @@ -0,0 +1,39 @@ +package com.techcourse.web.handler; + +import org.apache.coyote.http11.http.request.HttpMethod; +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpResponseBody; +import org.apache.coyote.http11.http.response.HttpResponseHeader; + +public class RootPageHandler implements Handler { + + private static final RootPageHandler instance = new RootPageHandler(); + private static final String ROOT_PATH = "/"; + private static final String ROOT_PATH_CONTENT = "Hello world!"; + private static final String ROOT_PATH_CONTENT_TYPE = "text/html"; + + private RootPageHandler() { + } + + public static RootPageHandler getInstance() { + return instance; + } + + @Override + public boolean isSupport(HttpRequestLine requestLine) { + HttpMethod method = requestLine.getMethod(); + String requestPath = requestLine.getRequestPath(); + + return method == HttpMethod.GET && requestPath.equals(ROOT_PATH); + } + + @Override + public HttpResponse handle(HttpRequest request) { + HttpResponseHeader responseHeader = new HttpResponseHeader(); + HttpResponseBody responseBody = new HttpResponseBody(ROOT_PATH_CONTENT_TYPE, ROOT_PATH_CONTENT.getBytes()); + + return HttpResponse.ok(responseHeader, responseBody); + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java new file mode 100644 index 0000000000..7deba19a1f --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java @@ -0,0 +1,62 @@ +package com.techcourse.web.handler; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.util.List; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.request.HttpRequestUrl; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpStatusCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RootPageHandlerTest { + + @DisplayName("/ 경로에 대한 요청을 처리할 수 있다.") + @Test + void isSupport() { + HttpRequestUrl requestUrl = HttpRequestUrl.from("/"); + HttpRequestLine requestLine = new HttpRequestLine("GET", requestUrl, "HTTP/1.1"); + RootPageHandler rootPageHandler = RootPageHandler.getInstance(); + + boolean isSupport = rootPageHandler.isSupport(requestLine); + + assertThat(isSupport).isTrue(); + } + + @DisplayName("GET 이외의 요청은 처리하지 않는다.") + @Test + void isNotSupport() { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("POST / HTTP/1.1", headers, null); + + RootPageHandler rootPageHandler = RootPageHandler.getInstance(); + + // then + assertThat(rootPageHandler.isSupport(request.getRequestLine())).isFalse(); + } + + @DisplayName("/ 요청을 처리한다.") + @Test + void isSupportWithQuery() throws IOException { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("GET / HTTP/1.1", headers, null); + + RootPageHandler rootPageHandler = RootPageHandler.getInstance(); + + // then + HttpResponse response = rootPageHandler.handle(request); + assertThat(rootPageHandler.isSupport(request.getRequestLine())).isTrue(); + assertThat(response) + .extracting("startLine") + .extracting("statusCode") + .isEqualTo(HttpStatusCode.OK); + assertThat(response) + .extracting("body") + .extracting("content") + .isEqualTo("Hello world!".getBytes()); + } +} From 8b01768eb7ab85950a6e66d57977de7a011d9452 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 11:13:18 +0900 Subject: [PATCH 14/41] =?UTF-8?q?feat:=20=EC=A0=95=EC=A0=81=20=EB=A6=AC?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/handler/ResourceHandler.java | 44 ++++++++++++++ .../web/handler/ResourceHandlerTest.java | 60 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tomcat/src/main/java/com/techcourse/web/handler/ResourceHandler.java create mode 100644 tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/ResourceHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/ResourceHandler.java new file mode 100644 index 0000000000..a2b6b3ef8c --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/handler/ResourceHandler.java @@ -0,0 +1,44 @@ +package com.techcourse.web.handler; + +import java.io.IOException; +import java.util.regex.Pattern; + +import org.apache.coyote.http11.http.request.HttpMethod; +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpResponseBody; +import org.apache.coyote.http11.http.response.HttpResponseHeader; + +import com.techcourse.web.util.ResourceLoader; + +public class ResourceHandler implements Handler { + + private static final ResourceHandler instance = new ResourceHandler(); + private static final String STATIC_FILE_EXTENSION_REGEX = ".*\\.(html|css|js|ico|svg)"; + private static final Pattern STATIC_FILE_PATTERN = Pattern.compile(STATIC_FILE_EXTENSION_REGEX); + + private ResourceHandler() { + } + + public static ResourceHandler getInstance() { + return instance; + } + + @Override + public boolean isSupport(HttpRequestLine requestLine) { + HttpMethod method = requestLine.getMethod(); + String requestPath = requestLine.getRequestPath(); + + return method == HttpMethod.GET && STATIC_FILE_PATTERN.matcher(requestPath).matches(); + } + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + HttpResponseHeader responseHeader = new HttpResponseHeader(); + String requestPath = request.getRequestLine().getRequestPath(); + HttpResponseBody responseBody = ResourceLoader.getInstance().loadResource(requestPath); + + return HttpResponse.ok(responseHeader, responseBody); + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java new file mode 100644 index 0000000000..97a45739f1 --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java @@ -0,0 +1,60 @@ +package com.techcourse.web.handler; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.List; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.request.HttpRequestUrl; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpStatusCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ResourceHandlerTest { + + + @DisplayName("정적 리소스 요청을 처리할 수 있다.") + @Test + void isSupport() { + HttpRequestUrl requestUrl = HttpRequestUrl.from("/css/test.css"); + HttpRequestLine requestLine = new HttpRequestLine("GET", requestUrl, "HTTP/1.1"); + ResourceHandler handler = ResourceHandler.getInstance(); + + boolean isSupport = handler.isSupport(requestLine); + + assertThat(isSupport).isTrue(); + } + + @DisplayName("GET 이외의 요청은 처리하지 않는다.") + @Test + void isNotSupport() { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("POST /js/test.js HTTP/1.1", headers, null); + + ResourceHandler handler = ResourceHandler.getInstance(); + + // then + assertThat(handler.isSupport(request.getRequestLine())).isFalse(); + } + + @DisplayName("정적 리소스에 대한 요청을 처리한다.") + @Test + void isSupportWithQuery() throws IOException { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("GET /index.html HTTP/1.1", headers, null); + + ResourceHandler handler = ResourceHandler.getInstance(); + + // then + HttpResponse response = handler.handle(request); + assertThat(handler.isSupport(request.getRequestLine())).isTrue(); + assertThat(response) + .extracting("startLine") + .extracting("statusCode") + .isEqualTo(HttpStatusCode.OK); + } +} From 661812253988f75e942131423b75dd413e25b2b1 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 11:13:30 +0900 Subject: [PATCH 15/41] =?UTF-8?q?feat:=20/login=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9A=94=EC=B2=AD=EC=9D=84=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/web/handler/LoginHandler.java | 76 +++++++++++++++++++ .../web/handler/LoginHandlerTest.java | 60 +++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java create mode 100644 tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java new file mode 100644 index 0000000000..d485873bd9 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -0,0 +1,76 @@ +package com.techcourse.web.handler; + +import java.io.IOException; +import java.util.Optional; + +import org.apache.coyote.http11.http.request.HttpMethod; +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.request.HttpRequestQuery; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpResponseBody; +import org.apache.coyote.http11.http.response.HttpResponseHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.model.User; +import com.techcourse.web.util.ResourceLoader; + +public class LoginHandler implements Handler { + + private static final Logger log = LoggerFactory.getLogger(LoginHandler.class); + private static final LoginHandler instance = new LoginHandler(); + + private LoginHandler() { + } + + public static LoginHandler getInstance() { + return instance; + } + + @Override + public boolean isSupport(HttpRequestLine requestLine) { + return requestLine.getRequestPath().startsWith("/login"); + } + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + HttpRequestLine requestLine = request.getRequestLine(); + HttpMethod method = requestLine.getMethod(); + + if (method == HttpMethod.GET) { + return handleGet(request); + } + + HttpResponseBody notFoundPage = ResourceLoader.getInstance().loadResource("/404.html"); + return HttpResponse.notFound(new HttpResponseHeader(), notFoundPage); + } + + private HttpResponse handleGet(HttpRequest request) throws IOException { + HttpRequestLine requestLine = request.getRequestLine(); + HttpRequestQuery query = requestLine.getQuery(); + checkUser(query); + + HttpResponseBody body = ResourceLoader.getInstance().loadResource("/login.html"); + return HttpResponse.ok(new HttpResponseHeader(), body); + } + + private void checkUser(HttpRequestQuery query) { + String account = query.getValue("account"); + Optional userOptional = InMemoryUserRepository.findByAccount(account); + if (userOptional.isEmpty()) { + log.info("user not found. account: {}", account); + return; + } + + User user = userOptional.get(); + String password = query.getValue("password"); + if (!user.checkPassword(password)) { + log.info("password not matched. account: {}", account); + return; + } + + log.info("user: {}", user); + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java new file mode 100644 index 0000000000..4ac4a7261e --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java @@ -0,0 +1,60 @@ +package com.techcourse.web.handler; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.util.List; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.request.HttpRequestUrl; +import org.apache.coyote.http11.http.response.HttpStatusCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LoginHandlerTest { + + @DisplayName("/login 으로 시작하는 페이지를 처리한다.") + @Test + void isSupport() { + HttpRequestUrl requestUrl = HttpRequestUrl.from("/login?account=hi"); + HttpRequestLine requestLine = new HttpRequestLine("GET", requestUrl, "HTTP/1.1"); + LoginHandler loginHandler = LoginHandler.getInstance(); + + boolean isSupport = loginHandler.isSupport(requestLine); + + assertThat(isSupport).isTrue(); + } + + @DisplayName("/login?account={account}&password={password} 요청을 처리한다.") + @Test + void isSupportWithQuery() throws IOException { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("GET /login?account=hi&password=hello HTTP/1.1", headers, null); + + LoginHandler loginHandler = LoginHandler.getInstance(); + + // then + assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); + assertThat(loginHandler.handle(request)) + .extracting("startLine") + .extracting("statusCode") + .isEqualTo(HttpStatusCode.OK); + } + + @DisplayName("POST 요청은 처리하지 않는다.") + @Test + void isNotSupport() throws IOException { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("POST /login?account=hi&password=hello HTTP/1.1", headers, null); + + LoginHandler loginHandler = LoginHandler.getInstance(); + + // then + assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); + assertThat(loginHandler.handle(request)) + .extracting("startLine") + .extracting("statusCode") + .isEqualTo(HttpStatusCode.NOT_FOUND); + } +} From a66d132518093de23bff7216b09d1842a940d97d Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 11:16:48 +0900 Subject: [PATCH 16/41] =?UTF-8?q?feat:=20=EC=9A=94=EC=B2=AD=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=B4=20=EC=A0=81=EC=A0=88=ED=95=9C=20=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=9F=AC=EB=A5=BC=20=EC=B0=BE=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/web/handler/Handler.java | 14 +++++ .../techcourse/web/handler/HandlerMapper.java | 28 +++++++++ .../web/handler/HandlerMapperTest.java | 60 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 tomcat/src/main/java/com/techcourse/web/handler/Handler.java create mode 100644 tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java create mode 100644 tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/Handler.java b/tomcat/src/main/java/com/techcourse/web/handler/Handler.java new file mode 100644 index 0000000000..4bd99759dc --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/handler/Handler.java @@ -0,0 +1,14 @@ +package com.techcourse.web.handler; + +import java.io.IOException; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.response.HttpResponse; + +public interface Handler { + + boolean isSupport(HttpRequestLine requestLine); + + HttpResponse handle(HttpRequest request) throws IOException; +} diff --git a/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java b/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java new file mode 100644 index 0000000000..58b11d0506 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java @@ -0,0 +1,28 @@ +package com.techcourse.web.handler; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; + +public class HandlerMapper { + + private static final List handlers = new ArrayList<>(); + + static { + handlers.add(RootPageHandler.getInstance()); + handlers.add(ResourceHandler.getInstance()); + handlers.add(LoginHandler.getInstance()); + } + + public static Handler findHandler(HttpRequest httpRequest) { + HttpRequestLine requestLine = httpRequest.getRequestLine(); + return handlers.stream() + .filter(handler -> handler.isSupport(requestLine)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("handler not found. " + + "path: " + requestLine.getRequestPath() + ", method: " + requestLine.getMethod()) + ); + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java new file mode 100644 index 0000000000..693275228b --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java @@ -0,0 +1,60 @@ +package com.techcourse.web.handler; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HandlerMapperTest { + + @DisplayName("/ 요청에는 RootPageHandler를 반환한다.") + @Test + void findHandler_WhenRequestRootPage() { + String requestPath = "/"; + + Class expectedHandler = RootPageHandler.class; + + runTest(requestPath, expectedHandler); + } + + @DisplayName("/login 요청에는 LoginHandler를 반환한다.") + @Test + void findHandler_WhenRequestLogin() { + String requestPath = "/login"; + + Class expectedHandler = LoginHandler.class; + + runTest(requestPath, expectedHandler); + } + + @DisplayName("정적 리소스 요청에는 ResourceHandler를 반환한다.") + @Test + void findHandler_WhenRequestResource() { + String requestPath = "/css/test.css"; + + Class expectedHandler = ResourceHandler.class; + + runTest(requestPath, expectedHandler); + } + + @DisplayName("찾는 핸들러가 없는 경우 예외을 던진다.") + @Test + void findHandler_WhenHandlerNotFound() { + HttpRequest request = new HttpRequest("GET /not-found HTTP/1.1", List.of(), null); + + assertThatThrownBy(() -> HandlerMapper.findHandler(request)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("handler not found. path: /not-found, method: GET"); + } + + private void runTest(String requestPath, Class expectedHandler) { + HttpRequest request = new HttpRequest("GET " + requestPath + " HTTP/1.1", List.of(), null); + + Handler handler = HandlerMapper.findHandler(request); + + assertThat(handler).isInstanceOf(expectedHandler); + } +} From 048c2635aed1be06587713e1b9178020d6ca43c1 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 11:17:17 +0900 Subject: [PATCH 17/41] =?UTF-8?q?refactor:=20=EA=B5=AC=ED=98=84=EB=90=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20Http11Processor=20=EC=97=90=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 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 a4719eccb0..5d39ec63c3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,16 +1,18 @@ package org.apache.coyote.http11; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; +import java.io.InputStream; import java.net.Socket; import org.apache.coyote.Processor; -import org.apache.coyote.http11.response.ResponseBody; +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.techcourse.exception.UncheckedServletException; +import com.techcourse.web.handler.Handler; +import com.techcourse.web.handler.HandlerMapper; public class Http11Processor implements Runnable, Processor { @@ -33,26 +35,21 @@ public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); final var outputStream = connection.getOutputStream()) { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - String requestLine = reader.readLine(); - String requestUrl = requestLine.split(" ")[1]; + String httpResponseMessage = createHttpResponseMessage(inputStream); - ResponseBody responseBody = ResourceLoader.getInstance().loadResource(requestUrl); - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + responseBody.getContentType() + ";charset=utf-8" + " ", - "Content-Length: " + responseBody.getContentLength() + " ", - "", - new String(responseBody.getContent()) - ); - - // 바디(바이너리 또는 텍스트 데이터)를 전송 - outputStream.write(response.getBytes()); + outputStream.write(httpResponseMessage.getBytes()); outputStream.flush(); - } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } + + private String createHttpResponseMessage(InputStream inputStream) throws IOException { + HttpRequest request = HttpRequestMessageReader.read(inputStream); + log.info("request: {}", request); + Handler handler = HandlerMapper.findHandler(request); + HttpResponse response = handler.handle(request); + + return response.toResponseMessage(); + } } From a42936987afd6f9c60530f2005c29a6ed82ab2d6 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 13:03:23 +0900 Subject: [PATCH 18/41] =?UTF-8?q?refactor:=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20=EB=A7=8C=EB=93=A4=20?= =?UTF-8?q?=EB=95=8C=20body=EA=B0=80=20=EC=97=86=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http11/http/response/HttpResponse.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java index 4c690a9b2f..09926ce33c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java @@ -22,7 +22,28 @@ public HttpResponse(HttpResponseStartLine startLine, HttpResponseHeader header, this.body = body; } + public static HttpResponse redirect(HttpResponseHeader responseHeader) { + return new HttpResponse(new HttpResponseStartLine(HTTP_VERSION, HttpStatusCode.FOUND), responseHeader, null); + } + public String toResponseMessage() { + if (body == null) { + return createResponseMessageWithoutBody(); + } + + return createResponseMessage(); + } + + private String createResponseMessageWithoutBody() { + header.addHeader("Content-Length", "0"); + return String.join("\r\n" + , startLine.toResponseMessage() + , header.toResponseMessage() + , "" + ); + } + + private String createResponseMessage() { String contentCharset = ";charset=utf-8"; header.addHeader("Content-Type", body.getContentType() + contentCharset); header.addHeader("Content-Length", String.valueOf(body.getContentLength())); From 433d41d3642225c193152b3ee217b81a20be5219 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 13:03:43 +0900 Subject: [PATCH 19/41] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20302?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/web/handler/LoginHandler.java | 62 ++++++++++++------- .../apache/coyote/http11/Http11Processor.java | 1 - .../http11/http/request/HttpRequestLine.java | 17 ++--- .../http11/http/request/HttpRequestQuery.java | 18 +++++- .../http11/http/request/HttpRequestUrl.java | 15 +++-- .../http11/http/response/HttpStatusCode.java | 5 +- 6 files changed, 75 insertions(+), 43 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index d485873bd9..63a0ed5ba8 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -7,6 +7,7 @@ import org.apache.coyote.http11.http.request.HttpRequest; import org.apache.coyote.http11.http.request.HttpRequestLine; import org.apache.coyote.http11.http.request.HttpRequestQuery; +import org.apache.coyote.http11.http.request.HttpRequestUrl; import org.apache.coyote.http11.http.response.HttpResponse; import org.apache.coyote.http11.http.response.HttpResponseBody; import org.apache.coyote.http11.http.response.HttpResponseHeader; @@ -21,6 +22,7 @@ public class LoginHandler implements Handler { private static final Logger log = LoggerFactory.getLogger(LoginHandler.class); private static final LoginHandler instance = new LoginHandler(); + private static final String LOGIN_PATH = "/login"; private LoginHandler() { } @@ -31,46 +33,64 @@ public static LoginHandler getInstance() { @Override public boolean isSupport(HttpRequestLine requestLine) { - return requestLine.getRequestPath().startsWith("/login"); + return requestLine.getRequestPath().startsWith(LOGIN_PATH); } @Override public HttpResponse handle(HttpRequest request) throws IOException { HttpRequestLine requestLine = request.getRequestLine(); + HttpRequestUrl requestUrl = request.getRequestLine().getHttpRequestUrl(); HttpMethod method = requestLine.getMethod(); - if (method == HttpMethod.GET) { - return handleGet(request); + if (method == HttpMethod.GET && LOGIN_PATH.equals(requestUrl.getRequestUrl())) { + return loadLoginPage(); + } + if (method == HttpMethod.GET && requestUrl.isQueryExist()) { + return login(request); } - HttpResponseBody notFoundPage = ResourceLoader.getInstance().loadResource("/404.html"); - return HttpResponse.notFound(new HttpResponseHeader(), notFoundPage); + return notFoundResponse(); } - private HttpResponse handleGet(HttpRequest request) throws IOException { - HttpRequestLine requestLine = request.getRequestLine(); - HttpRequestQuery query = requestLine.getQuery(); - checkUser(query); - + private HttpResponse loadLoginPage() throws IOException { HttpResponseBody body = ResourceLoader.getInstance().loadResource("/login.html"); return HttpResponse.ok(new HttpResponseHeader(), body); } - private void checkUser(HttpRequestQuery query) { + private HttpResponse login(HttpRequest request) { + HttpRequestQuery query = request.getRequestLine().getQuery(); String account = query.getValue("account"); - Optional userOptional = InMemoryUserRepository.findByAccount(account); - if (userOptional.isEmpty()) { - log.info("user not found. account: {}", account); - return; + String password = query.getValue("password"); + + HttpResponseHeader responseHeader = new HttpResponseHeader(); + responseHeader.addHeader("Location", getRedirectLocation(account, password)); + + return HttpResponse.redirect(responseHeader); + } + + private String getRedirectLocation(String account, String password) { + if (isUserNotExist(account, password)) { + return "/401.html"; } + return "/index.html"; + } - User user = userOptional.get(); - String password = query.getValue("password"); - if (!user.checkPassword(password)) { - log.info("password not matched. account: {}", account); - return; + private boolean isUserNotExist(String account, String password) { + if (account == null) { + log.info("account is required"); + return true; + } + if (password == null) { + log.info("password is required"); + return true; } - log.info("user: {}", user); + Optional userOptional = InMemoryUserRepository.findByAccount(account); + return userOptional.map(user -> !user.checkPassword(password)).orElse(true); + } + + private static HttpResponse notFoundResponse() throws IOException { + HttpResponseBody notFoundPage = ResourceLoader.getInstance().loadResource("/404.html"); + return HttpResponse.notFound(new HttpResponseHeader(), notFoundPage); } } 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 5d39ec63c3..5260eb7a15 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -46,7 +46,6 @@ public void process(final Socket connection) { private String createHttpResponseMessage(InputStream inputStream) throws IOException { HttpRequest request = HttpRequestMessageReader.read(inputStream); - log.info("request: {}", request); Handler handler = HandlerMapper.findHandler(request); HttpResponse response = handler.handle(request); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java index 9729205b9b..64998874bf 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestLine.java @@ -26,10 +26,6 @@ public static HttpRequestLine from(String requestLine) { ); } - public boolean isSameMethod(HttpMethod method) { - return this.method == method; - } - public HttpMethod getMethod() { return method; } @@ -42,16 +38,11 @@ public HttpRequestQuery getQuery() { return httpRequestUrl.getQuery(); } - public String getHttpVersion() { - return httpVersion; + public HttpRequestUrl getHttpRequestUrl() { + return httpRequestUrl; } - @Override - public String toString() { - return "HttpRequestLine{" + - "method='" + method + '\'' + - ", requestUrl=" + httpRequestUrl + - ", httpVersion='" + httpVersion + '\'' + - '}'; + public String getHttpVersion() { + return httpVersion; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java index c7f4aedb48..95259abd8c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestQuery.java @@ -1,5 +1,7 @@ package org.apache.coyote.http11.http.request; +import static org.reflections.Reflections.*; + import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; @@ -44,7 +46,8 @@ private static String parseValue(String queryPart) { public String getValue(String key) { if (isNotExistKey(key)) { - throw new IllegalArgumentException("Key not found. key: " + key); + log.info("Key not found: {}", key); + return null; } return query.get(key); } @@ -55,4 +58,17 @@ private boolean isNotExistKey(String key) { } return !query.containsKey(key); } + + public boolean isExist() { + return query != null; + } + + public String toUrl() { + if (query == null) { + return ""; + } + return query.entrySet().stream() + .map(e -> e.getKey() + QUERY_KEY_VALUE_DELIMITER + e.getValue()) + .collect(Collectors.joining(QUERY_PARAM_DELIMITER)); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java index 9141b77cb4..9b3583529c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java @@ -18,6 +18,10 @@ public HttpRequestUrl(String path, String query) { this.httpRequestQuery = HttpRequestQuery.from(query); } + public boolean isQueryExist() { + return httpRequestQuery.isExist(); + } + public String getPath() { return path; } @@ -26,11 +30,10 @@ public HttpRequestQuery getQuery() { return httpRequestQuery; } - @Override - public String toString() { - return "RequestUrl{" + - "path='" + path + '\'' + - ", query=" + httpRequestQuery + - '}'; + public String getRequestUrl() { + if (httpRequestQuery.isExist()) { + return path + "?" + httpRequestQuery.toUrl(); + } + return path; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java index e95752a187..91391be3cb 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpStatusCode.java @@ -4,10 +4,13 @@ public enum HttpStatusCode { OK(200, "OK"), + FOUND(302, "Found"), + BAD_REQUEST(400, "Bad Request"), NOT_FOUND(404, "Not Found"), - INTERNAL_SERVER_ERROR(500, "Internal Server Error"); + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + ; private final int code; private final String message; From 7781280e376ec470663ffccc029bc605e4063010 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 14:29:12 +0900 Subject: [PATCH 20/41] =?UTF-8?q?refactor:=20http=20=EC=9A=94=EC=B2=AD=20b?= =?UTF-8?q?ody=EB=A5=BC=20=EC=9D=BD=EC=9D=84=20=EB=95=8C=20content-length?= =?UTF-8?q?=20=ED=97=A4=EB=8D=94=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=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 --- .../http11/HttpRequestMessageReader.java | 25 ++++++++--- .../http11/HttpRequestMessageReaderTest.java | 41 ++++++++++++++++++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java index 5b3a6ddef1..37debe08db 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java @@ -16,18 +16,31 @@ public static HttpRequest read(InputStream inputStream) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String requestLine = reader.readLine(); List headers = readHeaders(reader); - String body = readBody(reader); + String body = readBody(reader, getContentLength(headers)); return new HttpRequest(requestLine, headers, body); } - private static String readBody(BufferedReader reader) throws IOException { - List result = new ArrayList<>(); - while (reader.ready()) { - result.add(reader.readLine()); + private static int getContentLength(List headers) { + return headers.stream() + .filter(header -> header.startsWith("Content-Length")) + .findFirst() + .map(header -> Integer.parseInt(header.split(": ")[1].strip())) + .orElse(0); + } + + private static String readBody(BufferedReader reader, int contentLength) throws IOException { + if (contentLength == 0) { + return ""; + } + + char[] body = new char[contentLength]; + int readLength = reader.read(body, 0, contentLength); + if (readLength != contentLength) { + throw new IllegalArgumentException("Content-Length mismatch"); } - return String.join("\r\n", result); + return new String(body); } private static List readHeaders(BufferedReader reader) throws IOException { diff --git a/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java b/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java index b0182181b6..c70cbaccec 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/HttpRequestMessageReaderTest.java @@ -27,7 +27,7 @@ void read() throws IOException { String httpRequest = String.join("\r\n", "POST /api/endpoint?hi=hello&bangga= HTTP/1.1 ", "Host: example.com ", - "Content-Length: 39 ", + "Content-Length: " + body.length(), "Content-Type: application/json ", "Accept: application/json, text/plain, */* ", "", @@ -36,6 +36,7 @@ void read() throws IOException { InputStream inputStream = new ByteArrayInputStream(httpRequest.getBytes()); HttpRequest request = HttpRequestMessageReader.read(inputStream); + System.out.println(request.getRequestBody()); assertThat(request.getRequestLine()).satisfies( requestLine -> { assertThat(requestLine.getMethod()).isEqualTo(HttpMethod.POST); @@ -46,7 +47,7 @@ void read() throws IOException { assertThat(request.getHeaders()).satisfies( headers -> { assertThat(headers.getValue("Host")).containsOnly("example.com"); - assertThat(headers.getValue("Content-Length")).containsOnly("39"); + assertThat(headers.getValue("Content-Length")).containsOnly(String.valueOf(body.length())); assertThat(headers.getValue("Content-Type")).containsOnly("application/json"); assertThat(headers.getValue("Accept")).containsExactly("application/json", "text/plain", "*/*"); } @@ -55,4 +56,40 @@ void read() throws IOException { "email\": \"john.doe@example.com", "isActive\": true"); } + + @DisplayName("Content-Length가 0인 경우 빈 body를 반환한다.") + @Test + void readEmptyBody() throws IOException { + String httpRequest = String.join("\r\n", + "GET /api/endpoint?hi=hello&bangga= HTTP/1.1 ", + "Host: example.com ", + "Content-Length: 0", + "Content-Type: application/json ", + "Accept: application/json, text/plain, */* ", + ""); + + InputStream inputStream = new ByteArrayInputStream(httpRequest.getBytes()); + HttpRequest request = HttpRequestMessageReader.read(inputStream); + + assertThat(request.getRequestBody()).isEmpty(); + } + + @DisplayName("Content-Length와 실제 body의 길이가 다른 경우 예외를 던진다.") + @Test + void readMismatchContentLength() { + String body = "Hello world!"; + String httpRequest = String.join("\r\n", + "POST /api/endpoint?hi=hello&bangga= HTTP/1.1 ", + "Host: example.com ", + "Content-Length: " + (body.length() + 1), + "Content-Type: application/json ", + "Accept: application/json, text/plain, */* ", + "", + body); + + InputStream inputStream = new ByteArrayInputStream(httpRequest.getBytes()); + assertThatThrownBy(() -> HttpRequestMessageReader.read(inputStream)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Content-Length mismatch"); + } } From f034f402f8e97e0047f213dfb393a5db645d7839 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 14:29:47 +0900 Subject: [PATCH 21/41] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/web/handler/Handler.java | 14 +++++++++ .../techcourse/web/handler/LoginHandler.java | 5 ---- .../web/handler/LoginHandlerTest.java | 30 ++++++++++++++----- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/web/handler/Handler.java b/tomcat/src/main/java/com/techcourse/web/handler/Handler.java index 4bd99759dc..34e7ed1a8e 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/Handler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/Handler.java @@ -5,10 +5,24 @@ import org.apache.coyote.http11.http.request.HttpRequest; import org.apache.coyote.http11.http.request.HttpRequestLine; import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpResponseBody; +import org.apache.coyote.http11.http.response.HttpResponseHeader; + +import com.techcourse.web.util.ResourceLoader; public interface Handler { boolean isSupport(HttpRequestLine requestLine); HttpResponse handle(HttpRequest request) throws IOException; + + default HttpResponse notFoundResponse() throws IOException { + HttpResponseBody notFoundPage = ResourceLoader.getInstance().loadResource("/404.html"); + return HttpResponse.notFound(new HttpResponseHeader(), notFoundPage); + } + + default HttpResponse redirect(HttpResponseHeader header, String location) { + header.addHeader("Location", location); + return HttpResponse.redirect(header); + } } diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index 63a0ed5ba8..cc1ca2e9aa 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -88,9 +88,4 @@ private boolean isUserNotExist(String account, String password) { Optional userOptional = InMemoryUserRepository.findByAccount(account); return userOptional.map(user -> !user.checkPassword(password)).orElse(true); } - - private static HttpResponse notFoundResponse() throws IOException { - HttpResponseBody notFoundPage = ResourceLoader.getInstance().loadResource("/404.html"); - return HttpResponse.notFound(new HttpResponseHeader(), notFoundPage); - } } diff --git a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java index 4ac4a7261e..faf5c4f24b 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java @@ -26,20 +26,37 @@ void isSupport() { assertThat(isSupport).isTrue(); } - @DisplayName("/login?account={account}&password={password} 요청을 처리한다.") + @DisplayName("로그인에 성공하면 /index.html로 리다이렉트한다.") @Test void isSupportWithQuery() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); - HttpRequest request = new HttpRequest("GET /login?account=hi&password=hello HTTP/1.1", headers, null); + HttpRequest request = new HttpRequest("GET /login?account=gugu&password=password HTTP/1.1", headers, null); LoginHandler loginHandler = LoginHandler.getInstance(); - // then assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); assertThat(loginHandler.handle(request)) - .extracting("startLine") - .extracting("statusCode") - .isEqualTo(HttpStatusCode.OK); + .extracting("header") + .extracting("headers") + .extracting("Location") + .isEqualTo(List.of("/index.html")); + + } + + @DisplayName("로그인에 실패하면 /401.html 로 리다이렉트한다.") + @Test + void isSupportWithQueryFail() throws IOException { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("GET /login?account=hi&password=hellofail HTTP/1.1", headers, null); + + LoginHandler loginHandler = LoginHandler.getInstance(); + + assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); + assertThat(loginHandler.handle(request)) + .extracting("header") + .extracting("headers") + .extracting("Location") + .isEqualTo(List.of("/401.html")); } @DisplayName("POST 요청은 처리하지 않는다.") @@ -50,7 +67,6 @@ void isNotSupport() throws IOException { LoginHandler loginHandler = LoginHandler.getInstance(); - // then assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); assertThat(loginHandler.handle(request)) .extracting("startLine") From 592554367cbc6ff5db54e66533d36bb7c2d4b390 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 14:34:06 +0900 Subject: [PATCH 22/41] =?UTF-8?q?feat:=20Form=20=EC=9A=94=EC=B2=AD=20body?= =?UTF-8?q?=EB=A5=BC=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=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 --- .../web/util/FormUrlEncodedParser.java | 38 ++++++++++++++++ .../web/util/FormUrlEncodedParserTest.java | 43 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 tomcat/src/main/java/com/techcourse/web/util/FormUrlEncodedParser.java create mode 100644 tomcat/src/test/java/com/techcourse/web/util/FormUrlEncodedParserTest.java diff --git a/tomcat/src/main/java/com/techcourse/web/util/FormUrlEncodedParser.java b/tomcat/src/main/java/com/techcourse/web/util/FormUrlEncodedParser.java new file mode 100644 index 0000000000..7928d40dcf --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/util/FormUrlEncodedParser.java @@ -0,0 +1,38 @@ +package com.techcourse.web.util; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class FormUrlEncodedParser { + + private static final String PARAMETER_SEPARATOR = "&"; + private static final String KEY_VALUE_SEPARATOR = "="; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + + public static Map parse(String body) { + validateBodyIsNotEmpty(body); + String[] pairs = body.split(PARAMETER_SEPARATOR); + + return Arrays.stream(pairs) + .collect(Collectors.toMap( + pair -> pair.split(KEY_VALUE_SEPARATOR)[KEY_INDEX], + FormUrlEncodedParser::getValue + )); + } + + private static void validateBodyIsNotEmpty(String body) { + if (body == null || body.isBlank()) { + throw new IllegalArgumentException("Body is empty"); + } + } + + private static String getValue(String pair) { + String[] keyValue = pair.split(KEY_VALUE_SEPARATOR); + if (keyValue.length == 1) { + return ""; + } + return keyValue[VALUE_INDEX]; + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/util/FormUrlEncodedParserTest.java b/tomcat/src/test/java/com/techcourse/web/util/FormUrlEncodedParserTest.java new file mode 100644 index 0000000000..1487776ecf --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/web/util/FormUrlEncodedParserTest.java @@ -0,0 +1,43 @@ +package com.techcourse.web.util; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FormUrlEncodedParserTest { + + @DisplayName("Form Url Encoded 문자열을 파싱한다.") + @Test + void parse() { + String body = "name=abc&password=1234"; + + Map result = FormUrlEncodedParser.parse(body); + + assertThat(result).containsEntry("name", "abc").containsEntry("password", "1234"); + } + + @DisplayName("입력된 문자열이 없는 경우 예외를 던진다.") + @Test + void parse_WithEmptyInput() { + String body = ""; + + assertThatThrownBy(() -> FormUrlEncodedParser.parse(body)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Body is empty"); + } + + @DisplayName("Key만 존재하면 빈 문자열로 Value를 반환한다.") + @Test + void parse_WithEmptyValue() { + String body = "name=&password=1234"; + + Map result = FormUrlEncodedParser.parse(body); + + assertThat(result).containsEntry("name", "").containsEntry("password", "1234"); + } +} From 15adb6850ca3878c593e94468ef628b9bcb7b3f0 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 14:43:56 +0900 Subject: [PATCH 23/41] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EC=9D=84=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=A0=20=EB=95=8C=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?account=EC=97=90=20=EB=8C=80=ED=95=9C=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/db/InMemoryUserRepository.java | 3 +++ .../db/InMemoryUserRepositoryTest.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tomcat/src/test/java/com/techcourse/db/InMemoryUserRepositoryTest.java diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index 3be99c6e10..644ad92e6a 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -16,6 +16,9 @@ public class InMemoryUserRepository { } public static void save(User user) { + if (database.containsKey(user.getAccount())) { + throw new IllegalArgumentException("account is already exist"); + } database.put(user.getAccount(), user); } diff --git a/tomcat/src/test/java/com/techcourse/db/InMemoryUserRepositoryTest.java b/tomcat/src/test/java/com/techcourse/db/InMemoryUserRepositoryTest.java new file mode 100644 index 0000000000..227684b282 --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/db/InMemoryUserRepositoryTest.java @@ -0,0 +1,21 @@ +package com.techcourse.db; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.techcourse.model.User; + +class InMemoryUserRepositoryTest { + + @DisplayName("이미 존재하는 계정이면 예외를 던진다.") + @Test + void save_whenExistingAccount() { + User user = new User("gugu", "password", "hi@hi.com"); + + assertThatThrownBy(() -> InMemoryUserRepository.save(user)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("account is already exist"); + } +} From 9fdcccde7c11dd13e0acd8596b0133b265e91b67 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 14:44:14 +0900 Subject: [PATCH 24/41] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/model/User.java | 13 +++ .../techcourse/web/handler/HandlerMapper.java | 1 + .../web/handler/RegisterHandler.java | 70 +++++++++++++++ .../http11/http/response/HttpResponse.java | 5 ++ .../web/handler/RegisterHandlerTest.java | 85 +++++++++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 tomcat/src/main/java/com/techcourse/web/handler/RegisterHandler.java create mode 100644 tomcat/src/test/java/com/techcourse/web/handler/RegisterHandlerTest.java diff --git a/tomcat/src/main/java/com/techcourse/model/User.java b/tomcat/src/main/java/com/techcourse/model/User.java index e8cf4c8e68..54490f5768 100644 --- a/tomcat/src/main/java/com/techcourse/model/User.java +++ b/tomcat/src/main/java/com/techcourse/model/User.java @@ -8,12 +8,25 @@ public class User { private final String email; public User(Long id, String account, String password, String email) { + validateIsNotEmptyValue(account, password, email); this.id = id; this.account = account; this.password = password; this.email = email; } + private void validateIsNotEmptyValue(String account, String password, String email) { + if (account == null || account.isBlank()) { + throw new IllegalArgumentException("Account is empty"); + } + if (password == null || password.isBlank()) { + throw new IllegalArgumentException("Password is empty"); + } + if (email == null || email.isBlank()) { + throw new IllegalArgumentException("Email is empty"); + } + } + public User(String account, String password, String email) { this(null, account, password, email); } diff --git a/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java b/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java index 58b11d0506..cb9ab4b3b9 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java @@ -14,6 +14,7 @@ public class HandlerMapper { handlers.add(RootPageHandler.getInstance()); handlers.add(ResourceHandler.getInstance()); handlers.add(LoginHandler.getInstance()); + handlers.add(RegisterHandler.getInstance()); } public static Handler findHandler(HttpRequest httpRequest) { diff --git a/tomcat/src/main/java/com/techcourse/web/handler/RegisterHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/RegisterHandler.java new file mode 100644 index 0000000000..ad7fa2d2a4 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/handler/RegisterHandler.java @@ -0,0 +1,70 @@ +package com.techcourse.web.handler; + +import java.io.IOException; +import java.util.Map; + +import org.apache.coyote.http11.http.request.HttpMethod; +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpResponseBody; +import org.apache.coyote.http11.http.response.HttpResponseHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.model.User; +import com.techcourse.web.util.FormUrlEncodedParser; +import com.techcourse.web.util.ResourceLoader; + +public class RegisterHandler implements Handler { + + private static final Logger log = LoggerFactory.getLogger(RegisterHandler.class); + private static final String REGISTER_PATH = "/register"; + private static final RegisterHandler instance = new RegisterHandler(); + + private RegisterHandler() { + } + + public static RegisterHandler getInstance() { + return instance; + } + + @Override + public boolean isSupport(HttpRequestLine requestLine) { + return requestLine.getRequestPath().startsWith(REGISTER_PATH); + } + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + HttpRequestLine requestLine = request.getRequestLine(); + HttpMethod method = requestLine.getMethod(); + + if (method == HttpMethod.GET) { + return loadRegisterPage(); + } + if (method == HttpMethod.POST) { + return register(request); + } + + return notFoundResponse(); + } + + private HttpResponse register(HttpRequest request) { + try { + Map data = FormUrlEncodedParser.parse(request.getRequestBody()); + User user = new User(data.get("account"), data.get("password"), data.get("email")); + InMemoryUserRepository.save(user); + + return redirect(new HttpResponseHeader(), "/index.html"); + } catch (IllegalArgumentException e) { + log.error("Failed to register user. {}", e.getMessage()); + return HttpResponse.badRequest(new HttpResponseHeader()); + } + } + + private HttpResponse loadRegisterPage() throws IOException { + HttpResponseBody body = ResourceLoader.getInstance().loadResource("/register.html"); + return HttpResponse.ok(new HttpResponseHeader(), body); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java index 09926ce33c..3766c2a137 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java @@ -26,6 +26,11 @@ public static HttpResponse redirect(HttpResponseHeader responseHeader) { return new HttpResponse(new HttpResponseStartLine(HTTP_VERSION, HttpStatusCode.FOUND), responseHeader, null); } + public static HttpResponse badRequest(HttpResponseHeader httpResponseHeader) { + return new HttpResponse(new HttpResponseStartLine(HTTP_VERSION, HttpStatusCode.BAD_REQUEST), httpResponseHeader, + null); + } + public String toResponseMessage() { if (body == null) { return createResponseMessageWithoutBody(); diff --git a/tomcat/src/test/java/com/techcourse/web/handler/RegisterHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/RegisterHandlerTest.java new file mode 100644 index 0000000000..f9aab73901 --- /dev/null +++ b/tomcat/src/test/java/com/techcourse/web/handler/RegisterHandlerTest.java @@ -0,0 +1,85 @@ +package com.techcourse.web.handler; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.util.List; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.request.HttpRequestUrl; +import org.apache.coyote.http11.http.response.HttpStatusCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RegisterHandlerTest { + + @DisplayName("GET /register 요청을 처리한다.") + @Test + void isSupport() { + HttpRequestUrl requestUrl = HttpRequestUrl.from("/register"); + HttpRequestLine requestLine = new HttpRequestLine("GET", requestUrl, "HTTP/1.1"); + RegisterHandler handler = RegisterHandler.getInstance(); + + boolean isSupport = handler.isSupport(requestLine); + + assertThat(isSupport).isTrue(); + } + + @DisplayName("회원가입에 성공한다.") + @Test + void register() throws IOException { + String body = "account=sangdol&password=123&email=hello@naver.com"; + HttpRequest request = new HttpRequest("POST /register HTTP/1.1", List.of(), body); + RegisterHandler handler = RegisterHandler.getInstance(); + + assertThat(handler.isSupport(request.getRequestLine())).isTrue(); + assertThat(handler.handle(request)) + .extracting("header") + .extracting("headers") + .extracting("Location") + .isEqualTo(List.of("/index.html")); + } + + @DisplayName("이미 존재하는 계정이면 400 에러를 반환한다.") + @Test + void register_whenExistingAccount() throws IOException { + String body = "account=gugu&password=123&email=hello@naver.coom"; + HttpRequest request = new HttpRequest("POST /register HTTP/1.1", List.of(), body); + RegisterHandler handler = RegisterHandler.getInstance(); + + assertThat(handler.isSupport(request.getRequestLine())).isTrue(); + assertThat(handler.handle(request)) + .extracting("startLine") + .extracting("statusCode") + .isEqualTo(HttpStatusCode.BAD_REQUEST); + } + + @DisplayName("회원가입을 위해서는 모든 값이 필요하다.") + @Test + void register_whenNotExistValue() throws IOException { + String body = "account=123&password=123"; + HttpRequest request = new HttpRequest("POST /register HTTP/1.1", List.of(), body); + RegisterHandler handler = RegisterHandler.getInstance(); + + assertThat(handler.isSupport(request.getRequestLine())).isTrue(); + assertThat(handler.handle(request)) + .extracting("startLine") + .extracting("statusCode") + .isEqualTo(HttpStatusCode.BAD_REQUEST); + } + + @DisplayName("빈 값을 입력할 수 없다.") + @Test + void register_whenEmptyValue() throws IOException { + String body = "account=123&password= &email=email@email.com"; + HttpRequest request = new HttpRequest("POST /register HTTP/1.1", List.of(), body); + RegisterHandler handler = RegisterHandler.getInstance(); + + assertThat(handler.isSupport(request.getRequestLine())).isTrue(); + assertThat(handler.handle(request)) + .extracting("startLine") + .extracting("statusCode") + .isEqualTo(HttpStatusCode.BAD_REQUEST); + } +} From b712e75bccb9ef98605922baf29e86dcd9885574 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 15:38:51 +0900 Subject: [PATCH 25/41] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=EC=8B=9C=20=EC=BF=A0=ED=82=A4=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/web/handler/LoginHandler.java | 16 +++++++ .../web/util/JsessionIdGenerator.java | 10 ++++ .../apache/coyote/http11/http/HttpCookie.java | 48 +++++++++++++++++++ .../http/request/HttpRequestHeader.java | 47 +++++++++++++++--- .../http/response/HttpResponseHeader.java | 7 +++ .../web/handler/LoginHandlerTest.java | 33 ++++++++++++- 6 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 tomcat/src/main/java/com/techcourse/web/util/JsessionIdGenerator.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index cc1ca2e9aa..3e4f13570e 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -3,8 +3,10 @@ import java.io.IOException; import java.util.Optional; +import org.apache.coyote.http11.http.HttpCookie; import org.apache.coyote.http11.http.request.HttpMethod; import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestHeader; import org.apache.coyote.http11.http.request.HttpRequestLine; import org.apache.coyote.http11.http.request.HttpRequestQuery; import org.apache.coyote.http11.http.request.HttpRequestUrl; @@ -16,6 +18,7 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; +import com.techcourse.web.util.JsessionIdGenerator; import com.techcourse.web.util.ResourceLoader; public class LoginHandler implements Handler { @@ -64,10 +67,23 @@ private HttpResponse login(HttpRequest request) { HttpResponseHeader responseHeader = new HttpResponseHeader(); responseHeader.addHeader("Location", getRedirectLocation(account, password)); + addJSessionId(request, responseHeader); return HttpResponse.redirect(responseHeader); } + private void addJSessionId(HttpRequest request, HttpResponseHeader responseHeader) { + HttpRequestHeader header = request.getHeaders(); + HttpCookie httpCookie = header.getHttpCookie(); + if (isNotExistJsessionid(httpCookie)) { + responseHeader.addJSessionId(JsessionIdGenerator.generate()); + } + } + + private boolean isNotExistJsessionid(HttpCookie httpCookie) { + return httpCookie == null || !httpCookie.hasJsessionId(); + } + private String getRedirectLocation(String account, String password) { if (isUserNotExist(account, password)) { return "/401.html"; diff --git a/tomcat/src/main/java/com/techcourse/web/util/JsessionIdGenerator.java b/tomcat/src/main/java/com/techcourse/web/util/JsessionIdGenerator.java new file mode 100644 index 0000000000..1f97b5f65c --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/util/JsessionIdGenerator.java @@ -0,0 +1,10 @@ +package com.techcourse.web.util; + +import java.util.UUID; + +public class JsessionIdGenerator { + + public static String generate() { + return UUID.randomUUID().toString(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java new file mode 100644 index 0000000000..5ed2e6db1f --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java @@ -0,0 +1,48 @@ +package org.apache.coyote.http11.http; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class HttpCookie { + + private static final String JSESSIONID = "JSESSIONID"; + private static final String COOKIE_VALUE_DELIMITER = "; "; + private static final String KEY_VALUE_DELIMITER = "="; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + + + private final Map cookie; + + public static HttpCookie from(String cookieValues) { + String[] cookies = cookieValues.split(COOKIE_VALUE_DELIMITER); + + Map cookie = Arrays.stream(cookies) + .collect(Collectors.toMap( + c -> c.split(KEY_VALUE_DELIMITER)[KEY_INDEX], + c -> c.split(KEY_VALUE_DELIMITER)[VALUE_INDEX] + )); + + return new HttpCookie(cookie); + } + + private HttpCookie(Map cookie) { + this.cookie = cookie; + } + + public String getValue(String key) { + if (isNotExistKey(key)) { + throw new IllegalArgumentException("Key not found. key: " + key); + } + return cookie.get(key); + } + + private boolean isNotExistKey(String key) { + return !cookie.containsKey(key); + } + + public boolean hasJsessionId() { + return cookie.containsKey(JSESSIONID) && getValue(JSESSIONID) != null; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java index 07d4d3fcf0..fe02019f9b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java @@ -6,18 +6,22 @@ import java.util.Map; import org.apache.coyote.http11.http.BaseHttpHeaders; +import org.apache.coyote.http11.http.HttpCookie; public class HttpRequestHeader extends BaseHttpHeaders { private static final int HEADER_KEY_INDEX = 0; private static final int HEADER_VALUE_INDEX = 1; - public HttpRequestHeader(Map> headers) { + private final HttpCookie httpCookie; + + public HttpRequestHeader(Map> headers, HttpCookie httpCookie) { super(headers); + this.httpCookie = httpCookie; } public static HttpRequestHeader from(List headers) { - return new HttpRequestHeader(initHeaders(headers)); + return new HttpRequestHeader(initHeaders(headers), initCookie(headers)); } private static Map> initHeaders(List headers) { @@ -26,17 +30,46 @@ private static Map> initHeaders(List headers) { } LinkedHashMap> result = new LinkedHashMap<>(); - headers.forEach(h -> { - String[] headerParts = h.split(HEADER_DELIMITER); - result.put(headerParts[HEADER_KEY_INDEX], parseHeaderValue(headerParts[HEADER_VALUE_INDEX])); - }); + headers.stream() + .filter(header -> !header.startsWith("Cookie")) + .forEach(h -> { + String[] headerParts = h.split(HEADER_DELIMITER); + result.put(headerParts[HEADER_KEY_INDEX], parseHeaderValue(h, headerParts[HEADER_VALUE_INDEX])); + }); return result; } - private static List parseHeaderValue(String headerPart) { + private static List parseHeaderValue(String header, String headerPart) { + if (header.startsWith("Cookie: ")) { + return List.of(headerPart); + } return Arrays.stream(headerPart.split(HEADER_VALUE_DELIMITER)) .map(String::strip) .toList(); } + + private static HttpCookie initCookie(List headers) { + String cookieHeader = getCookieHeader(headers); + if (cookieHeader == null) { + return null; + } + String cookieValues = cookieHeader.split(HEADER_DELIMITER)[HEADER_VALUE_INDEX]; + return HttpCookie.from(cookieValues); + } + + private static String getCookieHeader(List headers) { + if (headers == null || headers.isEmpty()) { + return null; + } + + return headers.stream() + .filter(header -> header.startsWith("Cookie")) + .findFirst() + .orElse(null); + } + + public HttpCookie getHttpCookie() { + return httpCookie; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java index 6fe720232d..49a919b6e6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java @@ -8,6 +8,9 @@ public class HttpResponseHeader extends BaseHttpHeaders { + private static final String SET_COOKIE = "Set-Cookie"; + private static final String JSESSIONID = "JSESSIONID="; + public HttpResponseHeader() { super(new LinkedHashMap<>()); } @@ -20,6 +23,10 @@ public void addHeader(String key, List values) { headers.put(key, values); } + public void addJSessionId(String sessionId) { + addHeader(SET_COOKIE, JSESSIONID + sessionId); + } + public String toResponseMessage() { List lines = new ArrayList<>(); headers.forEach((key, value) -> { diff --git a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java index faf5c4f24b..7caec5f61f 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java @@ -4,11 +4,13 @@ import java.io.IOException; import java.util.List; +import java.util.Map; import org.apache.coyote.http11.http.request.HttpRequest; import org.apache.coyote.http11.http.request.HttpRequestLine; import org.apache.coyote.http11.http.request.HttpRequestUrl; import org.apache.coyote.http11.http.response.HttpStatusCode; +import org.assertj.core.api.Condition; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -28,7 +30,7 @@ void isSupport() { @DisplayName("로그인에 성공하면 /index.html로 리다이렉트한다.") @Test - void isSupportWithQuery() throws IOException { + void succeedLogin() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); HttpRequest request = new HttpRequest("GET /login?account=gugu&password=password HTTP/1.1", headers, null); @@ -40,7 +42,36 @@ void isSupportWithQuery() throws IOException { .extracting("headers") .extracting("Location") .isEqualTo(List.of("/index.html")); + } + + @DisplayName("로그인에 성공하고, 쿠키가 존재하지 않으면 쿠키를 생성한다.") + @Test + void createCookie() throws IOException { + List headers = List.of("Host: example.com", "Accept: text/html"); + HttpRequest request = new HttpRequest("GET /login?account=gugu&password=password HTTP/1.1", headers, null); + + LoginHandler loginHandler = LoginHandler.getInstance(); + + assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); + assertThat(loginHandler.handle(request)) + .extracting("header") + .extracting("headers") + .extracting("Set-Cookie") + .isNotNull(); + } + + @DisplayName("로그인에 성공해도 쿠키가 존재하면 쿠키를 생성하지 않는다.") + @Test + void notCreateCookie() throws IOException { + List headers = List.of("Host: example.com", "Accept: text/html", "Cookie: JSESSIONID=1234"); + HttpRequest request = new HttpRequest("GET /login?account=gugu&password=password HTTP/1.1", headers, null); + + LoginHandler loginHandler = LoginHandler.getInstance(); + assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); + assertThat(loginHandler.handle(request)).extracting("header") + .extracting("headers") + .doesNotHave(new Condition<>(h -> ((Map)h).containsKey("Set-Cookie"), "Set-Cookie")); } @DisplayName("로그인에 실패하면 /401.html 로 리다이렉트한다.") From d8c737d1a6320414ff292af8aef2488ec8ce9717 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 16:14:39 +0900 Subject: [PATCH 26/41] =?UTF-8?q?feat:=20=EC=84=B8=EC=85=98=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/web/handler/LoginHandler.java | 42 ++++++++++++------- .../com/techcourse/web/util/LoginChecker.java | 22 ++++++++++ .../apache/coyote/http11/http/HttpCookie.java | 4 ++ .../coyote/http11/http/session/Session.java | 22 ++++++++++ .../http11/http/session/SessionManager.java | 23 ++++++++++ .../web/handler/LoginHandlerTest.java | 25 +++++++++++ 6 files changed, 123 insertions(+), 15 deletions(-) create mode 100644 tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/session/Session.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/session/SessionManager.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index 3e4f13570e..51c31d3e93 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -18,7 +18,9 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; +import org.apache.coyote.http11.http.session.SessionManager; import com.techcourse.web.util.JsessionIdGenerator; +import com.techcourse.web.util.LoginChecker; import com.techcourse.web.util.ResourceLoader; public class LoginHandler implements Handler { @@ -46,7 +48,7 @@ public HttpResponse handle(HttpRequest request) throws IOException { HttpMethod method = requestLine.getMethod(); if (method == HttpMethod.GET && LOGIN_PATH.equals(requestUrl.getRequestUrl())) { - return loadLoginPage(); + return loadLoginPage(request); } if (method == HttpMethod.GET && requestUrl.isQueryExist()) { return login(request); @@ -55,7 +57,10 @@ public HttpResponse handle(HttpRequest request) throws IOException { return notFoundResponse(); } - private HttpResponse loadLoginPage() throws IOException { + private HttpResponse loadLoginPage(HttpRequest request) throws IOException { + if (LoginChecker.isLoggedIn(request)) { + return redirect(new HttpResponseHeader(), "/index.html"); + } HttpResponseBody body = ResourceLoader.getInstance().loadResource("/login.html"); return HttpResponse.ok(new HttpResponseHeader(), body); } @@ -65,32 +70,39 @@ private HttpResponse login(HttpRequest request) { String account = query.getValue("account"); String password = query.getValue("password"); - HttpResponseHeader responseHeader = new HttpResponseHeader(); - responseHeader.addHeader("Location", getRedirectLocation(account, password)); - addJSessionId(request, responseHeader); + if (isUserNotExist(account, password)) { + return redirect(new HttpResponseHeader(), "/401.html"); + } + + User user = InMemoryUserRepository.findByAccount(account).get(); + HttpResponseHeader responseHeader = createResponseHeader(request, user); - return HttpResponse.redirect(responseHeader); + return redirect(responseHeader, "/index.html"); } - private void addJSessionId(HttpRequest request, HttpResponseHeader responseHeader) { + private HttpResponseHeader createResponseHeader(HttpRequest request, User user) { + HttpResponseHeader header = new HttpResponseHeader(); + String sessionId = addJSessionId(request, header); + SessionManager.createSession(sessionId, user); + + return header; + } + + private String addJSessionId(HttpRequest request, HttpResponseHeader responseHeader) { HttpRequestHeader header = request.getHeaders(); HttpCookie httpCookie = header.getHttpCookie(); if (isNotExistJsessionid(httpCookie)) { - responseHeader.addJSessionId(JsessionIdGenerator.generate()); + String sessionId = JsessionIdGenerator.generate(); + responseHeader.addJSessionId(sessionId); + return sessionId; } + return httpCookie.getJsessionid(); } private boolean isNotExistJsessionid(HttpCookie httpCookie) { return httpCookie == null || !httpCookie.hasJsessionId(); } - private String getRedirectLocation(String account, String password) { - if (isUserNotExist(account, password)) { - return "/401.html"; - } - return "/index.html"; - } - private boolean isUserNotExist(String account, String password) { if (account == null) { log.info("account is required"); diff --git a/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java b/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java new file mode 100644 index 0000000000..572fabe152 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java @@ -0,0 +1,22 @@ +package com.techcourse.web.util; + +import org.apache.coyote.http11.http.HttpCookie; +import org.apache.coyote.http11.http.request.HttpRequest; + +import org.apache.coyote.http11.http.session.Session; +import org.apache.coyote.http11.http.session.SessionManager; + +public class LoginChecker { + + public static boolean isLoggedIn(HttpRequest request) { + HttpCookie cookie = request.getHeaders().getHttpCookie(); + + String sessionId = cookie.getJsessionid(); + if (sessionId == null) { + return false; + } + + Session session = SessionManager.getSession(sessionId); + return session != null && session.getUser() != null; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java index 5ed2e6db1f..0111f611d7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java @@ -45,4 +45,8 @@ private boolean isNotExistKey(String key) { public boolean hasJsessionId() { return cookie.containsKey(JSESSIONID) && getValue(JSESSIONID) != null; } + + public String getJsessionid() { + return getValue(JSESSIONID); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/session/Session.java b/tomcat/src/main/java/org/apache/coyote/http11/http/session/Session.java new file mode 100644 index 0000000000..fafbcd5a3e --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/session/Session.java @@ -0,0 +1,22 @@ +package org.apache.coyote.http11.http.session; + +import com.techcourse.model.User; + +public class Session { + + private final String id; + private final User user; + + public Session(String id, User user) { + this.id = id; + this.user = user; + } + + public String getId() { + return id; + } + + public User getUser() { + return user; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/session/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/http/session/SessionManager.java new file mode 100644 index 0000000000..a367a2465f --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/session/SessionManager.java @@ -0,0 +1,23 @@ +package org.apache.coyote.http11.http.session; + +import java.util.HashMap; +import java.util.Map; + +import com.techcourse.model.User; + +public class SessionManager { + + private static final Map sessions = new HashMap<>(); + + public static void createSession(String id, User user) { + sessions.put(id, new Session(id, user)); + } + + public static void removeSession(String sessionId) { + sessions.remove(sessionId); + } + + public static Session getSession(String sessionId) { + return sessions.get(sessionId); + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java index 7caec5f61f..e42648334c 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java @@ -14,6 +14,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import com.techcourse.db.InMemoryUserRepository; +import com.techcourse.model.User; +import org.apache.coyote.http11.http.session.SessionManager; +import com.techcourse.web.util.JsessionIdGenerator; + class LoginHandlerTest { @DisplayName("/login 으로 시작하는 페이지를 처리한다.") @@ -28,6 +33,26 @@ void isSupport() { assertThat(isSupport).isTrue(); } + @DisplayName("이미 로그인 된 회원이 GET /login 요청을 보내면 /index.html로 리다이렉트한다.") + @Test + void alreadyLoggedIn() throws IOException { + User user = InMemoryUserRepository.findByAccount("gugu").get(); + String sessionId = JsessionIdGenerator.generate(); + SessionManager.createSession(sessionId, user); + + List headers = List.of("Host: example.com", "Accept: text/html", "Cookie: JSESSIONID=" + sessionId); + HttpRequest request = new HttpRequest("GET /login HTTP/1.1", headers, null); + + LoginHandler loginHandler = LoginHandler.getInstance(); + + assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); + assertThat(loginHandler.handle(request)) + .extracting("header") + .extracting("headers") + .extracting("Location") + .isEqualTo(List.of("/index.html")); + } + @DisplayName("로그인에 성공하면 /index.html로 리다이렉트한다.") @Test void succeedLogin() throws IOException { From a44e28e2e6db8984a0b79e4ca654af19206c8d52 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 16:16:26 +0900 Subject: [PATCH 27/41] style: reformat code --- .../main/java/com/techcourse/Application.java | 8 +- .../techcourse/db/InMemoryUserRepository.java | 39 +++-- .../exception/UncheckedServletException.java | 6 +- .../main/java/com/techcourse/model/User.java | 92 +++++----- .../techcourse/web/handler/LoginHandler.java | 2 +- .../com/techcourse/web/util/LoginChecker.java | 1 - .../java/org/apache/catalina/Manager.java | 52 +++--- .../apache/catalina/connector/Connector.java | 162 +++++++++--------- .../org/apache/catalina/startup/Tomcat.java | 28 +-- .../java/org/apache/coyote/Processor.java | 12 +- .../http11/HttpRequestMessageReader.java | 8 +- .../apache/coyote/http11/http/HttpCookie.java | 1 - 12 files changed, 204 insertions(+), 207 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/Application.java b/tomcat/src/main/java/com/techcourse/Application.java index 6dc0e04e1d..b862334c52 100644 --- a/tomcat/src/main/java/com/techcourse/Application.java +++ b/tomcat/src/main/java/com/techcourse/Application.java @@ -4,8 +4,8 @@ public class Application { - public static void main(String[] args) { - final var tomcat = new Tomcat(); - tomcat.start(); - } + public static void main(String[] args) { + final var tomcat = new Tomcat(); + tomcat.start(); + } } diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index 644ad92e6a..7413790026 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -8,23 +8,24 @@ public class InMemoryUserRepository { - private static final Map database = new ConcurrentHashMap<>(); - - static { - final User user = new User(1L, "gugu", "password", "hkkang@woowahan.com"); - database.put(user.getAccount(), user); - } - - public static void save(User user) { - if (database.containsKey(user.getAccount())) { - throw new IllegalArgumentException("account is already exist"); - } - database.put(user.getAccount(), user); - } - - public static Optional findByAccount(String account) { - return Optional.ofNullable(database.get(account)); - } - - private InMemoryUserRepository() {} + private static final Map database = new ConcurrentHashMap<>(); + + static { + final User user = new User(1L, "gugu", "password", "hkkang@woowahan.com"); + database.put(user.getAccount(), user); + } + + public static void save(User user) { + if (database.containsKey(user.getAccount())) { + throw new IllegalArgumentException("account is already exist"); + } + database.put(user.getAccount(), user); + } + + public static Optional findByAccount(String account) { + return Optional.ofNullable(database.get(account)); + } + + private InMemoryUserRepository() { + } } diff --git a/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java b/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java index 64466b42de..d89ead5f52 100644 --- a/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java +++ b/tomcat/src/main/java/com/techcourse/exception/UncheckedServletException.java @@ -2,7 +2,7 @@ public class UncheckedServletException extends RuntimeException { - public UncheckedServletException(Exception e) { - super(e); - } + public UncheckedServletException(Exception e) { + super(e); + } } diff --git a/tomcat/src/main/java/com/techcourse/model/User.java b/tomcat/src/main/java/com/techcourse/model/User.java index 54490f5768..115aa5dab5 100644 --- a/tomcat/src/main/java/com/techcourse/model/User.java +++ b/tomcat/src/main/java/com/techcourse/model/User.java @@ -2,50 +2,50 @@ public class User { - private final Long id; - private final String account; - private final String password; - private final String email; - - public User(Long id, String account, String password, String email) { - validateIsNotEmptyValue(account, password, email); - this.id = id; - this.account = account; - this.password = password; - this.email = email; - } - - private void validateIsNotEmptyValue(String account, String password, String email) { - if (account == null || account.isBlank()) { - throw new IllegalArgumentException("Account is empty"); - } - if (password == null || password.isBlank()) { - throw new IllegalArgumentException("Password is empty"); - } - if (email == null || email.isBlank()) { - throw new IllegalArgumentException("Email is empty"); - } - } - - public User(String account, String password, String email) { - this(null, account, password, email); - } - - public boolean checkPassword(String password) { - return this.password.equals(password); - } - - public String getAccount() { - return account; - } - - @Override - public String toString() { - return "User{" + - "id=" + id + - ", account='" + account + '\'' + - ", email='" + email + '\'' + - ", password='" + password + '\'' + - '}'; - } + private final Long id; + private final String account; + private final String password; + private final String email; + + public User(Long id, String account, String password, String email) { + validateIsNotEmptyValue(account, password, email); + this.id = id; + this.account = account; + this.password = password; + this.email = email; + } + + private void validateIsNotEmptyValue(String account, String password, String email) { + if (account == null || account.isBlank()) { + throw new IllegalArgumentException("Account is empty"); + } + if (password == null || password.isBlank()) { + throw new IllegalArgumentException("Password is empty"); + } + if (email == null || email.isBlank()) { + throw new IllegalArgumentException("Email is empty"); + } + } + + public User(String account, String password, String email) { + this(null, account, password, email); + } + + public boolean checkPassword(String password) { + return this.password.equals(password); + } + + public String getAccount() { + return account; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", account='" + account + '\'' + + ", email='" + email + '\'' + + ", password='" + password + '\'' + + '}'; + } } diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index 51c31d3e93..372b1ce000 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -13,12 +13,12 @@ import org.apache.coyote.http11.http.response.HttpResponse; import org.apache.coyote.http11.http.response.HttpResponseBody; import org.apache.coyote.http11.http.response.HttpResponseHeader; +import org.apache.coyote.http11.http.session.SessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; -import org.apache.coyote.http11.http.session.SessionManager; import com.techcourse.web.util.JsessionIdGenerator; import com.techcourse.web.util.LoginChecker; import com.techcourse.web.util.ResourceLoader; diff --git a/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java b/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java index 572fabe152..876b47317e 100644 --- a/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java +++ b/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java @@ -2,7 +2,6 @@ import org.apache.coyote.http11.http.HttpCookie; import org.apache.coyote.http11.http.request.HttpRequest; - import org.apache.coyote.http11.http.session.Session; import org.apache.coyote.http11.http.session.SessionManager; diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e5e8108636..85345f78ff 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -24,33 +24,31 @@ */ public interface Manager { - /** - * Add this Session to the set of active Sessions for this Manager. - * - * @param session Session to be added - */ - void add(HttpSession session); + /** + * Add this Session to the set of active Sessions for this Manager. + * + * @param session Session to be added + */ + void add(HttpSession session); - /** - * Return the active Session, associated with this Manager, with the - * specified session id (if any); otherwise return null. - * - * @param id The session id for the session to be returned - * - * @exception IllegalStateException if a new session cannot be - * instantiated for any reason - * @exception IOException if an input/output error occurs while - * processing this request - * - * @return the request session or {@code null} if a session with the - * requested ID could not be found - */ - HttpSession findSession(String id) throws IOException; + /** + * Return the active Session, associated with this Manager, with the + * specified session id (if any); otherwise return null. + * + * @param id The session id for the session to be returned + * @return the request session or {@code null} if a session with the + * requested ID could not be found + * @throws IllegalStateException if a new session cannot be + * instantiated for any reason + * @throws IOException if an input/output error occurs while + * processing this request + */ + HttpSession findSession(String id) throws IOException; - /** - * Remove this Session from the active Sessions for this Manager. - * - * @param session Session to be removed - */ - void remove(HttpSession session); + /** + * Remove this Session from the active Sessions for this Manager. + * + * @param session Session to be removed + */ + void remove(HttpSession session); } 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 9319ab163b..da06e9b179 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -11,85 +11,85 @@ public class Connector implements Runnable { - private static final Logger log = LoggerFactory.getLogger(Connector.class); - - private static final int DEFAULT_PORT = 8080; - private static final int DEFAULT_ACCEPT_COUNT = 100; - - private final ServerSocket serverSocket; - private boolean stopped; - - public Connector() { - this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); - } - - public Connector(final int port, final int acceptCount) { - this.serverSocket = createServerSocket(port, acceptCount); - this.stopped = false; - } - - private ServerSocket createServerSocket(final int port, final int acceptCount) { - try { - final int checkedPort = checkPort(port); - final int checkedAcceptCount = checkAcceptCount(acceptCount); - return new ServerSocket(checkedPort, checkedAcceptCount); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void start() { - var thread = new Thread(this); - thread.setDaemon(true); - thread.start(); - stopped = false; - log.info("Web Application Server started {} port.", serverSocket.getLocalPort()); - } - - @Override - public void run() { - // 클라이언트가 연결될때까지 대기한다. - while (!stopped) { - connect(); - } - } - - private void connect() { - try { - process(serverSocket.accept()); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - - private void process(final Socket connection) { - if (connection == null) { - return; - } - var processor = new Http11Processor(connection); - new Thread(processor).start(); - } - - public void stop() { - stopped = true; - try { - serverSocket.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - - private int checkPort(final int port) { - final var MIN_PORT = 1; - final var MAX_PORT = 65535; - - if (port < MIN_PORT || MAX_PORT < port) { - return DEFAULT_PORT; - } - return port; - } - - private int checkAcceptCount(final int acceptCount) { - return Math.max(acceptCount, DEFAULT_ACCEPT_COUNT); - } + private static final Logger log = LoggerFactory.getLogger(Connector.class); + + private static final int DEFAULT_PORT = 8080; + private static final int DEFAULT_ACCEPT_COUNT = 100; + + private final ServerSocket serverSocket; + private boolean stopped; + + public Connector() { + this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); + } + + public Connector(final int port, final int acceptCount) { + this.serverSocket = createServerSocket(port, acceptCount); + this.stopped = false; + } + + private ServerSocket createServerSocket(final int port, final int acceptCount) { + try { + final int checkedPort = checkPort(port); + final int checkedAcceptCount = checkAcceptCount(acceptCount); + return new ServerSocket(checkedPort, checkedAcceptCount); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void start() { + var thread = new Thread(this); + thread.setDaemon(true); + thread.start(); + stopped = false; + log.info("Web Application Server started {} port.", serverSocket.getLocalPort()); + } + + @Override + public void run() { + // 클라이언트가 연결될때까지 대기한다. + while (!stopped) { + connect(); + } + } + + private void connect() { + try { + process(serverSocket.accept()); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + private void process(final Socket connection) { + if (connection == null) { + return; + } + var processor = new Http11Processor(connection); + new Thread(processor).start(); + } + + public void stop() { + stopped = true; + try { + serverSocket.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + private int checkPort(final int port) { + final var MIN_PORT = 1; + final var MAX_PORT = 65535; + + if (port < MIN_PORT || MAX_PORT < port) { + return DEFAULT_PORT; + } + return port; + } + + private int checkAcceptCount(final int acceptCount) { + return Math.max(acceptCount, DEFAULT_ACCEPT_COUNT); + } } diff --git a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java index 61afa8df71..f63ab65999 100644 --- a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java +++ b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java @@ -8,20 +8,20 @@ public class Tomcat { - private static final Logger log = LoggerFactory.getLogger(Tomcat.class); + private static final Logger log = LoggerFactory.getLogger(Tomcat.class); - public void start() { - var connector = new Connector(); - connector.start(); + public void start() { + var connector = new Connector(); + connector.start(); - try { - // make the application wait until we press any key. - System.in.read(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } finally { - log.info("web server stop."); - connector.stop(); - } - } + try { + // make the application wait until we press any key. + System.in.read(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } finally { + log.info("web server stop."); + connector.stop(); + } + } } diff --git a/tomcat/src/main/java/org/apache/coyote/Processor.java b/tomcat/src/main/java/org/apache/coyote/Processor.java index 6604ab83de..47ab0b2dbc 100644 --- a/tomcat/src/main/java/org/apache/coyote/Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/Processor.java @@ -23,10 +23,10 @@ */ public interface Processor { - /** - * Process a connection. This is called whenever an event occurs (e.g. more - * data arrives) that allows processing to continue for a connection that is - * not currently being processed. - */ - void process(Socket socket); + /** + * Process a connection. This is called whenever an event occurs (e.g. more + * data arrives) that allows processing to continue for a connection that is + * not currently being processed. + */ + void process(Socket socket); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java index 37debe08db..73eb24b25f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java @@ -23,10 +23,10 @@ public static HttpRequest read(InputStream inputStream) throws IOException { private static int getContentLength(List headers) { return headers.stream() - .filter(header -> header.startsWith("Content-Length")) - .findFirst() - .map(header -> Integer.parseInt(header.split(": ")[1].strip())) - .orElse(0); + .filter(header -> header.startsWith("Content-Length")) + .findFirst() + .map(header -> Integer.parseInt(header.split(": ")[1].strip())) + .orElse(0); } private static String readBody(BufferedReader reader, int contentLength) throws IOException { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java index 0111f611d7..5740c4ecdd 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java @@ -12,7 +12,6 @@ public class HttpCookie { private static final int KEY_INDEX = 0; private static final int VALUE_INDEX = 1; - private final Map cookie; public static HttpCookie from(String cookieValues) { From ff51208aa9a59342b9bd5a09525cade8b6f3bdd1 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 18:12:11 +0900 Subject: [PATCH 28/41] =?UTF-8?q?style:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/web/handler/LoginHandlerTest.java | 2 +- .../java/com/techcourse/web/handler/ResourceHandlerTest.java | 4 +--- .../java/com/techcourse/web/handler/RootPageHandlerTest.java | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java index e42648334c..9d658f84af 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java @@ -101,7 +101,7 @@ void notCreateCookie() throws IOException { @DisplayName("로그인에 실패하면 /401.html 로 리다이렉트한다.") @Test - void isSupportWithQueryFail() throws IOException { + void handle_whenLoginFailed() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); HttpRequest request = new HttpRequest("GET /login?account=hi&password=hellofail HTTP/1.1", headers, null); diff --git a/tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java index 97a45739f1..371b139fc9 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/ResourceHandlerTest.java @@ -37,19 +37,17 @@ void isNotSupport() { ResourceHandler handler = ResourceHandler.getInstance(); - // then assertThat(handler.isSupport(request.getRequestLine())).isFalse(); } @DisplayName("정적 리소스에 대한 요청을 처리한다.") @Test - void isSupportWithQuery() throws IOException { + void handle() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); HttpRequest request = new HttpRequest("GET /index.html HTTP/1.1", headers, null); ResourceHandler handler = ResourceHandler.getInstance(); - // then HttpResponse response = handler.handle(request); assertThat(handler.isSupport(request.getRequestLine())).isTrue(); assertThat(response) diff --git a/tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java index 7deba19a1f..30f44b9972 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/RootPageHandlerTest.java @@ -41,7 +41,7 @@ void isNotSupport() { @DisplayName("/ 요청을 처리한다.") @Test - void isSupportWithQuery() throws IOException { + void handle() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); HttpRequest request = new HttpRequest("GET / HTTP/1.1", headers, null); From 202f711a0c3242aba435a5d3c0efe5efd06d6ef8 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 18:31:49 +0900 Subject: [PATCH 29/41] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=8B=9C=20POST=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=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 --- .../techcourse/web/handler/LoginHandler.java | 10 ++++---- .../http11/http/request/HttpRequestUrl.java | 10 +++++--- tomcat/src/main/resources/static/login.html | 2 +- .../web/handler/LoginHandlerTest.java | 23 ++++--------------- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index 372b1ce000..76aae96189 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -1,6 +1,7 @@ package com.techcourse.web.handler; import java.io.IOException; +import java.util.Map; import java.util.Optional; import org.apache.coyote.http11.http.HttpCookie; @@ -19,6 +20,7 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; +import com.techcourse.web.util.FormUrlEncodedParser; import com.techcourse.web.util.JsessionIdGenerator; import com.techcourse.web.util.LoginChecker; import com.techcourse.web.util.ResourceLoader; @@ -50,7 +52,7 @@ public HttpResponse handle(HttpRequest request) throws IOException { if (method == HttpMethod.GET && LOGIN_PATH.equals(requestUrl.getRequestUrl())) { return loadLoginPage(request); } - if (method == HttpMethod.GET && requestUrl.isQueryExist()) { + if (method == HttpMethod.POST) { return login(request); } @@ -66,9 +68,9 @@ private HttpResponse loadLoginPage(HttpRequest request) throws IOException { } private HttpResponse login(HttpRequest request) { - HttpRequestQuery query = request.getRequestLine().getQuery(); - String account = query.getValue("account"); - String password = query.getValue("password"); + Map body = FormUrlEncodedParser.parse(request.getRequestBody()); + String account = body.get("account"); + String password = body.get("password"); if (isUserNotExist(account, password)) { return redirect(new HttpResponseHeader(), "/401.html"); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java index 9b3583529c..63729f7d78 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestUrl.java @@ -2,15 +2,19 @@ public class HttpRequestUrl { + private static final String REQUEST_URL_DELIMITER = "\\?"; + private static final int REQUEST_PATH_INDEX = 0; + private static final int REQUEST_QUERY_INDEX = 1; + private final String path; private final HttpRequestQuery httpRequestQuery; public static HttpRequestUrl from(String requestUrl) { - String[] urlParts = requestUrl.split("\\?"); + String[] urlParts = requestUrl.split(REQUEST_URL_DELIMITER); if (urlParts.length == 1) { - return new HttpRequestUrl(urlParts[0], null); + return new HttpRequestUrl(urlParts[REQUEST_PATH_INDEX], null); } - return new HttpRequestUrl(urlParts[0], urlParts[1]); + return new HttpRequestUrl(urlParts[REQUEST_PATH_INDEX], urlParts[REQUEST_QUERY_INDEX]); } public HttpRequestUrl(String path, String query) { diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html index f4ed9de875..bc933357f2 100644 --- a/tomcat/src/main/resources/static/login.html +++ b/tomcat/src/main/resources/static/login.html @@ -20,7 +20,7 @@

로그인

-
+
diff --git a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java index 9d658f84af..31b8cf76ae 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/LoginHandlerTest.java @@ -57,7 +57,7 @@ void alreadyLoggedIn() throws IOException { @Test void succeedLogin() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); - HttpRequest request = new HttpRequest("GET /login?account=gugu&password=password HTTP/1.1", headers, null); + HttpRequest request = new HttpRequest("POST /login HTTP/1.1", headers, "account=gugu&password=password"); LoginHandler loginHandler = LoginHandler.getInstance(); @@ -73,7 +73,7 @@ void succeedLogin() throws IOException { @Test void createCookie() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); - HttpRequest request = new HttpRequest("GET /login?account=gugu&password=password HTTP/1.1", headers, null); + HttpRequest request = new HttpRequest("POST /login HTTP/1.1", headers, "account=gugu&password=password"); LoginHandler loginHandler = LoginHandler.getInstance(); @@ -89,7 +89,7 @@ void createCookie() throws IOException { @Test void notCreateCookie() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html", "Cookie: JSESSIONID=1234"); - HttpRequest request = new HttpRequest("GET /login?account=gugu&password=password HTTP/1.1", headers, null); + HttpRequest request = new HttpRequest("POST /login HTTP/1.1", headers, "account=gugu&password=password"); LoginHandler loginHandler = LoginHandler.getInstance(); @@ -103,7 +103,7 @@ void notCreateCookie() throws IOException { @Test void handle_whenLoginFailed() throws IOException { List headers = List.of("Host: example.com", "Accept: text/html"); - HttpRequest request = new HttpRequest("GET /login?account=hi&password=hellofail HTTP/1.1", headers, null); + HttpRequest request = new HttpRequest("POST /login HTTP/1.1", headers, "account=hi&password=hello"); LoginHandler loginHandler = LoginHandler.getInstance(); @@ -114,19 +114,4 @@ void handle_whenLoginFailed() throws IOException { .extracting("Location") .isEqualTo(List.of("/401.html")); } - - @DisplayName("POST 요청은 처리하지 않는다.") - @Test - void isNotSupport() throws IOException { - List headers = List.of("Host: example.com", "Accept: text/html"); - HttpRequest request = new HttpRequest("POST /login?account=hi&password=hello HTTP/1.1", headers, null); - - LoginHandler loginHandler = LoginHandler.getInstance(); - - assertThat(loginHandler.isSupport(request.getRequestLine())).isTrue(); - assertThat(loginHandler.handle(request)) - .extracting("startLine") - .extracting("statusCode") - .isEqualTo(HttpStatusCode.NOT_FOUND); - } } From 38833b5c87a741ca60483fa36024cd973978edd5 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 20:38:06 +0900 Subject: [PATCH 30/41] =?UTF-8?q?fix:=20=EC=84=B8=EC=85=98=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EB=95=8C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=EC=9D=B4=20=EC=95=88=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=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 --- tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java b/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java index 876b47317e..57d5da10f9 100644 --- a/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java +++ b/tomcat/src/main/java/com/techcourse/web/util/LoginChecker.java @@ -9,6 +9,9 @@ public class LoginChecker { public static boolean isLoggedIn(HttpRequest request) { HttpCookie cookie = request.getHeaders().getHttpCookie(); + if (cookie == null) { + return false; + } String sessionId = cookie.getJsessionid(); if (sessionId == null) { From 3c59f405968f88d006bf08889c64dfbaadb2b6e2 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 20:50:28 +0900 Subject: [PATCH 31/41] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/web/handler/Handler.java | 3 ++- .../http11/HttpRequestMessageReader.java | 3 ++- .../apache/coyote/http11/http/HttpHeader.java | 21 +++++++++++++++++++ .../http/request/HttpRequestHeader.java | 7 ++++--- .../http11/http/response/HttpResponse.java | 8 ++++--- .../http/response/HttpResponseHeader.java | 4 ++-- 6 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/http/HttpHeader.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/Handler.java b/tomcat/src/main/java/com/techcourse/web/handler/Handler.java index 34e7ed1a8e..f46a88ff9e 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/Handler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/Handler.java @@ -2,6 +2,7 @@ import java.io.IOException; +import org.apache.coyote.http11.http.HttpHeader; import org.apache.coyote.http11.http.request.HttpRequest; import org.apache.coyote.http11.http.request.HttpRequestLine; import org.apache.coyote.http11.http.response.HttpResponse; @@ -22,7 +23,7 @@ default HttpResponse notFoundResponse() throws IOException { } default HttpResponse redirect(HttpResponseHeader header, String location) { - header.addHeader("Location", location); + header.addHeader(HttpHeader.LOCATION.getName(), location); return HttpResponse.redirect(header); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java index 73eb24b25f..f684478787 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.coyote.http11.http.HttpHeader; import org.apache.coyote.http11.http.request.HttpRequest; public class HttpRequestMessageReader { @@ -23,7 +24,7 @@ public static HttpRequest read(InputStream inputStream) throws IOException { private static int getContentLength(List headers) { return headers.stream() - .filter(header -> header.startsWith("Content-Length")) + .filter(header -> header.startsWith(HttpHeader.CONTENT_LENGTH.getName())) .findFirst() .map(header -> Integer.parseInt(header.split(": ")[1].strip())) .orElse(0); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpHeader.java new file mode 100644 index 0000000000..9106322959 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpHeader.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11.http; + +public enum HttpHeader { + + LOCATION("Location"), + COOKIE("Cookie"), + SET_COOKIE("Set-Cookie"), + CONTENT_TYPE("Content-Type"), + CONTENT_LENGTH("Content-Length"), + ; + + private final String name; + + HttpHeader(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java index fe02019f9b..1b67193a7e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java @@ -7,6 +7,7 @@ import org.apache.coyote.http11.http.BaseHttpHeaders; import org.apache.coyote.http11.http.HttpCookie; +import org.apache.coyote.http11.http.HttpHeader; public class HttpRequestHeader extends BaseHttpHeaders { @@ -31,7 +32,7 @@ private static Map> initHeaders(List headers) { LinkedHashMap> result = new LinkedHashMap<>(); headers.stream() - .filter(header -> !header.startsWith("Cookie")) + .filter(header -> !header.startsWith(HttpHeader.COOKIE.getName())) .forEach(h -> { String[] headerParts = h.split(HEADER_DELIMITER); result.put(headerParts[HEADER_KEY_INDEX], parseHeaderValue(h, headerParts[HEADER_VALUE_INDEX])); @@ -41,7 +42,7 @@ private static Map> initHeaders(List headers) { } private static List parseHeaderValue(String header, String headerPart) { - if (header.startsWith("Cookie: ")) { + if (header.startsWith(HttpHeader.COOKIE.getName())) { return List.of(headerPart); } return Arrays.stream(headerPart.split(HEADER_VALUE_DELIMITER)) @@ -64,7 +65,7 @@ private static String getCookieHeader(List headers) { } return headers.stream() - .filter(header -> header.startsWith("Cookie")) + .filter(header -> header.startsWith(HttpHeader.COOKIE.getName())) .findFirst() .orElse(null); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java index 3766c2a137..7d104ee6b6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java @@ -1,5 +1,7 @@ package org.apache.coyote.http11.http.response; +import org.apache.coyote.http11.http.HttpHeader; + public class HttpResponse { private static final String HTTP_VERSION = "HTTP/1.1"; @@ -40,7 +42,7 @@ public String toResponseMessage() { } private String createResponseMessageWithoutBody() { - header.addHeader("Content-Length", "0"); + header.addHeader(HttpHeader.CONTENT_LENGTH.getName(), "0"); return String.join("\r\n" , startLine.toResponseMessage() , header.toResponseMessage() @@ -50,8 +52,8 @@ private String createResponseMessageWithoutBody() { private String createResponseMessage() { String contentCharset = ";charset=utf-8"; - header.addHeader("Content-Type", body.getContentType() + contentCharset); - header.addHeader("Content-Length", String.valueOf(body.getContentLength())); + header.addHeader(HttpHeader.CONTENT_TYPE.getName(), body.getContentType() + contentCharset); + header.addHeader(HttpHeader.CONTENT_LENGTH.getName(), String.valueOf(body.getContentLength())); return String.join("\r\n" , startLine.toResponseMessage() diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java index 49a919b6e6..d3b75ec062 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponseHeader.java @@ -5,10 +5,10 @@ import java.util.List; import org.apache.coyote.http11.http.BaseHttpHeaders; +import org.apache.coyote.http11.http.HttpHeader; public class HttpResponseHeader extends BaseHttpHeaders { - private static final String SET_COOKIE = "Set-Cookie"; private static final String JSESSIONID = "JSESSIONID="; public HttpResponseHeader() { @@ -24,7 +24,7 @@ public void addHeader(String key, List values) { } public void addJSessionId(String sessionId) { - addHeader(SET_COOKIE, JSESSIONID + sessionId); + addHeader(HttpHeader.SET_COOKIE.getName(), JSESSIONID + sessionId); } public String toResponseMessage() { From a1f685432df8bc5a6f04c17f74d227ddef33041f Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:10:40 +0900 Subject: [PATCH 32/41] =?UTF-8?q?refactor:=20Handler=EA=B0=80=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9C=BC=EB=A9=B4=20404?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=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 --- .../techcourse/web/handler/HandlerMapper.java | 9 ++--- .../web/handler/NotFoundHandler.java | 34 +++++++++++++++++++ .../web/handler/HandlerMapperTest.java | 8 ++--- 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 tomcat/src/main/java/com/techcourse/web/handler/NotFoundHandler.java diff --git a/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java b/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java index cb9ab4b3b9..9100053566 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/HandlerMapper.java @@ -2,9 +2,12 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.apache.coyote.http11.http.request.HttpRequest; import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpResponseHeader; public class HandlerMapper { @@ -20,10 +23,8 @@ public class HandlerMapper { public static Handler findHandler(HttpRequest httpRequest) { HttpRequestLine requestLine = httpRequest.getRequestLine(); return handlers.stream() - .filter(handler -> handler.isSupport(requestLine)) + .filter(h -> h.isSupport(requestLine)) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("handler not found. " + - "path: " + requestLine.getRequestPath() + ", method: " + requestLine.getMethod()) - ); + .orElse(NotFoundHandler.getInstance()); } } diff --git a/tomcat/src/main/java/com/techcourse/web/handler/NotFoundHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/NotFoundHandler.java new file mode 100644 index 0000000000..6fd89d8074 --- /dev/null +++ b/tomcat/src/main/java/com/techcourse/web/handler/NotFoundHandler.java @@ -0,0 +1,34 @@ +package com.techcourse.web.handler; + +import java.io.IOException; + +import org.apache.coyote.http11.http.request.HttpRequest; +import org.apache.coyote.http11.http.request.HttpRequestLine; +import org.apache.coyote.http11.http.response.HttpResponse; +import org.apache.coyote.http11.http.response.HttpResponseBody; +import org.apache.coyote.http11.http.response.HttpResponseHeader; + +import com.techcourse.web.util.ResourceLoader; + +public class NotFoundHandler implements Handler { + + private static final NotFoundHandler instance = new NotFoundHandler(); + + private NotFoundHandler() { + } + + public static NotFoundHandler getInstance() { + return instance; + } + + @Override + public boolean isSupport(HttpRequestLine requestLine) { + return true; + } + + @Override + public HttpResponse handle(HttpRequest request) throws IOException { + HttpResponseBody notFoundPage = ResourceLoader.getInstance().loadResource("/404.html"); + return HttpResponse.notFound(new HttpResponseHeader(), notFoundPage); + } +} diff --git a/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java index 693275228b..48ab62f5c7 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java @@ -40,14 +40,14 @@ void findHandler_WhenRequestResource() { runTest(requestPath, expectedHandler); } - @DisplayName("찾는 핸들러가 없는 경우 예외을 던진다.") + @DisplayName("찾는 핸들러가 없는 경우 404 핸들러를 반환한다.") @Test void findHandler_WhenHandlerNotFound() { HttpRequest request = new HttpRequest("GET /not-found HTTP/1.1", List.of(), null); - assertThatThrownBy(() -> HandlerMapper.findHandler(request)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("handler not found. path: /not-found, method: GET"); + Class expectedHandler = NotFoundHandler.class; + + runTest("/not-found", expectedHandler); } private void runTest(String requestPath, Class expectedHandler) { From 068883a54e6efc689fa93907c6cf326c3cb7d021 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:22:37 +0900 Subject: [PATCH 33/41] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20User?= =?UTF-8?q?=EB=A5=BC=20=EC=A4=91=EB=B3=B5=EC=9C=BC=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/db/InMemoryUserRepository.java | 8 ++++++++ .../main/java/com/techcourse/model/User.java | 8 ++++++++ .../techcourse/web/handler/LoginHandler.java | 19 ++++++------------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java index 7413790026..f8458a229f 100644 --- a/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java @@ -4,10 +4,14 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.techcourse.model.User; public class InMemoryUserRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepository.class); private static final Map database = new ConcurrentHashMap<>(); static { @@ -23,6 +27,10 @@ public static void save(User user) { } public static Optional findByAccount(String account) { + if (account == null) { + log.info("account not exist"); + return Optional.empty(); + } return Optional.ofNullable(database.get(account)); } diff --git a/tomcat/src/main/java/com/techcourse/model/User.java b/tomcat/src/main/java/com/techcourse/model/User.java index 115aa5dab5..83dc59dc0a 100644 --- a/tomcat/src/main/java/com/techcourse/model/User.java +++ b/tomcat/src/main/java/com/techcourse/model/User.java @@ -1,7 +1,11 @@ package com.techcourse.model; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class User { + private static final Logger log = LoggerFactory.getLogger(User.class); private final Long id; private final String account; private final String password; @@ -32,6 +36,10 @@ public User(String account, String password, String email) { } public boolean checkPassword(String password) { + if (password == null) { + log.info("password not exist"); + return false; + } return this.password.equals(password); } diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index 76aae96189..8ec91c4ce4 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -72,13 +72,12 @@ private HttpResponse login(HttpRequest request) { String account = body.get("account"); String password = body.get("password"); - if (isUserNotExist(account, password)) { + User user = InMemoryUserRepository.findByAccount(account).orElse(null); + if (isUserNotExist(user, password)) { return redirect(new HttpResponseHeader(), "/401.html"); } - User user = InMemoryUserRepository.findByAccount(account).get(); HttpResponseHeader responseHeader = createResponseHeader(request, user); - return redirect(responseHeader, "/index.html"); } @@ -105,17 +104,11 @@ private boolean isNotExistJsessionid(HttpCookie httpCookie) { return httpCookie == null || !httpCookie.hasJsessionId(); } - private boolean isUserNotExist(String account, String password) { - if (account == null) { - log.info("account is required"); - return true; - } - if (password == null) { - log.info("password is required"); + private boolean isUserNotExist(User user, String password) { + if (user == null) { + log.info("user is not exist"); return true; } - - Optional userOptional = InMemoryUserRepository.findByAccount(account); - return userOptional.map(user -> !user.checkPassword(password)).orElse(true); + return !user.checkPassword(password); } } From de48f3f2364b0533359073115c51ea3d6072ad94 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:29:42 +0900 Subject: [PATCH 34/41] =?UTF-8?q?refactor:=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EC=83=81=EC=88=98=EB=A5=BC=20public=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20RequestMessageReader=EC=97=90?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/HttpRequestMessageReader.java | 5 ++++- .../java/org/apache/coyote/http11/http/BaseHttpHeaders.java | 4 ++-- .../apache/coyote/http11/http/request/HttpRequestHeader.java | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java index f684478787..f8c8015759 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestMessageReader.java @@ -1,5 +1,8 @@ package org.apache.coyote.http11; +import static org.apache.coyote.http11.http.BaseHttpHeaders.*; +import static org.apache.coyote.http11.http.request.HttpRequestHeader.*; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -26,7 +29,7 @@ private static int getContentLength(List headers) { return headers.stream() .filter(header -> header.startsWith(HttpHeader.CONTENT_LENGTH.getName())) .findFirst() - .map(header -> Integer.parseInt(header.split(": ")[1].strip())) + .map(header -> Integer.parseInt(header.split(HEADER_DELIMITER)[HEADER_VALUE_INDEX].strip())) .orElse(0); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java index 1d9ab19abf..140df10847 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/BaseHttpHeaders.java @@ -5,8 +5,8 @@ public abstract class BaseHttpHeaders { - protected static final String HEADER_DELIMITER = ": "; - protected static final String HEADER_VALUE_DELIMITER = ", "; + public static final String HEADER_DELIMITER = ": "; + public static final String HEADER_VALUE_DELIMITER = ", "; protected final Map> headers; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java index 1b67193a7e..102668a351 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java @@ -11,8 +11,8 @@ public class HttpRequestHeader extends BaseHttpHeaders { - private static final int HEADER_KEY_INDEX = 0; - private static final int HEADER_VALUE_INDEX = 1; + public static final int HEADER_KEY_INDEX = 0; + public static final int HEADER_VALUE_INDEX = 1; private final HttpCookie httpCookie; From 8372959f48ee6cb38af58b906a9b3e219b0647a0 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:41:59 +0900 Subject: [PATCH 35/41] =?UTF-8?q?refactor:=20JSessionId=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=20=EC=9C=A0=EB=AC=B4=20=ED=99=95=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/http/HttpCookie.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java index 5740c4ecdd..93a58ae953 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/HttpCookie.java @@ -17,13 +17,12 @@ public class HttpCookie { public static HttpCookie from(String cookieValues) { String[] cookies = cookieValues.split(COOKIE_VALUE_DELIMITER); - Map cookie = Arrays.stream(cookies) - .collect(Collectors.toMap( + return Arrays.stream(cookies).collect(Collectors.collectingAndThen( + Collectors.toMap( c -> c.split(KEY_VALUE_DELIMITER)[KEY_INDEX], c -> c.split(KEY_VALUE_DELIMITER)[VALUE_INDEX] - )); - - return new HttpCookie(cookie); + ), + HttpCookie::new)); } private HttpCookie(Map cookie) { @@ -42,7 +41,7 @@ private boolean isNotExistKey(String key) { } public boolean hasJsessionId() { - return cookie.containsKey(JSESSIONID) && getValue(JSESSIONID) != null; + return cookie.containsKey(JSESSIONID) && cookie.get(JSESSIONID) != null; } public String getJsessionid() { From f056c5f7c2410d4f9ceb7a56d09c3db08f34cecf Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:42:43 +0900 Subject: [PATCH 36/41] =?UTF-8?q?refactor:=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=EC=97=90=EC=84=9C=EC=9D=98=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/http/request/HttpRequestHeader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java index 102668a351..9f6b84e448 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/request/HttpRequestHeader.java @@ -30,7 +30,7 @@ private static Map> initHeaders(List headers) { return null; } - LinkedHashMap> result = new LinkedHashMap<>(); + Map> result = new LinkedHashMap<>(); headers.stream() .filter(header -> !header.startsWith(HttpHeader.COOKIE.getName())) .forEach(h -> { From 6f3f4b726450d472a5b9c9b9dc88b4e0165b2ee0 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:45:42 +0900 Subject: [PATCH 37/41] =?UTF-8?q?refactor:=20HttpResponse=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95(public=20->=20private)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/http/response/HttpResponse.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java index 7d104ee6b6..f7d9ec40f1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/http/response/HttpResponse.java @@ -18,12 +18,6 @@ public static HttpResponse notFound(HttpResponseHeader header, HttpResponseBody return new HttpResponse(new HttpResponseStartLine(HTTP_VERSION, HttpStatusCode.NOT_FOUND), header, body); } - public HttpResponse(HttpResponseStartLine startLine, HttpResponseHeader header, HttpResponseBody body) { - this.startLine = startLine; - this.header = header; - this.body = body; - } - public static HttpResponse redirect(HttpResponseHeader responseHeader) { return new HttpResponse(new HttpResponseStartLine(HTTP_VERSION, HttpStatusCode.FOUND), responseHeader, null); } @@ -33,6 +27,12 @@ public static HttpResponse badRequest(HttpResponseHeader httpResponseHeader) { null); } + private HttpResponse(HttpResponseStartLine startLine, HttpResponseHeader header, HttpResponseBody body) { + this.startLine = startLine; + this.header = header; + this.body = body; + } + public String toResponseMessage() { if (body == null) { return createResponseMessageWithoutBody(); From 09e13b44ea3b801f47c0b11c3ee19066e892c0c6 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:50:50 +0900 Subject: [PATCH 38/41] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=EC=9D=98=20=EC=A0=9C=EB=84=A4=EB=A6=AD=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/web/handler/HandlerMapperTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java index 48ab62f5c7..0b96932671 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java @@ -15,7 +15,7 @@ class HandlerMapperTest { void findHandler_WhenRequestRootPage() { String requestPath = "/"; - Class expectedHandler = RootPageHandler.class; + Class expectedHandler = RootPageHandler.class; runTest(requestPath, expectedHandler); } @@ -25,7 +25,7 @@ void findHandler_WhenRequestRootPage() { void findHandler_WhenRequestLogin() { String requestPath = "/login"; - Class expectedHandler = LoginHandler.class; + Class expectedHandler = LoginHandler.class; runTest(requestPath, expectedHandler); } @@ -35,7 +35,7 @@ void findHandler_WhenRequestLogin() { void findHandler_WhenRequestResource() { String requestPath = "/css/test.css"; - Class expectedHandler = ResourceHandler.class; + Class expectedHandler = ResourceHandler.class; runTest(requestPath, expectedHandler); } @@ -43,11 +43,11 @@ void findHandler_WhenRequestResource() { @DisplayName("찾는 핸들러가 없는 경우 404 핸들러를 반환한다.") @Test void findHandler_WhenHandlerNotFound() { - HttpRequest request = new HttpRequest("GET /not-found HTTP/1.1", List.of(), null); + String requestPath = "/notfound"; - Class expectedHandler = NotFoundHandler.class; + Class expectedHandler = NotFoundHandler.class; - runTest("/not-found", expectedHandler); + runTest(requestPath, expectedHandler); } private void runTest(String requestPath, Class expectedHandler) { From b8c5de721ee5226faf00d15e2abe80a3cc949144 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:52:10 +0900 Subject: [PATCH 39/41] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=EC=9D=98=20=ED=83=80=EC=9E=85=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EB=B0=A9=EB=B2=95=20=EC=88=98=EC=A0=95(isInstanceo?= =?UTF-8?q?f=20->=20isExactlyInstanceOf)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/com/techcourse/web/handler/HandlerMapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java index 0b96932671..34b2a9fbed 100644 --- a/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java +++ b/tomcat/src/test/java/com/techcourse/web/handler/HandlerMapperTest.java @@ -55,6 +55,6 @@ private void runTest(String requestPath, Class expectedHandle Handler handler = HandlerMapper.findHandler(request); - assertThat(handler).isInstanceOf(expectedHandler); + assertThat(handler).isExactlyInstanceOf(expectedHandler); } } From a27514dc67605b08089f6ab4c9c98c92bdf23ad7 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 21:54:40 +0900 Subject: [PATCH 40/41] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20html=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/util/ResourceLoaderTest.java | 2 +- tomcat/src/test/resources/index.html | 106 ------------------ 2 files changed, 1 insertion(+), 107 deletions(-) delete mode 100644 tomcat/src/test/resources/index.html diff --git a/tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java b/tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java index 9e59dc33cb..f36d1cc9a2 100644 --- a/tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java +++ b/tomcat/src/test/java/com/techcourse/web/util/ResourceLoaderTest.java @@ -20,7 +20,7 @@ void loadResource() throws IOException { HttpResponseBody responseBody = resourceLoader.loadResource(filePath); - byte[] expected = Files.readAllBytes(Path.of(getClass().getResource(filePath).getPath())); + byte[] expected = Files.readAllBytes(Path.of(getClass().getResource("/static" + filePath).getPath())); assertThat(responseBody).satisfies(body -> { assertThat(body.getContentType()).isEqualTo("text/html"); assertThat(body.getContent()).isEqualTo(expected); diff --git a/tomcat/src/test/resources/index.html b/tomcat/src/test/resources/index.html deleted file mode 100644 index 18ac924d4e..0000000000 --- a/tomcat/src/test/resources/index.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - 대시보드 - - - - - -
-
- -
-
-
-
-

대시보드

- -
-
-
-
- - Bar Chart -
-
-
-
-
-
-
- - Pie Chart -
-
-
-
-
-
-
- -
-
- - - - - - - - From 329bb3c39439192cb63779080e7251b497513ce7 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 12 Sep 2024 22:05:08 +0900 Subject: [PATCH 41/41] =?UTF-8?q?refactor:=20sessionId=EB=A5=BC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=EC=8B=9C=20=ED=95=AD=EC=83=81=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/web/handler/LoginHandler.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java index 8ec91c4ce4..d7fc22272c 100644 --- a/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java +++ b/tomcat/src/main/java/com/techcourse/web/handler/LoginHandler.java @@ -2,14 +2,12 @@ import java.io.IOException; import java.util.Map; -import java.util.Optional; import org.apache.coyote.http11.http.HttpCookie; import org.apache.coyote.http11.http.request.HttpMethod; import org.apache.coyote.http11.http.request.HttpRequest; import org.apache.coyote.http11.http.request.HttpRequestHeader; import org.apache.coyote.http11.http.request.HttpRequestLine; -import org.apache.coyote.http11.http.request.HttpRequestQuery; import org.apache.coyote.http11.http.request.HttpRequestUrl; import org.apache.coyote.http11.http.response.HttpResponse; import org.apache.coyote.http11.http.response.HttpResponseBody; @@ -83,23 +81,13 @@ private HttpResponse login(HttpRequest request) { private HttpResponseHeader createResponseHeader(HttpRequest request, User user) { HttpResponseHeader header = new HttpResponseHeader(); - String sessionId = addJSessionId(request, header); + String sessionId = JsessionIdGenerator.generate(); + header.addJSessionId(sessionId); SessionManager.createSession(sessionId, user); return header; } - private String addJSessionId(HttpRequest request, HttpResponseHeader responseHeader) { - HttpRequestHeader header = request.getHeaders(); - HttpCookie httpCookie = header.getHttpCookie(); - if (isNotExistJsessionid(httpCookie)) { - String sessionId = JsessionIdGenerator.generate(); - responseHeader.addJSessionId(sessionId); - return sessionId; - } - return httpCookie.getJsessionid(); - } - private boolean isNotExistJsessionid(HttpCookie httpCookie) { return httpCookie == null || !httpCookie.hasJsessionId(); }