From 5092d782ec9352fc9e3061d79b10179b2968c5b2 Mon Sep 17 00:00:00 2001 From: 0chil <0@chll.it> Date: Wed, 13 Sep 2023 03:32:11 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EB=A7=A4=ED=95=91=20RequestMapping=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC,=20TargetPath=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/jwp/handler/HelloController.java | 23 ++++++ .../nextstep/jwp/handler/LoginController.java | 14 +++- .../jwp/handler/RegisterController.java | 11 +++ .../apache/coyote/http11/Http11Processor.java | 34 +++------ .../org/apache/coyote/http11/HttpTarget.java | 6 +- .../org/apache/coyote/http11/TargetPath.java | 75 +++++++++++++++++++ .../coyote/http11/controller/Controller.java | 2 + .../http11/controller/FileController.java | 72 +++++------------- .../http11/controller/RequestMapping.java | 26 +++++++ .../apache/coyote/http11/TargetPathTest.java | 44 +++++++++++ 10 files changed, 226 insertions(+), 81 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/jwp/handler/HelloController.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/TargetPath.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/controller/RequestMapping.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/TargetPathTest.java diff --git a/tomcat/src/main/java/nextstep/jwp/handler/HelloController.java b/tomcat/src/main/java/nextstep/jwp/handler/HelloController.java new file mode 100644 index 0000000000..bea0249261 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/handler/HelloController.java @@ -0,0 +1,23 @@ +package nextstep.jwp.handler; + +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.TargetPath; +import org.apache.coyote.http11.controller.Controller; +import org.apache.coyote.http11.header.ContentType; + +public class HelloController implements Controller { + + private static final TargetPath SUPPORTED_PATH = new TargetPath("/"); + + @Override + public boolean supports(HttpRequest httpRequest) { + return httpRequest.getTarget().getPath().equals(SUPPORTED_PATH) && httpRequest.getMethod().isGet(); + } + + @Override + public void handle(HttpRequest httpRequest, HttpResponse httpResponse) { + httpResponse.setContentType(new ContentType("text/html")); + httpResponse.setBody("Hello world!"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/handler/LoginController.java b/tomcat/src/main/java/nextstep/jwp/handler/LoginController.java index f73f26b05f..32695b2694 100644 --- a/tomcat/src/main/java/nextstep/jwp/handler/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/handler/LoginController.java @@ -4,6 +4,7 @@ import nextstep.jwp.model.User; import org.apache.coyote.http11.HttpRequest; import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.TargetPath; import org.apache.coyote.http11.body.FormData; import org.apache.coyote.http11.controller.FileController; import org.apache.coyote.http11.controller.GetAndPostController; @@ -16,12 +17,23 @@ public class LoginController extends GetAndPostController { private static final String SESSION_KEY = "JSESSIONID"; + private static final String SESSION_USER_KEY = "user"; + private static final String UNAUTHORIZED_LOCATION = "/401"; private static final String MAIN_LOCATION = "/index"; - private static final String SESSION_USER_KEY = "user"; + + private static final TargetPath SUPPORTED_PATH = new TargetPath("/login").autoComplete(); private final FileController fileHandler = new FileController(); + @Override + public boolean supports(HttpRequest httpRequest) { + if (httpRequest.getMethod().isGet() || httpRequest.getMethod().isPost()) { + return httpRequest.getTarget().getPath().autoComplete().equals(SUPPORTED_PATH); + } + return false; + } + @Override protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) { boolean isSignedIn = httpRequest.getSession(SESSION_KEY) diff --git a/tomcat/src/main/java/nextstep/jwp/handler/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/handler/RegisterController.java index 9de76a9215..b65deafceb 100644 --- a/tomcat/src/main/java/nextstep/jwp/handler/RegisterController.java +++ b/tomcat/src/main/java/nextstep/jwp/handler/RegisterController.java @@ -4,6 +4,7 @@ import nextstep.jwp.model.User; import org.apache.coyote.http11.HttpRequest; import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.TargetPath; import org.apache.coyote.http11.body.FormData; import org.apache.coyote.http11.controller.FileController; import org.apache.coyote.http11.controller.GetAndPostController; @@ -11,8 +12,18 @@ public class RegisterController extends GetAndPostController { private static final String MAIN_LOCATION = "/index"; + private static final TargetPath SUPPORTED_PATH = new TargetPath("/register").autoComplete(); + private final FileController fileHandler = new FileController(); + @Override + public boolean supports(HttpRequest httpRequest) { + if (httpRequest.getMethod().isGet() || httpRequest.getMethod().isPost()) { + return httpRequest.getTarget().getPath().autoComplete().equals(SUPPORTED_PATH); + } + return false; + } + @Override protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) { fileHandler.handle(httpRequest, httpResponse); 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 6a3de617da..aeafcf8c82 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,11 +1,12 @@ package org.apache.coyote.http11; import nextstep.jwp.exception.UncheckedServletException; +import nextstep.jwp.handler.HelloController; import nextstep.jwp.handler.LoginController; import nextstep.jwp.handler.RegisterController; import org.apache.coyote.Processor; -import org.apache.coyote.http11.controller.Controller; import org.apache.coyote.http11.controller.FileController; +import org.apache.coyote.http11.controller.RequestMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,25 +14,19 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; -import java.util.Map; +import java.util.List; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final Controller DEFAULT_CONTROLLER = new FileController(); - private static final LoginController LOGIN_CONTROLLER = new LoginController(); - private static final RegisterController REGISTER_CONTROLLER = new RegisterController(); - private static final Map PREDEFINED_CONTROLLERS = Map.of( - "/", (request, response) -> response.setBody("Hello world!"), - "/login", LOGIN_CONTROLLER, - "/login.html", LOGIN_CONTROLLER, - "/register", REGISTER_CONTROLLER, - "/register.html", REGISTER_CONTROLLER - ); - private static final String WHITE_SPACE = " "; - private final Socket connection; + private final RequestMapping requestMapping = new RequestMapping(List.of( + new HelloController(), + new LoginController(), + new FileController(), + new RegisterController() + )); public Http11Processor(final Socket connection) { this.connection = connection; @@ -50,7 +45,7 @@ public void process(final Socket connection) { HttpRequest httpRequest = HttpRequest.from(bufferedReader); HttpResponse httpResponse = new HttpResponse(); - handle(httpRequest, httpResponse); + requestMapping.handle(httpRequest, httpResponse); outputStream.write(httpResponse.toLine().getBytes()); outputStream.flush(); @@ -58,13 +53,4 @@ public void process(final Socket connection) { log.error(e.getMessage(), e); } } - - private void handle(final HttpRequest httpRequest, final HttpResponse httpResponse) { - if (PREDEFINED_CONTROLLERS.containsKey(httpRequest.getTarget().getPath())) { - PREDEFINED_CONTROLLERS.get(httpRequest.getTarget().getPath()) - .handle(httpRequest, httpResponse); - return; - } - DEFAULT_CONTROLLER.handle(httpRequest, httpResponse); - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpTarget.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpTarget.java index 21fd4591ab..49bf011714 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpTarget.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpTarget.java @@ -10,11 +10,11 @@ public class HttpTarget { private static final String EMPTY_VALUE = ""; - private final String path; + private final TargetPath path; private final Map queries; public HttpTarget(final String target) { - this.path = removeQueriesFrom(target); + this.path = new TargetPath(removeQueriesFrom(target)); this.queries = getQueriesFrom(target); } @@ -40,7 +40,7 @@ private static String removeQueriesFrom(final String target) { return target.substring(0, queryStringStart); } - public String getPath() { + public TargetPath getPath() { return path; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/TargetPath.java b/tomcat/src/main/java/org/apache/coyote/http11/TargetPath.java new file mode 100644 index 0000000000..f0d6bd1763 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/TargetPath.java @@ -0,0 +1,75 @@ +package org.apache.coyote.http11; + +import java.io.File; +import java.net.URL; +import java.util.Objects; + +public class TargetPath { + + private static final String DEFAULT_PAGE = "index"; + private static final String DEFAULT_EXTENSION = ".html"; + private static final String STATIC_RESOURCES_PATH = "static"; + + private final String path; + + public TargetPath(String path) { + this.path = path; + } + + private boolean endsWithRoot(String path) { + return path.endsWith("/"); + } + + private boolean hasExtensionIn(String path) { + int lastSlash = path.lastIndexOf('/'); + String lastSubPath = path.substring(lastSlash + 1); + if (lastSubPath.contains(".")) { + return lastSubPath.lastIndexOf('.') > 0; + } + return false; + } + + public String getPath() { + return path; + } + + public TargetPath autoComplete() { + if (endsWithRoot(path)) { + return new TargetPath(path + DEFAULT_PAGE + DEFAULT_EXTENSION); + } + if (!hasExtensionIn(path)) { + return new TargetPath(path + DEFAULT_EXTENSION); + } + return new TargetPath(path); + } + + public String getExtension() { + int lastSlash = autoComplete().path.lastIndexOf('/'); + String lastSubPath = autoComplete().path.substring(lastSlash + 1); + int extensionStart = lastSubPath.lastIndexOf('.'); + return lastSubPath.substring(extensionStart + 1); + } + + public File asStaticFile() { + URL resource = getClass().getClassLoader().getResource(STATIC_RESOURCES_PATH + autoComplete().path); + if (resource == null) { + return new File("/"); + } + return new File(resource.getPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TargetPath that = (TargetPath) o; + + return Objects.equals(path, that.path); + } + + @Override + public int hashCode() { + return path != null ? path.hashCode() : 0; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java index d22b4a145e..6ea900c071 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java @@ -5,5 +5,7 @@ public interface Controller { + boolean supports(HttpRequest httpRequest); + void handle(HttpRequest httpRequest, HttpResponse httpResponse); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/FileController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/FileController.java index ea70729232..1f86ecf066 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/controller/FileController.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/FileController.java @@ -1,86 +1,52 @@ package org.apache.coyote.http11.controller; +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.TargetPath; +import org.apache.coyote.http11.header.ContentType; + import java.io.File; import java.io.IOException; -import java.net.URL; import java.nio.file.Files; import java.util.Map; -import java.util.Objects; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.header.ContentType; public class FileController implements Controller { - private static final ClassLoader CLASS_LOADER = FileController.class.getClassLoader(); - - private static final String STATIC_RESOURCES_PATH = "static"; - private static final String DEFAULT_INDEX = "index"; - private static final String DEFAULT_EXTENSION = ".html"; - private static final Map CONTENT_TYPES_BY_EXTENSION = Map.of( "html", new ContentType("text/html"), "css", new ContentType("text/css") ); + @Override + public boolean supports(final HttpRequest httpRequest) { + TargetPath targetPath = httpRequest.getTarget().getPath().autoComplete(); + return targetPath.asStaticFile().exists(); + } + @Override public void handle(final HttpRequest httpRequest, final HttpResponse httpResponse) { try { - String targetPath = fillExtensionIfDoesNotExist(httpRequest.getTarget().getPath()); + TargetPath targetPath = httpRequest.getTarget().getPath().autoComplete(); + File staticFile = targetPath.asStaticFile(); + validatePresenceOf(staticFile); - File resource = getResourceFileFrom(targetPath); - String body = Files.readString(resource.toPath()); - httpResponse.setContentType(getContentTypeFrom(targetPath)); - httpResponse.setBody(body); + httpResponse.setContentType(getContentTypeBy(targetPath.getExtension())); + httpResponse.setBody(Files.readString(staticFile.toPath())); } catch (IOException exception) { throw new IllegalArgumentException("파일 읽기에 실패했습니다"); } } - private String fillExtensionIfDoesNotExist(String target) { - if (Objects.isNull(getExtensionOf(target))) { - return target + DEFAULT_EXTENSION; - } - return target; - } - - private ContentType getContentTypeFrom(final String target) { - String extension = getExtensionOf(target); + private ContentType getContentTypeBy(final String extension) { if (CONTENT_TYPES_BY_EXTENSION.containsKey(extension)) { return CONTENT_TYPES_BY_EXTENSION.get(extension); } return new ContentType("text/html"); } - private File getResourceFileFrom(final String target) { - String relativePath = getRelativePathFrom(target); - URL resource = CLASS_LOADER.getResource(relativePath); - validatePresenceOf(resource); - - String absolutePath = resource.getPath(); - return new File(absolutePath); - } - - private String getRelativePathFrom(final String target) { - if (target.endsWith("/")) { - return STATIC_RESOURCES_PATH + target + DEFAULT_INDEX; - } - return STATIC_RESOURCES_PATH + target; - } - - private void validatePresenceOf(final URL resource) { - if (Objects.isNull(resource)) { + private void validatePresenceOf(final File file) { + if (!file.exists()) { throw new IllegalArgumentException("리소스를 찾지 못했습니다."); } } - - private String getExtensionOf(String path) { - int lastSlash = path.lastIndexOf('/'); - String lastSubPath = path.substring(lastSlash + 1); - if (lastSubPath.contains(".")) { - int extensionStart = lastSubPath.lastIndexOf('.'); - return lastSubPath.substring(extensionStart + 1); - } - return null; - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/RequestMapping.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/RequestMapping.java new file mode 100644 index 0000000000..a8815db8ba --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/RequestMapping.java @@ -0,0 +1,26 @@ +package org.apache.coyote.http11.controller; + +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; + +import java.util.List; + +public class RequestMapping { + + private final List controllers; + + public RequestMapping(List controllers) { + this.controllers = controllers; + } + + public void handle(final HttpRequest httpRequest, final HttpResponse httpResponse) { + getControllerFor(httpRequest).handle(httpRequest, httpResponse); + } + + private Controller getControllerFor(final HttpRequest httpRequest) { + return controllers.stream() + .filter(it -> it.supports(httpRequest)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("적절한 컨트롤러를 찾지 못했습니다")); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/TargetPathTest.java b/tomcat/src/test/java/org/apache/coyote/http11/TargetPathTest.java new file mode 100644 index 0000000000..4101e3bfb5 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/TargetPathTest.java @@ -0,0 +1,44 @@ +package org.apache.coyote.http11; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class TargetPathTest { + + @Test + void 자동완성은_확장자가_없으면_기본값_html을_사용한다() { + assertThat(new TargetPath("/index").autoComplete().getPath()).isEqualTo("/index.html"); + } + + @Test + void 자동완성은_최상단_루트인_경우_기본_페이지를_사용한다() { + assertThat(new TargetPath("/").autoComplete().getPath()).isEqualTo("/index.html"); + assertThat(new TargetPath("/hi/hello/").autoComplete().getPath()).isEqualTo("/hi/hello/index.html"); + } + + @Test + void 다른_경로와_비교한다() { + TargetPath path = new TargetPath("/index"); + + assertThat(path).isEqualTo(new TargetPath("/index")); + assertThat(path.autoComplete()).isEqualTo(new TargetPath("/").autoComplete()); + } + + @Test + void 자동완성_후_다른_경로와_비교한다() { + TargetPath pathWithoutExtension = new TargetPath("/index"); + + assertThat(pathWithoutExtension.autoComplete()).isEqualTo(new TargetPath("/index.html").autoComplete()); + assertThat(pathWithoutExtension.autoComplete()).isEqualTo(new TargetPath("/index").autoComplete()); + } + + @Test + void 확장자를_가져온다() { + assertThat(new TargetPath("/hi.hello").getExtension()).isEqualTo("hello"); + } +}