diff --git a/study/build.gradle b/study/build.gradle
index 5c69542f84..87a1f0313c 100644
--- a/study/build.gradle
+++ b/study/build.gradle
@@ -19,7 +19,6 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
- implementation 'ch.qos.logback:logback-classic:1.5.7'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1'
diff --git a/study/src/main/java/cache/com/example/GreetingController.java b/study/src/main/java/cache/com/example/GreetingController.java
index c0053cda42..2416d9ac6e 100644
--- a/study/src/main/java/cache/com/example/GreetingController.java
+++ b/study/src/main/java/cache/com/example/GreetingController.java
@@ -1,12 +1,11 @@
package cache.com.example;
+import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
-import jakarta.servlet.http.HttpServletResponse;
-
@Controller
public class GreetingController {
diff --git a/study/src/main/java/cache/com/example/version/ResourceVersion.java b/study/src/main/java/cache/com/example/version/ResourceVersion.java
index 27a2f22813..215ba7a705 100644
--- a/study/src/main/java/cache/com/example/version/ResourceVersion.java
+++ b/study/src/main/java/cache/com/example/version/ResourceVersion.java
@@ -1,10 +1,9 @@
package cache.com.example.version;
-import org.springframework.stereotype.Component;
-
import jakarta.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
+import org.springframework.stereotype.Component;
@Component
public class ResourceVersion {
diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml
index 4e8655a962..e3503a5fb9 100644
--- a/study/src/main/resources/application.yml
+++ b/study/src/main/resources/application.yml
@@ -6,4 +6,5 @@ server:
accept-count: 1
max-connections: 1
threads:
+ min-spare: 2
max: 2
diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java
index e1b6cca042..8970522027 100644
--- a/study/src/test/java/study/FileTest.java
+++ b/study/src/test/java/study/FileTest.java
@@ -1,53 +1,54 @@
package study;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collections;
import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
/**
- * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
- * File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
+ * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
*/
@DisplayName("File 클래스 학습 테스트")
class FileTest {
/**
* resource 디렉터리 경로 찾기
- *
- * File 객체를 생성하려면 파일의 경로를 알아야 한다.
- * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다.
- * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까?
+ *
+ * File 객체를 생성하려면 파일의 경로를 알아야 한다. 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. resource 디렉터리의 경로는 어떻게 알아낼 수
+ * 있을까?
*/
@Test
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
final String fileName = "nextstep.txt";
// todo
- final String actual = "";
+ URL resource = getClass().getClassLoader().getResource(fileName);
+ final String actual = resource.getPath();
assertThat(actual).endsWith(fileName);
}
/**
* 파일 내용 읽기
- *
- * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
- * File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
+ *
+ * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
- void 파일의_내용을_읽는다() {
+ void 파일의_내용을_읽는다() throws IOException, URISyntaxException {
final String fileName = "nextstep.txt";
// todo
- final Path path = null;
+ URL resource = getClass().getClassLoader().getResource(fileName);
+ final Path path = Path.of(resource.toURI());
// todo
- final List actual = Collections.emptyList();
+ final List actual = Files.readAllLines(path);
assertThat(actual).containsOnly("nextstep");
}
diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java
index 47a79356b6..f874eb22c3 100644
--- a/study/src/test/java/study/IOStreamTest.java
+++ b/study/src/test/java/study/IOStreamTest.java
@@ -1,5 +1,6 @@
package study;
+import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -54,6 +55,7 @@ class OutputStream_학습_테스트 {
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
+ outputStream.write(bytes);
final String actual = outputStream.toString();
assertThat(actual).isEqualTo("nextstep");
@@ -78,7 +80,7 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
-
+ outputStream.flush();
verify(outputStream, atLeastOnce()).flush();
outputStream.close();
}
@@ -96,6 +98,9 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ try(outputStream) {
+ outputStream.flush();
+ }
verify(outputStream, atLeastOnce()).close();
}
@@ -128,7 +133,7 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
- final String actual = "";
+ final String actual = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
@@ -148,6 +153,9 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
+ try(inputStream) {
+
+ }
verify(inputStream, atLeastOnce()).close();
}
@@ -169,12 +177,13 @@ class FilterStream_학습_테스트 {
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@Test
- void 필터인_BufferedInputStream를_사용해보자() {
+ void 필터인_BufferedInputStream를_사용해보자() throws IOException {
final String text = "필터에 연결해보자.";
final InputStream inputStream = new ByteArrayInputStream(text.getBytes());
- final InputStream bufferedInputStream = null;
+ final InputStream bufferedInputStream = new BufferedInputStream(inputStream);
- final byte[] actual = new byte[0];
+ final byte[] actual = bufferedInputStream.readAllBytes();
+ bufferedInputStream.close();
assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
@@ -204,9 +213,22 @@ class InputStreamReader_학습_테스트 {
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
+ InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final StringBuilder actual = new StringBuilder();
+ try(BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
+ while (true) {
+ String read = bufferedReader.readLine();
+ if (read == null) {
+ break;
+ }
+ actual.append(read).append("\r\n");
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
assertThat(actual).hasToString(emoji);
}
}
diff --git a/tomcat/src/main/java/com/techcourse/controller/AbstractController.java b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java
new file mode 100644
index 0000000000..9601027862
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/controller/AbstractController.java
@@ -0,0 +1,26 @@
+package com.techcourse.controller;
+
+import java.io.IOException;
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.response.HttpResponse;
+
+public abstract class AbstractController implements Controller {
+
+ @Override
+ public void service(HttpRequest request, HttpResponse response) throws IOException {
+ String method = request.getRequestLine().getMethod();
+ if (method.equals("POST")) {
+ doPost(request, response);
+ } else if (method.equals("GET")) {
+ doGet(request, response);
+ } else {
+ response.setStatus405();
+ }
+ }
+
+ protected void doPost(HttpRequest request, HttpResponse response) throws IOException {
+ }
+
+ protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
+ }
+}
diff --git a/tomcat/src/main/java/com/techcourse/controller/Controller.java b/tomcat/src/main/java/com/techcourse/controller/Controller.java
new file mode 100644
index 0000000000..3e0cb98990
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/controller/Controller.java
@@ -0,0 +1,9 @@
+package com.techcourse.controller;
+
+import java.io.IOException;
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.response.HttpResponse;
+
+public interface Controller {
+ void service(HttpRequest request, HttpResponse response) throws IOException;
+}
diff --git a/tomcat/src/main/java/com/techcourse/controller/LoginController.java b/tomcat/src/main/java/com/techcourse/controller/LoginController.java
new file mode 100644
index 0000000000..184a98099e
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/controller/LoginController.java
@@ -0,0 +1,96 @@
+package com.techcourse.controller;
+
+import com.techcourse.db.InMemoryUserRepository;
+import com.techcourse.model.User;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import com.techcourse.session.Session;
+import com.techcourse.session.SessionManager;
+import com.techcourse.view.View;
+import com.techcourse.view.ViewResolver;
+import org.apache.coyote.http11.HttpCookie;
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.response.HttpResponse;
+
+public class LoginController extends AbstractController {
+
+ @Override
+ protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
+ Session session = extractSession(request);
+ if (session != null) {
+ User user = (User) session.getAttribute("user");
+ if (user != null) {
+ responseLoginSuccess(response, session);
+ return;
+ }
+ }
+ responseLoginPage(request, response);
+ }
+
+ @Override
+ protected void doPost(HttpRequest request, HttpResponse response) {
+ if (!request.hasBodyData()) {
+ throw new IllegalArgumentException("Query string is missing in the request");
+ }
+
+ Map requestFormData = request.getFormData();
+ String userName = requestFormData.get("account");
+ String password = requestFormData.get("password");
+
+ Optional account = InMemoryUserRepository.findByAccount(userName);
+ if (account.isEmpty()) {
+ responseLoginFail(response);
+ } else {
+ User user = account.get();
+ if (user.checkPassword(password)) {
+ Session session = saveSession(user);
+ responseLoginSuccess(response, session);
+ } else {
+ responseLoginFail(response);
+ }
+ }
+ }
+
+ private Session extractSession(HttpRequest request) {
+ String cookie = request.getCookie();
+ if (cookie == null) {
+ Map cookies = new HashMap<>();
+ for (String cookieParts : cookie.split(" ")) {
+ String[] keyAndValue = cookieParts.split("=");
+ cookies.put(keyAndValue[0], keyAndValue[1]);
+ }
+
+ String jsessionId = cookies.get("JSESSIONID");
+ return SessionManager.findSession(jsessionId);
+ }
+
+ return null;
+ }
+
+ private Session saveSession(User user) {
+ Session session = new Session();
+ session.setAttribute("user", user);
+ SessionManager.add(session);
+ return session;
+ }
+
+ private void responseLoginPage(HttpRequest request, HttpResponse response) throws IOException {
+ View view = ViewResolver.getView("/login.html");
+ response.setStatus200();
+ response.setResponseBody(view.getContent());
+ response.setContentType(request.getContentType());
+ }
+
+ private void responseLoginSuccess(HttpResponse response, Session session) {
+ response.setStatus302();
+ response.setLocation("/index.html");
+ response.setCookie(HttpCookie.ofJSessionId(session.getId()));
+ }
+
+ private void responseLoginFail(HttpResponse response) {
+ response.setStatus302();
+ response.setLocation("/401.html");
+ }
+}
diff --git a/tomcat/src/main/java/com/techcourse/controller/RegisterController.java b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java
new file mode 100644
index 0000000000..ad68c4e7ee
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/controller/RegisterController.java
@@ -0,0 +1,58 @@
+package com.techcourse.controller;
+
+import com.techcourse.db.InMemoryUserRepository;
+import com.techcourse.model.User;
+import java.io.IOException;
+import java.util.Map;
+import com.techcourse.view.View;
+import com.techcourse.view.ViewResolver;
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.response.HttpResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RegisterController extends AbstractController {
+
+ private static final Logger log = LoggerFactory.getLogger(RegisterController.class);
+
+ @Override
+ protected void doPost(HttpRequest request, HttpResponse response) {
+ try {
+ if (!request.hasBodyData()) {
+ throw new IllegalArgumentException("RequestBody is missing in the request");
+ }
+
+ Map requestFormData = request.getFormData();
+ String account = requestFormData.get("account");
+ String password = requestFormData.get("password");
+ String email = requestFormData.get("email");
+
+ User user = new User(account, password, email);
+ InMemoryUserRepository.save(user);
+ responseRegisterSuccess(response);
+
+ } catch (IllegalArgumentException e) {
+ response.setStatus400();
+ response.setResponseBody(e.getMessage());
+ log.info("Bad Request: {}", e.getMessage());
+
+ }
+ }
+
+ @Override
+ protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
+ responseRegisterPage(request, response);
+ }
+
+ private void responseRegisterSuccess(HttpResponse response) {
+ response.setStatus302();
+ response.setLocation("/index.html");
+ }
+
+ private void responseRegisterPage(HttpRequest request, HttpResponse response) throws IOException {
+ View view = ViewResolver.getView("/register.html");
+ response.setStatus200();
+ response.setResponseBody(view.getContent());
+ response.setContentType(request.getContentType());
+ }
+}
diff --git a/tomcat/src/main/java/com/techcourse/controller/ViewController.java b/tomcat/src/main/java/com/techcourse/controller/ViewController.java
new file mode 100644
index 0000000000..20159bf8e7
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/controller/ViewController.java
@@ -0,0 +1,50 @@
+package com.techcourse.controller;
+
+import java.io.IOException;
+import com.techcourse.view.View;
+import com.techcourse.view.ViewResolver;
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.response.HttpResponse;
+
+public class ViewController extends AbstractController {
+
+ @Override
+ protected void doPost(HttpRequest request, HttpResponse response) throws IOException {
+ responseNotFoundPage(request, response);
+ }
+
+ @Override
+ protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
+ String path = request.getRequestLine().getPath();
+
+ if (path.equals("/")) {
+ responseDefaultPage(request, response);
+ } else {
+ View view = ViewResolver.getView(path);
+ if (view == null) {
+ responseNotFoundPage(request, response);
+ } else {
+ responseResource(request, response, view);
+ }
+ }
+ }
+
+ private void responseDefaultPage(HttpRequest request, HttpResponse response) {
+ response.setStatus200();
+ response.setContentType(request.getContentType());
+ response.setResponseBody("Hello world!");
+ }
+
+ private void responseNotFoundPage(HttpRequest request, HttpResponse response) throws IOException {
+ View notFoundView = ViewResolver.getView("/404.html");
+ response.setStatus404();
+ response.setResponseBody(notFoundView.getContent());
+ response.setContentType(request.getContentType());
+ }
+
+ private void responseResource(HttpRequest request, HttpResponse response, View view) {
+ response.setStatus200();
+ response.setResponseBody(view.getContent());
+ response.setContentType(request.getContentType());
+ }
+}
diff --git a/tomcat/src/main/java/com/techcourse/handler/RequestMapping.java b/tomcat/src/main/java/com/techcourse/handler/RequestMapping.java
new file mode 100644
index 0000000000..d310fe3621
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/handler/RequestMapping.java
@@ -0,0 +1,36 @@
+package com.techcourse.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+import com.techcourse.controller.Controller;
+import com.techcourse.controller.LoginController;
+import com.techcourse.controller.RegisterController;
+import com.techcourse.controller.ViewController;
+import org.apache.coyote.http11.request.HttpRequest;
+
+public class RequestMapping {
+
+ private static final RequestMapping INSTANCE = new RequestMapping();
+
+ private final Map controllerMapping = new HashMap<>();
+
+ private RequestMapping() {
+ controllerMapping.put("/login", new LoginController());
+ controllerMapping.put("/register", new RegisterController());
+ }
+
+ public static RequestMapping getInstance() {
+ return INSTANCE;
+ }
+
+ public Controller getController(HttpRequest request) {
+ String path = request.getRequestLine().getPath();
+ Controller controller = controllerMapping.get(path);
+ if (controller == null) {
+ return new ViewController();
+ }
+
+ return controller;
+ }
+}
+
diff --git a/tomcat/src/main/java/com/techcourse/session/Session.java b/tomcat/src/main/java/com/techcourse/session/Session.java
new file mode 100644
index 0000000000..d136ac127d
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/session/Session.java
@@ -0,0 +1,28 @@
+package com.techcourse.session;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class Session {
+
+ private final String id;
+ private final Map values = new HashMap<>();
+
+ public Session() {
+ UUID uuid = UUID.randomUUID();
+ this.id = uuid.toString();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public Object getAttribute(String name) {
+ return values.get(name);
+ }
+
+ public void setAttribute(String name, Object value) {
+ values.put(name, value);
+ }
+}
diff --git a/tomcat/src/main/java/com/techcourse/session/SessionManager.java b/tomcat/src/main/java/com/techcourse/session/SessionManager.java
new file mode 100644
index 0000000000..491319a4a2
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/session/SessionManager.java
@@ -0,0 +1,20 @@
+package com.techcourse.session;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SessionManager {
+
+ private static final Map SESSIONS = new HashMap<>();
+
+ private SessionManager() {
+ }
+
+ public static void add(Session session) {
+ SESSIONS.put(session.getId(), session);
+ }
+
+ public static Session findSession(String sessionId) {
+ return SESSIONS.get(sessionId);
+ }
+}
diff --git a/tomcat/src/main/java/com/techcourse/view/View.java b/tomcat/src/main/java/com/techcourse/view/View.java
new file mode 100644
index 0000000000..d847989864
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/view/View.java
@@ -0,0 +1,14 @@
+package com.techcourse.view;
+
+public class View {
+
+ private final String content;
+
+ public View(String content) {
+ this.content = content;
+ }
+
+ public String getContent() {
+ return content;
+ }
+}
diff --git a/tomcat/src/main/java/com/techcourse/view/ViewResolver.java b/tomcat/src/main/java/com/techcourse/view/ViewResolver.java
new file mode 100644
index 0000000000..3e45b52759
--- /dev/null
+++ b/tomcat/src/main/java/com/techcourse/view/ViewResolver.java
@@ -0,0 +1,20 @@
+package com.techcourse.view;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+
+public class ViewResolver {
+
+ public static View getView(String viewName) throws IOException {
+ ClassLoader classLoader = ViewResolver.class.getClassLoader();
+ URL resource = classLoader.getResource("static" + viewName);
+ if (resource == null) {
+ return null;
+ }
+
+ File file = new File(resource.getFile());
+ return new View(new String(Files.readAllBytes(file.toPath())));
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
index bb14184757..cf870ab732 100644
--- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
+++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
@@ -1,13 +1,19 @@
package org.apache.coyote.http11;
import com.techcourse.exception.UncheckedServletException;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.Socket;
+import com.techcourse.controller.Controller;
+import com.techcourse.handler.RequestMapping;
import org.apache.coyote.Processor;
+import org.apache.coyote.http11.request.HttpRequest;
+import org.apache.coyote.http11.request.HttpRequestParser;
+import org.apache.coyote.http11.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.net.Socket;
-
public class Http11Processor implements Runnable, Processor {
private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);
@@ -27,19 +33,21 @@ public void run() {
@Override
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
final var outputStream = connection.getOutputStream()) {
- final var responseBody = "Hello world!";
+ HttpRequestParser httpRequestParser = new HttpRequestParser();
+ HttpRequest request = httpRequestParser.parseRequest(reader);
+
+ HttpResponse response = new HttpResponse();
- final var response = String.join("\r\n",
- "HTTP/1.1 200 OK ",
- "Content-Type: text/html;charset=utf-8 ",
- "Content-Length: " + responseBody.getBytes().length + " ",
- "",
- responseBody);
+ RequestMapping requestMapping = RequestMapping.getInstance();
+ Controller controller = requestMapping.getController(request);
+ controller.service(request, response);
- outputStream.write(response.getBytes());
+ outputStream.write(response.getResponse().getBytes());
outputStream.flush();
+
} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java
new file mode 100644
index 0000000000..15aafb04cf
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java
@@ -0,0 +1,24 @@
+package org.apache.coyote.http11;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class HttpCookie {
+
+ private static final String JSESSIONID = "JSESSIONID";
+
+ private Map cookies = new HashMap<>();
+
+ public static HttpCookie ofJSessionId(String sessionId) {
+ HttpCookie cookie = new HttpCookie();
+ cookie.cookies.put(JSESSIONID, sessionId);
+ return cookie;
+ }
+
+ private HttpCookie() {
+ }
+
+ public String getJsessionId() {
+ return cookies.get(JSESSIONID);
+ }
+}
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
new file mode 100644
index 0000000000..cd5d8828d5
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java
@@ -0,0 +1,40 @@
+package org.apache.coyote.http11.request;
+
+import java.util.Map;
+
+public class HttpRequest {
+
+ private final RequestLine requestLine;
+ private final RequestHeader header;
+ private final RequestBody requestBody;
+
+ public HttpRequest(RequestLine requestLine, RequestHeader header, RequestBody requestBody) {
+ this.requestLine = requestLine;
+ this.header = header;
+ this.requestBody = requestBody;
+ }
+
+ public String getHeader(String key) {
+ return header.getHeader(key);
+ }
+
+ public String getContentType() {
+ return header.getContentType();
+ }
+
+ public String getCookie() {
+ return header.getCookie();
+ }
+
+ public Map getFormData() {
+ return requestBody.getFormData();
+ }
+
+ public boolean hasBodyData() {
+ return requestBody != null;
+ }
+
+ public RequestLine getRequestLine() {
+ return requestLine;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java
new file mode 100644
index 0000000000..08e8feefe9
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java
@@ -0,0 +1,78 @@
+package org.apache.coyote.http11.request;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HttpRequestParser {
+
+ private static final Logger log = LoggerFactory.getLogger(HttpRequestParser.class);
+
+ public HttpRequest parseRequest(BufferedReader reader) throws IOException {
+ RequestLine requestLine = extractRequestLine(reader);
+ RequestHeader requestHeader = extractHeaders(reader);
+ RequestBody requestBody = extractRequestBody(reader, requestHeader);
+
+ return new HttpRequest(requestLine, requestHeader, requestBody);
+ }
+
+ private RequestLine extractRequestLine(BufferedReader reader) throws IOException {
+ String method = "";
+ String path = "";
+ String queryString = "";
+ String protocol = "";
+
+ String firstLine = reader.readLine();
+
+ if (firstLine != null) {
+ log.info("[Request] requestLine: {}", firstLine);
+
+ String[] requestLineParts = firstLine.split(" ");
+ method = requestLineParts[0];
+ String uri = requestLineParts[1];
+ protocol = requestLineParts[2];
+
+ int index = uri.indexOf("?");
+ if (index != -1) {
+ path = uri.substring(0, index);
+ queryString = uri.substring(index + 1);
+ } else {
+ path = uri;
+ queryString = null;
+ }
+ }
+
+ return new RequestLine(method, path, queryString, protocol);
+ }
+
+ private RequestHeader extractHeaders(BufferedReader reader) throws IOException {
+ Map headers = new HashMap<>();
+ while (true) {
+ String readLine = reader.readLine();
+ if (readLine == null || readLine.isEmpty()) {
+ break;
+ }
+
+ String[] headerLineParts = readLine.split(": ");
+ headers.put(headerLineParts[0], headerLineParts[1]);
+ }
+
+ log.info("[Request] header: {}", headers);
+ return new RequestHeader(headers);
+ }
+
+ private RequestBody extractRequestBody(BufferedReader reader, RequestHeader header) throws IOException {
+ try {
+ int contentLength = Integer.parseInt(header.getHeader("Content-Length"));
+ char[] buffer = new char[contentLength];
+ reader.read(buffer, 0, contentLength);
+ return new RequestBody(new String(buffer));
+
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java
new file mode 100644
index 0000000000..0a6e18a3ae
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java
@@ -0,0 +1,28 @@
+package org.apache.coyote.http11.request;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class RequestBody {
+
+ private final String content;
+
+ public RequestBody(String content) {
+ this.content = content;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public Map getFormData() {
+ Map formData = new HashMap<>();
+ String[] contentParts = content.split("&");
+ for (String keyValuePair : contentParts) {
+ String[] keyAndValue = keyValuePair.split("=");
+ formData.put(keyAndValue[0], keyAndValue[1]);
+ }
+
+ return formData;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeader.java
new file mode 100644
index 0000000000..5ba5c5deab
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeader.java
@@ -0,0 +1,24 @@
+package org.apache.coyote.http11.request;
+
+import java.util.Map;
+
+public class RequestHeader {
+
+ private final Map headers;
+
+ public RequestHeader(Map headers) {
+ this.headers = headers;
+ }
+
+ public String getHeader(String key) {
+ return headers.get(key);
+ }
+
+ public String getContentType() {
+ return headers.get("Accept").split(",")[0];
+ }
+
+ public String getCookie() {
+ return headers.get("Cookie");
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java
new file mode 100644
index 0000000000..0287df6478
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java
@@ -0,0 +1,36 @@
+package org.apache.coyote.http11.request;
+
+public class RequestLine {
+
+ private final String method;
+ private final String path;
+ private final String queryString;
+ private final String version;
+
+ public RequestLine(String method, String path, String queryString, String version) {
+ this.method = method;
+ this.path = path;
+ this.queryString = queryString;
+ this.version = version;
+ }
+
+ public boolean hasQueryString() {
+ return queryString != null;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getQueryString() {
+ return queryString;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+}
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
new file mode 100644
index 0000000000..380e0e802c
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java
@@ -0,0 +1,86 @@
+package org.apache.coyote.http11.response;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.coyote.http11.HttpCookie;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HttpResponse {
+
+ private static final Logger log = LoggerFactory.getLogger(HttpResponse.class);
+
+ private static final String LINE_SEPARATOR = System.lineSeparator();
+ private static final String LOCATION = "Location";
+ private static final String CONTENT_TYPE = "Content-Type";
+ private static final String CONTENT_LENGTH = "Content-Length";
+ private static final String COOKIE = "Set-Cookie";
+
+ private StatusLine statusLine;
+ private Map headers = new LinkedHashMap<>();
+ private String responseBody;
+
+ public void setStatus200() {
+ statusLine = new StatusLine("HTTP/1.1", "200", "OK");
+ }
+
+ public void setStatus302() {
+ statusLine = new StatusLine("HTTP/1.1", "302", "Found");
+ }
+
+ public void setStatus400() {
+ statusLine = new StatusLine("HTTP/1.1", "400", "Bad Request");
+ }
+
+ public void setStatus404() {
+ statusLine = new StatusLine("HTTP/1.1", "404", "Not Found");
+ }
+
+ public void setStatus405() {
+ statusLine = new StatusLine("HTTP/1.1", "405", "Method Not Allowed");
+ }
+
+ public void setLocation(String locationPath) {
+ headers.put(LOCATION, locationPath);
+ }
+
+ public void setContentType(String contentType) {
+ headers.put(CONTENT_TYPE, contentType);
+ }
+
+ public void setResponseBody(String content) {
+ responseBody = content;
+ headers.put(CONTENT_LENGTH, String.valueOf(responseBody.getBytes().length));
+ }
+
+ public void setCookie(HttpCookie cookie) {
+ headers.put(COOKIE, "JSESSIONID=" + cookie.getJsessionId());
+ }
+
+ public String getResponse() {
+ StringBuilder header = new StringBuilder();
+ if (headers.containsKey(LOCATION)) {
+ header.append(LOCATION + ": ").append(headers.get(LOCATION) + " ").append(LINE_SEPARATOR);
+ }
+ if (headers.containsKey(CONTENT_TYPE)) {
+ header.append(CONTENT_TYPE + ": ").append(headers.get(CONTENT_TYPE) + " ").append(LINE_SEPARATOR);
+ }
+ if (headers.containsKey(CONTENT_LENGTH)) {
+ header.append(CONTENT_LENGTH + ": ").append(headers.get(CONTENT_LENGTH) + " ").append(LINE_SEPARATOR);
+ }
+ if (headers.containsKey(COOKIE)) {
+ header.append(COOKIE + ": ").append(headers.get(COOKIE) + " ").append(LINE_SEPARATOR);
+ }
+
+ String response = String.join(LINE_SEPARATOR,
+ statusLine.getStatusLine(),
+ header.toString(),
+ responseBody
+ );
+
+ log.info("[Response] Status Line: {}", statusLine.getStatusLine());
+ log.info("[Response] Headers: {}", header);
+
+ return response;
+ }
+}
diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/StatusLine.java b/tomcat/src/main/java/org/apache/coyote/http11/response/StatusLine.java
new file mode 100644
index 0000000000..6c1d3b4389
--- /dev/null
+++ b/tomcat/src/main/java/org/apache/coyote/http11/response/StatusLine.java
@@ -0,0 +1,18 @@
+package org.apache.coyote.http11.response;
+
+public class StatusLine {
+
+ private final String version;
+ private final String statusCode;
+ private final String statusMessage;
+
+ public StatusLine(String version, String statusCode, String statusMessage) {
+ this.version = version;
+ this.statusCode = statusCode;
+ this.statusMessage = statusMessage;
+ }
+
+ public String getStatusLine() {
+ return String.join(" ", version, statusCode, statusMessage) + " ";
+ }
+}
diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html
index f4ed9de875..4db95d061f 100644
--- a/tomcat/src/main/resources/static/login.html
+++ b/tomcat/src/main/resources/static/login.html
@@ -20,7 +20,7 @@