From 0a73d91e27a890d7b989bbb71ca908f8596fcc9b Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Mon, 9 Sep 2024 13:32:31 +0900 Subject: [PATCH 01/38] refactor: use try with resources statement to prevent memory leak --- .../coyote/http11/Http11InputStreamReader.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java index 290fb164df..ed82e3bd61 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java @@ -14,13 +14,12 @@ public class Http11InputStreamReader { private static final Logger log = LoggerFactory.getLogger(Http11InputStreamReader.class); public static List read(InputStream inputStream) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - - List lines = new ArrayList<>(readHeaders(reader)); - int contentLength = getContentLength(lines); - lines.add(readBody(contentLength, reader)); - - return lines; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + List lines = new ArrayList<>(readHeaders(reader)); + int contentLength = getContentLength(lines); + lines.add(readBody(contentLength, reader)); + return lines; + } } private static int getContentLength(List headers) { From 7ca7dcfb230b3d2fcc4cd58bd234d1500e97a2fd Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Mon, 9 Sep 2024 13:47:49 +0900 Subject: [PATCH 02/38] refactor: rearrange method order on request --- .../controller/RegisterController.java | 2 +- .../org/apache/coyote/http11/HttpRequest.java | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java index 5ba88730f2..b83184c061 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java +++ b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java @@ -22,7 +22,7 @@ protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) @Override protected void doPost(HttpRequest request, HttpResponse.Builder responseBuilder) { - Map body = HttpRequest.extractParameters(request.body()); + Map body = request.extractUrlEncodedBody(); if (!body.containsKey("account") || !body.containsKey("password") || diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index 6a06e4c756..a0951765cf 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -39,11 +39,22 @@ public static HttpRequest parse(List lines) { return new HttpRequest(method, path, parameters, headers, cookies, protocolVersion, body); } - private static String extractBody(List lines) { - if (lines.size() > 1 && lines.get(lines.size() - 2).isEmpty()) { - return lines.getLast(); + private static Map extractParameters(String query) { + Map parameters = new HashMap<>(); + + if (query != null) { + String[] pairs = query.split("&"); + for (String pair : pairs) { + String[] keyValue = pair.split("="); + if (keyValue.length == 2) { + String key = keyValue[0]; + String value = URLDecoder.decode(keyValue[1], Charset.defaultCharset()); + parameters.put(key, value); + } + } } - return null; + + return parameters; } private static Map extractHeaders(List lines) { @@ -80,25 +91,18 @@ private static Map extractCookies(String cookieMessage) { return cookies; } - public static Map extractParameters(String query) { - Map parameters = new HashMap<>(); - - if (query != null) { - String[] pairs = query.split("&"); - for (String pair : pairs) { - String[] keyValue = pair.split("="); - if (keyValue.length == 2) { - String key = keyValue[0]; - String value = URLDecoder.decode(keyValue[1], Charset.defaultCharset()); - parameters.put(key, value); - } - } + private static String extractBody(List lines) { + if (lines.size() > 1 && lines.get(lines.size() - 2).isEmpty()) { + return lines.getLast(); } - - return parameters; + return null; } public HttpRequest updatePath(String path) { return new HttpRequest(method, path, parameters, headers, cookies, protocolVersion, body); } + + public Map extractUrlEncodedBody() { + return extractParameters(body); + } } From f32312644528a1bd035588ca5abc0567a2eb12f0 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Mon, 9 Sep 2024 13:48:28 +0900 Subject: [PATCH 03/38] refactor: remove unused method while adding method of major header --- .../src/main/java/org/apache/coyote/http11/HttpResponse.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java index 24f32edce7..06b1a4c2d4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -69,11 +69,6 @@ public Builder status(Status status) { return this; } - public Builder addHeader(String key, String value) { - headers.put(key, value); - return this; - } - public Builder addCookie(String key, String value) { cookies.put(key, value); return this; From 92bceb3557ff468f1710e3b7078fb2081c9efaa8 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Mon, 9 Sep 2024 13:57:59 +0900 Subject: [PATCH 04/38] refactor: use checking parameters exists with stream --- .../controller/LoginController.java | 47 ++++++++++--------- .../org/apache/coyote/http11/HttpRequest.java | 4 ++ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 2b8f24a9fb..6f5729f5e8 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -5,6 +5,7 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; import jakarta.servlet.http.HttpSession; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -23,28 +24,6 @@ public LoginController() { this.resourceController = new ResourceController(); } - private static void processSessionLogin(Builder responseBuilder, HttpSession session) { - User user = (User) Objects.requireNonNull(session).getAttribute("user"); - - log.info("이미 로그인한 사용자 입니다. - 아이디 : {}, 세션 ID : {}", user.getAccount(), session.getId()); - - responseBuilder.status(Status.FOUND) - .location("/index.html"); - } - - private static void processAccountLogin(Builder responseBuilder, User user) { - String sessionId = UUID.randomUUID().toString(); - JSession jSession = new JSession(sessionId); - jSession.setAttribute("user", user); - SessionManager.getInstance().add(jSession); - - log.info("계정 정보 로그인 성공! - 아이디 : {}, 세션 ID : {}", user.getAccount(), sessionId); - - responseBuilder.status(Status.FOUND) - .location("/index.html") - .addCookie(JSession.COOKIE_NAME, sessionId); - } - @Override protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) { HttpSession session = SessionManager.getInstance().getSession(request); @@ -53,7 +32,7 @@ protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) return; } - if (request.parameters().containsKey("account") && request.parameters().containsKey("password")) { + if (request.hasParameters(List.of("account", "password"))) { findValidUser(request).ifPresentOrElse( user -> processAccountLogin(responseBuilder, user), () -> responseBuilder.status(Status.FOUND).location("/401.html") @@ -64,6 +43,15 @@ protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) resourceController.doGet(request.updatePath("login.html"), responseBuilder); } + private void processSessionLogin(Builder responseBuilder, HttpSession session) { + User user = (User) Objects.requireNonNull(session).getAttribute("user"); + + log.info("이미 로그인한 사용자 입니다. - 아이디 : {}, 세션 ID : {}", user.getAccount(), session.getId()); + + responseBuilder.status(Status.FOUND) + .location("/index.html"); + } + private Optional findValidUser(HttpRequest request) { String account = request.parameters().get("account"); String password = request.parameters().get("password"); @@ -71,4 +59,17 @@ private Optional findValidUser(HttpRequest request) { return InMemoryUserRepository.findByAccount(account) .filter(user -> user.checkPassword(password)); } + + private void processAccountLogin(Builder responseBuilder, User user) { + String sessionId = UUID.randomUUID().toString(); + JSession jSession = new JSession(sessionId); + jSession.setAttribute("user", user); + SessionManager.getInstance().add(jSession); + + log.info("계정 정보 로그인 성공! - 아이디 : {}, 세션 ID : {}", user.getAccount(), sessionId); + + responseBuilder.status(Status.FOUND) + .location("/index.html") + .addCookie(JSession.COOKIE_NAME, sessionId); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index a0951765cf..6414ecda26 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -105,4 +105,8 @@ public HttpRequest updatePath(String path) { public Map extractUrlEncodedBody() { return extractParameters(body); } + + public boolean hasParameters(List keys) { + return keys.stream().allMatch(parameters::containsKey); + } } From c657b7f7a634cc555eb13a4a5100c88eeaa656c9 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Mon, 9 Sep 2024 14:00:26 +0900 Subject: [PATCH 05/38] refactor: remove logging on request with session --- .../com/techcourse/controller/LoginController.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 6f5729f5e8..c52c659de0 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -6,7 +6,6 @@ import com.techcourse.model.User; import jakarta.servlet.http.HttpSession; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.UUID; import org.apache.catalina.session.JSession; @@ -28,7 +27,7 @@ public LoginController() { protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) { HttpSession session = SessionManager.getInstance().getSession(request); if (session != null) { - processSessionLogin(responseBuilder, session); + processSessionLogin(responseBuilder); return; } @@ -43,11 +42,7 @@ protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) resourceController.doGet(request.updatePath("login.html"), responseBuilder); } - private void processSessionLogin(Builder responseBuilder, HttpSession session) { - User user = (User) Objects.requireNonNull(session).getAttribute("user"); - - log.info("이미 로그인한 사용자 입니다. - 아이디 : {}, 세션 ID : {}", user.getAccount(), session.getId()); - + private void processSessionLogin(Builder responseBuilder) { responseBuilder.status(Status.FOUND) .location("/index.html"); } @@ -66,7 +61,7 @@ private void processAccountLogin(Builder responseBuilder, User user) { jSession.setAttribute("user", user); SessionManager.getInstance().add(jSession); - log.info("계정 정보 로그인 성공! - 아이디 : {}, 세션 ID : {}", user.getAccount(), sessionId); + log.info("로그인 성공! - 아이디 : {}, 세션 ID : {}", user.getAccount(), sessionId); responseBuilder.status(Status.FOUND) .location("/index.html") From 2a8126275b16f12d28034fc602a81e8c4f387d4e Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Tue, 10 Sep 2024 16:00:04 +0900 Subject: [PATCH 06/38] refactor: move responsibility to create session to manager --- .../com/techcourse/controller/LoginController.java | 10 +++------- .../apache/catalina/session/SessionManager.java | 14 +++++++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index c52c659de0..42afab61fa 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -7,7 +7,6 @@ import jakarta.servlet.http.HttpSession; import java.util.List; import java.util.Optional; -import java.util.UUID; import org.apache.catalina.session.JSession; import org.apache.catalina.session.SessionManager; import org.apache.coyote.http11.HttpRequest; @@ -56,15 +55,12 @@ private Optional findValidUser(HttpRequest request) { } private void processAccountLogin(Builder responseBuilder, User user) { - String sessionId = UUID.randomUUID().toString(); - JSession jSession = new JSession(sessionId); - jSession.setAttribute("user", user); - SessionManager.getInstance().add(jSession); + HttpSession session = SessionManager.getInstance().createSession(user); - log.info("로그인 성공! - 아이디 : {}, 세션 ID : {}", user.getAccount(), sessionId); + log.info("로그인 성공! - 아이디 : {}, 세션 ID : {}", user.getAccount(), session.getId()); responseBuilder.status(Status.FOUND) .location("/index.html") - .addCookie(JSession.COOKIE_NAME, sessionId); + .addCookie(JSession.COOKIE_NAME, session.getId()); } } diff --git a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java index efa41aaeb8..a54ffac545 100644 --- a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java @@ -1,8 +1,10 @@ package org.apache.catalina.session; +import com.techcourse.model.User; import jakarta.servlet.http.HttpSession; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.apache.catalina.Manager; import org.apache.coyote.http11.HttpRequest; @@ -10,6 +12,7 @@ public class SessionManager implements Manager { private static final Map SESSIONS = new HashMap<>(); private static final SessionManager SESSION_MANAGER = new SessionManager(); + private static final String ATTRIBUTE_USER_NAME = "user"; private SessionManager() { } @@ -27,7 +30,7 @@ public void add(HttpSession session) { public HttpSession findSession(String id) { HttpSession session = SESSIONS.get(id); - if (session == null || session.getAttribute("user") == null) { + if (session == null || session.getAttribute(ATTRIBUTE_USER_NAME) == null) { return null; } @@ -39,6 +42,15 @@ public void remove(HttpSession session) { SESSIONS.remove(session.getId()); } + public HttpSession createSession(User user) { + String sessionId = UUID.randomUUID().toString(); + JSession jSession = new JSession(sessionId); + jSession.setAttribute(ATTRIBUTE_USER_NAME, user); + add(jSession); + + return jSession; + } + public HttpSession getSession(HttpRequest request) { String sessionId = request.cookies().get(JSession.COOKIE_NAME); if (sessionId == null) { From db5648ae85bd9f4e0723841e1f2d80b1465c1a48 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Tue, 10 Sep 2024 16:04:42 +0900 Subject: [PATCH 07/38] refactor: move responsibility to create session to manager --- .../coyote/http11/Http11InputStreamReader.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java index ed82e3bd61..290fb164df 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11InputStreamReader.java @@ -14,12 +14,13 @@ public class Http11InputStreamReader { private static final Logger log = LoggerFactory.getLogger(Http11InputStreamReader.class); public static List read(InputStream inputStream) throws IOException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - List lines = new ArrayList<>(readHeaders(reader)); - int contentLength = getContentLength(lines); - lines.add(readBody(contentLength, reader)); - return lines; - } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + + List lines = new ArrayList<>(readHeaders(reader)); + int contentLength = getContentLength(lines); + lines.add(readBody(contentLength, reader)); + + return lines; } private static int getContentLength(List headers) { From b049dbfab4b21f33615a34234a9197c252955c8e Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Tue, 10 Sep 2024 17:35:03 +0900 Subject: [PATCH 08/38] refactor: simplify method lines and extract methods --- .../controller/LoginController.java | 22 +++++++---------- .../controller/RegisterController.java | 24 ++++++++++++------- .../main/java/com/techcourse/model/User.java | 6 +++++ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 42afab61fa..0f3c32409e 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -6,7 +6,6 @@ import com.techcourse.model.User; import jakarta.servlet.http.HttpSession; import java.util.List; -import java.util.Optional; import org.apache.catalina.session.JSession; import org.apache.catalina.session.SessionManager; import org.apache.coyote.http11.HttpRequest; @@ -24,20 +23,14 @@ public LoginController() { @Override protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) { - HttpSession session = SessionManager.getInstance().getSession(request); - if (session != null) { + if (SessionManager.getInstance().getSession(request) != null) { processSessionLogin(responseBuilder); return; } - if (request.hasParameters(List.of("account", "password"))) { - findValidUser(request).ifPresentOrElse( - user -> processAccountLogin(responseBuilder, user), - () -> responseBuilder.status(Status.FOUND).location("/401.html") - ); + processAccountLogin(request, responseBuilder); return; } - resourceController.doGet(request.updatePath("login.html"), responseBuilder); } @@ -46,15 +39,18 @@ private void processSessionLogin(Builder responseBuilder) { .location("/index.html"); } - private Optional findValidUser(HttpRequest request) { + private void processAccountLogin(HttpRequest request, HttpResponse.Builder responseBuilder) { String account = request.parameters().get("account"); String password = request.parameters().get("password"); - return InMemoryUserRepository.findByAccount(account) - .filter(user -> user.checkPassword(password)); + InMemoryUserRepository.findByAccount(account) + .filter(user -> user.checkPassword(password)) + .ifPresentOrElse( + user -> processLoginSuccess(responseBuilder, user), + () -> responseBuilder.status(Status.FOUND).location("/401.html")); } - private void processAccountLogin(Builder responseBuilder, User user) { + private void processLoginSuccess(Builder responseBuilder, User user) { HttpSession session = SessionManager.getInstance().createSession(user); log.info("로그인 성공! - 아이디 : {}, 세션 ID : {}", user.getAccount(), session.getId()); diff --git a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java index b83184c061..842c4cc513 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java +++ b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java @@ -23,21 +23,29 @@ protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) @Override protected void doPost(HttpRequest request, HttpResponse.Builder responseBuilder) { Map body = request.extractUrlEncodedBody(); - - if (!body.containsKey("account") || - !body.containsKey("password") || - !body.containsKey("email")) { + if (isValidBody(body)) { responseBuilder.status(Status.BAD_REQUEST); return; } - - String account = body.get("account"); - if (InMemoryUserRepository.findByAccount(account).isPresent()) { + if (existAccount(body.get("account"))) { responseBuilder.status(Status.CONFLICT); return; } + postProcess(responseBuilder, body); + } + + private boolean isValidBody(Map body) { + return !body.containsKey("account") || + !body.containsKey("password") || + !body.containsKey("email"); + } + + private boolean existAccount(String account) { + return InMemoryUserRepository.findByAccount(account).isPresent(); + } - InMemoryUserRepository.save(new User(account, body.get("password"), body.get("email"))); + private void postProcess(HttpResponse.Builder responseBuilder, Map body) { + InMemoryUserRepository.save(new User(body)); responseBuilder.status(Status.FOUND) .location("/index.html"); } diff --git a/tomcat/src/main/java/com/techcourse/model/User.java b/tomcat/src/main/java/com/techcourse/model/User.java index e8cf4c8e68..8c37286fdf 100644 --- a/tomcat/src/main/java/com/techcourse/model/User.java +++ b/tomcat/src/main/java/com/techcourse/model/User.java @@ -1,5 +1,7 @@ package com.techcourse.model; +import java.util.Map; + public class User { private final Long id; @@ -18,6 +20,10 @@ public User(String account, String password, String email) { this(null, account, password, email); } + public User(Map body) { + this(body.get("account"), body.get("password"), body.get("email")); + } + public boolean checkPassword(String password) { return this.password.equals(password); } From 2300bef10d4e13c5344a5c0bd0ce4f829a38ffb5 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 11 Sep 2024 10:41:32 +0900 Subject: [PATCH 09/38] refactor: move and add enum to encapsulate primitive value --- .../controller/AbstractController.java | 4 +-- .../com/techcourse/controller/Controller.java | 4 +-- .../techcourse/controller/HomeController.java | 6 ++--- .../controller/LoginController.java | 8 +++--- .../controller/RegisterController.java | 6 ++--- .../techcourse/controller/RequestMapping.java | 2 +- .../controller/ResourceController.java | 6 ++--- .../catalina/session/SessionManager.java | 2 +- .../apache/coyote/http11/Http11Processor.java | 2 ++ .../http11/{ => request}/HttpRequest.java | 2 +- .../apache/coyote/http11/request/Method.java | 17 ++++++++++++ .../http11/{ => response}/HttpResponse.java | 27 +++++++++---------- .../http11/response/ProtocolVersion.java | 18 +++++++++++++ .../coyote/http11/{ => response}/Status.java | 10 ++++++- .../controller/HomeControllerTest.java | 6 ++--- .../controller/LoginControllerTest.java | 6 ++--- .../controller/RegisterControllerTest.java | 6 ++--- 17 files changed, 88 insertions(+), 44 deletions(-) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/HttpRequest.java (98%) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/Method.java rename tomcat/src/main/java/org/apache/coyote/http11/{ => response}/HttpResponse.java (78%) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java rename tomcat/src/main/java/org/apache/coyote/http11/{ => response}/Status.java (69%) diff --git a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java index cada57aae4..0f8e80c312 100644 --- a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java +++ b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java @@ -1,7 +1,7 @@ package com.techcourse.controller; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; public abstract class AbstractController implements Controller { diff --git a/tomcat/src/main/java/com/techcourse/controller/Controller.java b/tomcat/src/main/java/com/techcourse/controller/Controller.java index ef38bd1ec4..4afd05b7f5 100644 --- a/tomcat/src/main/java/com/techcourse/controller/Controller.java +++ b/tomcat/src/main/java/com/techcourse/controller/Controller.java @@ -1,7 +1,7 @@ package com.techcourse.controller; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; public interface Controller { void service(HttpRequest request, HttpResponse.Builder responseBuilder); diff --git a/tomcat/src/main/java/com/techcourse/controller/HomeController.java b/tomcat/src/main/java/com/techcourse/controller/HomeController.java index 47141dcc32..668aee9bed 100644 --- a/tomcat/src/main/java/com/techcourse/controller/HomeController.java +++ b/tomcat/src/main/java/com/techcourse/controller/HomeController.java @@ -1,8 +1,8 @@ package com.techcourse.controller; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.Status; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.Status; public class HomeController extends AbstractController { diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 0f3c32409e..6e80331802 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -8,10 +8,10 @@ import java.util.List; import org.apache.catalina.session.JSession; import org.apache.catalina.session.SessionManager; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.HttpResponse.Builder; -import org.apache.coyote.http11.Status; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponse.Builder; +import org.apache.coyote.http11.response.Status; public class LoginController extends AbstractController { diff --git a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java index 842c4cc513..54d9a858cb 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java +++ b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java @@ -3,9 +3,9 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; import java.util.Map; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.Status; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.Status; public class RegisterController extends AbstractController { diff --git a/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java index a7587aac2d..fcc4cdb234 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java +++ b/tomcat/src/main/java/com/techcourse/controller/RequestMapping.java @@ -2,7 +2,7 @@ import java.util.Map; import java.util.function.Predicate; -import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.request.HttpRequest; public class RequestMapping { diff --git a/tomcat/src/main/java/com/techcourse/controller/ResourceController.java b/tomcat/src/main/java/com/techcourse/controller/ResourceController.java index fb596f3721..2c95125a17 100644 --- a/tomcat/src/main/java/com/techcourse/controller/ResourceController.java +++ b/tomcat/src/main/java/com/techcourse/controller/ResourceController.java @@ -3,9 +3,9 @@ import com.techcourse.StaticResourceReader; import java.io.IOException; import java.net.URLConnection; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.Status; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java index a54ffac545..31d92acde0 100644 --- a/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/session/SessionManager.java @@ -6,7 +6,7 @@ import java.util.Map; import java.util.UUID; import org.apache.catalina.Manager; -import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.request.HttpRequest; public class SessionManager implements Manager { 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 874a906045..b35b1e0796 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -6,6 +6,8 @@ import java.net.Socket; import java.util.List; import org.apache.coyote.Processor; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java similarity index 98% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 6414ecda26..b88489ccfe 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.request; import java.net.URLDecoder; import java.nio.charset.Charset; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java new file mode 100644 index 0000000000..58c1b3f609 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java @@ -0,0 +1,17 @@ +package org.apache.coyote.http11.request; + +public enum Method { + GET("GET"), + POST("POST"), + ; + + private final String value; + + Method(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java similarity index 78% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java rename to tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index 06b1a4c2d4..795a1a3292 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.response; import java.util.Arrays; import java.util.HashMap; @@ -6,13 +6,14 @@ import java.util.Map.Entry; import java.util.stream.Collectors; -public record HttpResponse(String protocolVersion, int statusCode, String statusText, +public record HttpResponse(ProtocolVersion protocolVersion, Status status, Map headers, Map cookies, byte[] body) { private static final String CRLF = "\r\n"; + private static final ProtocolVersion DEFAULT_PROTOCOL = ProtocolVersion.HTTP11; public static Builder builder() { - return new Builder().protocolVersion("HTTP/1.1"); + return new Builder().protocolVersion(DEFAULT_PROTOCOL); } private static byte[] mergeByteArrays(byte[] array1, byte[] array2) { @@ -26,7 +27,8 @@ private static byte[] mergeByteArrays(byte[] array1, byte[] array2) { public byte[] toMessage() { StringBuilder builder = new StringBuilder(); - builder.append(protocolVersion).append(" ").append(statusCode).append(" ").append(statusText).append(" "); + builder.append(protocolVersion).append(" ").append(status.getCode()).append(" ").append(status.getMessage()) + .append(" "); headers.forEach((key, value) -> builder.append(CRLF).append(key).append(": ").append(value).append(" ")); if (!cookies.isEmpty()) { @@ -48,9 +50,8 @@ public byte[] toMessage() { public static class Builder { private final Map headers; private final Map cookies; - private String protocolVersion; - private int statusCode; - private String statusText; + private ProtocolVersion protocolVersion; + private Status status; private byte[] body; private Builder() { @@ -58,14 +59,13 @@ private Builder() { cookies = new HashMap<>(); } - public Builder protocolVersion(String protocolVersion) { + public Builder protocolVersion(ProtocolVersion protocolVersion) { this.protocolVersion = protocolVersion; return this; } public Builder status(Status status) { - this.statusCode = status.getCode(); - this.statusText = status.getMessage(); + this.status = status; return this; } @@ -90,7 +90,7 @@ public Builder body(byte[] body) { } public HttpResponse build() { - return new HttpResponse(protocolVersion, statusCode, statusText, headers, cookies, body); + return new HttpResponse(protocolVersion, status, headers, cookies, body); } @Override @@ -98,9 +98,8 @@ public String toString() { return "Builder{" + "headers=" + headers + ", cookies=" + cookies + - ", protocolVersion='" + protocolVersion + '\'' + - ", statusCode=" + statusCode + - ", statusText='" + statusText + '\'' + + ", protocolVersion=" + protocolVersion + + ", status=" + status + ", body=" + Arrays.toString(body) + '}'; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java new file mode 100644 index 0000000000..68c93e72a4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java @@ -0,0 +1,18 @@ +package org.apache.coyote.http11.response; + +public enum ProtocolVersion { + HTTP1("HTTP/1.0"), + HTTP11("HTTP/1.1"), + HTTP2("HTTP/2.0"), + ; + + private final String value; + + ProtocolVersion(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Status.java b/tomcat/src/main/java/org/apache/coyote/http11/response/Status.java similarity index 69% rename from tomcat/src/main/java/org/apache/coyote/http11/Status.java rename to tomcat/src/main/java/org/apache/coyote/http11/response/Status.java index 86b7b16bb3..6d2f41b078 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Status.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/Status.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.response; public enum Status { OK(200, "OK"), @@ -24,4 +24,12 @@ public int getCode() { public String getMessage() { return message; } + + @Override + public String toString() { + return "Status{" + + "code=" + code + + ", message='" + message + '\'' + + '}'; + } } diff --git a/tomcat/src/test/java/com/techcourse/controller/HomeControllerTest.java b/tomcat/src/test/java/com/techcourse/controller/HomeControllerTest.java index 613ed030dc..1ecd7ed91d 100644 --- a/tomcat/src/test/java/com/techcourse/controller/HomeControllerTest.java +++ b/tomcat/src/test/java/com/techcourse/controller/HomeControllerTest.java @@ -3,9 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.HttpResponse.Builder; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponse.Builder; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/tomcat/src/test/java/com/techcourse/controller/LoginControllerTest.java b/tomcat/src/test/java/com/techcourse/controller/LoginControllerTest.java index c2f8cf8feb..26dac512a1 100644 --- a/tomcat/src/test/java/com/techcourse/controller/LoginControllerTest.java +++ b/tomcat/src/test/java/com/techcourse/controller/LoginControllerTest.java @@ -3,9 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.HttpResponse.Builder; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponse.Builder; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/tomcat/src/test/java/com/techcourse/controller/RegisterControllerTest.java b/tomcat/src/test/java/com/techcourse/controller/RegisterControllerTest.java index 9f87107516..9ca7d9a525 100644 --- a/tomcat/src/test/java/com/techcourse/controller/RegisterControllerTest.java +++ b/tomcat/src/test/java/com/techcourse/controller/RegisterControllerTest.java @@ -3,9 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; -import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.HttpResponse.Builder; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponse.Builder; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From f0e2106cda0e9eb8b360b9c15c5a4d67a5cac155 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 11 Sep 2024 11:10:12 +0900 Subject: [PATCH 10/38] refactor: add method enum for request encapsulation fields --- .../coyote/http11/request/HttpRequest.java | 8 +++--- .../apache/coyote/http11/request/Method.java | 9 +++++++ .../http11/request/ProtocolVersion.java | 27 +++++++++++++++++++ .../coyote/http11/response/HttpResponse.java | 1 + .../http11/response/ProtocolVersion.java | 18 ------------- 5 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/ProtocolVersion.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index b88489ccfe..6d22e242e8 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -9,17 +9,17 @@ import java.util.regex.Pattern; public record HttpRequest( - String method, + Method method, String path, Map parameters, Map headers, Map cookies, - String protocolVersion, + ProtocolVersion protocolVersion, String body) { public static HttpRequest parse(List lines) { String[] startLineParts = lines.getFirst().split(" "); - String method = startLineParts[0]; + Method method = Method.from(startLineParts[0]); String path = ""; Map parameters = Map.of(); @@ -30,7 +30,7 @@ public static HttpRequest parse(List lines) { parameters = extractParameters(matcher.group(3)); } - String protocolVersion = startLineParts[2]; + ProtocolVersion protocolVersion = ProtocolVersion.from(startLineParts[2]); Map headers = extractHeaders(lines); Map cookies = extractCookies(headers.get("Cookie")); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java index 58c1b3f609..206bb89567 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java @@ -1,5 +1,7 @@ package org.apache.coyote.http11.request; +import java.util.Arrays; + public enum Method { GET("GET"), POST("POST"), @@ -11,6 +13,13 @@ public enum Method { this.value = value; } + public static Method from(String value) { + return Arrays.stream(Method.values()) + .filter(method -> method.value.equals(value)) + .findAny() + .orElse(null); + } + public String getValue() { return value; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/ProtocolVersion.java b/tomcat/src/main/java/org/apache/coyote/http11/request/ProtocolVersion.java new file mode 100644 index 0000000000..5eed0d9f24 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/ProtocolVersion.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http11.request; + +import java.util.Arrays; + +public enum ProtocolVersion { + HTTP1("HTTP/1.0"), + HTTP11("HTTP/1.1"), + HTTP2("HTTP/2.0"), + ; + + private final String value; + + ProtocolVersion(String value) { + this.value = value; + } + + public static ProtocolVersion from(String value) { + return Arrays.stream(values()) + .filter(version -> version.value.equals(value)) + .findAny() + .orElse(null); + } + + public String getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index 795a1a3292..b97b6ec57a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; +import org.apache.coyote.http11.request.ProtocolVersion; public record HttpResponse(ProtocolVersion protocolVersion, Status status, Map headers, Map cookies, byte[] body) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java deleted file mode 100644 index 68c93e72a4..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ProtocolVersion.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.apache.coyote.http11.response; - -public enum ProtocolVersion { - HTTP1("HTTP/1.0"), - HTTP11("HTTP/1.1"), - HTTP2("HTTP/2.0"), - ; - - private final String value; - - ProtocolVersion(String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} From 9890bd4b11b8fe92a9bd28466b7c8c11a1597c87 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 11 Sep 2024 12:16:38 +0900 Subject: [PATCH 11/38] refactor: extract constant on request and message --- .../coyote/http11/request/HttpRequest.java | 21 +++++--- .../coyote/http11/response/HttpResponse.java | 48 ++++++++++++++----- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 6d22e242e8..e02c2248e3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -17,8 +17,15 @@ public record HttpRequest( ProtocolVersion protocolVersion, String body) { + private static final String DELIMITER_COOKIE = "; "; + private static final String DELIMITER_HEADER = ": "; + private static final String DELIMITER_SPACE = " "; + private static final String DELIMITER_VALUE = "="; + private static final String DELIMITER_PARAMETER_KEYSET = "&"; + private static final String HEADER_NAME_COOKIE = "Cookie"; + public static HttpRequest parse(List lines) { - String[] startLineParts = lines.getFirst().split(" "); + String[] startLineParts = lines.getFirst().split(DELIMITER_SPACE); Method method = Method.from(startLineParts[0]); String path = ""; @@ -32,7 +39,7 @@ public static HttpRequest parse(List lines) { ProtocolVersion protocolVersion = ProtocolVersion.from(startLineParts[2]); Map headers = extractHeaders(lines); - Map cookies = extractCookies(headers.get("Cookie")); + Map cookies = extractCookies(headers.get(HEADER_NAME_COOKIE)); String body = extractBody(lines); @@ -43,9 +50,9 @@ private static Map extractParameters(String query) { Map parameters = new HashMap<>(); if (query != null) { - String[] pairs = query.split("&"); + String[] pairs = query.split(DELIMITER_PARAMETER_KEYSET); for (String pair : pairs) { - String[] keyValue = pair.split("="); + String[] keyValue = pair.split(DELIMITER_VALUE); if (keyValue.length == 2) { String key = keyValue[0]; String value = URLDecoder.decode(keyValue[1], Charset.defaultCharset()); @@ -61,7 +68,7 @@ private static Map extractHeaders(List lines) { Map headers = new HashMap<>(); for (int i = 1; i < lines.size() - 2; i++) { - String[] lineParts = lines.get(i).trim().split(": "); + String[] lineParts = lines.get(i).trim().split(DELIMITER_HEADER); if (lineParts.length >= 2) { headers.put(lineParts[0], lineParts[1]); } @@ -77,8 +84,8 @@ private static Map extractCookies(String cookieMessage) { Map cookies = new HashMap<>(); - for (String entry : cookieMessage.split("; ")) { - int delimiterIndex = entry.indexOf("="); + for (String entry : cookieMessage.split(DELIMITER_COOKIE)) { + int delimiterIndex = entry.indexOf(DELIMITER_VALUE); if (delimiterIndex == -1) { continue; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index b97b6ec57a..2c79f1cc04 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -12,6 +12,11 @@ public record HttpResponse(ProtocolVersion protocolVersion, Status status, private static final String CRLF = "\r\n"; private static final ProtocolVersion DEFAULT_PROTOCOL = ProtocolVersion.HTTP11; + private static final String DELIMITER_COOKIE = "; "; + private static final String DELIMITER_HEADER = ": "; + private static final String DELIMITER_SPACE = " "; + private static final String HEADER_NAME_SET_COOKIE = "Set-Cookie: "; + private static final String HEADER_NAME_CONTENT_LENGTH = "Content-Length: "; public static Builder builder() { return new Builder().protocolVersion(DEFAULT_PROTOCOL); @@ -28,24 +33,43 @@ private static byte[] mergeByteArrays(byte[] array1, byte[] array2) { public byte[] toMessage() { StringBuilder builder = new StringBuilder(); - builder.append(protocolVersion).append(" ").append(status.getCode()).append(" ").append(status.getMessage()) - .append(" "); - headers.forEach((key, value) -> builder.append(CRLF).append(key).append(": ").append(value).append(" ")); + buildFirstLine(builder); + buildHeaders(builder); + buildCookies(builder); + if (body != null && body.length > 0) { + buildBody(builder); + return mergeByteArrays(builder.toString().getBytes(), body); + } + return builder.toString().getBytes(); + } + + private void buildFirstLine(StringBuilder builder) { + builder.append(protocolVersion).append(DELIMITER_SPACE) + .append(status.getCode()).append(DELIMITER_SPACE) + .append(status.getMessage()).append(DELIMITER_SPACE); + } + + private void buildHeaders(StringBuilder builder) { + headers.forEach((key, value) -> builder.append(CRLF) + .append(key).append(DELIMITER_HEADER) + .append(value).append(DELIMITER_SPACE)); + } + + private void buildCookies(StringBuilder builder) { if (!cookies.isEmpty()) { String cookiesMessage = cookies.entrySet().stream() .map(Entry::toString) - .collect(Collectors.joining("; ")); - builder.append(CRLF).append("Set-Cookie: ").append(cookiesMessage).append(" "); - } - - if (body != null && body.length > 0) { - builder.append(CRLF).append("Content-Length: ").append(body.length).append(" "); - builder.append(CRLF.repeat(2)); - return mergeByteArrays(builder.toString().getBytes(), body); + .collect(Collectors.joining(DELIMITER_COOKIE)); + builder.append(CRLF) + .append(HEADER_NAME_SET_COOKIE).append(cookiesMessage).append(DELIMITER_SPACE); } + } - return builder.toString().getBytes(); + private void buildBody(StringBuilder builder) { + builder.append(CRLF).append(HEADER_NAME_CONTENT_LENGTH).append(body.length).append(DELIMITER_SPACE) + .append(CRLF) + .append(CRLF); } public static class Builder { From 38ae2666f51288f7c749cbe4ce970443a43150ee Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 11 Sep 2024 13:24:54 +0900 Subject: [PATCH 12/38] refactor: use stream to reduce indent --- .../coyote/http11/request/HttpRequest.java | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index e02c2248e3..c80926f3e0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -2,11 +2,14 @@ import java.net.URLDecoder; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public record HttpRequest( Method method, @@ -21,7 +24,7 @@ public record HttpRequest( private static final String DELIMITER_HEADER = ": "; private static final String DELIMITER_SPACE = " "; private static final String DELIMITER_VALUE = "="; - private static final String DELIMITER_PARAMETER_KEYSET = "&"; + private static final String DELIMITER_PARAMETER_ENTRY = "&"; private static final String HEADER_NAME_COOKIE = "Cookie"; public static HttpRequest parse(List lines) { @@ -47,34 +50,24 @@ public static HttpRequest parse(List lines) { } private static Map extractParameters(String query) { - Map parameters = new HashMap<>(); - - if (query != null) { - String[] pairs = query.split(DELIMITER_PARAMETER_KEYSET); - for (String pair : pairs) { - String[] keyValue = pair.split(DELIMITER_VALUE); - if (keyValue.length == 2) { - String key = keyValue[0]; - String value = URLDecoder.decode(keyValue[1], Charset.defaultCharset()); - parameters.put(key, value); - } - } + if (query == null) { + return Map.of(); } - - return parameters; + return Arrays.stream(query.split(DELIMITER_PARAMETER_ENTRY)) + .map(keyValue -> keyValue.split(DELIMITER_VALUE)) + .filter(entry -> entry.length == 2) + .collect(Collectors.toMap( + entry -> entry[0], + entry -> URLDecoder.decode(entry[1], Charset.defaultCharset()) + )); } private static Map extractHeaders(List lines) { - Map headers = new HashMap<>(); - - for (int i = 1; i < lines.size() - 2; i++) { - String[] lineParts = lines.get(i).trim().split(DELIMITER_HEADER); - if (lineParts.length >= 2) { - headers.put(lineParts[0], lineParts[1]); - } - } - - return headers; + return IntStream.range(1, lines.size() - 3) + .mapToObj(lines::get) + .map(String::trim) + .map(line -> line.split(DELIMITER_HEADER)) + .collect(Collectors.toMap(entry -> entry[0], entry -> entry[1])); } private static Map extractCookies(String cookieMessage) { From a9feb02cd162eb442a862d86d348e2d452f0fe2e Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 01:15:26 +0900 Subject: [PATCH 13/38] fix: use value of enum to fix message --- .../controller/AbstractController.java | 5 ++- .../techcourse/controller/HomeController.java | 4 ++ .../coyote/http11/request/HttpRequest.java | 37 ++++++++----------- .../coyote/http11/response/HttpResponse.java | 5 ++- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java index 0f8e80c312..d4bfcdde91 100644 --- a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java +++ b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java @@ -1,16 +1,17 @@ package com.techcourse.controller; import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.Method; import org.apache.coyote.http11.response.HttpResponse; public abstract class AbstractController implements Controller { @Override public void service(HttpRequest request, HttpResponse.Builder responseBuilder) { - if (request.method().equals("GET")) { + if (request.method().equals(Method.GET)) { doGet(request, responseBuilder); } - if (request.method().equals("POST")) { + if (request.method().equals(Method.POST)) { doPost(request, responseBuilder); } } diff --git a/tomcat/src/main/java/com/techcourse/controller/HomeController.java b/tomcat/src/main/java/com/techcourse/controller/HomeController.java index 668aee9bed..42a256cd1e 100644 --- a/tomcat/src/main/java/com/techcourse/controller/HomeController.java +++ b/tomcat/src/main/java/com/techcourse/controller/HomeController.java @@ -3,9 +3,13 @@ import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; import org.apache.coyote.http11.response.Status; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class HomeController extends AbstractController { + private static final Logger log = LoggerFactory.getLogger(HomeController.class); + @Override protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) { responseBuilder.status(Status.OK) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index c80926f3e0..8bce080aa5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -3,7 +3,6 @@ import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -14,10 +13,10 @@ public record HttpRequest( Method method, String path, + ProtocolVersion protocolVersion, Map parameters, Map headers, Map cookies, - ProtocolVersion protocolVersion, String body) { private static final String DELIMITER_COOKIE = "; "; @@ -46,7 +45,7 @@ public static HttpRequest parse(List lines) { String body = extractBody(lines); - return new HttpRequest(method, path, parameters, headers, cookies, protocolVersion, body); + return new HttpRequest(method, path, protocolVersion, parameters, headers, cookies, body); } private static Map extractParameters(String query) { @@ -63,10 +62,16 @@ private static Map extractParameters(String query) { } private static Map extractHeaders(List lines) { - return IntStream.range(1, lines.size() - 3) - .mapToObj(lines::get) + int endHeaderIndex = IntStream.range(0, lines.size()) + .filter(i -> lines.get(i).isEmpty()) + .findFirst() + .orElse(lines.size()); + + return lines.stream() + .limit(endHeaderIndex) .map(String::trim) .map(line -> line.split(DELIMITER_HEADER)) + .filter(entry -> entry.length == 2) .collect(Collectors.toMap(entry -> entry[0], entry -> entry[1])); } @@ -74,21 +79,11 @@ private static Map extractCookies(String cookieMessage) { if (cookieMessage == null) { return Map.of(); } - - Map cookies = new HashMap<>(); - - for (String entry : cookieMessage.split(DELIMITER_COOKIE)) { - int delimiterIndex = entry.indexOf(DELIMITER_VALUE); - if (delimiterIndex == -1) { - continue; - } - - String key = entry.substring(0, delimiterIndex).trim(); - String value = entry.substring(delimiterIndex + 1).trim(); - cookies.put(key, value); - } - - return cookies; + + return Arrays.stream(cookieMessage.split(DELIMITER_COOKIE)) + .map(cookie -> cookie.split(DELIMITER_VALUE)) + .filter(entry -> entry.length == 2) + .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); } private static String extractBody(List lines) { @@ -99,7 +94,7 @@ private static String extractBody(List lines) { } public HttpRequest updatePath(String path) { - return new HttpRequest(method, path, parameters, headers, cookies, protocolVersion, body); + return new HttpRequest(method, path, protocolVersion, parameters, headers, cookies, body); } public Map extractUrlEncodedBody() { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index 2c79f1cc04..797a4af450 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -19,7 +19,8 @@ public record HttpResponse(ProtocolVersion protocolVersion, Status status, private static final String HEADER_NAME_CONTENT_LENGTH = "Content-Length: "; public static Builder builder() { - return new Builder().protocolVersion(DEFAULT_PROTOCOL); + return new Builder().protocolVersion(DEFAULT_PROTOCOL) + .status(Status.OK); } private static byte[] mergeByteArrays(byte[] array1, byte[] array2) { @@ -45,7 +46,7 @@ public byte[] toMessage() { } private void buildFirstLine(StringBuilder builder) { - builder.append(protocolVersion).append(DELIMITER_SPACE) + builder.append(protocolVersion.getValue()).append(DELIMITER_SPACE) .append(status.getCode()).append(DELIMITER_SPACE) .append(status.getMessage()).append(DELIMITER_SPACE); } From 44144994211dc7bfb7dd4368b91e268d16691e32 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 02:05:01 +0900 Subject: [PATCH 14/38] test: fix failed test about cache --- study/build.gradle | 37 +++++++++---------- .../cachecontrol/CacheHandlerInterceptor.java | 18 --------- .../example/cachecontrol/CacheWebConfig.java | 26 ++++++------- .../com/example/version/HandlebarsConfig.java | 26 +++++++++++++ .../version/VersionHandlebarsHelper.java | 12 ++++-- study/src/main/resources/application.yml | 3 -- 6 files changed, 66 insertions(+), 56 deletions(-) delete mode 100644 study/src/main/java/cache/com/example/cachecontrol/CacheHandlerInterceptor.java create mode 100644 study/src/main/java/cache/com/example/version/HandlebarsConfig.java diff --git a/study/build.gradle b/study/build.gradle index 7bdce4f751..0670a677a5 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -1,37 +1,36 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.3.0' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'java' + id 'org.springframework.boot' version '3.3.0' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' } group 'camp.nextstep' version '1.0-SNAPSHOT' java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation 'org.apache.commons:commons-lang3:3.14.0' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' - implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.apache.commons:commons-lang3:3.14.0' + implementation 'com.github.jknack:handlebars-springmvc:4.4.0' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.assertj:assertj-core:3.26.0' - testImplementation 'org.mockito:mockito-core:5.12.0' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.assertj:assertj-core:3.26.0' + testImplementation 'org.mockito:mockito-core:5.12.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2' } test { - maxParallelForks 3 - useJUnitPlatform() + maxParallelForks 3 + useJUnitPlatform() } diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheHandlerInterceptor.java b/study/src/main/java/cache/com/example/cachecontrol/CacheHandlerInterceptor.java deleted file mode 100644 index ed47d0541a..0000000000 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheHandlerInterceptor.java +++ /dev/null @@ -1,18 +0,0 @@ -package cache.com.example.cachecontrol; - -import com.google.common.net.HttpHeaders; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.CacheControl; -import org.springframework.web.servlet.HandlerInterceptor; - -public class CacheHandlerInterceptor implements HandlerInterceptor { - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - CacheControl cacheControl = CacheControl.noCache().cachePrivate(); - response.addHeader(HttpHeaders.CACHE_CONTROL, cacheControl.getHeaderValue()); - - return true; - } -} 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 f3e04d3a3d..b2525ffe5c 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -1,28 +1,28 @@ package cache.com.example.cachecontrol; -import com.google.common.net.HttpHeaders; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES; + import java.time.Duration; import org.springframework.context.annotation.Configuration; import org.springframework.http.CacheControl; -import org.springframework.web.servlet.HandlerInterceptor; 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(new CacheHandlerInterceptor()).addPathPatterns("/"); - registry.addInterceptor(new HandlerInterceptor() { - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - CacheControl cacheControl = CacheControl.maxAge(Duration.ofDays(365)).cachePublic(); - response.addHeader(HttpHeaders.CACHE_CONTROL, cacheControl.getHeaderValue()); - return true; - } - }).addPathPatterns("/resources/**"); + WebContentInterceptor cacheInterceptor = new WebContentInterceptor(); + cacheInterceptor.setCacheControl(CacheControl.noCache().cachePrivate()); + registry.addInterceptor(cacheInterceptor) + .excludePathPatterns(PREFIX_STATIC_RESOURCES + "/**"); + + WebContentInterceptor resourceInterceptor = new WebContentInterceptor(); + CacheControl cacheControl = CacheControl.maxAge(Duration.ofDays(365)).cachePublic(); + resourceInterceptor.setCacheControl(cacheControl); + registry.addInterceptor(resourceInterceptor) + .addPathPatterns(PREFIX_STATIC_RESOURCES + "/**"); } } diff --git a/study/src/main/java/cache/com/example/version/HandlebarsConfig.java b/study/src/main/java/cache/com/example/version/HandlebarsConfig.java new file mode 100644 index 0000000000..79f73b97e9 --- /dev/null +++ b/study/src/main/java/cache/com/example/version/HandlebarsConfig.java @@ -0,0 +1,26 @@ +package cache.com.example.version; + +import com.github.jknack.handlebars.springmvc.HandlebarsViewResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.ViewResolver; + +@Configuration +public class HandlebarsConfig { + + private final VersionHandlebarsHelper versionHandlebarsHelper; + + public HandlebarsConfig(VersionHandlebarsHelper versionHandlebarsHelper) { + this.versionHandlebarsHelper = versionHandlebarsHelper; + } + + @Bean + public ViewResolver handlebarsViewResolver() { + HandlebarsViewResolver viewResolver = new HandlebarsViewResolver(); + viewResolver.registerHelper("staticUrls", versionHandlebarsHelper); + viewResolver.setPrefix("classpath:/templates/"); + viewResolver.setSuffix(".html"); + + return viewResolver; + } +} \ No newline at end of file diff --git a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java b/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java index a8e004466a..8bf617189f 100644 --- a/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java +++ b/study/src/main/java/cache/com/example/version/VersionHandlebarsHelper.java @@ -1,13 +1,14 @@ package cache.com.example.version; +import com.github.jknack.handlebars.Helper; import com.github.jknack.handlebars.Options; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper; +import org.springframework.stereotype.Component; -@HandlebarsHelper -public class VersionHandlebarsHelper { +@Component +public class VersionHandlebarsHelper implements Helper { private static final Logger log = LoggerFactory.getLogger(VersionHandlebarsHelper.class); @@ -22,4 +23,9 @@ public String staticUrls(String path, Options options) { log.debug("static url : {}", path); return String.format("/resources/%s%s", version.getVersion(), path); } + + @Override + public Object apply(Object context, Options options) { + return staticUrls(context.toString(), options); + } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 8b74bdfd88..db798e1815 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -1,6 +1,3 @@ -handlebars: - suffix: .html - server: tomcat: accept-count: 1 From 5cabc8d89d8c1f1bcd2e1dc420c8c245fe31d6dd Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 02:23:14 +0900 Subject: [PATCH 15/38] refactor: remove excluding path --- .../java/cache/com/example/cachecontrol/CacheWebConfig.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java index b2525ffe5c..45dc4fea68 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -16,13 +16,12 @@ public class CacheWebConfig implements WebMvcConfigurer { public void addInterceptors(final InterceptorRegistry registry) { WebContentInterceptor cacheInterceptor = new WebContentInterceptor(); cacheInterceptor.setCacheControl(CacheControl.noCache().cachePrivate()); - registry.addInterceptor(cacheInterceptor) - .excludePathPatterns(PREFIX_STATIC_RESOURCES + "/**"); + registry.addInterceptor(cacheInterceptor); WebContentInterceptor resourceInterceptor = new WebContentInterceptor(); CacheControl cacheControl = CacheControl.maxAge(Duration.ofDays(365)).cachePublic(); resourceInterceptor.setCacheControl(cacheControl); registry.addInterceptor(resourceInterceptor) - .addPathPatterns(PREFIX_STATIC_RESOURCES + "/**"); + .addPathPatterns(PREFIX_STATIC_RESOURCES + "/static/**"); } } From ddbd91c5b5b41633b4788fd65f6ab2411330cd58 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 14:57:05 +0900 Subject: [PATCH 16/38] fix: complete cache busting of static resources --- study/build.gradle | 36 +++++++++---------- .../example/cachecontrol/CacheWebConfig.java | 5 +-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/study/build.gradle b/study/build.gradle index 0670a677a5..c4d76b6702 100644 --- a/study/build.gradle +++ b/study/build.gradle @@ -1,36 +1,36 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.3.0' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'java' + id 'org.springframework.boot' version '3.3.0' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' } group 'camp.nextstep' version '1.0-SNAPSHOT' java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation 'org.apache.commons:commons-lang3:3.14.0' - implementation 'com.github.jknack:handlebars-springmvc:4.4.0' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.apache.commons:commons-lang3:3.14.0' + implementation 'com.github.jknack:handlebars-springmvc:4.4.0' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.assertj:assertj-core:3.26.0' - testImplementation 'org.mockito:mockito-core:5.12.0' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.assertj:assertj-core:3.26.0' + testImplementation 'org.mockito:mockito-core:5.12.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2' } test { - maxParallelForks 3 - useJUnitPlatform() + maxParallelForks 3 + useJUnitPlatform() } 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 45dc4fea68..823229ca53 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -16,12 +16,13 @@ public class CacheWebConfig implements WebMvcConfigurer { public void addInterceptors(final InterceptorRegistry registry) { WebContentInterceptor cacheInterceptor = new WebContentInterceptor(); cacheInterceptor.setCacheControl(CacheControl.noCache().cachePrivate()); - registry.addInterceptor(cacheInterceptor); + registry.addInterceptor(cacheInterceptor) + .addPathPatterns("/"); WebContentInterceptor resourceInterceptor = new WebContentInterceptor(); CacheControl cacheControl = CacheControl.maxAge(Duration.ofDays(365)).cachePublic(); resourceInterceptor.setCacheControl(cacheControl); registry.addInterceptor(resourceInterceptor) - .addPathPatterns(PREFIX_STATIC_RESOURCES + "/static/**"); + .addPathPatterns(PREFIX_STATIC_RESOURCES + "/**"); } } From f1b6c5570a3353689728d4c438029cd44b472949 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:08:48 +0900 Subject: [PATCH 17/38] fix: add etag filter --- .../cache/com/example/etag/EtagFilterConfiguration.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 c51500216c..bd497e9c11 100644 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java @@ -10,6 +10,11 @@ public class EtagFilterConfiguration { @Bean public FilterRegistrationBean shallowEtagHeaderFilter() { - return new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); + FilterRegistrationBean filterFilterRegistrationBean = new FilterRegistrationBean<>( + new ShallowEtagHeaderFilter()); + filterFilterRegistrationBean.addUrlPatterns("/etag"); + filterFilterRegistrationBean.addUrlPatterns("/resources/*"); + + return filterFilterRegistrationBean; } } From cf327d23aaa1882e3cdc32199cd559de9f14ab6b Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:13:09 +0900 Subject: [PATCH 18/38] refactor: remove unused method --- .../java/com/techcourse/controller/AbstractController.java | 4 ++-- .../java/org/apache/coyote/http11/request/HttpRequest.java | 6 +++++- .../main/java/org/apache/coyote/http11/request/Method.java | 4 ---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java index d4bfcdde91..ed9a3e1d0c 100644 --- a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java +++ b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java @@ -8,10 +8,10 @@ public abstract class AbstractController implements Controller { @Override public void service(HttpRequest request, HttpResponse.Builder responseBuilder) { - if (request.method().equals(Method.GET)) { + if (request.isMethod(Method.GET)) { doGet(request, responseBuilder); } - if (request.method().equals(Method.POST)) { + if (request.isMethod(Method.POST)) { doPost(request, responseBuilder); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 8bce080aa5..74ceb2d8f0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -79,7 +79,7 @@ private static Map extractCookies(String cookieMessage) { if (cookieMessage == null) { return Map.of(); } - + return Arrays.stream(cookieMessage.split(DELIMITER_COOKIE)) .map(cookie -> cookie.split(DELIMITER_VALUE)) .filter(entry -> entry.length == 2) @@ -104,4 +104,8 @@ public Map extractUrlEncodedBody() { public boolean hasParameters(List keys) { return keys.stream().allMatch(parameters::containsKey); } + + public boolean isMethod(Method method) { + return method.equals(this.method); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java index 206bb89567..423466285b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java @@ -19,8 +19,4 @@ public static Method from(String value) { .findAny() .orElse(null); } - - public String getValue() { - return value; - } } From 6d68afbdb2430f9084f4ad137e910c732d7e446e Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:17:19 +0900 Subject: [PATCH 19/38] refactor: move package to common position --- .../org/apache/coyote/http11/{request => }/ProtocolVersion.java | 2 +- .../main/java/org/apache/coyote/http11/request/HttpRequest.java | 1 + .../src/main/java/org/apache/coyote/http11/request/Method.java | 2 +- .../java/org/apache/coyote/http11/response/HttpResponse.java | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) rename tomcat/src/main/java/org/apache/coyote/http11/{request => }/ProtocolVersion.java (92%) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/ProtocolVersion.java b/tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java similarity index 92% rename from tomcat/src/main/java/org/apache/coyote/http11/request/ProtocolVersion.java rename to tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java index 5eed0d9f24..ba6963cae1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/ProtocolVersion.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11; import java.util.Arrays; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 74ceb2d8f0..ccd3dca094 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -9,6 +9,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.apache.coyote.http11.ProtocolVersion; public record HttpRequest( Method method, diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java index 423466285b..4b7cfd981d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Method.java @@ -17,6 +17,6 @@ public static Method from(String value) { return Arrays.stream(Method.values()) .filter(method -> method.value.equals(value)) .findAny() - .orElse(null); + .orElseThrow(() -> new IllegalArgumentException("HTTP Method not found: " + value)); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index 797a4af450..6ed386a6d5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; -import org.apache.coyote.http11.request.ProtocolVersion; +import org.apache.coyote.http11.ProtocolVersion; public record HttpResponse(ProtocolVersion protocolVersion, Status status, Map headers, Map cookies, byte[] body) { From c6513c9532c8c5709e7e2540f2f7f0dd148528e9 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:19:43 +0900 Subject: [PATCH 20/38] refactor: optimize new line --- .../org/apache/coyote/http11/response/HttpResponse.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index 6ed386a6d5..bb1efb938d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -7,8 +7,12 @@ import java.util.stream.Collectors; import org.apache.coyote.http11.ProtocolVersion; -public record HttpResponse(ProtocolVersion protocolVersion, Status status, - Map headers, Map cookies, byte[] body) { +public record HttpResponse( + ProtocolVersion protocolVersion, + Status status, + Map headers, + Map cookies, + byte[] body) { private static final String CRLF = "\r\n"; private static final ProtocolVersion DEFAULT_PROTOCOL = ProtocolVersion.HTTP11; From 02da74a411cec3bd32bc3f9c5724e4df80a7cd86 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:20:55 +0900 Subject: [PATCH 21/38] refactor: change new line style --- .../java/org/apache/coyote/http11/request/HttpRequest.java | 3 ++- .../java/org/apache/coyote/http11/response/HttpResponse.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index ccd3dca094..93d38f1ad7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -18,7 +18,8 @@ public record HttpRequest( Map parameters, Map headers, Map cookies, - String body) { + String body +) { private static final String DELIMITER_COOKIE = "; "; private static final String DELIMITER_HEADER = ": "; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index bb1efb938d..65d3745456 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -12,7 +12,8 @@ public record HttpResponse( Status status, Map headers, Map cookies, - byte[] body) { + byte[] body +) { private static final String CRLF = "\r\n"; private static final ProtocolVersion DEFAULT_PROTOCOL = ProtocolVersion.HTTP11; From 190f098c8dc61f1c867b0f8f562cc3b6c77225dd Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:25:15 +0900 Subject: [PATCH 22/38] refactor: extract all constant at http response --- .../coyote/http11/response/HttpResponse.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index 65d3745456..26ba09480f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -17,11 +17,14 @@ public record HttpResponse( private static final String CRLF = "\r\n"; private static final ProtocolVersion DEFAULT_PROTOCOL = ProtocolVersion.HTTP11; - private static final String DELIMITER_COOKIE = "; "; - private static final String DELIMITER_HEADER = ": "; + private static final String DELIMITER_SEMICOLON = "; "; + private static final String DELIMITER_COLON = ": "; private static final String DELIMITER_SPACE = " "; private static final String HEADER_NAME_SET_COOKIE = "Set-Cookie: "; private static final String HEADER_NAME_CONTENT_LENGTH = "Content-Length: "; + private static final String HEADER_NAME_CONTENT_TYPE = "Content-Type"; + private static final String HEADER_NAME_LOCATION = "Location"; + private static final String HEADER_VALUE_CONTENT_TYPE_CHARSET = "charset=utf-8"; public static Builder builder() { return new Builder().protocolVersion(DEFAULT_PROTOCOL) @@ -58,7 +61,7 @@ private void buildFirstLine(StringBuilder builder) { private void buildHeaders(StringBuilder builder) { headers.forEach((key, value) -> builder.append(CRLF) - .append(key).append(DELIMITER_HEADER) + .append(key).append(DELIMITER_COLON) .append(value).append(DELIMITER_SPACE)); } @@ -66,7 +69,7 @@ private void buildCookies(StringBuilder builder) { if (!cookies.isEmpty()) { String cookiesMessage = cookies.entrySet().stream() .map(Entry::toString) - .collect(Collectors.joining(DELIMITER_COOKIE)); + .collect(Collectors.joining(DELIMITER_SEMICOLON)); builder.append(CRLF) .append(HEADER_NAME_SET_COOKIE).append(cookiesMessage).append(DELIMITER_SPACE); } @@ -106,12 +109,13 @@ public Builder addCookie(String key, String value) { } public Builder contentType(String value) { - headers.put("Content-Type", value + ";charset=utf-8"); + headers.put(HEADER_NAME_CONTENT_TYPE, + String.join(DELIMITER_SEMICOLON, value, HEADER_VALUE_CONTENT_TYPE_CHARSET)); return this; } public Builder location(String value) { - headers.put("Location", value); + headers.put(HEADER_NAME_LOCATION, value); return this; } From 6a468c167fdfa955e1d58d45449c886c8fe1ef02 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:29:24 +0900 Subject: [PATCH 23/38] refactor: extract filtering method on http request --- .../apache/coyote/http11/request/HttpRequest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 93d38f1ad7..b7b2d74714 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -56,7 +56,7 @@ private static Map extractParameters(String query) { } return Arrays.stream(query.split(DELIMITER_PARAMETER_ENTRY)) .map(keyValue -> keyValue.split(DELIMITER_VALUE)) - .filter(entry -> entry.length == 2) + .filter(HttpRequest::isLengthTwo) .collect(Collectors.toMap( entry -> entry[0], entry -> URLDecoder.decode(entry[1], Charset.defaultCharset()) @@ -71,10 +71,13 @@ private static Map extractHeaders(List lines) { return lines.stream() .limit(endHeaderIndex) - .map(String::trim) .map(line -> line.split(DELIMITER_HEADER)) - .filter(entry -> entry.length == 2) - .collect(Collectors.toMap(entry -> entry[0], entry -> entry[1])); + .filter(HttpRequest::isLengthTwo) + .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); + } + + private static boolean isLengthTwo(String[] entry) { + return entry.length == 2; } private static Map extractCookies(String cookieMessage) { @@ -84,7 +87,7 @@ private static Map extractCookies(String cookieMessage) { return Arrays.stream(cookieMessage.split(DELIMITER_COOKIE)) .map(cookie -> cookie.split(DELIMITER_VALUE)) - .filter(entry -> entry.length == 2) + .filter(entry -> isLengthTwo(entry)) .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); } From 8631162129f0a0e619fffb6f58e2819e4aeea7f9 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 15:53:50 +0900 Subject: [PATCH 24/38] refactor: change map located on http request and response to first class collection --- .../coyote/http11/common/HttpCookies.java | 31 ++++++++++++++ .../coyote/http11/common/HttpHeaders.java | 22 ++++++++++ .../http11/{ => common}/ProtocolVersion.java | 2 +- .../coyote/http11/request/HttpParameters.java | 21 ++++++++++ .../coyote/http11/request/HttpRequest.java | 40 +++++++++++-------- .../coyote/http11/response/HttpResponse.java | 24 +++++------ 6 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookies.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/common/HttpHeaders.java rename tomcat/src/main/java/org/apache/coyote/http11/{ => common}/ProtocolVersion.java (92%) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/HttpParameters.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookies.java b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookies.java new file mode 100644 index 0000000000..03abb2cf78 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookies.java @@ -0,0 +1,31 @@ +package org.apache.coyote.http11.common; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +public class HttpCookies { + + private static final String DELIMITER_SEMICOLON = "; "; + + private final Map values = new HashMap<>(); + + public void put(String key, String value) { + values.put(key, value); + } + + public String get(String key) { + return values.get(key); + } + + public boolean isEmpty() { + return values.isEmpty(); + } + + public String toMessage() { + return values.entrySet().stream() + .map(Entry::toString) + .collect(Collectors.joining(DELIMITER_SEMICOLON)); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpHeaders.java new file mode 100644 index 0000000000..c91a6306c6 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpHeaders.java @@ -0,0 +1,22 @@ +package org.apache.coyote.http11.common; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public class HttpHeaders { + + private final Map values = new HashMap<>(); + + public void put(String key, String value) { + values.put(key, value); + } + + public String get(String key) { + return values.get(key); + } + + public void forEach(BiConsumer consumer) { + values.forEach(consumer); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java b/tomcat/src/main/java/org/apache/coyote/http11/common/ProtocolVersion.java similarity index 92% rename from tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java rename to tomcat/src/main/java/org/apache/coyote/http11/common/ProtocolVersion.java index ba6963cae1..ed1dd3d2a9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ProtocolVersion.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/ProtocolVersion.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.common; import java.util.Arrays; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpParameters.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpParameters.java new file mode 100644 index 0000000000..664c323dde --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpParameters.java @@ -0,0 +1,21 @@ +package org.apache.coyote.http11.request; + +import java.util.HashMap; +import java.util.Map; + +public class HttpParameters { + + private final Map values = new HashMap<>(); + + public void put(String key, String value) { + values.put(key, value); + } + + public String get(String key) { + return values.get(key); + } + + public boolean containsKey(String key) { + return values.containsKey(key); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index b7b2d74714..06b60dfe25 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -9,15 +9,17 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.apache.coyote.http11.ProtocolVersion; +import org.apache.coyote.http11.common.HttpCookies; +import org.apache.coyote.http11.common.HttpHeaders; +import org.apache.coyote.http11.common.ProtocolVersion; public record HttpRequest( Method method, String path, ProtocolVersion protocolVersion, - Map parameters, - Map headers, - Map cookies, + HttpParameters parameters, + HttpHeaders headers, + HttpCookies cookies, String body ) { @@ -33,17 +35,17 @@ public static HttpRequest parse(List lines) { Method method = Method.from(startLineParts[0]); String path = ""; - Map parameters = Map.of(); + HttpParameters parameters = new HttpParameters(); Pattern pattern = Pattern.compile("([^?]+)(\\?(.*))?"); Matcher matcher = pattern.matcher(startLineParts[1]); if (matcher.find()) { path = matcher.group(1); - parameters = extractParameters(matcher.group(3)); + extractParameters(matcher.group(3)).forEach(parameters::put); } ProtocolVersion protocolVersion = ProtocolVersion.from(startLineParts[2]); - Map headers = extractHeaders(lines); - Map cookies = extractCookies(headers.get(HEADER_NAME_COOKIE)); + HttpHeaders headers = extractHeaders(lines); + HttpCookies cookies = extractCookies(headers.get(HEADER_NAME_COOKIE)); String body = extractBody(lines); @@ -63,32 +65,38 @@ private static Map extractParameters(String query) { )); } - private static Map extractHeaders(List lines) { + private static HttpHeaders extractHeaders(List lines) { int endHeaderIndex = IntStream.range(0, lines.size()) .filter(i -> lines.get(i).isEmpty()) .findFirst() .orElse(lines.size()); - return lines.stream() + HttpHeaders httpHeaders = new HttpHeaders(); + lines.stream() .limit(endHeaderIndex) .map(line -> line.split(DELIMITER_HEADER)) .filter(HttpRequest::isLengthTwo) - .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); + .forEach(entry -> httpHeaders.put(entry[0], entry[1])); + + return httpHeaders; } private static boolean isLengthTwo(String[] entry) { return entry.length == 2; } - private static Map extractCookies(String cookieMessage) { + private static HttpCookies extractCookies(String cookieMessage) { if (cookieMessage == null) { - return Map.of(); + return new HttpCookies(); } - return Arrays.stream(cookieMessage.split(DELIMITER_COOKIE)) + HttpCookies httpCookies = new HttpCookies(); + Arrays.stream(cookieMessage.split(DELIMITER_COOKIE)) .map(cookie -> cookie.split(DELIMITER_VALUE)) - .filter(entry -> isLengthTwo(entry)) - .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); + .filter(HttpRequest::isLengthTwo) + .forEach(entry -> httpCookies.put(entry[0], entry[1])); + + return httpCookies; } private static String extractBody(List lines) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index 26ba09480f..b5d5f71526 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -1,17 +1,15 @@ package org.apache.coyote.http11.response; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import org.apache.coyote.http11.ProtocolVersion; +import org.apache.coyote.http11.common.HttpCookies; +import org.apache.coyote.http11.common.HttpHeaders; +import org.apache.coyote.http11.common.ProtocolVersion; public record HttpResponse( ProtocolVersion protocolVersion, Status status, - Map headers, - Map cookies, + HttpHeaders headers, + HttpCookies cookies, byte[] body ) { @@ -67,9 +65,7 @@ private void buildHeaders(StringBuilder builder) { private void buildCookies(StringBuilder builder) { if (!cookies.isEmpty()) { - String cookiesMessage = cookies.entrySet().stream() - .map(Entry::toString) - .collect(Collectors.joining(DELIMITER_SEMICOLON)); + String cookiesMessage = cookies.toMessage(); builder.append(CRLF) .append(HEADER_NAME_SET_COOKIE).append(cookiesMessage).append(DELIMITER_SPACE); } @@ -82,15 +78,15 @@ private void buildBody(StringBuilder builder) { } public static class Builder { - private final Map headers; - private final Map cookies; + private final HttpHeaders headers; + private final HttpCookies cookies; private ProtocolVersion protocolVersion; private Status status; private byte[] body; private Builder() { - headers = new HashMap<>(); - cookies = new HashMap<>(); + headers = new HttpHeaders(); + cookies = new HttpCookies(); } public Builder protocolVersion(ProtocolVersion protocolVersion) { From 004010c930df855dd12e56faf1f6949768a46c8d Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 16:02:40 +0900 Subject: [PATCH 25/38] refactor: extract to improve readability --- .../java/org/apache/coyote/http11/request/HttpRequest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 06b60dfe25..c73d894595 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -100,12 +100,16 @@ private static HttpCookies extractCookies(String cookieMessage) { } private static String extractBody(List lines) { - if (lines.size() > 1 && lines.get(lines.size() - 2).isEmpty()) { + if (lines.size() > 1 && existBodySeparatorEmptyLine(lines)) { return lines.getLast(); } return null; } + private static boolean existBodySeparatorEmptyLine(List lines) { + return lines.get(lines.size() - 2).isEmpty(); + } + public HttpRequest updatePath(String path) { return new HttpRequest(method, path, protocolVersion, parameters, headers, cookies, body); } From ebb02c0dd7d87592c9972cb511fc91adf1ac2bf6 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 16:04:39 +0900 Subject: [PATCH 26/38] refactor: change variable name to camelcase --- .../main/java/org/apache/coyote/http11/Http11Processor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 b35b1e0796..936683f542 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -14,7 +14,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final RequestMapping REQUEST_MAPPING = new RequestMapping(); + private static final RequestMapping requestMapping = new RequestMapping(); private final Socket connection; @@ -37,7 +37,7 @@ public void process(final Socket connection) { // log.debug(request.toString()); HttpResponse.Builder responseBuilder = HttpResponse.builder(); - REQUEST_MAPPING.getController(request) + requestMapping.getController(request) .service(request, responseBuilder); HttpResponse response = responseBuilder.build(); // log.debug(response.toString()); From e5d5d3cae30b9d358bd8b43e7e5f5d506525e394 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Thu, 12 Sep 2024 16:16:15 +0900 Subject: [PATCH 27/38] feat: login with post method --- .../controller/LoginController.java | 30 +++-- .../controller/RegisterController.java | 4 +- tomcat/src/main/resources/static/login.html | 125 +++++++++--------- 3 files changed, 85 insertions(+), 74 deletions(-) diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java index 6e80331802..a1241e1148 100644 --- a/tomcat/src/main/java/com/techcourse/controller/LoginController.java +++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java @@ -5,7 +5,7 @@ import com.techcourse.db.InMemoryUserRepository; import com.techcourse.model.User; import jakarta.servlet.http.HttpSession; -import java.util.List; +import java.util.Map; import org.apache.catalina.session.JSession; import org.apache.catalina.session.SessionManager; import org.apache.coyote.http11.request.HttpRequest; @@ -24,24 +24,32 @@ public LoginController() { @Override protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) { if (SessionManager.getInstance().getSession(request) != null) { - processSessionLogin(responseBuilder); + responseBuilder.status(Status.FOUND) + .location("/index.html"); return; } - if (request.hasParameters(List.of("account", "password"))) { - processAccountLogin(request, responseBuilder); + resourceController.doGet(request.updatePath("login.html"), responseBuilder); + } + + @Override + protected void doPost(HttpRequest request, Builder responseBuilder) { + Map body = request.extractUrlEncodedBody(); + if (isInvalidBody(body)) { + responseBuilder.status(Status.FOUND) + .location("/login"); return; } - resourceController.doGet(request.updatePath("login.html"), responseBuilder); + processAccountLogin(body, responseBuilder); } - private void processSessionLogin(Builder responseBuilder) { - responseBuilder.status(Status.FOUND) - .location("/index.html"); + private boolean isInvalidBody(Map body) { + return !body.containsKey("account") || + !body.containsKey("password"); } - private void processAccountLogin(HttpRequest request, HttpResponse.Builder responseBuilder) { - String account = request.parameters().get("account"); - String password = request.parameters().get("password"); + private void processAccountLogin(Map body, HttpResponse.Builder responseBuilder) { + String account = body.get("account"); + String password = body.get("password"); InMemoryUserRepository.findByAccount(account) .filter(user -> user.checkPassword(password)) diff --git a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java index 54d9a858cb..eb7986ab6a 100644 --- a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java +++ b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java @@ -23,7 +23,7 @@ protected void doGet(HttpRequest request, HttpResponse.Builder responseBuilder) @Override protected void doPost(HttpRequest request, HttpResponse.Builder responseBuilder) { Map body = request.extractUrlEncodedBody(); - if (isValidBody(body)) { + if (isInvalidBody(body)) { responseBuilder.status(Status.BAD_REQUEST); return; } @@ -34,7 +34,7 @@ protected void doPost(HttpRequest request, HttpResponse.Builder responseBuilder) postProcess(responseBuilder, body); } - private boolean isValidBody(Map body) { + private boolean isInvalidBody(Map body) { return !body.containsKey("account") || !body.containsKey("password") || !body.containsKey("email"); diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html index f4ed9de875..101b5feefb 100644 --- a/tomcat/src/main/resources/static/login.html +++ b/tomcat/src/main/resources/static/login.html @@ -1,66 +1,69 @@ - - - - - - - 로그인 - - - - -
-
-
-
-
-
-
-

로그인

-
-
-
- - -
-
- - -
-
- -
-
-
- -
-
-
-
-
-
-