From ff07df4d25d728e035822f5e32db79f6b1d267ed Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Thu, 7 Sep 2023 14:03:59 +0900 Subject: [PATCH 01/25] =?UTF-8?q?docs:=203,=204=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index af83630d50..b39d297741 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,12 @@ - [x] POST 방식으로 회원가입 - [x] Cookie에 JSESSIONID 값 저장하기 - [x] Session 구현하기 + +- 3단계 - 리팩터링 + - [ ] HttpRequest 클래스 구현하기 + - [ ] HttpResponse 클래스 구현하기 + - [ ] Controller 인터페이스 추가하기 + +- 4단계 - 동시성 확장하기 + - [ ] Executors로 Thread Pool 적용 + - [ ] 동시성 컬렉션 사용하기 From 0e157056583d05ef43f7d9b01123333202318d70 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Fri, 8 Sep 2023 12:07:21 +0900 Subject: [PATCH 02/25] =?UTF-8?q?refactor:=20RequestLine=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/request/RequestLine.java | 31 +++++++++++++++++ .../http11/request/RequestLineTest.java | 34 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/request/RequestLineTest.java 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..cf79d6ee9d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java @@ -0,0 +1,31 @@ +package org.apache.coyote.http11.request; + +public class RequestLine { + + private final String method; + private final String path; + private final String protocol; + + private RequestLine(final String method, final String path, final String protocol) { + this.method = method; + this.path = path; + this.protocol = protocol; + } + + public static RequestLine from(final String line) { + final String[] splitLine = line.split(" "); + return new RequestLine(splitLine[0], splitLine[1], splitLine[2]); + } + + public String getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public String getProtocol() { + return protocol; + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestLineTest.java b/tomcat/src/test/java/org/apache/coyote/http11/request/RequestLineTest.java new file mode 100644 index 0000000000..68bea5e6f4 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/request/RequestLineTest.java @@ -0,0 +1,34 @@ +package org.apache.coyote.http11.request; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class RequestLineTest { + + @Test + void 문자열을_받아_RequestLine_객체를_생성한다() { + // given + final var line = "GET / HTTP/1.1"; + final var expectedMethod = "GET"; + final var expectedPath = "/"; + final var expectedProtocol = "HTTP/1.1"; + + // when + final var actual = RequestLine.from(line); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(actual.getMethod()) + .isEqualTo(expectedMethod); + softAssertions.assertThat(actual.getPath()) + .isEqualTo(expectedPath); + softAssertions.assertThat(actual.getProtocol()) + .isEqualTo(expectedProtocol); + }); + } +} From b042c0882ce5fad107ff0fced9815f26bc4f82cc Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Fri, 8 Sep 2023 12:07:47 +0900 Subject: [PATCH 03/25] =?UTF-8?q?refactor:=20RequestHeader=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/request/RequestHeader.java | 33 +++++ .../http11/request/RequestHeaderTest.java | 118 ++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeader.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/request/RequestHeaderTest.java 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..fc06d17313 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeader.java @@ -0,0 +1,33 @@ +package org.apache.coyote.http11.request; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class RequestHeader { + + private static final String CONTENT_TYPE = "Content-Type"; + private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; + + private final Map headers; + + private RequestHeader(final Map headers) { + this.headers = headers; + } + + public static RequestHeader from(final List lines) { + final Map headers = lines.stream() + .map(it -> it.split(": ")) + .collect(Collectors.toMap(it -> it[0], it -> it[1])); + return new RequestHeader(headers); + } + + public boolean isNotFormContentType() { + return !Objects.equals(this.getValue(CONTENT_TYPE), FORM_CONTENT_TYPE); + } + + public String getValue(final String key) { + return headers.getOrDefault(key, null); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/RequestHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/http11/request/RequestHeaderTest.java new file mode 100644 index 0000000000..99c90e2634 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/request/RequestHeaderTest.java @@ -0,0 +1,118 @@ +package org.apache.coyote.http11.request; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class RequestHeaderTest { + + @Test + void 문자열_리스트를_받아_RequestHeader_객체를_생성한다() { + // given + final var line1 = "HOST: localhost:8080"; + final var line2 = "Connection-Length: 59"; + final var line3 = "Content-Type: application/x-www-form-urlencoded"; + final var line4 = "Accept: */*"; + final var lines = List.of(line1, line2, line3, line4); + + final var expectedHost = "localhost:8080"; + final var expectedConnectionLength = "59"; + final var expectedContentType = "application/x-www-form-urlencoded"; + final var expectedAccept = "*/*"; + + // when + final var actual = RequestHeader.from(lines); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(actual.getValue("HOST")) + .isEqualTo(expectedHost); + softAssertions.assertThat(actual.getValue("Connection-Length")) + .isEqualTo(expectedConnectionLength); + softAssertions.assertThat(actual.getValue("Content-Type")) + .isEqualTo(expectedContentType); + softAssertions.assertThat(actual.getValue("Accept")) + .isEqualTo(expectedAccept); + }); + } + + @Nested + class isNotFormContentType_테스트 { + + @Test + void Content_Type이_Form_형식이_아니면_true를_반환한다() { + // given + final var line1 = "HOST: localhost:8080"; + final var line2 = "Connection-Length: 59"; + final var line3 = "Content-Type: text/html"; + final var line4 = "Accept: */*"; + final var lines = List.of(line1, line2, line3, line4); + final var requestHeader = RequestHeader.from(lines); + + // when + final var actual = requestHeader.isNotFormContentType(); + + // then + assertThat(actual).isTrue(); + } + + @Test + void Content_Type이_Form_형식이면_false를_반환한다() { + // given + final var line1 = "HOST: localhost:8080"; + final var line2 = "Connection-Length: 59"; + final var line3 = "Content-Type: application/x-www-form-urlencoded"; + final var line4 = "Accept: */*"; + final var lines = List.of(line1, line2, line3, line4); + final var requestHeader = RequestHeader.from(lines); + + // when + final var actual = requestHeader.isNotFormContentType(); + + // then + assertThat(actual).isFalse(); + } + } + + @Nested + class getValue_테스트 { + + @Test + void key를_통해_해당하는_header_값을_조회한다() { + // given + final var lines = Collections.singletonList("HOST: localhost:8080"); + final var requestHeader = RequestHeader.from(lines); + final var key = "HOST"; + final var expected = "localhost:8080"; + + // when + final var actual = requestHeader.getValue(key); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void key가_존재하지_않으면_null_값을_반환한다() { + // given + final var lines = Collections.singletonList("HOST: localhost:8080"); + final var requestHeader = RequestHeader.from(lines); + final var key = "Content-Type"; + + // when + final var actual = requestHeader.getValue(key); + + // then + assertThat(actual).isNull(); + } + } +} From d20cedcb3e8b286c51746815ecbf0ddf980fba4b Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Fri, 8 Sep 2023 12:08:47 +0900 Subject: [PATCH 04/25] =?UTF-8?q?refactor:=20HttpRequest=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../apache/coyote/http11/Http11Processor.java | 53 +++------------ .../coyote/http11/request/HttpRequest.java | 67 +++++++++++++++++++ .../http11/request/HttpRequestTest.java | 59 ++++++++++++++++ 4 files changed, 136 insertions(+), 45 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/request/HttpRequestTest.java diff --git a/README.md b/README.md index b39d297741..482417a943 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - [x] Session 구현하기 - 3단계 - 리팩터링 - - [ ] HttpRequest 클래스 구현하기 + - [x] HttpRequest 클래스 구현하기 - [ ] HttpResponse 클래스 구현하기 - [ ] Controller 인터페이스 추가하기 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 616f4b60f1..d770beeadf 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -4,6 +4,7 @@ import nextstep.jwp.exception.UncheckedServletException; import nextstep.jwp.model.User; import org.apache.coyote.Processor; +import org.apache.coyote.http11.request.HttpRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,23 +14,18 @@ import java.nio.file.Files; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Optional; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); private static final SessionManager SESSION_MANAGER = new SessionManager(); - private static final String REQUEST_LINE = "Request-Line"; - private static final String COOKIE = "Cookie"; - private static final String CONTENT_LENGTH = "Content-Length"; private static final String TEXT_HTML = "text/html;"; private static final String TEXT_CSS = "text/css;"; private static final String INDEX_PAGE = "/index.html"; private static final String NOT_FOUND_PAGE = "/404.html"; private static final String HTML_EXTENSION = ".html"; - private static final String CONTENT_TYPE = "Content-Type"; - private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; + private static final String COOKIE = "Cookie"; private final Socket connection; @@ -48,15 +44,9 @@ public void process(final Socket connection) { try (final var inputStreamReader = new InputStreamReader(connection.getInputStream()); final var bufferedReader = new BufferedReader(inputStreamReader); final OutputStream outputStream = connection.getOutputStream()) { - final Map requestHeader = readRequestHeader(bufferedReader); - final String uri = requestHeader.get(REQUEST_LINE).split(" ")[1]; - final String contentType = requestHeader.get(CONTENT_TYPE); + final var httpRequest = HttpRequest.from(bufferedReader); - final String cookieHeader = requestHeader.getOrDefault(COOKIE, null); - final var cookie = HttpCookie.from(cookieHeader); - - final String requestBody = readRequestBody(bufferedReader, requestHeader, contentType); - final String response = handleRequest(uri, requestBody, cookie); + final String response = handleRequest(httpRequest); outputStream.write(response.getBytes()); outputStream.flush(); @@ -65,21 +55,12 @@ public void process(final Socket connection) { } } - private String readRequestBody(final BufferedReader bufferedReader, final Map requestHeader, - final String contentType) throws IOException { - if (!Objects.equals(contentType, FORM_CONTENT_TYPE)) { - return null; - } - final var contentLength = Integer.parseInt(requestHeader.get(CONTENT_LENGTH)); - final var buffer = new char[contentLength]; - bufferedReader.read(buffer, 0, contentLength); - final var requestBody = new String(buffer); - - log.info("Request-Body: {}", requestBody); - return requestBody; - } + private String handleRequest(final HttpRequest httpRequest) throws IOException { + final String uri = httpRequest.getRequestLine().getPath(); + final String cookieHeader = httpRequest.getRequestHeader().getValue(COOKIE); + final var cookie = HttpCookie.from(cookieHeader); + final String requestBody = httpRequest.getRequestBody(); - private String handleRequest(final String uri, final String requestBody, final HttpCookie cookie) throws IOException { final String path = uri.split("\\?")[0]; if (path.equals("/")) { @@ -210,20 +191,4 @@ private URL findResourceUrl(final String uri) { } return fileUrl; } - - private Map readRequestHeader(final BufferedReader bufferedReader) throws IOException { - final var requestHeader = new HashMap(); - - String line = bufferedReader.readLine(); - if (line == null) { - return Map.of(); - } - requestHeader.put(REQUEST_LINE, line); - - while (!"".equals(line = bufferedReader.readLine())) { - final String[] splitLine = line.split(": "); - requestHeader.put(splitLine[0], splitLine[1]); - } - return requestHeader; - } } 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..9631f95654 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -0,0 +1,67 @@ +package org.apache.coyote.http11.request; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; + +public class HttpRequest { + + private static final String CONTENT_LENGTH = "Content-Length"; + + private final RequestLine requestLine; + private final RequestHeader requestHeader; + private final String requestBody; + + private HttpRequest(final RequestLine requestLine, final RequestHeader requestHeader, final String requestBody) { + this.requestLine = requestLine; + this.requestHeader = requestHeader; + this.requestBody = requestBody; + } + + public static HttpRequest from(final BufferedReader bufferedReader) throws IOException { + final RequestLine requestLine = readRequestLine(bufferedReader); + final RequestHeader requestHeader = readRequestHeader(bufferedReader); + final String requestBody = readRequestBody(bufferedReader, requestHeader); + return new HttpRequest(requestLine, requestHeader, requestBody); + } + + private static RequestLine readRequestLine(final BufferedReader bufferedReader) throws IOException { + final String line = bufferedReader.readLine(); + if (line == null) { + return null; + } + return RequestLine.from(line); + } + + private static RequestHeader readRequestHeader(final BufferedReader bufferedReader) throws IOException { + final var lines = new ArrayList(); + String line; + while (!"".equals(line = bufferedReader.readLine())) { + lines.add(line); + } + return RequestHeader.from(lines); + } + + private static String readRequestBody(final BufferedReader bufferedReader, final RequestHeader requestHeader) + throws IOException { + if (requestHeader.isNotFormContentType()) { + return null; + } + final var contentLength = Integer.parseInt(requestHeader.getValue(CONTENT_LENGTH)); + final var buffer = new char[contentLength]; + bufferedReader.read(buffer, 0, contentLength); + return new String(buffer); + } + + public RequestLine getRequestLine() { + return requestLine; + } + + public RequestHeader getRequestHeader() { + return requestHeader; + } + + public String getRequestBody() { + return requestBody; + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/request/HttpRequestTest.java b/tomcat/src/test/java/org/apache/coyote/http11/request/HttpRequestTest.java new file mode 100644 index 0000000000..1d131859a6 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/request/HttpRequestTest.java @@ -0,0 +1,59 @@ +package org.apache.coyote.http11.request; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.stream.Collectors; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class HttpRequestTest { + + @Test + void BufferedReader_통한_입력_값으로_HttpRequest_객체를_생성한다() throws IOException { + // given + final var requestLine = "POST /register HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Content-Length: 58", + "Content-Type: application/x-www-form-urlencoded", + "Accept: */*"); + final var requestBody = "account=mango&password=password&email=mango%40woowahan.com"; + final var request = String.join("\r\n", requestLine, requestHeader, "", requestBody); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + + final var expectedRequestLine = RequestLine.from(requestLine); + final var splitRequestHeader = Arrays.stream(requestHeader.split("\r\n")) + .collect(Collectors.toList()); + final var expectedRequestHeader = RequestHeader.from(splitRequestHeader); + + // when + final var actual = HttpRequest.from(bufferedReader); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(actual.getRequestLine()) + .usingRecursiveComparison() + .isEqualTo(expectedRequestLine); + softAssertions.assertThat(actual.getRequestHeader()) + .usingRecursiveComparison() + .isEqualTo(expectedRequestHeader); + softAssertions.assertThat(actual.getRequestBody()) + .isEqualTo(requestBody); + }); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } +} From 6f254a0b3eb891f8ba1a23cbf88ef8c0865bf6da Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Fri, 8 Sep 2023 15:52:46 +0900 Subject: [PATCH 05/25] =?UTF-8?q?refactor:=20StatusLine=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/response/HttpStatus.java | 24 ++++++++++ .../coyote/http11/response/StatusLine.java | 29 ++++++++++++ .../http11/response/StatusLineTest.java | 47 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/StatusLine.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/response/StatusLineTest.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java new file mode 100644 index 0000000000..e63c45b3c3 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java @@ -0,0 +1,24 @@ +package org.apache.coyote.http11.response; + +public enum HttpStatus { + + OK(200, "OK"), + FOUND(302, "Found"), + ; + + private final int code; + private final String message; + + HttpStatus(final int code, final String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/tomcat/src/main/java/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..d07c6e5700 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/StatusLine.java @@ -0,0 +1,29 @@ +package org.apache.coyote.http11.response; + +public class StatusLine { + + private final String protocol; + private final HttpStatus httpStatus; + + private StatusLine(final String protocol, final HttpStatus httpStatus) { + this.protocol = protocol; + this.httpStatus = httpStatus; + } + + public static StatusLine of(final String protocol, final HttpStatus httpStatus) { + return new StatusLine(protocol, httpStatus); + } + + public String getProtocol() { + return protocol; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String toString() { + return String.join(" ", protocol, String.valueOf(httpStatus.getCode()), httpStatus.getMessage()); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/StatusLineTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/StatusLineTest.java new file mode 100644 index 0000000000..67388910df --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/response/StatusLineTest.java @@ -0,0 +1,47 @@ +package org.apache.coyote.http11.response; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class StatusLineTest { + + @Test + void StatusLine_객체를_생성한다() { + // given + final var protocol = "HTTP/1.1"; + final var httpStatus = HttpStatus.OK; + + // when + final var actual = StatusLine.of(protocol, httpStatus); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(actual.getProtocol()) + .isEqualTo(protocol); + softAssertions.assertThat(actual.getHttpStatus()) + .isEqualTo(httpStatus); + }); + } + + @Test + void StatusLine_객체를_메세지로_출력한다() { + // given + final var protocol = "HTTP/1.1"; + final var httpStatus = HttpStatus.OK; + final var statusLine = StatusLine.of(protocol, httpStatus); + + final var expected = "HTTP/1.1 200 OK"; + + // when + final var actual = statusLine.toString(); + + // then + assertThat(actual).isEqualTo(expected); + } +} From ab51a0927d74893cb0fe16e78b1f103fdfe08b8f Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Fri, 8 Sep 2023 15:55:49 +0900 Subject: [PATCH 06/25] =?UTF-8?q?refactor:=20ResponseHeader=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http11/response/ResponseHeader.java | 35 +++++++++++ .../http11/response/ResponseHeaderTest.java | 60 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeader.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/response/ResponseHeaderTest.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeader.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeader.java new file mode 100644 index 0000000000..a8bf5ac103 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeader.java @@ -0,0 +1,35 @@ +package org.apache.coyote.http11.response; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ResponseHeader { + + private final Map headers; + + private ResponseHeader(final Map headers) { + this.headers = headers; + } + + public static ResponseHeader createEmpty() { + return new ResponseHeader(new LinkedHashMap<>()); + } + + public void addHeader(final String key, final String value) { + headers.put(key, value); + } + + public Map getHeaders() { + return headers; + } + + @Override + public String toString() { + final StringBuilder stringBuilder = new StringBuilder(); + for (final var headerEntrySet : headers.entrySet()) { + stringBuilder.append(headerEntrySet.getKey()).append(": ").append(headerEntrySet.getValue()); + stringBuilder.append("\r\n"); + } + return stringBuilder.toString().trim(); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseHeaderTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseHeaderTest.java new file mode 100644 index 0000000000..6399fc5821 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseHeaderTest.java @@ -0,0 +1,60 @@ +package org.apache.coyote.http11.response; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ResponseHeaderTest { + + @Test + void 비어있는_ResponseHeader_객체를_생성한다() { + // given & when + final var actual = ResponseHeader.createEmpty(); + + // then + assertThat(actual.getHeaders()).isEmpty(); + } + + @Test + void 특정_header를_추가한다() { + // given + final var key = "Content-Type"; + final var value = "text/html"; + final var responseHeader = ResponseHeader.createEmpty(); + + // when + responseHeader.addHeader(key, value); + + // then + assertSoftly(softAssertions -> { + final var headers = responseHeader.getHeaders(); + softAssertions.assertThat(headers.containsKey(key)) + .isTrue(); + softAssertions.assertThat(headers.get(key)) + .isEqualTo(value); + }); + } + + @Test + void ResponseHeader_객체를_메세지로_출력한다() { + // given + final var responseHeader = ResponseHeader.createEmpty(); + responseHeader.addHeader("Content-Type", "text/html"); + responseHeader.addHeader("Content-Length", "80"); + + final var expected = String.join("\r\n", + "Content-Type: text/html", + "Content-Length: 80"); + + // when + final var actual = responseHeader.toString(); + + // then + assertThat(actual).isEqualTo(expected); + } +} From e29572fb70e05b357a8b2dc43dadf6e5f261cd70 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Fri, 8 Sep 2023 15:57:03 +0900 Subject: [PATCH 07/25] =?UTF-8?q?refactor:=20HttpResponse=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../apache/coyote/http11/Http11Processor.java | 77 ++++++++++--------- .../coyote/http11/response/HttpResponse.java | 49 ++++++++++++ .../coyote/http11/Http11ProcessorTest.java | 23 +++--- .../http11/response/HttpResponseTest.java | 63 +++++++++++++++ 5 files changed, 164 insertions(+), 50 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java diff --git a/README.md b/README.md index 482417a943..73f657c1d5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - 3단계 - 리팩터링 - [x] HttpRequest 클래스 구현하기 - - [ ] HttpResponse 클래스 구현하기 + - [x] HttpResponse 클래스 구현하기 - [ ] Controller 인터페이스 추가하기 - 4단계 - 동시성 확장하기 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 d770beeadf..d7be95aa33 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -5,6 +5,10 @@ import nextstep.jwp.model.User; import org.apache.coyote.Processor; import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpStatus; +import org.apache.coyote.http11.response.ResponseHeader; +import org.apache.coyote.http11.response.StatusLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +24,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); private static final SessionManager SESSION_MANAGER = new SessionManager(); - private static final String TEXT_HTML = "text/html;"; + private static final String TEXT_HTML = "text/html;charset=utf-8"; private static final String TEXT_CSS = "text/css;"; private static final String INDEX_PAGE = "/index.html"; private static final String NOT_FOUND_PAGE = "/404.html"; @@ -57,38 +61,34 @@ public void process(final Socket connection) { private String handleRequest(final HttpRequest httpRequest) throws IOException { final String uri = httpRequest.getRequestLine().getPath(); - final String cookieHeader = httpRequest.getRequestHeader().getValue(COOKIE); - final var cookie = HttpCookie.from(cookieHeader); - final String requestBody = httpRequest.getRequestBody(); - final String path = uri.split("\\?")[0]; if (path.equals("/")) { - return get200ResponseMessage(path, "Hello world!"); + return get200ResponseMessage(httpRequest, path, "Hello world!"); } if (path.equals("/login")) { final String responseBody = findStaticResource(path + HTML_EXTENSION); - return handleLoginRequest(uri, requestBody, responseBody, cookie); + return handleLoginRequest(httpRequest, uri, responseBody); } if (path.equals("/register")) { final String responseBody = findStaticResource(path + HTML_EXTENSION); - return handleRegisterRequest(uri, requestBody, responseBody, cookie); + return handleRegisterRequest(httpRequest, uri, responseBody); } final String responseBody = findStaticResource(path); - return get200ResponseMessage(path, responseBody); + return get200ResponseMessage(httpRequest, path, responseBody); } - private String handleRegisterRequest(final String uri, final String requestBody, final String responseBody, - final HttpCookie cookie) { + private String handleRegisterRequest(final HttpRequest httpRequest, final String uri, final String responseBody) { final String[] splitUri = uri.split("\\?"); + final String requestBody = httpRequest.getRequestBody(); if (requestBody == null && splitUri.length == 1) { - return get200ResponseMessage(splitUri[0], responseBody); + return get200ResponseMessage(httpRequest, splitUri[0], responseBody); } final Map requestBodyValues = getRequestParameters(requestBody, splitUri); final var user = new User(requestBodyValues.get("account"), requestBodyValues.get("password"), requestBodyValues.get("email")); InMemoryUserRepository.save(user); - return get302ResponseMessage(INDEX_PAGE, cookie.getJSessionId(), false); + return get302ResponseMessage(httpRequest, INDEX_PAGE, null); } private Map getRequestParameters(final String requestBody, final String[] uri) { @@ -108,29 +108,31 @@ private Map parseRequestBody(final String requestBody) { return requestBodyValues; } - private String handleLoginRequest(final String uri, final String requestBody, final String responseBody, - final HttpCookie cookie) { + private String handleLoginRequest(final HttpRequest httpRequest, final String uri, final String responseBody) { + final String cookieHeader = httpRequest.getRequestHeader().getValue(COOKIE); + final var cookie = HttpCookie.from(cookieHeader); final String[] splitUri = uri.split("\\?"); + final String requestBody = httpRequest.getRequestBody(); final User user = findUserBySessionId(cookie.getJSessionId()); if (user != null) { log.info("User: {}", user); - return get302ResponseMessage(INDEX_PAGE, cookie.getJSessionId(), false); + return get302ResponseMessage(httpRequest, INDEX_PAGE, null); } if (requestBody == null && splitUri.length == 1) { - return get200ResponseMessage(splitUri[0], responseBody); + return get200ResponseMessage(httpRequest, splitUri[0], responseBody); } - return handleFirstLogin(requestBody, cookie, splitUri); + return handleFirstLogin(httpRequest, requestBody, splitUri); } - private String handleFirstLogin(final String requestBody, final HttpCookie cookie, final String[] uri) { + private String handleFirstLogin(final HttpRequest httpRequest, final String requestBody, final String[] uri) { final Map requestBodyValues = getRequestParameters(requestBody, uri); final Optional user = InMemoryUserRepository.findByAccount(requestBodyValues.get("account")); if (user.isEmpty() || !user.get().checkPassword(requestBodyValues.get("password"))) { - return get302ResponseMessage("/401.html", cookie.getJSessionId(), false); + return get302ResponseMessage(httpRequest, "/401.html", null); } final String sessionId = addSession(user.get()); log.info("User: {}", user.get()); - return get302ResponseMessage(INDEX_PAGE, sessionId, true); + return get302ResponseMessage(httpRequest, INDEX_PAGE, sessionId); } private String addSession(final User user) { @@ -149,14 +151,14 @@ private User findUserBySessionId(final String sessionId) { return (User) session.getAttribute("user"); } - private String get200ResponseMessage(final String path, final String responseBody) { - final String contentType = getContentType(path); - return String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: " + contentType + "charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); + private String get200ResponseMessage(final HttpRequest httpRequest, final String path, final String responseBody) { + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK); + final var responseHeader = ResponseHeader.createEmpty(); + responseHeader.addHeader("Content-Type", getContentType(path)); + responseHeader.addHeader("Content-Length", String.valueOf(responseBody.getBytes().length)); + + final var httpResponse = HttpResponse.createGetResponse(statusLine, responseHeader, responseBody); + return httpResponse.toString(); } private String getContentType(final String path) { @@ -166,16 +168,15 @@ private String getContentType(final String path) { return TEXT_HTML; } - private String get302ResponseMessage(final String location, final String sessionId, final boolean setCookie) { - if (setCookie) { - return String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: " + location + " ", - "Set-Cookie: JSESSIONID=" + sessionId); + private String get302ResponseMessage(final HttpRequest httpRequest, final String location, final String newSessionId) { + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.FOUND); + final var responseHeader = ResponseHeader.createEmpty(); + responseHeader.addHeader("Location", location); + if (newSessionId != null) { + responseHeader.addHeader("Set-Cookie", "JSESSIONID=" + newSessionId); } - return String.join("\r\n", - "HTTP/1.1 302 Found ", - "Location: " + location); + final var httpResponse = HttpResponse.createPostResponse(statusLine, responseHeader); + return httpResponse.toString(); } private String findStaticResource(final String uri) throws IOException { 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..7425178851 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -0,0 +1,49 @@ +package org.apache.coyote.http11.response; + +public class HttpResponse { + + private final StatusLine statusLine; + private final ResponseHeader responseHeader; + private final String responseBody; + + private HttpResponse(final StatusLine statusLine, final ResponseHeader responseHeader, final String responseBody) { + this.statusLine = statusLine; + this.responseHeader = responseHeader; + this.responseBody = responseBody; + } + + public static HttpResponse createGetResponse(final StatusLine statusLine, final ResponseHeader responseHeader, + final String responseBody) { + return new HttpResponse(statusLine, responseHeader, responseBody); + } + + public static HttpResponse createPostResponse(final StatusLine statusLine, final ResponseHeader responseHeader) { + return new HttpResponse(statusLine, responseHeader, null); + } + + public StatusLine getStatusLine() { + return statusLine; + } + + public ResponseHeader getResponseHeader() { + return responseHeader; + } + + public String getResponseBody() { + return responseBody; + } + + @Override + public String toString() { + if (responseBody == null) { + return String.join("\r\n", + statusLine.toString(), + responseHeader.toString()); + } + return String.join("\r\n", + statusLine.toString(), + responseHeader.toString(), + "", + responseBody); + } +} diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java index 512b919f09..833b6406cf 100644 --- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java @@ -1,13 +1,14 @@ package nextstep.org.apache.coyote.http11; -import support.StubSocket; import org.apache.coyote.http11.Http11Processor; import org.junit.jupiter.api.Test; +import support.StubSocket; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; +import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; @@ -24,9 +25,9 @@ void process() { // then var expected = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 12 ", + "HTTP/1.1 200 OK", + "Content-Type: text/html;charset=utf-8", + "Content-Length: 12", "", "Hello world!"); @@ -37,9 +38,9 @@ void process() { void index() throws IOException { // given final String httpRequest= String.join("\r\n", - "GET /index.html HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", + "GET /index.html HTTP/1.1", + "Host: localhost:8080", + "Connection: keep-alive", "", ""); @@ -51,11 +52,11 @@ void index() throws IOException { // then final URL resource = getClass().getClassLoader().getResource("static/index.html"); - var expected = "HTTP/1.1 200 OK \r\n" + - "Content-Type: text/html;charset=utf-8 \r\n" + - "Content-Length: 5564 \r\n" + + var expected = "HTTP/1.1 200 OK\r\n" + + "Content-Type: text/html;charset=utf-8\r\n" + + "Content-Length: 5564\r\n" + "\r\n"+ - new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + new String(Files.readAllBytes(new File(Objects.requireNonNull(resource).getFile()).toPath())); assertThat(socket.output()).isEqualTo(expected); } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java new file mode 100644 index 0000000000..cee024bea7 --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java @@ -0,0 +1,63 @@ +package org.apache.coyote.http11.response; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class HttpResponseTest { + + @Nested + class 객체_생성_테스트 { + + @Test + void GET_메서드에_대한_HttpResponse_객체를_생성한다() { + // given + final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var responseHeader = ResponseHeader.createEmpty(); + responseHeader.addHeader("Content-Type", "text/html"); + responseHeader.addHeader("Content-Length", "12"); + final var responseBody = "Hello World!"; + + // when + final var actual = HttpResponse.createGetResponse(statusLine, responseHeader, responseBody); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(actual.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(statusLine); + softAssertions.assertThat(actual.getResponseHeader()) + .usingRecursiveComparison() + .isEqualTo(responseHeader); + softAssertions.assertThat(actual.getResponseBody()) + .isEqualTo(responseBody); + }); + } + + @Test + void POST_메서드에_대한_HttpResponse_객체를_생성한다() { + // given + final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var responseHeader = ResponseHeader.createEmpty(); + responseHeader.addHeader("Location", "/index.html"); + + // when + final var actual = HttpResponse.createPostResponse(statusLine, responseHeader); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(actual.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(statusLine); + softAssertions.assertThat(actual.getResponseHeader()) + .usingRecursiveComparison() + .isEqualTo(responseHeader); + }); + } + } +} From 9c8a92363550def2f08797ec206192c68ea6fa50 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 16:49:44 +0900 Subject: [PATCH 08/25] =?UTF-8?q?feat:=20ResponseBody=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/response/HttpResponse.java | 11 ++-- .../coyote/http11/response/ResponseBody.java | 38 ++++++++++++++ .../http11/response/HttpResponseTest.java | 3 +- .../http11/response/ResponseBodyTest.java | 50 +++++++++++++++++++ 4 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/response/ResponseBodyTest.java 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 7425178851..5b7c6ea94d 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 @@ -4,16 +4,17 @@ public class HttpResponse { private final StatusLine statusLine; private final ResponseHeader responseHeader; - private final String responseBody; + private final ResponseBody responseBody; - private HttpResponse(final StatusLine statusLine, final ResponseHeader responseHeader, final String responseBody) { + private HttpResponse(final StatusLine statusLine, final ResponseHeader responseHeader, + final ResponseBody responseBody) { this.statusLine = statusLine; this.responseHeader = responseHeader; this.responseBody = responseBody; } public static HttpResponse createGetResponse(final StatusLine statusLine, final ResponseHeader responseHeader, - final String responseBody) { + final ResponseBody responseBody) { return new HttpResponse(statusLine, responseHeader, responseBody); } @@ -29,7 +30,7 @@ public ResponseHeader getResponseHeader() { return responseHeader; } - public String getResponseBody() { + public ResponseBody getResponseBody() { return responseBody; } @@ -44,6 +45,6 @@ public String toString() { statusLine.toString(), responseHeader.toString(), "", - responseBody); + responseBody.getBody()); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java new file mode 100644 index 0000000000..1ceb413326 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java @@ -0,0 +1,38 @@ +package org.apache.coyote.http11.response; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; + +public class ResponseBody { + + private final String body; + + private ResponseBody(final String body) { + this.body = body; + } + + public static ResponseBody fromUri(final String uri) throws IOException { + final URL fileUrl = findResourceUrl(uri); + final String filePath = fileUrl.getPath(); + final String body = Files.readString(new File(filePath).toPath()); + return new ResponseBody(body); + } + + public static ResponseBody fromText(final String text) { + return new ResponseBody(text); + } + + private static URL findResourceUrl(final String uri) { + final URL fileUrl = ResponseBody.class.getClassLoader().getResource("./static" + uri); + if (fileUrl == null) { + return ResponseBody.class.getClassLoader().getResource("./static/404.html"); + } + return fileUrl; + } + + public String getBody() { + return body; + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java index cee024bea7..e43072816f 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java @@ -21,7 +21,7 @@ class 객체_생성_테스트 { final var responseHeader = ResponseHeader.createEmpty(); responseHeader.addHeader("Content-Type", "text/html"); responseHeader.addHeader("Content-Length", "12"); - final var responseBody = "Hello World!"; + final var responseBody = ResponseBody.fromText("Hello World!"); // when final var actual = HttpResponse.createGetResponse(statusLine, responseHeader, responseBody); @@ -35,6 +35,7 @@ class 객체_생성_테스트 { .usingRecursiveComparison() .isEqualTo(responseHeader); softAssertions.assertThat(actual.getResponseBody()) + .usingRecursiveComparison() .isEqualTo(responseBody); }); } diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseBodyTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseBodyTest.java new file mode 100644 index 0000000000..b041d257de --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/response/ResponseBodyTest.java @@ -0,0 +1,50 @@ +package org.apache.coyote.http11.response; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ResponseBodyTest { + + @Nested + class 객체_생성_테스트 { + + @Test + void URI로_ResponseBody_객체를_생성한다() throws IOException { + // given + final var uri = "/index.html"; + + final var fileUrl = ResponseBody.class.getClassLoader().getResource("./static" + uri); + final var filePath = Objects.requireNonNull(fileUrl).getPath(); + final var expected = Files.readString(new File(filePath).toPath()); + + // when + final var actual = ResponseBody.fromUri(uri); + + // then + assertThat(actual.getBody()).isEqualTo(expected); + } + + @Test + void text로_ResponseBody_객체를_생성한다() { + // given + final var text = "Hello World!"; + + // when + final var actual = ResponseBody.fromText(text); + + // then + assertThat(actual.getBody()).isEqualTo(text); + } + } +} From 930fb1039e3c45fe8259f6dba1d9678b19c6be16 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 16:51:17 +0900 Subject: [PATCH 09/25] =?UTF-8?q?test:=20HttpResponse=20toString=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http11/response/HttpResponseTest.java | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java index e43072816f..3aa4ab740f 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @SuppressWarnings("NonAsciiCharacters") @@ -43,7 +44,7 @@ class 객체_생성_테스트 { @Test void POST_메서드에_대한_HttpResponse_객체를_생성한다() { // given - final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.FOUND); final var responseHeader = ResponseHeader.createEmpty(); responseHeader.addHeader("Location", "/index.html"); @@ -61,4 +62,51 @@ class 객체_생성_테스트 { }); } } + + @Nested + class toString_테스트 { + + @Test + void ResponseBody가_없는_경우의_HttpResponse_메세지를_출력한다() { + // given + final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.FOUND); + final var responseHeader = ResponseHeader.createEmpty(); + responseHeader.addHeader("Location", "/index.html"); + final var httpResponse = HttpResponse.createPostResponse(statusLine, responseHeader); + + final var expected = String.join("\r\n", + "HTTP/1.1 302 Found", + "Location: /index.html"); + + // when + final var actual = httpResponse.toString(); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void ResponseBody가_있는_경우의_HttpResponse_메세지를_출력한다() { + // given + final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var responseHeader = ResponseHeader.createEmpty(); + responseHeader.addHeader("Content-Type", "text/html"); + responseHeader.addHeader("Content-Length", "12"); + final var responseBody = ResponseBody.fromText("Hello World!"); + final var httpResponse = HttpResponse.createGetResponse(statusLine, responseHeader, responseBody); + + final var expected = String.join("\r\n", + "HTTP/1.1 200 OK", + "Content-Type: text/html", + "Content-Length: 12", + "", + "Hello World!"); + + // when + final var actual = httpResponse.toString(); + + // then + assertThat(actual).isEqualTo(expected); + } + } } From 8214f8a36dc6f2702273ed9c337e24ba8c64b90d Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 17:52:35 +0900 Subject: [PATCH 10/25] =?UTF-8?q?feat:=20HttpResponse=20=EB=B9=88=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/response/HttpResponse.java | 25 +++--- .../http11/response/HttpResponseTest.java | 78 ++++++------------- 2 files changed, 38 insertions(+), 65 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 5b7c6ea94d..087bdc0472 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 @@ -2,9 +2,9 @@ public class HttpResponse { - private final StatusLine statusLine; + private StatusLine statusLine; private final ResponseHeader responseHeader; - private final ResponseBody responseBody; + private ResponseBody responseBody; private HttpResponse(final StatusLine statusLine, final ResponseHeader responseHeader, final ResponseBody responseBody) { @@ -13,13 +13,8 @@ private HttpResponse(final StatusLine statusLine, final ResponseHeader responseH this.responseBody = responseBody; } - public static HttpResponse createGetResponse(final StatusLine statusLine, final ResponseHeader responseHeader, - final ResponseBody responseBody) { - return new HttpResponse(statusLine, responseHeader, responseBody); - } - - public static HttpResponse createPostResponse(final StatusLine statusLine, final ResponseHeader responseHeader) { - return new HttpResponse(statusLine, responseHeader, null); + public static HttpResponse createEmpty() { + return new HttpResponse(null, ResponseHeader.createEmpty(), null); } public StatusLine getStatusLine() { @@ -34,6 +29,18 @@ public ResponseBody getResponseBody() { return responseBody; } + public void setStatusLine(final StatusLine statusLine) { + this.statusLine = statusLine; + } + + public void addResponseHeader(final String key, final String value) { + this.responseHeader.addHeader(key, value); + } + + public void setResponseBody(final ResponseBody responseBody) { + this.responseBody = responseBody; + } + @Override public String toString() { if (responseBody == null) { diff --git a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java index 3aa4ab740f..82cc6b670c 100644 --- a/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java +++ b/tomcat/src/test/java/org/apache/coyote/http11/response/HttpResponseTest.java @@ -12,55 +12,20 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class HttpResponseTest { - @Nested - class 객체_생성_테스트 { - - @Test - void GET_메서드에_대한_HttpResponse_객체를_생성한다() { - // given - final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); - final var responseHeader = ResponseHeader.createEmpty(); - responseHeader.addHeader("Content-Type", "text/html"); - responseHeader.addHeader("Content-Length", "12"); - final var responseBody = ResponseBody.fromText("Hello World!"); - - // when - final var actual = HttpResponse.createGetResponse(statusLine, responseHeader, responseBody); - - // then - assertSoftly(softAssertions -> { - softAssertions.assertThat(actual.getStatusLine()) - .usingRecursiveComparison() - .isEqualTo(statusLine); - softAssertions.assertThat(actual.getResponseHeader()) - .usingRecursiveComparison() - .isEqualTo(responseHeader); - softAssertions.assertThat(actual.getResponseBody()) - .usingRecursiveComparison() - .isEqualTo(responseBody); - }); - } - - @Test - void POST_메서드에_대한_HttpResponse_객체를_생성한다() { - // given - final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.FOUND); - final var responseHeader = ResponseHeader.createEmpty(); - responseHeader.addHeader("Location", "/index.html"); - - // when - final var actual = HttpResponse.createPostResponse(statusLine, responseHeader); - - // then - assertSoftly(softAssertions -> { - softAssertions.assertThat(actual.getStatusLine()) - .usingRecursiveComparison() - .isEqualTo(statusLine); - softAssertions.assertThat(actual.getResponseHeader()) - .usingRecursiveComparison() - .isEqualTo(responseHeader); - }); - } + @Test + void 비어있는_HttpResponse_객체를_생성한다() { + // given & when + final var actual = HttpResponse.createEmpty(); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(actual.getStatusLine()) + .isNull(); + softAssertions.assertThat(actual.getResponseHeader().getHeaders()) + .isEmpty(); + softAssertions.assertThat(actual.getResponseBody()) + .isNull(); + }); } @Nested @@ -69,10 +34,10 @@ class toString_테스트 { @Test void ResponseBody가_없는_경우의_HttpResponse_메세지를_출력한다() { // given + final var httpResponse = HttpResponse.createEmpty(); final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.FOUND); - final var responseHeader = ResponseHeader.createEmpty(); - responseHeader.addHeader("Location", "/index.html"); - final var httpResponse = HttpResponse.createPostResponse(statusLine, responseHeader); + httpResponse.setStatusLine(statusLine); + httpResponse.addResponseHeader("Location", "/index.html"); final var expected = String.join("\r\n", "HTTP/1.1 302 Found", @@ -88,12 +53,13 @@ class toString_테스트 { @Test void ResponseBody가_있는_경우의_HttpResponse_메세지를_출력한다() { // given + final var httpResponse = HttpResponse.createEmpty(); final var statusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); - final var responseHeader = ResponseHeader.createEmpty(); - responseHeader.addHeader("Content-Type", "text/html"); - responseHeader.addHeader("Content-Length", "12"); + httpResponse.setStatusLine(statusLine); + httpResponse.addResponseHeader("Content-Type", "text/html"); + httpResponse.addResponseHeader("Content-Length", "12"); final var responseBody = ResponseBody.fromText("Hello World!"); - final var httpResponse = HttpResponse.createGetResponse(statusLine, responseHeader, responseBody); + httpResponse.setResponseBody(responseBody); final var expected = String.join("\r\n", "HTTP/1.1 200 OK", From cd9bd0253dda121b279ea72c4e43230205ee3bbf Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 19:02:53 +0900 Subject: [PATCH 11/25] =?UTF-8?q?feat:=20Controller=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/AbstractController.java | 59 +++++++++++++++++++ .../nextstep/jwp/controller/Controller.java | 11 ++++ .../jwp/exception/HttpRequestException.java | 8 +++ 3 files changed, 78 insertions(+) create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/Controller.java create mode 100644 tomcat/src/main/java/nextstep/jwp/exception/HttpRequestException.java diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java new file mode 100644 index 0000000000..a6455dddbd --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -0,0 +1,59 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.exception.HttpRequestException; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public abstract class AbstractController implements Controller { + + protected static final String TEXT_HTML = "text/html;charset=utf-8"; + protected static final String TEXT_CSS = "text/css;"; + protected static final String INDEX_PAGE = "/index.html"; + protected static final String UNAUTHORIZED_PAGE = "/401.html"; + protected static final String HEADER_COOKIE = "Cookie"; + protected static final String HEADER_LOCATION = "Location"; + protected static final String HEADER_SET_COOKIE = "Set-Cookie"; + protected static final String HEADER_CONTENT_TYPE = "Content-Type"; + protected static final String HEADER_CONTENT_LENGTH = "Content-Length"; + protected static final String HTTP_METHOD_EXCEPTION_MESSAGE = "올바르지 않은 HTTP Method 입니다."; + + @Override + public void service(final HttpRequest request, final HttpResponse response) throws IOException { + if (request.getRequestLine().getMethod().equals("POST")) { + doPost(request, response); + return; + } + if (request.getRequestLine().getMethod().equals("GET")) { + doGet(request, response); + return; + } + throw new HttpRequestException(HTTP_METHOD_EXCEPTION_MESSAGE); + } + + protected abstract void doPost(final HttpRequest request, final HttpResponse response) throws IOException; + + protected abstract void doGet(final HttpRequest request, final HttpResponse response) throws IOException; + + protected Map getRequestParameters(final HttpRequest request) { + final String[] uri = request.getRequestLine().getPath().split("\\?"); + final String requestBody = request.getRequestBody(); + if (requestBody == null) { + return parseRequestBody(uri[1]); + } + return parseRequestBody(requestBody); + } + + protected Map parseRequestBody(final String requestBody) { + final var requestBodyValues = new HashMap(); + final String[] splitRequestBody = requestBody.split("&"); + for (var value : splitRequestBody) { + final String[] splitValue = value.split("="); + requestBodyValues.put(splitValue[0], splitValue[1]); + } + return requestBodyValues; + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/Controller.java b/tomcat/src/main/java/nextstep/jwp/controller/Controller.java new file mode 100644 index 0000000000..c5ffa07fb4 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/Controller.java @@ -0,0 +1,11 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +import java.io.IOException; + +public interface Controller { + + void service(final HttpRequest request, final HttpResponse httpResponse) throws IOException; +} diff --git a/tomcat/src/main/java/nextstep/jwp/exception/HttpRequestException.java b/tomcat/src/main/java/nextstep/jwp/exception/HttpRequestException.java new file mode 100644 index 0000000000..236f600558 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/exception/HttpRequestException.java @@ -0,0 +1,8 @@ +package nextstep.jwp.exception; + +public class HttpRequestException extends RuntimeException { + + public HttpRequestException(final String message) { + super(message); + } +} From 1dca664554e184e10ba1e1f34c148a39fb694e66 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 19:03:45 +0900 Subject: [PATCH 12/25] =?UTF-8?q?feat:=20HomeController=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/HomeController.java | 26 ++++++ .../jwp/controller/HomeControllerTest.java | 91 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/HomeController.java create mode 100644 tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java diff --git a/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java new file mode 100644 index 0000000000..73de990131 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java @@ -0,0 +1,26 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.exception.HttpRequestException; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpStatus; +import org.apache.coyote.http11.response.ResponseBody; +import org.apache.coyote.http11.response.StatusLine; + +public class HomeController extends AbstractController { + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + throw new HttpRequestException(HTTP_METHOD_EXCEPTION_MESSAGE); + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) { + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); + final var responseBody = ResponseBody.fromText("Hello world!"); + response.setStatusLine(statusLine); + response.addResponseHeader("Content-Type", TEXT_HTML); + response.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); + response.setResponseBody(responseBody); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java new file mode 100644 index 0000000000..f3cdc3f0d0 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java @@ -0,0 +1,91 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.exception.HttpRequestException; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.*; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class HomeControllerTest { + + @Test + void POST_요청이_들어오면_예외를_반환한다() throws IOException { + // given + final var homeController = new HomeController(); + final var requestLine = "POST / HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Content-Length: 58", + "Content-Type: application/x-www-form-urlencoded", + "Accept: */*"); + final var requestBody = "account=mango&password=password&email=mango%40woowahan.com"; + final var request = String.join("\r\n", requestLine, requestHeader, "", requestBody); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + // when & then + assertThatThrownBy(() -> homeController.doPost(httpRequest, httpResponse)) + .isInstanceOf(HttpRequestException.class); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + + @Test + void GET_요청이_들어오면_HttpResponse에_응답을_처리한다() throws IOException { + // given + final var homeController = new HomeController(); + final var requestLine = "GET / HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var expectedResponseHeader = ResponseHeader.createEmpty(); + final var expectedResponseBody = ResponseBody.fromText("Hello world!"); + expectedResponseHeader.addHeader("Content-Type", "text/html;charset=utf-8"); + expectedResponseHeader.addHeader("Content-Length", String.valueOf(expectedResponseBody.getBody().getBytes().length)); + + // when + homeController.doGet(httpRequest, httpResponse); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); + softAssertions.assertThat(httpResponse.getResponseHeader()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseHeader); + softAssertions.assertThat(httpResponse.getResponseBody()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseBody); + }); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } +} From 2cc44492237181eca80ae9d86a11fed75ee0abf0 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 19:04:02 +0900 Subject: [PATCH 13/25] =?UTF-8?q?feat:=20ResourceController=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/ResourceController.java | 36 ++++++++ .../controller/ResourceControllerTest.java | 91 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java create mode 100644 tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java diff --git a/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java new file mode 100644 index 0000000000..53899996aa --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java @@ -0,0 +1,36 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.exception.HttpRequestException; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpStatus; +import org.apache.coyote.http11.response.ResponseBody; +import org.apache.coyote.http11.response.StatusLine; + +import java.io.IOException; + +public class ResourceController extends AbstractController { + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + throw new HttpRequestException(HTTP_METHOD_EXCEPTION_MESSAGE); + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws IOException { + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); + final String uri = request.getRequestLine().getPath().split("\\?")[0]; + final var responseBody = ResponseBody.fromUri(uri); + response.setStatusLine(statusLine); + response.addResponseHeader("Content-Type", getContentType(uri)); + response.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); + response.setResponseBody(responseBody); + } + + private String getContentType(final String uri) { + if (uri.endsWith(".css")) { + return TEXT_CSS; + } + return TEXT_HTML; + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java new file mode 100644 index 0000000000..a435546546 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java @@ -0,0 +1,91 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.exception.HttpRequestException; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.*; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ResourceControllerTest { + + @Test + void POST_요청이_들어오면_예외를_반환한다() throws IOException { + // given + final var resourceController = new ResourceController(); + final var requestLine = "POST /index.html HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Content-Length: 58", + "Content-Type: application/x-www-form-urlencoded", + "Accept: */*"); + final var requestBody = "account=mango&password=password&email=mango%40woowahan.com"; + final var request = String.join("\r\n", requestLine, requestHeader, "", requestBody); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + // when & then + assertThatThrownBy(() -> resourceController.doPost(httpRequest, httpResponse)) + .isInstanceOf(HttpRequestException.class); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + + @Test + void GET_요청이_들어오면_HttpResponse에_응답을_처리한다() throws IOException { + // given + final var resourceController = new ResourceController(); + final var requestLine = "GET /index.html HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var expectedResponseHeader = ResponseHeader.createEmpty(); + final var expectedResponseBody = ResponseBody.fromUri("/index.html"); + expectedResponseHeader.addHeader("Content-Type", "text/html;charset=utf-8"); + expectedResponseHeader.addHeader("Content-Length", String.valueOf(expectedResponseBody.getBody().getBytes().length)); + + // when + resourceController.doGet(httpRequest, httpResponse); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); + softAssertions.assertThat(httpResponse.getResponseHeader()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseHeader); + softAssertions.assertThat(httpResponse.getResponseBody()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseBody); + }); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } +} From 38f1da80d80534629a24fd1200f844dea6b46c3e Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 19:04:15 +0900 Subject: [PATCH 14/25] =?UTF-8?q?feat:=20RegisterController=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/RegisterController.java | 37 +++++++ .../controller/RegisterControllerTest.java | 102 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java create mode 100644 tomcat/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java new file mode 100644 index 0000000000..4287406652 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -0,0 +1,37 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpStatus; +import org.apache.coyote.http11.response.ResponseBody; +import org.apache.coyote.http11.response.StatusLine; + +import java.io.IOException; +import java.util.Map; + +public class RegisterController extends AbstractController { + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + final Map requestBodyValues = getRequestParameters(request); + final var user = new User(requestBodyValues.get("account"), requestBodyValues.get("password"), + requestBodyValues.get("email")); + InMemoryUserRepository.save(user); + + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.FOUND); + response.setStatusLine(statusLine); + response.addResponseHeader("Location", INDEX_PAGE); + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws IOException { + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); + final var responseBody = ResponseBody.fromUri("/register.html"); + response.setStatusLine(statusLine); + response.addResponseHeader("Content-Type", TEXT_HTML); + response.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); + response.setResponseBody(responseBody); + } +} diff --git a/tomcat/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java new file mode 100644 index 0000000000..99f5876d31 --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java @@ -0,0 +1,102 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.*; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class RegisterControllerTest { + + @Test + void POST_요청이_들어오면_HttpResponse에_응답을_처리한다() throws IOException { + // given + final var registerController = new RegisterController(); + final var requestLine = "POST /register HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Content-Length: 58", + "Content-Type: application/x-www-form-urlencoded", + "Accept: */*"); + final var requestBody = "account=mango&password=password&email=mango%40woowahan.com"; + final var request = String.join("\r\n", requestLine, requestHeader, "", requestBody); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.FOUND); + final var expectedResponseHeader = ResponseHeader.createEmpty(); + expectedResponseHeader.addHeader("Location", "/index.html"); + + // when + registerController.doPost(httpRequest, httpResponse); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); + softAssertions.assertThat(httpResponse.getResponseHeader()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseHeader); + }); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + + @Test + void GET_요청이_들어오면_HttpResponse에_응답을_처리한다() throws IOException { + // given + final var registerController = new RegisterController(); + final var requestLine = "GET /register HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var expectedResponseHeader = ResponseHeader.createEmpty(); + final var expectedResponseBody = ResponseBody.fromUri("/register.html"); + expectedResponseHeader.addHeader("Content-Type", "text/html;charset=utf-8"); + expectedResponseHeader.addHeader("Content-Length", String.valueOf(expectedResponseBody.getBody().getBytes().length)); + + // when + registerController.doGet(httpRequest, httpResponse); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); + softAssertions.assertThat(httpResponse.getResponseHeader()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseHeader); + softAssertions.assertThat(httpResponse.getResponseBody()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseBody); + }); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } +} From 78cf2d516baeac860c8ca3b3f3ccb7433023553f Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Sun, 10 Sep 2023 19:16:53 +0900 Subject: [PATCH 15/25] =?UTF-8?q?feat:=20LoginController=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/LoginController.java | 80 +++++++++ .../apache/coyote/http11/Http11Processor.java | 159 +++--------------- .../jwp/controller/LoginControllerTest.java | 101 +++++++++++ 3 files changed, 201 insertions(+), 139 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/jwp/controller/LoginController.java create mode 100644 tomcat/src/test/java/nextstep/jwp/controller/LoginControllerTest.java diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java new file mode 100644 index 0000000000..ac2beaaf91 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -0,0 +1,80 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.coyote.http11.HttpCookie; +import org.apache.coyote.http11.Session; +import org.apache.coyote.http11.SessionManager; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpStatus; +import org.apache.coyote.http11.response.ResponseBody; +import org.apache.coyote.http11.response.StatusLine; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +public class LoginController extends AbstractController { + + private static final SessionManager SESSION_MANAGER = new SessionManager(); + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + final String cookieHeader = request.getRequestHeader().getValue(HEADER_COOKIE); + final var cookie = HttpCookie.from(cookieHeader); + final User user = findUserBySessionId(cookie.getJSessionId()); + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.FOUND); + response.setStatusLine(statusLine); + if (user == null) { + handleFirstLogin(request, response); + return; + } + response.addResponseHeader(HEADER_LOCATION, INDEX_PAGE); + } + + private void handleFirstLogin(final HttpRequest request, final HttpResponse response) { + final Map requestBodyValues = getRequestParameters(request); + final Optional user = InMemoryUserRepository.findByAccount(requestBodyValues.get("account")); + if (user.isEmpty() || !user.get().checkPassword(requestBodyValues.get("password"))) { + response.addResponseHeader(HEADER_LOCATION, UNAUTHORIZED_PAGE); + return; + } + final String sessionId = addSession(user.get()); + response.addResponseHeader(HEADER_LOCATION, INDEX_PAGE); + response.addResponseHeader(HEADER_SET_COOKIE, "JSESSIONID=" + sessionId); + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws IOException { + final String cookieHeader = request.getRequestHeader().getValue(HEADER_COOKIE); + final var cookie = HttpCookie.from(cookieHeader); + final User user = findUserBySessionId(cookie.getJSessionId()); + if (user != null) { + doPost(request, response); + return; + } + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); + final var responseBody = ResponseBody.fromUri("/login.html"); + response.setStatusLine(statusLine); + response.addResponseHeader(HEADER_CONTENT_TYPE, TEXT_HTML); + response.addResponseHeader(HEADER_CONTENT_LENGTH, String.valueOf(responseBody.getBody().getBytes().length)); + response.setResponseBody(responseBody); + } + + private User findUserBySessionId(final String sessionId) { + if (sessionId == null) { + return null; + } + final Session session = SESSION_MANAGER.findSession(sessionId) + .orElseGet(Session::create); + return (User) session.getAttribute("user"); + } + + private String addSession(final User user) { + final var session = Session.create(); + session.setAttribute("user", user); + SESSION_MANAGER.add(session); + return session.getId(); + } +} 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 d7be95aa33..d6dbe9f35f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,35 +1,25 @@ package org.apache.coyote.http11; -import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.controller.HomeController; +import nextstep.jwp.controller.LoginController; +import nextstep.jwp.controller.RegisterController; +import nextstep.jwp.controller.ResourceController; import nextstep.jwp.exception.UncheckedServletException; -import nextstep.jwp.model.User; import org.apache.coyote.Processor; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; -import org.apache.coyote.http11.response.HttpStatus; -import org.apache.coyote.http11.response.ResponseHeader; -import org.apache.coyote.http11.response.StatusLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.Socket; -import java.net.URL; -import java.nio.file.Files; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final SessionManager SESSION_MANAGER = new SessionManager(); - private static final String TEXT_HTML = "text/html;charset=utf-8"; - private static final String TEXT_CSS = "text/css;"; - private static final String INDEX_PAGE = "/index.html"; - private static final String NOT_FOUND_PAGE = "/404.html"; - private static final String HTML_EXTENSION = ".html"; - private static final String COOKIE = "Cookie"; private final Socket connection; @@ -63,133 +53,24 @@ private String handleRequest(final HttpRequest httpRequest) throws IOException { final String uri = httpRequest.getRequestLine().getPath(); final String path = uri.split("\\?")[0]; + final var httpResponse = HttpResponse.createEmpty(); if (path.equals("/")) { - return get200ResponseMessage(httpRequest, path, "Hello world!"); + final var homeController = new HomeController(); + homeController.service(httpRequest, httpResponse); + return httpResponse.toString(); } if (path.equals("/login")) { - final String responseBody = findStaticResource(path + HTML_EXTENSION); - return handleLoginRequest(httpRequest, uri, responseBody); + final var loginController = new LoginController(); + loginController.service(httpRequest, httpResponse); + return httpResponse.toString(); } if (path.equals("/register")) { - final String responseBody = findStaticResource(path + HTML_EXTENSION); - return handleRegisterRequest(httpRequest, uri, responseBody); + final var registerController = new RegisterController(); + registerController.service(httpRequest, httpResponse); + return httpResponse.toString(); } - final String responseBody = findStaticResource(path); - return get200ResponseMessage(httpRequest, path, responseBody); - } - - private String handleRegisterRequest(final HttpRequest httpRequest, final String uri, final String responseBody) { - final String[] splitUri = uri.split("\\?"); - final String requestBody = httpRequest.getRequestBody(); - if (requestBody == null && splitUri.length == 1) { - return get200ResponseMessage(httpRequest, splitUri[0], responseBody); - } - final Map requestBodyValues = getRequestParameters(requestBody, splitUri); - final var user = new User(requestBodyValues.get("account"), requestBodyValues.get("password"), - requestBodyValues.get("email")); - InMemoryUserRepository.save(user); - return get302ResponseMessage(httpRequest, INDEX_PAGE, null); - } - - private Map getRequestParameters(final String requestBody, final String[] uri) { - if (requestBody == null) { - return parseRequestBody(uri[1]); - } - return parseRequestBody(requestBody); - } - - private Map parseRequestBody(final String requestBody) { - final var requestBodyValues = new HashMap(); - final String[] splitRequestBody = requestBody.split("&"); - for (var value : splitRequestBody) { - final String[] splitValue = value.split("="); - requestBodyValues.put(splitValue[0], splitValue[1]); - } - return requestBodyValues; - } - - private String handleLoginRequest(final HttpRequest httpRequest, final String uri, final String responseBody) { - final String cookieHeader = httpRequest.getRequestHeader().getValue(COOKIE); - final var cookie = HttpCookie.from(cookieHeader); - final String[] splitUri = uri.split("\\?"); - final String requestBody = httpRequest.getRequestBody(); - final User user = findUserBySessionId(cookie.getJSessionId()); - if (user != null) { - log.info("User: {}", user); - return get302ResponseMessage(httpRequest, INDEX_PAGE, null); - } - if (requestBody == null && splitUri.length == 1) { - return get200ResponseMessage(httpRequest, splitUri[0], responseBody); - } - return handleFirstLogin(httpRequest, requestBody, splitUri); - } - - private String handleFirstLogin(final HttpRequest httpRequest, final String requestBody, final String[] uri) { - final Map requestBodyValues = getRequestParameters(requestBody, uri); - final Optional user = InMemoryUserRepository.findByAccount(requestBodyValues.get("account")); - if (user.isEmpty() || !user.get().checkPassword(requestBodyValues.get("password"))) { - return get302ResponseMessage(httpRequest, "/401.html", null); - } - final String sessionId = addSession(user.get()); - log.info("User: {}", user.get()); - return get302ResponseMessage(httpRequest, INDEX_PAGE, sessionId); - } - - private String addSession(final User user) { - final var session = Session.create(); - session.setAttribute("user", user); - SESSION_MANAGER.add(session); - return session.getId(); - } - - private User findUserBySessionId(final String sessionId) { - if (sessionId == null) { - return null; - } - final Session session = SESSION_MANAGER.findSession(sessionId) - .orElseGet(Session::create); - return (User) session.getAttribute("user"); - } - - private String get200ResponseMessage(final HttpRequest httpRequest, final String path, final String responseBody) { - final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK); - final var responseHeader = ResponseHeader.createEmpty(); - responseHeader.addHeader("Content-Type", getContentType(path)); - responseHeader.addHeader("Content-Length", String.valueOf(responseBody.getBytes().length)); - - final var httpResponse = HttpResponse.createGetResponse(statusLine, responseHeader, responseBody); - return httpResponse.toString(); - } - - private String getContentType(final String path) { - if (path.endsWith(".css")) { - return TEXT_CSS; - } - return TEXT_HTML; - } - - private String get302ResponseMessage(final HttpRequest httpRequest, final String location, final String newSessionId) { - final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.FOUND); - final var responseHeader = ResponseHeader.createEmpty(); - responseHeader.addHeader("Location", location); - if (newSessionId != null) { - responseHeader.addHeader("Set-Cookie", "JSESSIONID=" + newSessionId); - } - final var httpResponse = HttpResponse.createPostResponse(statusLine, responseHeader); + final var resourceController = new ResourceController(); + resourceController.service(httpRequest, httpResponse); return httpResponse.toString(); } - - private String findStaticResource(final String uri) throws IOException { - final URL fileUrl = findResourceUrl(uri); - final String filePath = fileUrl.getPath(); - return Files.readString(new File(filePath).toPath()); - } - - private URL findResourceUrl(final String uri) { - final URL fileUrl = getClass().getClassLoader().getResource("./static" + uri); - if (fileUrl == null) { - return getClass().getClassLoader().getResource("./static" + NOT_FOUND_PAGE); - } - return fileUrl; - } } diff --git a/tomcat/src/test/java/nextstep/jwp/controller/LoginControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/LoginControllerTest.java new file mode 100644 index 0000000000..7fcd99d12f --- /dev/null +++ b/tomcat/src/test/java/nextstep/jwp/controller/LoginControllerTest.java @@ -0,0 +1,101 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.*; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class LoginControllerTest { + + @Test + void POST_요청이_들어오면_HttpResponse에_응답을_처리한다() throws IOException { + // given + final var loginController = new LoginController(); + final var requestLine = "POST /login HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Content-Length: 30", + "Content-Type: application/x-www-form-urlencoded", + "Accept: */*"); + final var requestBody = "account=gugu&password=password"; + final var request = String.join("\r\n", requestLine, requestHeader, "", requestBody); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.FOUND); + final var expectedResponseHeader = ResponseHeader.createEmpty(); + expectedResponseHeader.addHeader("Location", "/index.html"); + + // when + loginController.doPost(httpRequest, httpResponse); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); + softAssertions.assertThat(httpResponse.getResponseHeader().getHeaders().get("Location")) + .isEqualTo(expectedResponseHeader.getHeaders().get("Location")); + }); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + + @Test + void GET_요청이_들어오면_HttpResponse에_응답을_처리한다() throws IOException { + // given + final var loginController = new LoginController(); + final var requestLine = "GET /login HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + final var httpResponse = HttpResponse.createEmpty(); + + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.OK); + final var expectedResponseHeader = ResponseHeader.createEmpty(); + final var expectedResponseBody = ResponseBody.fromUri("/login.html"); + expectedResponseHeader.addHeader("Content-Type", "text/html;charset=utf-8"); + expectedResponseHeader.addHeader("Content-Length", String.valueOf(expectedResponseBody.getBody().getBytes().length)); + + // when + loginController.doGet(httpRequest, httpResponse); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); + softAssertions.assertThat(httpResponse.getResponseHeader()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseHeader); + softAssertions.assertThat(httpResponse.getResponseBody()) + .usingRecursiveComparison() + .isEqualTo(expectedResponseBody); + }); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } +} From 6ed87dadf35b1895eda94c3cd6aa94d3d2719ca4 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Mon, 11 Sep 2023 00:02:42 +0900 Subject: [PATCH 16/25] =?UTF-8?q?refactor:=20RequestMapping=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 27 +--- .../apache/coyote/http11/RequestMapping.java | 27 ++++ .../coyote/http11/RequestMappingTest.java | 127 ++++++++++++++++++ 3 files changed, 156 insertions(+), 25 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/RequestMapping.java create mode 100644 tomcat/src/test/java/org/apache/coyote/http11/RequestMappingTest.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index d6dbe9f35f..b773850cda 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,9 +1,5 @@ package org.apache.coyote.http11; -import nextstep.jwp.controller.HomeController; -import nextstep.jwp.controller.LoginController; -import nextstep.jwp.controller.RegisterController; -import nextstep.jwp.controller.ResourceController; import nextstep.jwp.exception.UncheckedServletException; import org.apache.coyote.Processor; import org.apache.coyote.http11.request.HttpRequest; @@ -39,7 +35,6 @@ public void process(final Socket connection) { final var bufferedReader = new BufferedReader(inputStreamReader); final OutputStream outputStream = connection.getOutputStream()) { final var httpRequest = HttpRequest.from(bufferedReader); - final String response = handleRequest(httpRequest); outputStream.write(response.getBytes()); @@ -50,27 +45,9 @@ public void process(final Socket connection) { } private String handleRequest(final HttpRequest httpRequest) throws IOException { - final String uri = httpRequest.getRequestLine().getPath(); - final String path = uri.split("\\?")[0]; - final var httpResponse = HttpResponse.createEmpty(); - if (path.equals("/")) { - final var homeController = new HomeController(); - homeController.service(httpRequest, httpResponse); - return httpResponse.toString(); - } - if (path.equals("/login")) { - final var loginController = new LoginController(); - loginController.service(httpRequest, httpResponse); - return httpResponse.toString(); - } - if (path.equals("/register")) { - final var registerController = new RegisterController(); - registerController.service(httpRequest, httpResponse); - return httpResponse.toString(); - } - final var resourceController = new ResourceController(); - resourceController.service(httpRequest, httpResponse); + final var controller = RequestMapping.getController(httpRequest); + controller.service(httpRequest, httpResponse); return httpResponse.toString(); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestMapping.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestMapping.java new file mode 100644 index 0000000000..cf185c7b7a --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestMapping.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http11; + +import nextstep.jwp.controller.*; +import org.apache.coyote.http11.request.HttpRequest; + +import java.util.HashMap; +import java.util.Map; + +public class RequestMapping { + + private static final Map mappers = new HashMap<>(); + + static { + mappers.put("/", new HomeController()); + mappers.put("/login", new LoginController()); + mappers.put("/register", new RegisterController()); + } + + private RequestMapping() { + } + + public static Controller getController(final HttpRequest request) { + final String uri = request.getRequestLine().getPath(); + final String path = uri.split("\\?")[0]; + return mappers.getOrDefault(path, new ResourceController()); + } +} diff --git a/tomcat/src/test/java/org/apache/coyote/http11/RequestMappingTest.java b/tomcat/src/test/java/org/apache/coyote/http11/RequestMappingTest.java new file mode 100644 index 0000000000..44f14ff7ac --- /dev/null +++ b/tomcat/src/test/java/org/apache/coyote/http11/RequestMappingTest.java @@ -0,0 +1,127 @@ +package org.apache.coyote.http11; + +import nextstep.jwp.controller.HomeController; +import nextstep.jwp.controller.LoginController; +import nextstep.jwp.controller.RegisterController; +import nextstep.jwp.controller.ResourceController; +import org.apache.coyote.http11.request.HttpRequest; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class RequestMappingTest { + + @Nested + class getController_테스트 { + + @Test + void HomeController_객체를_반환한다() throws IOException { + // given + final var requestLine = "GET / HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + + // when + final var controller = RequestMapping.getController(httpRequest); + + // then + assertThat(controller).isInstanceOf(HomeController.class); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + + @Test + void LoginController_객체를_반환한다() throws IOException { + // given + final var requestLine = "GET /login HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + + // when + final var controller = RequestMapping.getController(httpRequest); + + // then + assertThat(controller).isInstanceOf(LoginController.class); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + + @Test + void RegisterController_객체를_반환한다() throws IOException { + // given + final var requestLine = "GET /register HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + + // when + final var controller = RequestMapping.getController(httpRequest); + + // then + assertThat(controller).isInstanceOf(RegisterController.class); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + + @Test + void ResourceController_객체를_반환한다() throws IOException { + // given + final var requestLine = "GET /index.html HTTP/1.1"; + final var requestHeader = String.join("\r\n", + "Host: localhost:8080", + "Connection: keep-alive", + "Accept: */*", + ""); + final var request = String.join("\r\n", requestLine, requestHeader, ""); + final var inputStream = new ByteArrayInputStream(request.getBytes()); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var httpRequest = HttpRequest.from(bufferedReader); + + // when + final var controller = RequestMapping.getController(httpRequest); + + // then + assertThat(controller).isInstanceOf(ResourceController.class); + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + } + } +} From 51529d5435ed3d44a667413c2728816af9eccc18 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Mon, 11 Sep 2023 00:27:37 +0900 Subject: [PATCH 17/25] =?UTF-8?q?test:=20=EC=BA=90=EC=8B=9C=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/cachecontrol/CacheWebConfig.java | 6 ++++++ .../com/example/etag/EtagFilterConfiguration.java | 15 +++++++++++---- .../example/version/CacheBustingWebConfig.java | 6 +++++- study/src/main/resources/application.yml | 3 +++ 4 files changed, 25 insertions(+), 5 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 305b1f1e1e..6a45567463 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -1,13 +1,19 @@ package cache.com.example.cachecontrol; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.WebContentInterceptor; @Configuration public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + final var cacheControl = CacheControl.noCache().cachePrivate(); + final var webContentInterceptor = new WebContentInterceptor(); + webContentInterceptor.addCacheMapping(cacheControl, "/**"); + registry.addInterceptor(webContentInterceptor); } } diff --git a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java index 41ef7a3d9a..b4b89d5e22 100644 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java @@ -1,12 +1,19 @@ package cache.com.example.etag; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ShallowEtagHeaderFilter; + +import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES; @Configuration public class EtagFilterConfiguration { -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + final var filterRegistrationBean = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter()); + filterRegistrationBean.addUrlPatterns("/etag", PREFIX_STATIC_RESOURCES + "/*"); + return filterRegistrationBean; + } } diff --git a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java index 6da6d2c795..5a422e5b2e 100644 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java @@ -2,9 +2,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.time.Duration; + @Configuration public class CacheBustingWebConfig implements WebMvcConfigurer { @@ -20,6 +23,7 @@ public CacheBustingWebConfig(ResourceVersion version) { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .addResourceLocations("classpath:/static/") + .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic()); } } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..385c11d5f1 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -7,3 +7,6 @@ server: max-connections: 1 threads: max: 2 + compression: + enabled: true + min-response-size: 10 From 4b8aefc65ff96cf875428084827b7a8e8c71b79a Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Mon, 11 Sep 2023 01:33:23 +0900 Subject: [PATCH 18/25] =?UTF-8?q?test:=20Thread=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/thread/stage0/SynchronizationTest.java | 2 +- study/src/test/java/thread/stage0/ThreadPoolsTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java index 0333c18e3b..b463c2b984 100644 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ b/study/src/test/java/thread/stage0/SynchronizationTest.java @@ -41,7 +41,7 @@ private static final class SynchronizedMethods { private int sum = 0; - public void calculate() { + public synchronized void calculate() { setSum(getSum() + 1); } diff --git a/study/src/test/java/thread/stage0/ThreadPoolsTest.java b/study/src/test/java/thread/stage0/ThreadPoolsTest.java index 238611ebfe..03efdabc8d 100644 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ b/study/src/test/java/thread/stage0/ThreadPoolsTest.java @@ -31,8 +31,8 @@ void testNewFixedThreadPool() { executor.submit(logWithSleep("hello fixed thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; - final int expectedQueueSize = 0; + final int expectedPoolSize = 2; + final int expectedQueueSize = 1; assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size()); @@ -46,7 +46,7 @@ void testNewCachedThreadPool() { executor.submit(logWithSleep("hello cached thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; + final int expectedPoolSize = 3; final int expectedQueueSize = 0; assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); From 7081ed7d910c0a7645380679e1a1ff4eca0adc43 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Mon, 11 Sep 2023 01:53:43 +0900 Subject: [PATCH 19/25] =?UTF-8?q?feat:=20Thread=20Pool=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- .../java/org/apache/catalina/connector/Connector.java | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 73f657c1d5..ac5d0b200b 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ - 3단계 - 리팩터링 - [x] HttpRequest 클래스 구현하기 - [x] HttpResponse 클래스 구현하기 - - [ ] Controller 인터페이스 추가하기 + - [x] Controller 인터페이스 추가하기 - 4단계 - 동시성 확장하기 - - [ ] Executors로 Thread Pool 적용 + - [x] Executors로 Thread Pool 적용 - [ ] 동시성 컬렉션 사용하기 diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java index 3b2c4dda7c..21a4cfb895 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -8,6 +8,8 @@ import java.io.UncheckedIOException; import java.net.ServerSocket; import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class Connector implements Runnable { @@ -15,17 +17,20 @@ public class Connector implements Runnable { private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_ACCEPT_COUNT = 100; + private static final int MAX_THREAD_POOL_SIZE = 250; + private final ExecutorService executorService; private final ServerSocket serverSocket; private boolean stopped; public Connector() { - this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); + this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, MAX_THREAD_POOL_SIZE); } - public Connector(final int port, final int acceptCount) { + public Connector(final int port, final int acceptCount, final int maxThreads) { this.serverSocket = createServerSocket(port, acceptCount); this.stopped = false; + this.executorService = Executors.newFixedThreadPool(maxThreads); } private ServerSocket createServerSocket(final int port, final int acceptCount) { @@ -67,7 +72,7 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + executorService.execute(processor); } public void stop() { From baf547693c5469bd1cc7662863b09802db86e4f7 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Mon, 11 Sep 2023 01:54:37 +0900 Subject: [PATCH 20/25] =?UTF-8?q?refactor:=20SessionManager=20=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=EC=84=B1=20=EC=BB=AC=EB=A0=89=EC=85=98=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../main/java/org/apache/coyote/http11/SessionManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ac5d0b200b..cea9ee57e2 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ - 4단계 - 동시성 확장하기 - [x] Executors로 Thread Pool 적용 - - [ ] 동시성 컬렉션 사용하기 + - [x] 동시성 컬렉션 사용하기 diff --git a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java index da868eec24..7516f736ce 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java @@ -2,13 +2,13 @@ import org.apache.catalina.Manager; -import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; public class SessionManager implements Manager { - private static final Map SESSIONS = new HashMap<>(); + private static final Map SESSIONS = new ConcurrentHashMap<>(); @Override public void add(final Session session) { From c6e09551d8276f1515b6d1f3dd95f804ffdc7cb8 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Thu, 14 Sep 2023 16:51:17 +0900 Subject: [PATCH 21/25] =?UTF-8?q?refactor:=20405=20Method=20Not=20Allowed?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/jwp/controller/HomeController.java | 4 ++-- .../jwp/controller/ResourceController.java | 4 ++-- .../coyote/http11/response/HttpStatus.java | 1 + .../jwp/controller/HomeControllerTest.java | 15 ++++++++++----- .../jwp/controller/ResourceControllerTest.java | 16 +++++++++++----- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java index 73de990131..1a91d2cda9 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java @@ -1,6 +1,5 @@ package nextstep.jwp.controller; -import nextstep.jwp.exception.HttpRequestException; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; import org.apache.coyote.http11.response.HttpStatus; @@ -11,7 +10,8 @@ public class HomeController extends AbstractController { @Override protected void doPost(final HttpRequest request, final HttpResponse response) { - throw new HttpRequestException(HTTP_METHOD_EXCEPTION_MESSAGE); + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED); + response.setStatusLine(statusLine); } @Override diff --git a/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java index 53899996aa..39b50a2a29 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java @@ -1,6 +1,5 @@ package nextstep.jwp.controller; -import nextstep.jwp.exception.HttpRequestException; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.HttpResponse; import org.apache.coyote.http11.response.HttpStatus; @@ -13,7 +12,8 @@ public class ResourceController extends AbstractController { @Override protected void doPost(final HttpRequest request, final HttpResponse response) { - throw new HttpRequestException(HTTP_METHOD_EXCEPTION_MESSAGE); + final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED); + response.setStatusLine(statusLine); } @Override diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java index e63c45b3c3..6f1b38d890 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatus.java @@ -4,6 +4,7 @@ public enum HttpStatus { OK(200, "OK"), FOUND(302, "Found"), + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), ; private final int code; diff --git a/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java index f3cdc3f0d0..0bff895422 100644 --- a/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java +++ b/tomcat/src/test/java/nextstep/jwp/controller/HomeControllerTest.java @@ -1,6 +1,5 @@ package nextstep.jwp.controller; -import nextstep.jwp.exception.HttpRequestException; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.*; import org.junit.jupiter.api.DisplayNameGeneration; @@ -12,7 +11,7 @@ import java.io.IOException; import java.io.InputStreamReader; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @SuppressWarnings("NonAsciiCharacters") @@ -38,9 +37,15 @@ class HomeControllerTest { final var httpRequest = HttpRequest.from(bufferedReader); final var httpResponse = HttpResponse.createEmpty(); - // when & then - assertThatThrownBy(() -> homeController.doPost(httpRequest, httpResponse)) - .isInstanceOf(HttpRequestException.class); + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.METHOD_NOT_ALLOWED); + + // when + homeController.doPost(httpRequest, httpResponse); + + // then + assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); bufferedReader.close(); inputStreamReader.close(); inputStream.close(); diff --git a/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java b/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java index a435546546..e1532ff5a0 100644 --- a/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java +++ b/tomcat/src/test/java/nextstep/jwp/controller/ResourceControllerTest.java @@ -1,6 +1,5 @@ package nextstep.jwp.controller; -import nextstep.jwp.exception.HttpRequestException; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.response.*; import org.junit.jupiter.api.DisplayNameGeneration; @@ -12,7 +11,7 @@ import java.io.IOException; import java.io.InputStreamReader; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @SuppressWarnings("NonAsciiCharacters") @@ -38,9 +37,16 @@ class ResourceControllerTest { final var httpRequest = HttpRequest.from(bufferedReader); final var httpResponse = HttpResponse.createEmpty(); - // when & then - assertThatThrownBy(() -> resourceController.doPost(httpRequest, httpResponse)) - .isInstanceOf(HttpRequestException.class); + final var expectedStatusLine = StatusLine.of("HTTP/1.1", HttpStatus.METHOD_NOT_ALLOWED); + + // when + resourceController.doPost(httpRequest, httpResponse); + + // then + assertThat(httpResponse.getStatusLine()) + .usingRecursiveComparison() + .isEqualTo(expectedStatusLine); + bufferedReader.close(); inputStreamReader.close(); inputStream.close(); From 8cbc245bfd252264b571db25e1be86282956ea78 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Thu, 14 Sep 2023 17:05:38 +0900 Subject: [PATCH 22/25] =?UTF-8?q?refactor:=20HttpMethod=20enum=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/request/HttpMethod.java | 5 +++++ .../org/apache/coyote/http11/request/RequestLine.java | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/request/HttpMethod.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpMethod.java new file mode 100644 index 0000000000..452dc83505 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpMethod.java @@ -0,0 +1,5 @@ +package org.apache.coyote.http11.request; + +public enum HttpMethod { + POST, GET +} 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 index cf79d6ee9d..81f814b049 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java @@ -2,11 +2,11 @@ public class RequestLine { - private final String method; + private final HttpMethod method; private final String path; private final String protocol; - private RequestLine(final String method, final String path, final String protocol) { + private RequestLine(final HttpMethod method, final String path, final String protocol) { this.method = method; this.path = path; this.protocol = protocol; @@ -14,11 +14,11 @@ private RequestLine(final String method, final String path, final String protoco public static RequestLine from(final String line) { final String[] splitLine = line.split(" "); - return new RequestLine(splitLine[0], splitLine[1], splitLine[2]); + return new RequestLine(HttpMethod.valueOf(splitLine[0]), splitLine[1], splitLine[2]); } public String getMethod() { - return method; + return method.name(); } public String getPath() { From a386e73a4b5c7a65406dccaf4981c8ed5f519a21 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Thu, 14 Sep 2023 17:24:15 +0900 Subject: [PATCH 23/25] =?UTF-8?q?refactor:=20Request=20Parameter=20?= =?UTF-8?q?=EA=B5=AC=ED=95=98=EB=8A=94=20=EC=B1=85=EC=9E=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/AbstractController.java | 21 ------------------- .../jwp/controller/LoginController.java | 2 +- .../jwp/controller/RegisterController.java | 2 +- .../coyote/http11/request/HttpRequest.java | 20 ++++++++++++++++++ 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java index a6455dddbd..de6c225368 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -5,8 +5,6 @@ import org.apache.coyote.http11.response.HttpResponse; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; public abstract class AbstractController implements Controller { @@ -37,23 +35,4 @@ public void service(final HttpRequest request, final HttpResponse response) thro protected abstract void doPost(final HttpRequest request, final HttpResponse response) throws IOException; protected abstract void doGet(final HttpRequest request, final HttpResponse response) throws IOException; - - protected Map getRequestParameters(final HttpRequest request) { - final String[] uri = request.getRequestLine().getPath().split("\\?"); - final String requestBody = request.getRequestBody(); - if (requestBody == null) { - return parseRequestBody(uri[1]); - } - return parseRequestBody(requestBody); - } - - protected Map parseRequestBody(final String requestBody) { - final var requestBodyValues = new HashMap(); - final String[] splitRequestBody = requestBody.split("&"); - for (var value : splitRequestBody) { - final String[] splitValue = value.split("="); - requestBodyValues.put(splitValue[0], splitValue[1]); - } - return requestBodyValues; - } } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index ac2beaaf91..fe48220519 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -34,7 +34,7 @@ protected void doPost(final HttpRequest request, final HttpResponse response) { } private void handleFirstLogin(final HttpRequest request, final HttpResponse response) { - final Map requestBodyValues = getRequestParameters(request); + final Map requestBodyValues = request.getRequestParameters(); final Optional user = InMemoryUserRepository.findByAccount(requestBodyValues.get("account")); if (user.isEmpty() || !user.get().checkPassword(requestBodyValues.get("password"))) { response.addResponseHeader(HEADER_LOCATION, UNAUTHORIZED_PAGE); diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java index 4287406652..0790a49fbe 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -15,7 +15,7 @@ public class RegisterController extends AbstractController { @Override protected void doPost(final HttpRequest request, final HttpResponse response) { - final Map requestBodyValues = getRequestParameters(request); + final Map requestBodyValues = request.getRequestParameters(); final var user = new User(requestBodyValues.get("account"), requestBodyValues.get("password"), requestBodyValues.get("email")); InMemoryUserRepository.save(user); 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 9631f95654..2a3efb68ba 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,6 +3,8 @@ import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; public class HttpRequest { @@ -53,6 +55,24 @@ private static String readRequestBody(final BufferedReader bufferedReader, final return new String(buffer); } + public Map getRequestParameters() { + final String[] uri = requestLine.getPath().split("\\?"); + if (requestBody == null) { + return parseRequestBody(uri[1]); + } + return parseRequestBody(requestBody); + } + + private Map parseRequestBody(final String requestBody) { + final var requestBodyValues = new HashMap(); + final String[] splitRequestBody = requestBody.split("&"); + for (var value : splitRequestBody) { + final String[] splitValue = value.split("="); + requestBodyValues.put(splitValue[0], splitValue[1]); + } + return requestBodyValues; + } + public RequestLine getRequestLine() { return requestLine; } From aafb47ef4fade99c9bbc164409af158d58d0e2d4 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Thu, 14 Sep 2023 17:44:34 +0900 Subject: [PATCH 24/25] =?UTF-8?q?refactor:=20HttpRequest=EC=97=90=EC=84=9C?= =?UTF-8?q?=20Cookie=20=EA=B0=92=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=B1=85=EC=9E=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/jwp/controller/AbstractController.java | 1 - .../main/java/nextstep/jwp/controller/LoginController.java | 6 ++---- .../java/org/apache/coyote/http11/request/HttpRequest.java | 7 +++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java index de6c225368..4fc2c9b0dc 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -12,7 +12,6 @@ public abstract class AbstractController implements Controller { protected static final String TEXT_CSS = "text/css;"; protected static final String INDEX_PAGE = "/index.html"; protected static final String UNAUTHORIZED_PAGE = "/401.html"; - protected static final String HEADER_COOKIE = "Cookie"; protected static final String HEADER_LOCATION = "Location"; protected static final String HEADER_SET_COOKIE = "Set-Cookie"; protected static final String HEADER_CONTENT_TYPE = "Content-Type"; diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index fe48220519..c5ff722ddf 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -21,8 +21,7 @@ public class LoginController extends AbstractController { @Override protected void doPost(final HttpRequest request, final HttpResponse response) { - final String cookieHeader = request.getRequestHeader().getValue(HEADER_COOKIE); - final var cookie = HttpCookie.from(cookieHeader); + final HttpCookie cookie = request.getCookie(); final User user = findUserBySessionId(cookie.getJSessionId()); final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.FOUND); response.setStatusLine(statusLine); @@ -47,8 +46,7 @@ private void handleFirstLogin(final HttpRequest request, final HttpResponse resp @Override protected void doGet(final HttpRequest request, final HttpResponse response) throws IOException { - final String cookieHeader = request.getRequestHeader().getValue(HEADER_COOKIE); - final var cookie = HttpCookie.from(cookieHeader); + final HttpCookie cookie = request.getCookie(); final User user = findUserBySessionId(cookie.getJSessionId()); if (user != null) { doPost(request, response); 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 2a3efb68ba..ab5269bdb9 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 @@ -1,5 +1,7 @@ package org.apache.coyote.http11.request; +import org.apache.coyote.http11.HttpCookie; + import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; @@ -73,6 +75,11 @@ private Map parseRequestBody(final String requestBody) { return requestBodyValues; } + public HttpCookie getCookie() { + final String cookieHeader = requestHeader.getValue("Cookie"); + return HttpCookie.from(cookieHeader); + } + public RequestLine getRequestLine() { return requestLine; } From 37a4aaa6d411dba795a7a9784a33a51ed479cf93 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol Date: Thu, 14 Sep 2023 18:27:12 +0900 Subject: [PATCH 25/25] =?UTF-8?q?refactor:=20httpRequest,=20httpResponse?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=EB=AA=85=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwp/controller/AbstractController.java | 14 +++---- .../jwp/controller/HomeController.java | 18 ++++----- .../jwp/controller/LoginController.java | 38 +++++++++---------- .../jwp/controller/RegisterController.java | 22 +++++------ .../jwp/controller/ResourceController.java | 20 +++++----- 5 files changed, 56 insertions(+), 56 deletions(-) diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java index 4fc2c9b0dc..d5bbb2a27e 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -19,19 +19,19 @@ public abstract class AbstractController implements Controller { protected static final String HTTP_METHOD_EXCEPTION_MESSAGE = "올바르지 않은 HTTP Method 입니다."; @Override - public void service(final HttpRequest request, final HttpResponse response) throws IOException { - if (request.getRequestLine().getMethod().equals("POST")) { - doPost(request, response); + public void service(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException { + if (httpRequest.getRequestLine().getMethod().equals("POST")) { + doPost(httpRequest, httpResponse); return; } - if (request.getRequestLine().getMethod().equals("GET")) { - doGet(request, response); + if (httpRequest.getRequestLine().getMethod().equals("GET")) { + doGet(httpRequest, httpResponse); return; } throw new HttpRequestException(HTTP_METHOD_EXCEPTION_MESSAGE); } - protected abstract void doPost(final HttpRequest request, final HttpResponse response) throws IOException; + protected abstract void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException; - protected abstract void doGet(final HttpRequest request, final HttpResponse response) throws IOException; + protected abstract void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException; } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java index 1a91d2cda9..c193a90348 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/HomeController.java @@ -9,18 +9,18 @@ public class HomeController extends AbstractController { @Override - protected void doPost(final HttpRequest request, final HttpResponse response) { - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED); - response.setStatusLine(statusLine); + protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) { + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED); + httpResponse.setStatusLine(statusLine); } @Override - protected void doGet(final HttpRequest request, final HttpResponse response) { - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); + protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) { + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK); final var responseBody = ResponseBody.fromText("Hello world!"); - response.setStatusLine(statusLine); - response.addResponseHeader("Content-Type", TEXT_HTML); - response.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); - response.setResponseBody(responseBody); + httpResponse.setStatusLine(statusLine); + httpResponse.addResponseHeader("Content-Type", TEXT_HTML); + httpResponse.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); + httpResponse.setResponseBody(responseBody); } } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index c5ff722ddf..8550fb3f19 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -20,44 +20,44 @@ public class LoginController extends AbstractController { private static final SessionManager SESSION_MANAGER = new SessionManager(); @Override - protected void doPost(final HttpRequest request, final HttpResponse response) { - final HttpCookie cookie = request.getCookie(); + protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) { + final HttpCookie cookie = httpRequest.getCookie(); final User user = findUserBySessionId(cookie.getJSessionId()); - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.FOUND); - response.setStatusLine(statusLine); + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.FOUND); + httpResponse.setStatusLine(statusLine); if (user == null) { - handleFirstLogin(request, response); + handleFirstLogin(httpRequest, httpResponse); return; } - response.addResponseHeader(HEADER_LOCATION, INDEX_PAGE); + httpResponse.addResponseHeader(HEADER_LOCATION, INDEX_PAGE); } - private void handleFirstLogin(final HttpRequest request, final HttpResponse response) { - final Map requestBodyValues = request.getRequestParameters(); + private void handleFirstLogin(final HttpRequest httpRequest, final HttpResponse httpResponse) { + final Map requestBodyValues = httpRequest.getRequestParameters(); final Optional user = InMemoryUserRepository.findByAccount(requestBodyValues.get("account")); if (user.isEmpty() || !user.get().checkPassword(requestBodyValues.get("password"))) { - response.addResponseHeader(HEADER_LOCATION, UNAUTHORIZED_PAGE); + httpResponse.addResponseHeader(HEADER_LOCATION, UNAUTHORIZED_PAGE); return; } final String sessionId = addSession(user.get()); - response.addResponseHeader(HEADER_LOCATION, INDEX_PAGE); - response.addResponseHeader(HEADER_SET_COOKIE, "JSESSIONID=" + sessionId); + httpResponse.addResponseHeader(HEADER_LOCATION, INDEX_PAGE); + httpResponse.addResponseHeader(HEADER_SET_COOKIE, "JSESSIONID=" + sessionId); } @Override - protected void doGet(final HttpRequest request, final HttpResponse response) throws IOException { - final HttpCookie cookie = request.getCookie(); + protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException { + final HttpCookie cookie = httpRequest.getCookie(); final User user = findUserBySessionId(cookie.getJSessionId()); if (user != null) { - doPost(request, response); + doPost(httpRequest, httpResponse); return; } - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK); final var responseBody = ResponseBody.fromUri("/login.html"); - response.setStatusLine(statusLine); - response.addResponseHeader(HEADER_CONTENT_TYPE, TEXT_HTML); - response.addResponseHeader(HEADER_CONTENT_LENGTH, String.valueOf(responseBody.getBody().getBytes().length)); - response.setResponseBody(responseBody); + httpResponse.setStatusLine(statusLine); + httpResponse.addResponseHeader(HEADER_CONTENT_TYPE, TEXT_HTML); + httpResponse.addResponseHeader(HEADER_CONTENT_LENGTH, String.valueOf(responseBody.getBody().getBytes().length)); + httpResponse.setResponseBody(responseBody); } private User findUserBySessionId(final String sessionId) { diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java index 0790a49fbe..bdc32dc7c7 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -14,24 +14,24 @@ public class RegisterController extends AbstractController { @Override - protected void doPost(final HttpRequest request, final HttpResponse response) { - final Map requestBodyValues = request.getRequestParameters(); + protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) { + final Map requestBodyValues = httpRequest.getRequestParameters(); final var user = new User(requestBodyValues.get("account"), requestBodyValues.get("password"), requestBodyValues.get("email")); InMemoryUserRepository.save(user); - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.FOUND); - response.setStatusLine(statusLine); - response.addResponseHeader("Location", INDEX_PAGE); + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.FOUND); + httpResponse.setStatusLine(statusLine); + httpResponse.addResponseHeader("Location", INDEX_PAGE); } @Override - protected void doGet(final HttpRequest request, final HttpResponse response) throws IOException { - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); + protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException { + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK); final var responseBody = ResponseBody.fromUri("/register.html"); - response.setStatusLine(statusLine); - response.addResponseHeader("Content-Type", TEXT_HTML); - response.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); - response.setResponseBody(responseBody); + httpResponse.setStatusLine(statusLine); + httpResponse.addResponseHeader("Content-Type", TEXT_HTML); + httpResponse.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); + httpResponse.setResponseBody(responseBody); } } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java index 39b50a2a29..c58a0d5ef6 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/ResourceController.java @@ -11,20 +11,20 @@ public class ResourceController extends AbstractController { @Override - protected void doPost(final HttpRequest request, final HttpResponse response) { - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED); - response.setStatusLine(statusLine); + protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) { + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED); + httpResponse.setStatusLine(statusLine); } @Override - protected void doGet(final HttpRequest request, final HttpResponse response) throws IOException { - final var statusLine = StatusLine.of(request.getRequestLine().getProtocol(), HttpStatus.OK); - final String uri = request.getRequestLine().getPath().split("\\?")[0]; + protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException { + final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK); + final String uri = httpRequest.getRequestLine().getPath().split("\\?")[0]; final var responseBody = ResponseBody.fromUri(uri); - response.setStatusLine(statusLine); - response.addResponseHeader("Content-Type", getContentType(uri)); - response.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); - response.setResponseBody(responseBody); + httpResponse.setStatusLine(statusLine); + httpResponse.addResponseHeader("Content-Type", getContentType(uri)); + httpResponse.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length)); + httpResponse.setResponseBody(responseBody); } private String getContentType(final String uri) {