From ffae83435dcbdf166b000921374d7142697bd00d Mon Sep 17 00:00:00 2001 From: yenawee Date: Tue, 12 Sep 2023 22:13:58 +0900 Subject: [PATCH] =?UTF-8?q?[=ED=86=B0=EC=BA=A3=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20-=203,=204=20=EB=8B=A8=EA=B3=84]=20-=20?= =?UTF-8?q?=EC=A0=9C=EB=82=98(=EC=9C=84=EC=98=88=EB=82=98)=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=A0=9C=EC=B6=9C=ED=95=A9=EB=8B=88=EB=8B=A4=20(#4?= =?UTF-8?q?58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 기능 요구사항 업데이트 * refactor: ContentType svg 추가 * refactor: 문자열 상수화. java convention * refactor: HttpStatus 추가 * feat: 정적파일 못 읽어올때 404 페이지로 리턴 * feat: controller interface 구현 * feat: uri 에 따른 컨트롤러 생성 * feat: RequestMapping 객체로 분기 처리 위임 * feat: HttpResponse 정적팩터리메소드 추가 * feat: HttpResponse 빌더패턴 삭제. setter 로 구현 * refactor: RequestMapping 분기 처리 제거 * refactor: SessionManager 동시성 컬렉션 사용 * feat: Executors 로 ThreadPool 적용 * fix: LoginController NPE 처리 * fix: Controller 싱글톤 적용 * feat: 404, 500 리다이렉션 처리 수정 * fix: test 깨지는 것 수정 * refactor: Header 상수화 * refactor: sonarlint 분석 반영 * refactor: LoginController if/else 분기 수정 * refactor: 문자열 상수화 --- README.md | 11 ++ .../jwp/controller/AbstractController.java | 37 +++++ .../jwp/controller/DefaultController.java | 36 +++++ .../jwp/controller/LoginController.java | 92 +++++++++++ .../jwp/controller/RegisterController.java | 51 ++++++ .../jwp/controller/RootController.java | 22 +++ .../apache/catalina/connector/Connector.java | 16 +- .../catalina/manager/SessionManager.java | 12 +- .../java/org/apache/coyote/Controller.java | 8 + .../org/apache/coyote/RequestMapping.java | 36 +++++ .../org/apache/coyote/common/ContentType.java | 5 +- .../org/apache/coyote/common/Headers.java | 16 ++ .../org/apache/coyote/common/HttpCookie.java | 31 ++-- .../apache/coyote/http11/Http11Processor.java | 147 ++---------------- .../coyote/request/HttpRequestBody.java | 1 + .../coyote/request/HttpRequestHeader.java | 17 +- .../coyote/request/HttpRequestLine.java | 12 +- .../org/apache/coyote/request/RequestUri.java | 1 - .../apache/coyote/response/HttpResponse.java | 86 +++++----- .../apache/coyote/response/HttpStatus.java | 4 +- .../apache/coyote/response/StatusLine.java | 8 +- .../org/apache/coyote/utils/FileUtils.java | 11 +- .../coyote/http11/Http11ProcessorTest.java | 23 ++- 23 files changed, 454 insertions(+), 229 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/DefaultController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/LoginController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/RootController.java create mode 100644 tomcat/src/main/java/org/apache/coyote/Controller.java create mode 100644 tomcat/src/main/java/org/apache/coyote/RequestMapping.java create mode 100644 tomcat/src/main/java/org/apache/coyote/common/Headers.java diff --git a/README.md b/README.md index 9d01d7c0d1..378414cb10 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,14 @@ - [x] Login 도 POST 로 변경 - [x] Cookie에 JSESSIONID 값 저장하기 - [x] Session 구현하기 + +### 3단계 - 리팩터링 + +- [x] HttpRequest 클래스 구현하기 +- [x] HttpResponse 클래스 구현하기 +- [x] Controller 인터페이스 추가하기 + +### 4단계 - 동시성 확장하기 + +- [x] Executors로 Thread Pool 적용 +- [x] 동시성 컬렉션 사용하기 diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java new file mode 100644 index 0000000000..1b67118737 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -0,0 +1,37 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.Controller; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.coyote.common.ContentType.HTML; +import static org.apache.coyote.common.Headers.LOCATION; +import static org.apache.coyote.response.HttpStatus.FOUND; + +public class AbstractController implements Controller { + + protected static final Logger log = LoggerFactory.getLogger(AbstractController.class); + + @Override + public void service(HttpRequest request, HttpResponse response) throws Exception { + + try { + if (request.getRequestLine().isGetMethod()) { + doGet(request, response); + } + if (request.getRequestLine().isPostMethod()) { + doPost(request, response); + } + } catch (Exception e) { + response.setStatus(FOUND); + response.setContentType(HTML); + response.addHeader(LOCATION, "/500.html"); + } + } + + protected void doPost(HttpRequest request, HttpResponse response) throws Exception { /* NOOP */ } + + protected void doGet(HttpRequest request, HttpResponse response) throws Exception { /* NOOP */ } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/DefaultController.java b/tomcat/src/main/java/nextstep/jwp/controller/DefaultController.java new file mode 100644 index 0000000000..b33e02a82c --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/DefaultController.java @@ -0,0 +1,36 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.Controller; +import org.apache.coyote.common.ContentType; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.RequestUri; +import org.apache.coyote.response.HttpResponse; +import org.apache.coyote.utils.FileUtils; + +import static org.apache.coyote.common.ContentType.HTML; +import static org.apache.coyote.common.Headers.LOCATION; +import static org.apache.coyote.response.HttpStatus.FOUND; + +public class DefaultController extends AbstractController { + + private static final Controller INSTANCE = new DefaultController(); + + public static Controller getInstance() { + return INSTANCE; + } + + @Override + protected void doGet(HttpRequest request, HttpResponse response) throws Exception { + RequestUri requestUri = request.getRequestLine().getRequestUri(); + + try { + FileUtils.readFile(requestUri.getPath()); + response.setContentType(ContentType.from(requestUri.getExtension())); + response.setBody(FileUtils.readFile(requestUri.getPath())); + } catch (NullPointerException e) { + response.setStatus(FOUND); + response.setContentType(HTML); + response.addHeader(LOCATION, "/404.html"); + } + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java new file mode 100644 index 0000000000..896be89fe8 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -0,0 +1,92 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.catalina.manager.SessionManager; +import org.apache.coyote.Controller; +import org.apache.coyote.common.HttpCookie; +import org.apache.coyote.common.Session; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.HttpRequestBody; +import org.apache.coyote.response.HttpResponse; +import org.apache.coyote.utils.FileUtils; + +import java.util.Optional; + +import static org.apache.coyote.common.ContentType.HTML; +import static org.apache.coyote.common.Headers.JSESSIONID; +import static org.apache.coyote.common.Headers.LOCATION; +import static org.apache.coyote.response.HttpStatus.FOUND; +import static org.apache.coyote.response.HttpStatus.OK; + +public class LoginController extends AbstractController { + + private static final Controller INSTANCE = new LoginController(); + + public static Controller getInstance() { + return INSTANCE; + } + + @Override + protected void doPost(HttpRequest request, HttpResponse response) throws Exception { + HttpRequestBody httpRequestBody = request.getRequestBody(); + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); + + Optional user = InMemoryUserRepository.findByAccount(account); + + if (user.isPresent()) { + user.filter(userInfo -> userInfo.checkPassword(password)) + .ifPresentOrElse(registerUser -> { + log.info(registerUser.toString()); + loginSuccess(registerUser, response); + } + , () -> { + log.warn("비밀번호가 틀렸습니다"); + loginFail(response); + }); + return; + } + log.warn("미가입회원입니다"); + loginFail(response); + } + + private void loginSuccess(User user, HttpResponse response) { + Session session = new Session(); + session.setAttribute("user", user); + SessionManager.add(session); + + HttpCookie cookie = HttpCookie.of(JSESSIONID + "=" + session.getId()); + + response.setStatus(FOUND); + response.setContentType(HTML); + response.addHeader(LOCATION, "/index.html"); + response.setCookie(cookie); + } + + private void loginFail(HttpResponse response) { + response.setStatus(FOUND); + response.setContentType(HTML); + response.addHeader(LOCATION, "/401.html"); + } + + + @Override + protected void doGet(HttpRequest request, HttpResponse response) throws Exception { + HttpCookie cookie = request.getRequestHeader().getCookie(); + + String jsessionid = cookie.getValue(JSESSIONID); + Optional session = SessionManager.findSession(jsessionid); + + if (session.isPresent()) { + response.setStatus(FOUND); + response.addHeader(LOCATION, "/index.html"); + response.setContentType(HTML); + return; + } + + response.setStatus(OK); + response.setContentType(HTML); + response.setBody(FileUtils.readFile("/login.html")); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java new file mode 100644 index 0000000000..b085c72d60 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -0,0 +1,51 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.coyote.Controller; +import org.apache.coyote.common.ContentType; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.HttpRequestBody; +import org.apache.coyote.request.RequestUri; +import org.apache.coyote.response.HttpResponse; +import org.apache.coyote.utils.FileUtils; + +import static org.apache.coyote.common.ContentType.HTML; +import static org.apache.coyote.common.Headers.LOCATION; +import static org.apache.coyote.response.HttpStatus.FOUND; + +public class RegisterController extends AbstractController { + + private static final Controller INSTANCE = new RegisterController(); + + public static Controller getInstance() { + return INSTANCE; + } + + @Override + protected void doPost(HttpRequest request, HttpResponse response) throws Exception { + HttpRequestBody httpRequestBody = request.getRequestBody(); + + String account = httpRequestBody.getValue("account"); + String password = httpRequestBody.getValue("password"); + String email = httpRequestBody.getValue("email"); + + if (InMemoryUserRepository.findByAccount(account).isPresent()) { + throw new IllegalArgumentException("중복 ID 입니다"); + } + + InMemoryUserRepository.save(new User(account, password, email)); + + response.setStatus(FOUND); + response.setContentType(HTML); + response.addHeader(LOCATION, "/index.html"); + } + + @Override + protected void doGet(HttpRequest request, HttpResponse response) throws Exception { + RequestUri requestUri = request.getRequestLine().getRequestUri(); + + response.setContentType(ContentType.from(requestUri.getExtension())); + response.setBody(FileUtils.readFile("/register.html")); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RootController.java b/tomcat/src/main/java/nextstep/jwp/controller/RootController.java new file mode 100644 index 0000000000..343f824a57 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/RootController.java @@ -0,0 +1,22 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.Controller; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +import static org.apache.coyote.common.ContentType.HTML; + +public class RootController extends AbstractController { + + private static final Controller INSTANCE = new RootController(); + + public static Controller getInstance() { + return INSTANCE; + } + + @Override + protected void doGet(HttpRequest request, HttpResponse response) throws Exception { + response.setContentType(HTML); + response.setBody("Hello world!"); + } +} 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..4205e2b7d1 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -4,10 +4,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.awt.*; import java.io.IOException; import java.io.UncheckedIOException; import java.net.ServerSocket; import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class Connector implements Runnable { @@ -15,8 +18,11 @@ public class Connector implements Runnable { private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_ACCEPT_COUNT = 100; + private static final int DEFAULT_MAX_THREADS = 250; private final ServerSocket serverSocket; + private final ExecutorService executorService; + private boolean stopped; public Connector() { @@ -26,6 +32,13 @@ public Connector() { public Connector(final int port, final int acceptCount) { this.serverSocket = createServerSocket(port, acceptCount); this.stopped = false; + this.executorService = Executors.newFixedThreadPool(DEFAULT_MAX_THREADS); + } + + public Connector(final Container container, final int port, final int acceptCount, final int maxThreads) { + this.serverSocket = createServerSocket(port, acceptCount); + this.stopped = false; + this.executorService = Executors.newFixedThreadPool(maxThreads); } private ServerSocket createServerSocket(final int port, final int acceptCount) { @@ -67,13 +80,14 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + executorService.execute(processor); } public void stop() { stopped = true; try { serverSocket.close(); + executorService.shutdown(); } catch (IOException e) { log.error(e.getMessage(), e); } diff --git a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java index 894d6f93e4..7e5fdd1f68 100644 --- a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -2,12 +2,13 @@ import org.apache.coyote.common.Session; -import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; public class SessionManager { - private static final Map SESSIONS = new HashMap<>(); + private static final Map SESSIONS = new ConcurrentHashMap<>(); private SessionManager() { } @@ -16,8 +17,11 @@ public static void add(final Session session) { SESSIONS.put(session.getId(), session); } - public static Session findSession(final String id) { - return SESSIONS.get(id); + public static Optional findSession(final String id) { + if (id == null) { + return Optional.empty(); + } + return Optional.ofNullable(SESSIONS.get(id)); } public void remove(final String id) { diff --git a/tomcat/src/main/java/org/apache/coyote/Controller.java b/tomcat/src/main/java/org/apache/coyote/Controller.java new file mode 100644 index 0000000000..140c89eb96 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/Controller.java @@ -0,0 +1,8 @@ +package org.apache.coyote; + +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.response.HttpResponse; + +public interface Controller { + void service(HttpRequest request, HttpResponse response) throws Exception; +} diff --git a/tomcat/src/main/java/org/apache/coyote/RequestMapping.java b/tomcat/src/main/java/org/apache/coyote/RequestMapping.java new file mode 100644 index 0000000000..73b64adcf4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/RequestMapping.java @@ -0,0 +1,36 @@ +package org.apache.coyote; + +import nextstep.jwp.controller.DefaultController; +import nextstep.jwp.controller.LoginController; +import nextstep.jwp.controller.RegisterController; +import nextstep.jwp.controller.RootController; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.RequestUri; + +import java.util.Map; + +public class RequestMapping { + + private static final Map mappers; + + static { + mappers = Map.of( + "/", RootController.getInstance(), + "/login", LoginController.getInstance(), + "/register", RegisterController.getInstance()); + } + + + public Controller getController(HttpRequest request) { + RequestUri requestUri = request.getRequestLine().getRequestUri(); + + String path = requestUri.getPath(); + + return mappers.entrySet() + .stream() + .filter(entry -> path.equals(entry.getKey())) + .map(Map.Entry::getValue) + .findFirst() + .orElse(DefaultController.getInstance()); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/common/ContentType.java b/tomcat/src/main/java/org/apache/coyote/common/ContentType.java index 0745b2c857..d1875eb861 100644 --- a/tomcat/src/main/java/org/apache/coyote/common/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/common/ContentType.java @@ -6,7 +6,8 @@ public enum ContentType { HTML("html", "text/html"), CSS("css", "text/css"), JAVASCRIPT("js", "text/javascript"), - FAVICON("ico", "image/x-icon"); + FAVICON("ico", "image/x-icon"), + SVG("svg", "image/svg+xml"); private final String extension; private final String type; @@ -18,7 +19,7 @@ public enum ContentType { public static ContentType from(String extension) { return Arrays.stream(values()) - .filter(it -> it.getExtension().equals(extension)) + .filter(contentType -> contentType.getExtension().equals(extension)) .findFirst() .orElse(HTML); } diff --git a/tomcat/src/main/java/org/apache/coyote/common/Headers.java b/tomcat/src/main/java/org/apache/coyote/common/Headers.java new file mode 100644 index 0000000000..03e1325653 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/common/Headers.java @@ -0,0 +1,16 @@ +package org.apache.coyote.common; + +public class Headers { + + private Headers() { + } + + public static final String LOCATION = "Location"; + public static final String JSESSIONID = "JSESSIONID"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String SET_COOKIE = "Set-Cookie"; + public static final String CHARSET_UTF8 = ";charset=utf-8"; + public static final String COOKIE = "Cookie"; + public static final String CRLF = "\r\n"; +} diff --git a/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java index 9910d8f67f..145b7f2680 100644 --- a/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/common/HttpCookie.java @@ -8,6 +8,11 @@ public class HttpCookie { + private static final String KEY_VALUE_SEPERATOR = "="; + private static final String COOKIE_SEPERATOR = "; "; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + private final Map cookie; public HttpCookie(Map cookie) { @@ -18,20 +23,24 @@ public static HttpCookie of(String cookieString) { Map cookies = new LinkedHashMap<>(); if (StringUtils.isNotBlank(cookieString)) { - String[] cookiePairs = cookieString.split("; "); - for (String cookiePair : cookiePairs) { - String[] parts = cookiePair.split("="); - if (parts.length == 2) { - String name = parts[0]; - String value = parts[1]; - cookies.put(name, value); - } - } + String[] cookiePairs = cookieString.split(COOKIE_SEPERATOR); + generateCookie(cookies, cookiePairs); } return new HttpCookie(cookies); } + private static void generateCookie(Map cookies, String[] cookiePairs) { + for (String cookiePair : cookiePairs) { + String[] parts = cookiePair.split(KEY_VALUE_SEPERATOR); + if (parts.length == 2) { + String name = parts[KEY_INDEX]; + String value = parts[VALUE_INDEX]; + cookies.put(name, value); + } + } + } + public String getValue(String key) { return cookie.get(key); } @@ -39,8 +48,8 @@ public String getValue(String key) { public String convertToHeader() { return cookie.entrySet() .stream() - .map(entry -> entry.getKey() + "=" + entry.getValue()) - .collect(Collectors.joining("; ")); + .map(entry -> entry.getKey() + KEY_VALUE_SEPERATOR + entry.getValue()) + .collect(Collectors.joining(COOKIE_SEPERATOR)); } @Override 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 7bd72c524d..6fbe73b0ef 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,17 +1,14 @@ package org.apache.coyote.http11; -import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.exception.UncheckedServletException; -import nextstep.jwp.model.User; -import org.apache.catalina.manager.SessionManager; +import org.apache.coyote.Controller; import org.apache.coyote.Processor; -import org.apache.coyote.common.ContentType; -import org.apache.coyote.common.HttpCookie; -import org.apache.coyote.common.Session; -import org.apache.coyote.request.*; +import org.apache.coyote.RequestMapping; +import org.apache.coyote.request.HttpRequest; +import org.apache.coyote.request.HttpRequestBody; +import org.apache.coyote.request.HttpRequestHeader; +import org.apache.coyote.request.HttpRequestLine; import org.apache.coyote.response.HttpResponse; -import org.apache.coyote.utils.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,11 +18,6 @@ import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.Objects; -import java.util.Optional; - -import static org.apache.coyote.common.ContentType.HTML; -import static org.apache.coyote.response.HttpStatus.FOUND; -import static org.apache.coyote.response.HttpStatus.OK; public class Http11Processor implements Runnable, Processor { @@ -49,42 +41,17 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream(); final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - HttpRequest httpRequest = generateHttpRequest(reader); - - RequestUri requestUri = httpRequest.getRequestLine().getRequestUri(); - - String path = requestUri.getPath(); - - HttpResponse response = null; - - if (path.equals("/")) { - response = new HttpResponse.Builder() - .contentType(HTML) - .body("Hello world!") - .build(); - } else if (path.equals("/login") && httpRequest.getRequestLine().isGetMethod()) { - response = getLoginHttpResponse(httpRequest); - } else if (path.equals("/login") && httpRequest.getRequestLine().isPostMethod()) { - response = postLoginHttpResponse(httpRequest); - } else if (path.equals("/register") && httpRequest.getRequestLine().isGetMethod()) { - response = new HttpResponse.Builder() - .contentType(ContentType.from(requestUri.getExtension())) - .body(FileUtils.readFile("/register.html")) - .build(); - } else if (path.equals("/register") && httpRequest.getRequestLine().isPostMethod()) { - response = postRegisterHttpResponse(httpRequest); - } else { - response = new HttpResponse.Builder() - .contentType(ContentType.from(requestUri.getExtension())) - .body(FileUtils.readFile(requestUri.getPath())) - .build(); - } + HttpRequest request = generateHttpRequest(reader); + + HttpResponse response = HttpResponse.createDefaultResponse(); + Controller controller = new RequestMapping().getController(request); + + controller.service(request, response); outputStream.write(response.getResponse().getBytes()); outputStream.flush(); - } catch (IOException | - UncheckedServletException e) { + } catch (Exception e) { log.error(e.getMessage(), e); } } @@ -122,92 +89,4 @@ private HttpRequestHeader getRequestHeader(BufferedReader reader) throws IOExcep return HttpRequestHeader.from(requestHeader.toString()); } - - private HttpResponse getLoginHttpResponse(HttpRequest httpRequest) throws IOException { - HttpCookie cookie = httpRequest.getRequestHeader().getCookie(); - - String jsessionid = cookie.getValue("JSESSIONID"); - Session session = SessionManager.findSession(jsessionid); - - if (session != null) { - return new HttpResponse.Builder() - .status(FOUND) - .header("Location", "/index.html") - .contentType(HTML) - .build(); - } - - return new HttpResponse.Builder() - .status(OK) - .contentType(HTML) - .body(FileUtils.readFile("/login.html")) - .build(); - } - - private HttpResponse postLoginHttpResponse(HttpRequest httpRequest) { - HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); - String account = httpRequestBody.getValue("account"); - String password = httpRequestBody.getValue("password"); - - Optional user = InMemoryUserRepository.findByAccount(account); - - if (user.isPresent() && user.get().checkPassword(password)) { - log.info(user.toString()); - - return loginSuccess(user); - - } else if (user.isPresent()) { - log.warn("비밀번호가 틀렸습니다"); - - return loginFail(); - - } else { - log.warn("미가입회원입니다"); - - return loginFail(); - } - } - - private HttpResponse loginSuccess(Optional user) { - Session session = new Session(); - session.setAttribute("user", user); - SessionManager.add(session); - - HttpCookie cookie = HttpCookie.of("JSESSIONID=" + session.getId()); - - return new HttpResponse.Builder() - .status(FOUND) - .contentType(HTML) - .header("Location", "/index.html") - .setCookie(cookie) - .build(); - } - - private HttpResponse loginFail() { - return new HttpResponse.Builder() - .status(FOUND) - .contentType(HTML) - .header("Location", "/401.html") - .build(); - } - - private HttpResponse postRegisterHttpResponse(HttpRequest httpRequest) { - HttpRequestBody httpRequestBody = httpRequest.getRequestBody(); - - String account = httpRequestBody.getValue("account"); - String password = httpRequestBody.getValue("password"); - String email = httpRequestBody.getValue("email"); - - if (InMemoryUserRepository.findByAccount(account).isPresent()) { - throw new IllegalArgumentException("중복 ID 입니다"); - } - - InMemoryUserRepository.save(new User(account, password, email)); - - return new HttpResponse.Builder() - .status(FOUND) - .contentType(HTML) - .header("Location", "/index.html") - .build(); - } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java index 52f6c109c3..c45d7a6f50 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestBody.java @@ -7,6 +7,7 @@ public class HttpRequestBody { private static final String BODY_SEPERATOR = "="; private static final String SEPERATOR = "&"; + private final Map body; private HttpRequestBody(Map body) { diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java index 941c64500a..7292ab52be 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestHeader.java @@ -7,10 +7,13 @@ import java.util.List; import java.util.Map; +import static org.apache.coyote.common.Headers.*; + public class HttpRequestHeader { - private static final String SEPERATOR = "\r\n"; private static final String HEADER_SEPERATOR = ": "; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; private final Map headers; @@ -20,14 +23,14 @@ private HttpRequestHeader(Map headers) { public static HttpRequestHeader from(String requestHeader) { Map headers = new LinkedHashMap<>(); - String[] splitedLines = requestHeader.split(SEPERATOR); + String[] splitedLines = requestHeader.split(CRLF); List splited = Arrays.asList(splitedLines); for (String line : splited) { String[] parts = line.split(HEADER_SEPERATOR); if (parts.length == 2) { - String key = parts[0].trim(); - String value = parts[1].trim(); + String key = parts[KEY_INDEX].trim(); + String value = parts[VALUE_INDEX].trim(); headers.put(key, value); } } @@ -35,16 +38,16 @@ public static HttpRequestHeader from(String requestHeader) { } public int getContentLength() { - String contentLength = headers.get("Content-Length"); + String contentLength = headers.get(CONTENT_LENGTH); try { return Integer.parseInt(contentLength); } catch (NumberFormatException e) { + return KEY_INDEX; } - return 0; } public HttpCookie getCookie() { - String cookie = headers.get("Cookie"); + String cookie = headers.get(COOKIE); return HttpCookie.of(cookie); } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java index 73e65d503f..1d941920a9 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/request/HttpRequestLine.java @@ -3,7 +3,12 @@ public class HttpRequestLine { + private static final String GET = "GET"; + private static final String POST = "POST"; private static final String SEPERATOR = " "; + private static final int METHOD_INDEX = 0; + private static final int REQUEST_URI_INDEX = 1; + private static final int VERSION_INDEX = 2; private final String method; private final RequestUri requestUri; @@ -18,7 +23,8 @@ public HttpRequestLine(String method, RequestUri requestUri, String version) { public static HttpRequestLine from(String requestLine) { String[] splitedLines = requestLine.split(SEPERATOR); - return new HttpRequestLine(splitedLines[0], RequestUri.from(splitedLines[1]), splitedLines[2]); + return new HttpRequestLine(splitedLines[METHOD_INDEX], + RequestUri.from(splitedLines[REQUEST_URI_INDEX]), splitedLines[VERSION_INDEX]); } public RequestUri getRequestUri() { @@ -26,10 +32,10 @@ public RequestUri getRequestUri() { } public boolean isGetMethod() { - return method.equals("GET"); + return method.equals(GET); } public boolean isPostMethod() { - return method.equals("POST"); + return method.equals(POST); } } diff --git a/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java b/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java index a39949709c..fc12ba43c1 100644 --- a/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java +++ b/tomcat/src/main/java/org/apache/coyote/request/RequestUri.java @@ -6,7 +6,6 @@ public class RequestUri { - private static final String SEPERATOR = "\\?"; private static final String QUERY_SEPARATOR = "&"; private static final String KEY_VALUE_SEPEARTOR = "="; private static final String DOT = "."; diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java index 416617a2f7..15d518f76a 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpResponse.java @@ -6,63 +6,53 @@ import java.util.LinkedHashMap; import java.util.Map; +import static org.apache.coyote.common.Headers.*; + public class HttpResponse { + private static final String DEFAULT_VERSION = "HTTP/1.1"; + private final StatusLine statusLine; private final HttpResponseHeader headers; - private final String body; - private HttpResponse(Builder builder) { - this.statusLine = new StatusLine(builder.version, builder.httpStatus); - this.headers = new HttpResponseHeader(builder.headers); - this.body = builder.body; + private String body; + + private HttpResponse(StatusLine statusLine, HttpResponseHeader headers, String body) { + this.statusLine = statusLine; + this.headers = headers; + this.body = body; + } + + public static HttpResponse createDefaultResponse() { + HttpStatus httpStatus = HttpStatus.OK; + Map headers = new LinkedHashMap<>(); + String body = ""; + + return new HttpResponse(new StatusLine(DEFAULT_VERSION, httpStatus), new HttpResponseHeader(headers), body); } public String getResponse() { - return statusLine.getStatusLine() + "\r\n" + headers.getResponseHeader() + "\r\n" + body; + return statusLine.getStatusLine() + CRLF + headers.getResponseHeader() + CRLF + body; + } + + public void setContentType(ContentType contentType) { + this.headers.add(CONTENT_TYPE, contentType.getType() + CHARSET_UTF8); + } + + public void setBody(String body) { + addHeader(CONTENT_LENGTH, String.valueOf(body.length())); + this.body = body; + } + + public void setStatus(HttpStatus status) { + this.statusLine.setStatus(status); + } + + public void addHeader(String key, String value) { + this.headers.add(key, value); } - public static class Builder { - private final String version; - private final Map headers; - private HttpStatus httpStatus; - private String body; - - public Builder() { - this.version = "HTTP/1.1"; - this.httpStatus = HttpStatus.OK; - this.headers = new LinkedHashMap<>(); - this.body = ""; - } - - public Builder header(String key, String value) { - this.headers.put(key, value); - return this; - } - - public Builder status(HttpStatus httpStatus) { - this.httpStatus = httpStatus; - return this; - } - - public Builder contentType(ContentType contentType) { - this.headers.put("Content-Type", contentType.getType() + ";charset=utf-8"); - return this; - } - - public Builder setCookie(HttpCookie cookie) { - this.headers.put("Set-Cookie", cookie.convertToHeader()); - return this; - } - - public Builder body(String body) { - this.headers.put("Content-Length", String.valueOf(body.getBytes().length)); - this.body = body; - return this; - } - - public HttpResponse build() { - return new HttpResponse(this); - } + public void setCookie(HttpCookie cookie) { + this.headers.add(SET_COOKIE, cookie.convertToHeader()); } } diff --git a/tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java index 38ab9c26e1..d59ed6aa30 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java +++ b/tomcat/src/main/java/org/apache/coyote/response/HttpStatus.java @@ -4,7 +4,9 @@ public enum HttpStatus { OK(200), FOUND(302), - UNAUTHORIZED(401); + UNAUTHORIZED(401), + NOT_FOUND(404), + INTERNAL_SERVER_ERROR(500); private final int code; diff --git a/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java b/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java index dfa2528ca0..430f96f1c3 100644 --- a/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java +++ b/tomcat/src/main/java/org/apache/coyote/response/StatusLine.java @@ -4,8 +4,8 @@ public class StatusLine { private static final String SEPERATOR = " "; - private final String version; - private final HttpStatus httpStatus; + private String version; + private HttpStatus httpStatus; public StatusLine(String version, HttpStatus httpStatus) { this.version = version; @@ -15,4 +15,8 @@ public StatusLine(String version, HttpStatus httpStatus) { public String getStatusLine() { return version + SEPERATOR + httpStatus.getHttpStatus() + SEPERATOR; } + + public void setStatus(HttpStatus status) { + this.httpStatus = status; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java b/tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java index 377fee426d..1382608b9a 100644 --- a/tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java +++ b/tomcat/src/main/java/org/apache/coyote/utils/FileUtils.java @@ -1,16 +1,23 @@ package org.apache.coyote.utils; import java.io.IOException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Objects; public class FileUtils { private static final String STATIC_DIRECTORY = "static"; + private FileUtils() { + } + public static String readFile(String path) throws IOException { - final Path filePath = Path.of(Objects.requireNonNull(FileUtils.class.getClassLoader().getResource(STATIC_DIRECTORY + path)).getPath()); + URL resource = FileUtils.class + .getClassLoader() + .getResource(STATIC_DIRECTORY + path); + + Path filePath = Path.of(resource.getPath()); return new String(Files.readAllBytes(filePath)); } diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java index adb4135adb..2d82c88e3b 100644 --- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java @@ -54,13 +54,14 @@ void index() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/index.html"); - var expected = "HTTP/1.1 200 OK \r\n" + - "Content-Type: text/html;charset=utf-8 \r\n" + - "Content-Length: 5564 \r\n" + - "\r\n" + - new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - assertThat(socket.output()).isEqualTo(expected); + String responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + + HttpResponse expected = HttpResponse.createDefaultResponse(); + expected.setContentType(HTML); + expected.setBody(responseBody); + + assertThat(socket.output()).isEqualTo(expected.getResponse()); } @Test @@ -83,13 +84,9 @@ void index() throws IOException { final URL resource = getClass().getClassLoader().getResource("static/login.html"); String responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - HttpResponse expected = new HttpResponse.Builder() - .status(OK) - .contentType(HTML) - .header("Content-Length", String.valueOf(responseBody.length())) - .body(responseBody) - .build(); - + HttpResponse expected = HttpResponse.createDefaultResponse(); + expected.setContentType(HTML); + expected.setBody(responseBody); assertThat(socket.output()).isEqualTo(expected.getResponse()); }