From e8af9f386cb9d69c42d3cbaeea680a7b874ec8b5 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Sun, 3 Sep 2023 19:12:24 +0900 Subject: [PATCH 01/29] =?UTF-8?q?test:=20study=20Test=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 44 ++++--- study/src/test/java/study/IOStreamTest.java | 132 +++++++++++--------- 2 files changed, 91 insertions(+), 85 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..5f6304fa40 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,53 +1,51 @@ package study; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; /** - * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. - * File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. + * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. */ @DisplayName("File 클래스 학습 테스트") class FileTest { /** * resource 디렉터리 경로 찾기 - * - * File 객체를 생성하려면 파일의 경로를 알아야 한다. - * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. - * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? + *

+ * File 객체를 생성하려면 파일의 경로를 알아야 한다. 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. resource 디렉터리의 경로는 어떻게 알아낼 수 + * 있을까? */ @Test - void resource_디렉터리에_있는_파일의_경로를_찾는다() { + void resource_디렉터리에_있는_파일의_경로를_찾는다() throws IOException { final String fileName = "nextstep.txt"; // todo - final String actual = ""; + final URL resource = getClass().getClassLoader().getResource(fileName); + assert resource != null; + final String actual = resource.getPath(); assertThat(actual).endsWith(fileName); } /** * 파일 내용 읽기 - * - * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. - * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. + *

+ * 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws IOException { final String fileName = "nextstep.txt"; - - // todo - final Path path = null; - - // todo - final List actual = Collections.emptyList(); + final URL resource = getClass().getClassLoader().getResource(fileName); + final Path path = new File(resource.getPath()).toPath(); + final List actual = Files.readAllLines(path); assertThat(actual).containsOnly("nextstep"); } diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..88771c0a78 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -1,45 +1,51 @@ package study; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - /** - * 자바는 스트림(Stream)으로부터 I/O를 사용한다. - * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. - * - * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. - * FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. - * FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환) - * - * Stream은 데이터를 바이트로 읽고 쓴다. - * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. - * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다. + * 자바는 스트림(Stream)으로부터 I/O를 사용한다. 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. + *

+ * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. FilterStream은 읽거나 쓰는 + * 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환) + *

+ * Stream은 데이터를 바이트로 읽고 쓴다. 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 + * 처리할 수 있다. */ @DisplayName("Java I/O Stream 클래스 학습 테스트") class IOStreamTest { /** * OutputStream 학습하기 - * - * 자바의 기본 출력 클래스는 java.io.OutputStream이다. - * OutputStream의 write(int b) 메서드는 기반 메서드이다. + *

+ * 자바의 기본 출력 클래스는 java.io.OutputStream이다. OutputStream의 write(int b) 메서드는 기반 메서드이다. * public abstract void write(int b) throws IOException; */ @Nested class OutputStream_학습_테스트 { /** - * OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다. - * OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다. - * 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때, - * 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다. - * + * OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다. OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 + * 사용한다. 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때, 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 + * 사용한다. + *

* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다. * write(byte[] data)write(byte b[], int off, int len) 메서드는 * 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다. @@ -53,6 +59,7 @@ class OutputStream_학습_테스트 { * todo * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 */ + outputStream.write(bytes); final String actual = outputStream.toString(); @@ -61,13 +68,10 @@ class OutputStream_학습_테스트 { } /** - * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. - * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. - * - * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. - * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. - * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 - * 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다. + * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. + *

+ * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. Stream은 + * 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다. */ @Test void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException { @@ -78,24 +82,26 @@ class OutputStream_학습_테스트 { * flush를 사용해서 테스트를 통과시킨다. * ByteArrayOutputStream과 어떤 차이가 있을까? */ + outputStream.flush(); verify(outputStream, atLeastOnce()).flush(); outputStream.close(); } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. */ @Test void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException { final OutputStream outputStream = mock(OutputStream.class); - + final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112}; /** * todo * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (outputStream) { + } verify(outputStream, atLeastOnce()).close(); } @@ -103,20 +109,18 @@ class OutputStream_학습_테스트 { /** * InputStream 학습하기 - * - * 자바의 기본 입력 클래스는 java.io.InputStream이다. - * InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다. - * InputStream의 read() 메서드는 기반 메서드이다. + *

+ * 자바의 기본 입력 클래스는 java.io.InputStream이다. InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다. InputStream의 read() 메서드는 기반 + * 메서드이다. * public abstract int read() throws IOException; - * + *

* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다. */ @Nested class InputStream_학습_테스트 { /** - * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. - * int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다. + * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다. * 그리고 Stream 끝에 도달하면 -1을 반환한다. */ @Test @@ -128,7 +132,8 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + + final String actual = new String(inputStream.readAllBytes()); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -136,8 +141,7 @@ class InputStream_학습_테스트 { } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. */ @Test void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -148,33 +152,33 @@ class InputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (inputStream) { + } verify(inputStream, atLeastOnce()).close(); } } /** * FilterStream 학습하기 - * - * 필터는 필터 스트림, reader, writer로 나뉜다. - * 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. - * reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다. + *

+ * 필터는 필터 스트림, reader, writer로 나뉜다. 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 + * 텍스트를 처리하는 데 사용된다. */ @Nested class FilterStream_학습_테스트 { /** - * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다. - * InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다. - * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? + * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다. InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다. 버퍼 크기를 지정하지 + * 않으면 버퍼의 기본 사이즈는 얼마일까? */ @Test - void 필터인_BufferedInputStream를_사용해보자() { + void 필터인_BufferedInputStream를_사용해보자() throws IOException { final String text = "필터에 연결해보자."; final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; + final InputStream bufferedInputStream = new BufferedInputStream(inputStream); - final byte[] actual = new byte[0]; + final byte[] actual = bufferedInputStream.readAllBytes(); assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); @@ -182,30 +186,34 @@ class FilterStream_학습_테스트 { } /** - * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. - * 문자열이 아닌 바이트 단위로 처리하려니 불편하다. - * 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. - * reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. - * 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다. + * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. 문자열이 아닌 바이트 단위로 처리하려니 불편하다. 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. reader, + * writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다. */ @Nested class InputStreamReader_학습_테스트 { /** - * InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다. - * 읽어온 문자(char)를 문자열(String)로 처리하자. - * 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. + * InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다. 읽어온 문자(char)를 문자열(String)로 처리하자. 필터인 BufferedReader를 사용하면 + * readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다. */ @Test - void BufferedReader를_사용하여_문자열을_읽어온다() { + void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException { final String emoji = String.join("\r\n", "😀😃😄😁😆😅😂🤣🥲☺️😊", "😇🙂🙃😉😌😍🥰😘😗😙😚", "😋😛😝😜🤪🤨🧐🤓😎🥸🤩", ""); final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); final StringBuilder actual = new StringBuilder(); + String line = bufferedReader.readLine(); + while (line != null) { + actual.append(line); + actual.append("\r\n"); + line = bufferedReader.readLine(); + } assertThat(actual).hasToString(emoji); } From 65cd22ee8dcf747d9a230b4f25e8d52a74f8af43 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 13:15:50 +0900 Subject: [PATCH 02/29] =?UTF-8?q?feat:=20html=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++ .../apache/coyote/http11/Http11Processor.java | 30 +++---- .../org/apache/coyote/http11/Request.java | 38 +++++++++ .../org/apache/coyote/http11/RequestLine.java | 47 +++++++++++ .../org/apache/coyote/http11/RequestURI.java | 79 +++++++++++++++++++ .../org/apache/coyote/http11/Response.java | 13 +++ .../coyote/http11/handler/HandlerMapper.java | 65 +++++++++++++++ .../coyote/http11/handler/HandlerStatus.java | 53 +++++++++++++ 8 files changed, 325 insertions(+), 14 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Request.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Response.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java diff --git a/README.md b/README.md index b24f542e33..1c2915c987 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ # 톰캣 구현하기 + +## HTTP 서버 구현하기 +### html 파일 응답하기 +* [x] HTTP Request Header에서 필요한 정보를 파싱한다. + * [x] HTTP 메소드를 저장한다. + * [x] Request URI(html 파일)를 저장한다. + * [x] HTTP 버전을 저장한다. +* [x] Request URI에 해당하는 파일을 responseBody로 돌려준다. + +### CSS 지원하기 +* [ ] 요청 리소스가 CSS or JS인 경우 Response Header에 적절한 Content-Type을 보낸다. + +### Query String 파싱 +* [ ] Request URI에서 QueryString을 파싱해 저장한다. 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 7f1b2c7e96..8b8b1210af 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,21 +1,26 @@ package org.apache.coyote.http11; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; import nextstep.jwp.exception.UncheckedServletException; import org.apache.coyote.Processor; +import org.apache.coyote.http11.handler.HandlerMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.Socket; - public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); private final Socket connection; + private final HandlerMapper handlerMapper; + public Http11Processor(final Socket connection) { this.connection = connection; + this.handlerMapper = new HandlerMapper(); } @Override @@ -27,21 +32,18 @@ public void run() { @Override public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { - - final var responseBody = "Hello world!"; - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - + final var outputStream = connection.getOutputStream(); + final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + final Request request = Request.from(bufferedReader); + final var response = getResponse(request); outputStream.write(response.getBytes()); outputStream.flush(); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } + + private String getResponse(final Request request) { + return handlerMapper.handle(request).getValue(); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/Request.java new file mode 100644 index 0000000000..530f675f91 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Request.java @@ -0,0 +1,38 @@ +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; + +public class Request { + private final RequestLine requestLine; + private final RequestHeaders requestHeaders; + private final RequestForms requestForms; + + public Request(final RequestLine requestLine, final RequestHeaders requestHeaders, final RequestForms requestForms) { + this.requestLine = requestLine; + this.requestHeaders = requestHeaders; + this.requestForms = requestForms; + } + + public static Request from(final BufferedReader br) throws IOException { + final RequestLine requestLine = RequestLine.from(br.readLine()); + final RequestHeaders requestHeaders = RequestHeaders.from(br); + final RequestForms requestForms = createRequestBody(br, requestHeaders); + return new Request(requestLine, requestHeaders, requestForms); + } + public RequestLine getRequestLine() { + return requestLine; + } + + private static RequestForms createRequestBody(BufferedReader br, RequestHeaders requestHeaders) + throws IOException { + if (!requestHeaders.hasContentType()) { + return new RequestForms(null); + } + final int contentLength = Integer.parseInt((String) requestHeaders.get("Content-Length")); + final char[] buffer = new char[contentLength]; + br.read(buffer, 0, contentLength); + final String requestBody = new String(buffer); + return RequestForms.from(requestBody); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java new file mode 100644 index 0000000000..29b86b61c0 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java @@ -0,0 +1,47 @@ +package org.apache.coyote.http11; + +public class RequestLine { + private static final String REQUEST_HEADER_DELIMITER = " "; + private final String httpMethod; + private final RequestURI requestURI; + private final String httpVersion; + + public RequestLine(final String httpMethod, final RequestURI requestURI, final String httpVersion) { + this.httpMethod = httpMethod; + this.requestURI = requestURI; + this.httpVersion = httpVersion; + } + + public static RequestLine from(final String requestHeaderFirstLine) { + final String[] splitedLine = requestHeaderFirstLine.split(REQUEST_HEADER_DELIMITER); + return new RequestLine( + splitedLine[0], + new RequestURI(splitedLine[1]), + splitedLine[2] + ); + } + + public boolean isExistRequestFile() { + return this.requestURI.isExistFile(); + } + + public String getPath() { + return this.requestURI.getPath(); + } + + public String readFile() { + return this.requestURI.readFile(); + } + + public RequestURI getRequestURI() { + return requestURI; + } + + public String getHttpMethod() { + return httpMethod; + } + + public String getHttpVersion() { + return httpVersion; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java new file mode 100644 index 0000000000..74e8bbd584 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java @@ -0,0 +1,79 @@ +package org.apache.coyote.http11; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.Map; + +public class RequestURI { + private static final String ROOT_FOLDER = "static"; + private final String path; + private final QueryParameter queryParameter; + private final File file; + + public RequestURI(final String path, final QueryParameter queryParameter, final File file) { + this.path = path; + this.queryParameter = queryParameter; + this.file = file; + } + + public RequestURI(final String uri) { + this.path = parsePath(uri); + this.queryParameter = parseQueryString(uri); + + final URL resource = getClass().getClassLoader().getResource(ROOT_FOLDER + uri); + if (resource == null) { + this.file = null; + return; + } + final File findedFile = new File(resource.getPath()); + if (findedFile.isFile()) { + this.file = findedFile; + return; + } + this.file = null; + } + + private String parsePath(String uri) { + int index = uri.indexOf("?"); + if (index == -1) { + return uri; + } + + return uri.substring(0, index); + } + + private QueryParameter parseQueryString(String uri) { + int index = uri.indexOf("?"); + if (index == -1) { + return QueryParameter.EMPTY; + } + + return new QueryParameter(uri.substring(index + 1)); + } + + public String readFile() { + try { + return new String(Files.readAllBytes(file.toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("파일을 읽던 중 에러가 발생했습니다."); + } + } + + public int getFileLength() throws IOException { + return new String(Files.readAllBytes(file.toPath())).getBytes().length; + } + + public boolean isExistFile() { + return this.file == null; + } + + public String getPath() { + return this.path; + } + + public Map getQueryParameter() { + return queryParameter.getQueryParameter(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/Response.java new file mode 100644 index 0000000000..9344b1f10c --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Response.java @@ -0,0 +1,13 @@ +package org.apache.coyote.http11; + +public class Response { + private final String value; + + public Response(final String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java new file mode 100644 index 0000000000..490ed354b3 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -0,0 +1,65 @@ +package org.apache.coyote.http11.handler; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import org.apache.coyote.http11.Request; +import org.apache.coyote.http11.RequestLine; +import org.apache.coyote.http11.Response; + +public class HandlerMapper { + private static Map> HANDLERS = new HashMap<>(); + + public HandlerMapper() { + init(); + } + + public void init() { + HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); + } + + public Response rootHandler(final Request request) { + final var responseBody = "Hello world!"; + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + + public Response htmlHandler(final Request request) { + final var responseBody = request.getRequestLine().readFile(); + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response handle(final Request request) { + final RequestLine requestLine = request.getRequestLine(); + final String path = requestLine.getPath(); + final String httpMethod = requestLine.getHttpMethod(); + final Map queryParameter = requestLine.getRequestURI().getQueryParameter(); + final Set queryParameterKeys = queryParameter.keySet(); + final HandlerStatus handlerStatus = new HandlerStatus(httpMethod, path, queryParameterKeys); + + final Function handler = HANDLERS.get(handlerStatus); + if (handler != null) { + return handler.apply(request); + } + + if (requestLine.getPath().endsWith(".html")) { + return htmlHandler(request); + } + throw new IllegalArgumentException("매핑되는 핸들러가 존재하지 않습니다."); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java new file mode 100644 index 0000000000..9da40b22fc --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java @@ -0,0 +1,53 @@ +package org.apache.coyote.http11.handler; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class HandlerStatus { + private final String httpMethod; + private final String path; + private final Set queryParameterKeys; + + public HandlerStatus(final String httpMethod, final String path) { + this.httpMethod = httpMethod; + this.path = path; + this.queryParameterKeys = new HashSet<>(); + } + + public HandlerStatus(final String httpMethod, final String path, final Set queryParameterKeys) { + this.httpMethod = httpMethod; + this.path = path; + this.queryParameterKeys = queryParameterKeys; + } + + public String getHttpMethod() { + return httpMethod; + } + + public String getPath() { + return path; + } + + public Set getQueryParameterKeys() { + return queryParameterKeys; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HandlerStatus)) { + return false; + } + final HandlerStatus that = (HandlerStatus) o; + return Objects.equals(httpMethod, that.httpMethod) && Objects.equals(path, that.path) + && Objects.equals(queryParameterKeys, that.queryParameterKeys); + } + + @Override + public int hashCode() { + return Objects.hash(httpMethod, path, queryParameterKeys); + } +} From 3c5f347dc05d61da780ccd506270034f5e08678b Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 13:23:22 +0900 Subject: [PATCH 03/29] =?UTF-8?q?feat:=20css=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../coyote/http11/handler/HandlerMapper.java | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c2915c987..71b62f2573 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ * [x] Request URI에 해당하는 파일을 responseBody로 돌려준다. ### CSS 지원하기 -* [ ] 요청 리소스가 CSS or JS인 경우 Response Header에 적절한 Content-Type을 보낸다. +* [x] 요청 리소스가 CSS or JS인 경우 Response Header에 적절한 Content-Type을 보낸다. ### Query String 파싱 * [ ] Request URI에서 QueryString을 파싱해 저장한다. diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 490ed354b3..4dce40602b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -44,6 +44,31 @@ public Response htmlHandler(final Request request) { return new Response(response); } + + public Response cssHandler(final Request request) { + final var responseBody = request.getRequestLine().readFile(); + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/css;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response jsHandler(final Request request) { + final var responseBody = request.getRequestLine().readFile(); + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/javascript;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + public Response handle(final Request request) { final RequestLine requestLine = request.getRequestLine(); final String path = requestLine.getPath(); @@ -59,6 +84,10 @@ public Response handle(final Request request) { if (requestLine.getPath().endsWith(".html")) { return htmlHandler(request); + } else if (requestLine.getPath().endsWith(".css")) { + return cssHandler(request); + } else if (requestLine.getPath().endsWith(".js")) { + return jsHandler(request); } throw new IllegalArgumentException("매핑되는 핸들러가 존재하지 않습니다."); } From 0acb786c4647cd48111d6b7707f84958466f6f3c Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 13:49:21 +0900 Subject: [PATCH 04/29] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=91=EB=8B=B5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++- .../apache/coyote/http11/QueryParameter.java | 27 ++++++++++++ .../coyote/http11/handler/HandlerMapper.java | 43 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java diff --git a/README.md b/README.md index 71b62f2573..571001813d 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,7 @@ ### CSS 지원하기 * [x] 요청 리소스가 CSS or JS인 경우 Response Header에 적절한 Content-Type을 보낸다. -### Query String 파싱 -* [ ] Request URI에서 QueryString을 파싱해 저장한다. +### 로그인 페이지 응답 +* [x] Request URI에서 QueryString을 파싱해 저장한다. +* [x] QueryString에서 유저의 정보를 추출해 콘솔로 출력한다. +* [x] 로그인 페이지를 응답한다. diff --git a/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java b/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java new file mode 100644 index 0000000000..7744359159 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class QueryParameter { + public static QueryParameter EMPTY = new QueryParameter(new HashMap<>()); + private Map queryParameter = new HashMap<>(); + + public QueryParameter(Map queryParameter) { + this.queryParameter = queryParameter; + } + + public QueryParameter(final String uri) { + final int index = uri.indexOf("?"); + final String queryString = uri.substring(index + 1); + final String[] splitedQueryStrings = queryString.split("&"); // account=gugu + for (final String str : splitedQueryStrings) { + final int strIndex = str.indexOf("="); + queryParameter.put(str.substring(0, strIndex), str.substring(strIndex + 1)); + } + } + + public Map getQueryParameter() { + return queryParameter; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 4dce40602b..f295ba6e69 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -1,9 +1,17 @@ package org.apache.coyote.http11.handler; +import static org.reflections.Reflections.log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.Function; +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; import org.apache.coyote.http11.Request; import org.apache.coyote.http11.RequestLine; import org.apache.coyote.http11.Response; @@ -17,6 +25,8 @@ public HandlerMapper() { public void init() { HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); + HANDLERS.put(new HandlerStatus("GET", "/login", Set.of("account", "password")), + this::loginWithQueryParameterHandler); } public Response rootHandler(final Request request) { @@ -69,6 +79,39 @@ public Response jsHandler(final Request request) { return new Response(response); } + public Response loginWithQueryParameterHandler(final Request request) { + final RequestLine requestLine = request.getRequestLine(); + final var queryParameter = requestLine.getRequestURI().getQueryParameter(); + + final URL resource = getClass().getClassLoader().getResource("static/login.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("login.html이 존재하지 않습니다."); + } + + final User user = InMemoryUserRepository.findByAccount(queryParameter.get("account")) + .orElseGet(null); + + if (user != null) { + if (!user.checkPassword(queryParameter.get("password"))) { + log.error("유저의 아이디와 비밀번호가 일치하지않습니다."); + } else { + log.info("user : " + user); + } + } + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + + return new Response(response); + } + public Response handle(final Request request) { final RequestLine requestLine = request.getRequestLine(); final String path = requestLine.getPath(); From 3588636b3f6c7a31f92036eac5473103e4565b13 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 13:57:16 +0900 Subject: [PATCH 05/29] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 571001813d..8b7c05e9ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 톰캣 구현하기 -## HTTP 서버 구현하기 + ### html 파일 응답하기 * [x] HTTP Request Header에서 필요한 정보를 파싱한다. * [x] HTTP 메소드를 저장한다. @@ -11,7 +11,13 @@ ### CSS 지원하기 * [x] 요청 리소스가 CSS or JS인 경우 Response Header에 적절한 Content-Type을 보낸다. -### 로그인 페이지 응답 -* [x] Request URI에서 QueryString을 파싱해 저장한다. -* [x] QueryString에서 유저의 정보를 추출해 콘솔로 출력한다. +### 로그인 * [x] 로그인 페이지를 응답한다. + * [x] Request URI에서 QueryString을 파싱해 저장한다. + * [x] QueryString에서 유저의 정보를 추출해 콘솔로 출력한다. +* [ ] 로그인에 성공하면 index 페이지로 리다이렉트한다. +* [ ] 로그인에 실패하면 401 페이지로 리다이렉트한다. + +### 회원가입 +* [ ] 회원가입 페이지를 응답한다. +* [ ] 회원가입을 수행하면 index 페이지로 리다이렉트한다. From b31ca0922e9c212c17f2e43b3f342cff828ac2a4 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 14:31:57 +0900 Subject: [PATCH 06/29] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../jwp/db/InMemoryUserRepository.java | 14 ++++- .../org/apache/coyote/http11/Request.java | 12 +++- .../apache/coyote/http11/RequestForms.java | 27 +++++++++ .../coyote/http11/handler/HandlerMapper.java | 56 ++++++++++++++++++- tomcat/src/main/resources/static/login.html | 2 +- 6 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/RequestForms.java diff --git a/README.md b/README.md index 8b7c05e9ce..3e03c64d2f 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ * [x] 로그인 페이지를 응답한다. * [x] Request URI에서 QueryString을 파싱해 저장한다. * [x] QueryString에서 유저의 정보를 추출해 콘솔로 출력한다. -* [ ] 로그인에 성공하면 index 페이지로 리다이렉트한다. -* [ ] 로그인에 실패하면 401 페이지로 리다이렉트한다. +* [x] 로그인에 성공하면 index 페이지로 리다이렉트한다. +* [x] 로그인에 실패하면 401 페이지로 리다이렉트한다. ### 회원가입 * [ ] 회원가입 페이지를 응답한다. diff --git a/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java b/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java index 1ca30e8383..8d1f3b5391 100644 --- a/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java +++ b/tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java @@ -1,10 +1,9 @@ package nextstep.jwp.db; -import nextstep.jwp.model.User; - import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import nextstep.jwp.model.User; public class InMemoryUserRepository { @@ -23,5 +22,14 @@ public static Optional findByAccount(String account) { return Optional.ofNullable(database.get(account)); } - private InMemoryUserRepository() {} + public static Optional findByAccountAndPassword(final String account, final String password) { + final Optional user = Optional.ofNullable(database.get(account)); + if (user.isPresent() && !user.get().checkPassword(password)) { + return Optional.empty(); + } + return user; + } + + private InMemoryUserRepository() { + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/Request.java index 530f675f91..a35842805f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Request.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Request.java @@ -8,7 +8,8 @@ public class Request { private final RequestHeaders requestHeaders; private final RequestForms requestForms; - public Request(final RequestLine requestLine, final RequestHeaders requestHeaders, final RequestForms requestForms) { + public Request(final RequestLine requestLine, final RequestHeaders requestHeaders, + final RequestForms requestForms) { this.requestLine = requestLine; this.requestHeaders = requestHeaders; this.requestForms = requestForms; @@ -20,6 +21,7 @@ public static Request from(final BufferedReader br) throws IOException { final RequestForms requestForms = createRequestBody(br, requestHeaders); return new Request(requestLine, requestHeaders, requestForms); } + public RequestLine getRequestLine() { return requestLine; } @@ -35,4 +37,12 @@ private static RequestForms createRequestBody(BufferedReader br, RequestHeaders final String requestBody = new String(buffer); return RequestForms.from(requestBody); } + + public RequestHeaders getRequestHeaders() { + return requestHeaders; + } + + public RequestForms getRequestForms() { + return requestForms; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestForms.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestForms.java new file mode 100644 index 0000000000..1d2d0f487e --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestForms.java @@ -0,0 +1,27 @@ +package org.apache.coyote.http11; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class RequestForms { + private final Map requestForms; + + public RequestForms(final Map requestForms) { + this.requestForms = requestForms; + } + + public static RequestForms from(final String values) { + final Map requestForms = new LinkedHashMap<>(); + + final String[] splitedValues = values.split("&"); + for (final String value : splitedValues) { + final String[] formPair = value.split("="); + requestForms.put(formPair[0], formPair[1]); + } + return new RequestForms(requestForms); + } + + public Map getRequestForms() { + return requestForms; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index f295ba6e69..469429938a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -8,6 +8,7 @@ import java.nio.file.Files; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import nextstep.jwp.db.InMemoryUserRepository; @@ -27,6 +28,7 @@ public void init() { HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); HANDLERS.put(new HandlerStatus("GET", "/login", Set.of("account", "password")), this::loginWithQueryParameterHandler); + HANDLERS.put(new HandlerStatus("POST", "/login"), this::loginFormHandler); } public Response rootHandler(final Request request) { @@ -41,7 +43,6 @@ public Response rootHandler(final Request request) { return new Response(response); } - public Response htmlHandler(final Request request) { final var responseBody = request.getRequestLine().readFile(); @@ -112,6 +113,59 @@ public Response loginWithQueryParameterHandler(final Request request) { return new Response(response); } + public Response loginFormHandler(final Request request) { + // request form에서 loginRequest생성 + final Map requestForms = request.getRequestForms().getRequestForms(); + Optional user = login(requestForms.get("account"), requestForms.get("password")); + if (user.isPresent()) { // 성공 301 & index.html + return loginSuccess(); + } + + // 실패 401html + return loginFail(); + } + + private Response loginSuccess() { + final URL resource = getClass().getClassLoader().getResource("static/index.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("index.html이 존재하지 않습니다."); + } + + // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 + final var response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + private Response loginFail() { + final URL resource = getClass().getClassLoader().getResource("static/401.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("index.html이 존재하지 않습니다."); + } + + final var response = String.join("\r\n", + "HTTP/1.1 401 Unauthorized ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + private Optional login(final String account, final String password) { + return InMemoryUserRepository.findByAccountAndPassword(account, password); + } + public Response handle(final Request request) { final RequestLine requestLine = request.getRequestLine(); final String path = requestLine.getPath(); diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html index f4ed9de875..bc933357f2 100644 --- a/tomcat/src/main/resources/static/login.html +++ b/tomcat/src/main/resources/static/login.html @@ -20,7 +20,7 @@

로그인

-
+
From 23143b8fc53bf1a3d50222e41ad4d9d1602045b7 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 14:43:28 +0900 Subject: [PATCH 07/29] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../coyote/http11/handler/HandlerMapper.java | 70 +++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3e03c64d2f..1fa808eb28 100644 --- a/README.md +++ b/README.md @@ -19,5 +19,5 @@ * [x] 로그인에 실패하면 401 페이지로 리다이렉트한다. ### 회원가입 -* [ ] 회원가입 페이지를 응답한다. -* [ ] 회원가입을 수행하면 index 페이지로 리다이렉트한다. +* [x] 회원가입 페이지를 응답한다. +* [x] 회원가입을 수행하면 index 페이지로 리다이렉트한다. diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 469429938a..4be613be74 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -26,9 +26,12 @@ public HandlerMapper() { public void init() { HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); + HANDLERS.put(new HandlerStatus("GET", "/login"), this::loginHandler); HANDLERS.put(new HandlerStatus("GET", "/login", Set.of("account", "password")), this::loginWithQueryParameterHandler); HANDLERS.put(new HandlerStatus("POST", "/login"), this::loginFormHandler); + HANDLERS.put(new HandlerStatus("GET", "/register"), this::registerHandler); + HANDLERS.put(new HandlerStatus("POST", "/register"), this::registerFormHandler); } public Response rootHandler(final Request request) { @@ -80,6 +83,25 @@ public Response jsHandler(final Request request) { return new Response(response); } + public Response loginHandler(final Request request) { + final URL resource = getClass().getClassLoader().getResource("static/login.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("login.html이 존재하지 않습니다."); + } + + // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + public Response loginWithQueryParameterHandler(final Request request) { final RequestLine requestLine = request.getRequestLine(); final var queryParameter = requestLine.getRequestURI().getQueryParameter(); @@ -114,14 +136,11 @@ public Response loginWithQueryParameterHandler(final Request request) { } public Response loginFormHandler(final Request request) { - // request form에서 loginRequest생성 final Map requestForms = request.getRequestForms().getRequestForms(); Optional user = login(requestForms.get("account"), requestForms.get("password")); - if (user.isPresent()) { // 성공 301 & index.html + if (user.isPresent()) { return loginSuccess(); } - - // 실패 401html return loginFail(); } @@ -166,6 +185,49 @@ private Optional login(final String account, final String password) { return InMemoryUserRepository.findByAccountAndPassword(account, password); } + public Response registerHandler(final Request request) { + final URL resource = getClass().getClassLoader().getResource("static/register.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("register.html이 존재하지 않습니다."); + } + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response registerFormHandler(final Request request) { + final Map requestForms = request.getRequestForms().getRequestForms(); + final String account = requestForms.get("account"); + final String email = requestForms.get("email"); + final String password = requestForms.get("password"); + InMemoryUserRepository.save(new User(account, password, email)); + + final URL resource = getClass().getClassLoader().getResource("static/index.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("index.html이 존재하지 않습니다."); + } + + // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 + final var response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + public Response handle(final Request request) { final RequestLine requestLine = request.getRequestLine(); final String path = requestLine.getPath(); From b7a56b4996c71c55c801fca1f8328fe1cc813972 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 17:03:46 +0900 Subject: [PATCH 08/29] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/QueryParameter.java | 54 +++--- .../org/apache/coyote/http11/RequestURI.java | 158 +++++++++--------- 2 files changed, 106 insertions(+), 106 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java b/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java index 7744359159..f7563997f5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java @@ -1,27 +1,27 @@ -package org.apache.coyote.http11; - -import java.util.HashMap; -import java.util.Map; - -public class QueryParameter { - public static QueryParameter EMPTY = new QueryParameter(new HashMap<>()); - private Map queryParameter = new HashMap<>(); - - public QueryParameter(Map queryParameter) { - this.queryParameter = queryParameter; - } - - public QueryParameter(final String uri) { - final int index = uri.indexOf("?"); - final String queryString = uri.substring(index + 1); - final String[] splitedQueryStrings = queryString.split("&"); // account=gugu - for (final String str : splitedQueryStrings) { - final int strIndex = str.indexOf("="); - queryParameter.put(str.substring(0, strIndex), str.substring(strIndex + 1)); - } - } - - public Map getQueryParameter() { - return queryParameter; - } -} +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class QueryParameter { + public static final QueryParameter EMPTY = new QueryParameter(new HashMap<>()); + private Map parameters = new HashMap<>(); + + public QueryParameter(Map parameters) { + this.parameters = parameters; + } + + public QueryParameter(final String uri) { + final int index = uri.indexOf("?"); + final String queryString = uri.substring(index + 1); + final String[] splitedQueryStrings = queryString.split("&"); // account=gugu + for (final String str : splitedQueryStrings) { + final int strIndex = str.indexOf("="); + parameters.put(str.substring(0, strIndex), str.substring(strIndex + 1)); + } + } + + public Map getParameters() { + return parameters; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java index 74e8bbd584..4767dea69f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java @@ -1,79 +1,79 @@ -package org.apache.coyote.http11; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.util.Map; - -public class RequestURI { - private static final String ROOT_FOLDER = "static"; - private final String path; - private final QueryParameter queryParameter; - private final File file; - - public RequestURI(final String path, final QueryParameter queryParameter, final File file) { - this.path = path; - this.queryParameter = queryParameter; - this.file = file; - } - - public RequestURI(final String uri) { - this.path = parsePath(uri); - this.queryParameter = parseQueryString(uri); - - final URL resource = getClass().getClassLoader().getResource(ROOT_FOLDER + uri); - if (resource == null) { - this.file = null; - return; - } - final File findedFile = new File(resource.getPath()); - if (findedFile.isFile()) { - this.file = findedFile; - return; - } - this.file = null; - } - - private String parsePath(String uri) { - int index = uri.indexOf("?"); - if (index == -1) { - return uri; - } - - return uri.substring(0, index); - } - - private QueryParameter parseQueryString(String uri) { - int index = uri.indexOf("?"); - if (index == -1) { - return QueryParameter.EMPTY; - } - - return new QueryParameter(uri.substring(index + 1)); - } - - public String readFile() { - try { - return new String(Files.readAllBytes(file.toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("파일을 읽던 중 에러가 발생했습니다."); - } - } - - public int getFileLength() throws IOException { - return new String(Files.readAllBytes(file.toPath())).getBytes().length; - } - - public boolean isExistFile() { - return this.file == null; - } - - public String getPath() { - return this.path; - } - - public Map getQueryParameter() { - return queryParameter.getQueryParameter(); - } -} +package org.apache.coyote.http11; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.Map; + +public class RequestURI { + private static final String ROOT_FOLDER = "static"; + private final String path; + private final QueryParameter queryParameter; + private final File file; + + public RequestURI(final String path, final QueryParameter queryParameter, final File file) { + this.path = path; + this.queryParameter = queryParameter; + this.file = file; + } + + public RequestURI(final String uri) { + this.path = parsePath(uri); + this.queryParameter = parseQueryString(uri); + + final URL resource = getClass().getClassLoader().getResource(ROOT_FOLDER + uri); + if (resource == null) { + this.file = null; + return; + } + final File findedFile = new File(resource.getPath()); + if (findedFile.isFile()) { + this.file = findedFile; + return; + } + this.file = null; + } + + private String parsePath(String uri) { + int index = uri.indexOf("?"); + if (index == -1) { + return uri; + } + + return uri.substring(0, index); + } + + private QueryParameter parseQueryString(String uri) { + int index = uri.indexOf("?"); + if (index == -1) { + return QueryParameter.EMPTY; + } + + return new QueryParameter(uri.substring(index + 1)); + } + + public String readFile() { + try { + return new String(Files.readAllBytes(file.toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("파일을 읽던 중 에러가 발생했습니다."); + } + } + + public int getFileLength() throws IOException { + return new String(Files.readAllBytes(file.toPath())).getBytes().length; + } + + public boolean isExistFile() { + return this.file == null; + } + + public String getPath() { + return this.path; + } + + public Map getQueryParameter() { + return queryParameter.getParameters(); + } +} From c769ba605858bc7b0021b9fc3b7772fa77d4a56a Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 17:07:37 +0900 Subject: [PATCH 09/29] =?UTF-8?q?refactor:=20final=20=ED=82=A4=EC=9B=8C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/apache/coyote/http11/QueryParameter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java b/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java index f7563997f5..96355d3b96 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java @@ -7,7 +7,7 @@ public class QueryParameter { public static final QueryParameter EMPTY = new QueryParameter(new HashMap<>()); private Map parameters = new HashMap<>(); - public QueryParameter(Map parameters) { + public QueryParameter(final Map parameters) { this.parameters = parameters; } From 7a2ea76b884b12b48966fa15cbec49a53bfa4d67 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 17:18:30 +0900 Subject: [PATCH 10/29] =?UTF-8?q?feat:=20RequestHeaders=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 --- .../apache/coyote/http11/RequestHeaders.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java new file mode 100644 index 0000000000..361a223cd3 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.util.Map; +import java.util.stream.Collectors; + +public class RequestHeaders { + Map headers; + + public RequestHeaders(final Map headers) { + this.headers = headers; + } + + public static RequestHeaders from(final BufferedReader br) { + Map headers = br.lines() + .takeWhile(line -> !line.equals("")) + .map(line -> line.split(": ")) + .collect(Collectors.toMap(line -> line[0], line -> line[1])); + + return new RequestHeaders(headers); + } + + boolean hasContentType() { + return headers.containsKey("Content-Type"); + } + + public Object get(final String headerKey) { + return headers.get(headerKey); + } +} From 69f2c371a05cdad09dbe12f9446544325e7283a9 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 17:24:58 +0900 Subject: [PATCH 11/29] =?UTF-8?q?refactor:=20user=EA=B0=80=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9C=BC=EB=A9=B4=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EB=A5=BC=20=EB=8D=98=EC=A7=80=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/handler/HandlerMapper.java | 493 +++++++++--------- 1 file changed, 240 insertions(+), 253 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 4be613be74..58395874d1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -1,253 +1,240 @@ -package org.apache.coyote.http11.handler; - -import static org.reflections.Reflections.log; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.model.User; -import org.apache.coyote.http11.Request; -import org.apache.coyote.http11.RequestLine; -import org.apache.coyote.http11.Response; - -public class HandlerMapper { - private static Map> HANDLERS = new HashMap<>(); - - public HandlerMapper() { - init(); - } - - public void init() { - HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); - HANDLERS.put(new HandlerStatus("GET", "/login"), this::loginHandler); - HANDLERS.put(new HandlerStatus("GET", "/login", Set.of("account", "password")), - this::loginWithQueryParameterHandler); - HANDLERS.put(new HandlerStatus("POST", "/login"), this::loginFormHandler); - HANDLERS.put(new HandlerStatus("GET", "/register"), this::registerHandler); - HANDLERS.put(new HandlerStatus("POST", "/register"), this::registerFormHandler); - } - - public Response rootHandler(final Request request) { - final var responseBody = "Hello world!"; - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response htmlHandler(final Request request) { - final var responseBody = request.getRequestLine().readFile(); - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - - public Response cssHandler(final Request request) { - final var responseBody = request.getRequestLine().readFile(); - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/css;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response jsHandler(final Request request) { - final var responseBody = request.getRequestLine().readFile(); - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/javascript;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response loginHandler(final Request request) { - final URL resource = getClass().getClassLoader().getResource("static/login.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("login.html이 존재하지 않습니다."); - } - - // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response loginWithQueryParameterHandler(final Request request) { - final RequestLine requestLine = request.getRequestLine(); - final var queryParameter = requestLine.getRequestURI().getQueryParameter(); - - final URL resource = getClass().getClassLoader().getResource("static/login.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("login.html이 존재하지 않습니다."); - } - - final User user = InMemoryUserRepository.findByAccount(queryParameter.get("account")) - .orElseGet(null); - - if (user != null) { - if (!user.checkPassword(queryParameter.get("password"))) { - log.error("유저의 아이디와 비밀번호가 일치하지않습니다."); - } else { - log.info("user : " + user); - } - } - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - - return new Response(response); - } - - public Response loginFormHandler(final Request request) { - final Map requestForms = request.getRequestForms().getRequestForms(); - Optional user = login(requestForms.get("account"), requestForms.get("password")); - if (user.isPresent()) { - return loginSuccess(); - } - return loginFail(); - } - - private Response loginSuccess() { - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("index.html이 존재하지 않습니다."); - } - - // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 - final var response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - private Response loginFail() { - final URL resource = getClass().getClassLoader().getResource("static/401.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("index.html이 존재하지 않습니다."); - } - - final var response = String.join("\r\n", - "HTTP/1.1 401 Unauthorized ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - private Optional login(final String account, final String password) { - return InMemoryUserRepository.findByAccountAndPassword(account, password); - } - - public Response registerHandler(final Request request) { - final URL resource = getClass().getClassLoader().getResource("static/register.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("register.html이 존재하지 않습니다."); - } - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response registerFormHandler(final Request request) { - final Map requestForms = request.getRequestForms().getRequestForms(); - final String account = requestForms.get("account"); - final String email = requestForms.get("email"); - final String password = requestForms.get("password"); - InMemoryUserRepository.save(new User(account, password, email)); - - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("index.html이 존재하지 않습니다."); - } - - // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 - final var response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response handle(final Request request) { - final RequestLine requestLine = request.getRequestLine(); - final String path = requestLine.getPath(); - final String httpMethod = requestLine.getHttpMethod(); - final Map queryParameter = requestLine.getRequestURI().getQueryParameter(); - final Set queryParameterKeys = queryParameter.keySet(); - final HandlerStatus handlerStatus = new HandlerStatus(httpMethod, path, queryParameterKeys); - - final Function handler = HANDLERS.get(handlerStatus); - if (handler != null) { - return handler.apply(request); - } - - if (requestLine.getPath().endsWith(".html")) { - return htmlHandler(request); - } else if (requestLine.getPath().endsWith(".css")) { - return cssHandler(request); - } else if (requestLine.getPath().endsWith(".js")) { - return jsHandler(request); - } - throw new IllegalArgumentException("매핑되는 핸들러가 존재하지 않습니다."); - } -} +package org.apache.coyote.http11.handler; + +import static org.reflections.Reflections.log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.coyote.http11.Request; +import org.apache.coyote.http11.RequestLine; +import org.apache.coyote.http11.Response; + +public class HandlerMapper { + private static Map> HANDLERS = new HashMap<>(); + + public HandlerMapper() { + init(); + } + + public void init() { + HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); + HANDLERS.put(new HandlerStatus("GET", "/login"), this::loginHandler); + HANDLERS.put(new HandlerStatus("GET", "/login", Set.of("account", "password")), + this::loginWithQueryParameterHandler); + HANDLERS.put(new HandlerStatus("POST", "/login"), this::loginFormHandler); + HANDLERS.put(new HandlerStatus("GET", "/register"), this::registerHandler); + HANDLERS.put(new HandlerStatus("POST", "/register"), this::registerFormHandler); + } + + public Response rootHandler(final Request request) { + final var responseBody = "Hello world!"; + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response htmlHandler(final Request request) { + final var responseBody = request.getRequestLine().readFile(); + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + + public Response cssHandler(final Request request) { + final var responseBody = request.getRequestLine().readFile(); + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/css;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response jsHandler(final Request request) { + final var responseBody = request.getRequestLine().readFile(); + + final String response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/javascript;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response loginHandler(final Request request) { + final URL resource = getClass().getClassLoader().getResource("static/login.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("login.html이 존재하지 않습니다."); + } + + // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response loginWithQueryParameterHandler(final Request request) { + final RequestLine requestLine = request.getRequestLine(); + final var queryParameter = requestLine.getRequestURI().getQueryParameter(); + final User user = InMemoryUserRepository.findByAccountAndPassword( + queryParameter.get("account"), queryParameter.get("password")) + .orElseThrow(() -> new IllegalArgumentException("아이디와 비밀번호가 일치하는 사용자가 존재하지 않습니다.")); + log.info("user : " + user); + + + final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + final URL resource = classLoader.getResource("static/" + "index.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (final IOException e) { + throw new IllegalArgumentException("index.html" + "이 존재하지 않습니다."); + } + return new Response(responseBody); + } + + public Response loginFormHandler(final Request request) { + final Map requestForms = request.getRequestForms().getRequestForms(); + Optional user = login(requestForms.get("account"), requestForms.get("password")); + if (user.isPresent()) { + return loginSuccess(); + } + return loginFail(); + } + + private Response loginSuccess() { + final URL resource = getClass().getClassLoader().getResource("static/index.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("index.html이 존재하지 않습니다."); + } + + // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 + final var response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + private Response loginFail() { + final URL resource = getClass().getClassLoader().getResource("static/401.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("index.html이 존재하지 않습니다."); + } + + final var response = String.join("\r\n", + "HTTP/1.1 401 Unauthorized ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + private Optional login(final String account, final String password) { + return InMemoryUserRepository.findByAccountAndPassword(account, password); + } + + public Response registerHandler(final Request request) { + final URL resource = getClass().getClassLoader().getResource("static/register.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("register.html이 존재하지 않습니다."); + } + + final var response = String.join("\r\n", + "HTTP/1.1 200 OK ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response registerFormHandler(final Request request) { + final Map requestForms = request.getRequestForms().getRequestForms(); + final String account = requestForms.get("account"); + final String email = requestForms.get("email"); + final String password = requestForms.get("password"); + InMemoryUserRepository.save(new User(account, password, email)); + + final URL resource = getClass().getClassLoader().getResource("static/index.html"); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (IOException e) { + throw new IllegalArgumentException("index.html이 존재하지 않습니다."); + } + + // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 + final var response = String.join("\r\n", + "HTTP/1.1 302 Found ", + "Content-Type: text/html;charset=utf-8 ", + "Content-Length: " + responseBody.getBytes().length + " ", + "", + responseBody); + return new Response(response); + } + + public Response handle(final Request request) { + final RequestLine requestLine = request.getRequestLine(); + final String path = requestLine.getPath(); + final String httpMethod = requestLine.getHttpMethod(); + final Map queryParameter = requestLine.getRequestURI().getQueryParameter(); + final Set queryParameterKeys = queryParameter.keySet(); + final HandlerStatus handlerStatus = new HandlerStatus(httpMethod, path, queryParameterKeys); + + final Function handler = HANDLERS.get(handlerStatus); + if (handler != null) { + return handler.apply(request); + } + + if (requestLine.getPath().endsWith(".html")) { + return htmlHandler(request); + } else if (requestLine.getPath().endsWith(".css")) { + return cssHandler(request); + } else if (requestLine.getPath().endsWith(".js")) { + return jsHandler(request); + } + throw new IllegalArgumentException("매핑되는 핸들러가 존재하지 않습니다."); + } +} From 2879d910f3204dcacaeab566dfa6c80d8bb0550a Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 19:25:25 +0900 Subject: [PATCH 12/29] =?UTF-8?q?refactor:=20Response=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/ContentType.java | 45 +++++ .../apache/coyote/http11/Http11Processor.java | 98 +++++------ .../org/apache/coyote/http11/HttpStatus.java | 23 +++ .../org/apache/coyote/http11/RequestLine.java | 94 +++++------ .../org/apache/coyote/http11/RequestURI.java | 10 +- .../org/apache/coyote/http11/Response.java | 82 +++++++-- .../apache/coyote/http11/ResponseHeaders.java | 35 ++++ .../coyote/http11/handler/HandlerMapper.java | 159 ++---------------- 8 files changed, 285 insertions(+), 261 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/ContentType.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java new file mode 100644 index 0000000000..f999390f05 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java @@ -0,0 +1,45 @@ +package org.apache.coyote.http11; + +import java.util.Arrays; + +public enum ContentType { + HTML(".html", "text/html"), + CSS(".css", "text/css"), + JS(".js", "text/javascript"); + + private final String extension; + private final String headerValue; + + ContentType(final String extension, final String headerValue) { + this.extension = extension; + this.headerValue = headerValue; + } + + public static ContentType findByFileName(final String fileName) { + final String extension = getExtension(fileName); + return findByExtension(extension); + } + + public static String getExtension(final String fileName) { + final int lastDotIndex = fileName.lastIndexOf('.'); + if (lastDotIndex > 0) { + return fileName.substring(lastDotIndex); + } + throw new IllegalArgumentException("파일 확장자가 존재하지 않습니다."); + } + + public static ContentType findByExtension(final String extension) { + return Arrays.stream(ContentType.values()) + .filter(contentType -> contentType.extension.equals(extension)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("지원하지 않는 파일형식입니다.")); + } + + public String getExtension() { + return extension; + } + + public String getHeaderValue() { + return headerValue; + } +} 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 8b8b1210af..b08f7ab9c2 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,49 +1,49 @@ -package org.apache.coyote.http11; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.Socket; -import nextstep.jwp.exception.UncheckedServletException; -import org.apache.coyote.Processor; -import org.apache.coyote.http11.handler.HandlerMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Http11Processor implements Runnable, Processor { - - private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - - private final Socket connection; - - private final HandlerMapper handlerMapper; - - public Http11Processor(final Socket connection) { - this.connection = connection; - this.handlerMapper = new HandlerMapper(); - } - - @Override - public void run() { - log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort()); - process(connection); - } - - @Override - public void process(final Socket connection) { - try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream(); - final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { - final Request request = Request.from(bufferedReader); - final var response = getResponse(request); - outputStream.write(response.getBytes()); - outputStream.flush(); - } catch (IOException | UncheckedServletException e) { - log.error(e.getMessage(), e); - } - } - - private String getResponse(final Request request) { - return handlerMapper.handle(request).getValue(); - } -} +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import nextstep.jwp.exception.UncheckedServletException; +import org.apache.coyote.Processor; +import org.apache.coyote.http11.handler.HandlerMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Http11Processor implements Runnable, Processor { + + private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + + private final Socket connection; + + private final HandlerMapper handlerMapper; + + public Http11Processor(final Socket connection) { + this.connection = connection; + this.handlerMapper = new HandlerMapper(); + } + + @Override + public void run() { + log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort()); + process(connection); + } + + @Override + public void process(final Socket connection) { + try (final var inputStream = connection.getInputStream(); + final var outputStream = connection.getOutputStream(); + final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + final Request request = Request.from(bufferedReader); + final String response = getResponse(request); + outputStream.write(response.getBytes()); + outputStream.flush(); + } catch (IOException | UncheckedServletException e) { + log.error(e.getMessage(), e); + } + } + + private String getResponse(final Request request) { + return handlerMapper.handle(request).getResponse(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java new file mode 100644 index 0000000000..395b4645e1 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java @@ -0,0 +1,23 @@ +package org.apache.coyote.http11; + +public enum HttpStatus { + OK("OK", 200), + FOUND("Found", 302), + UNAUTHORIZED("Unauthorized", 401); + + private final String message; + private final int code; + + HttpStatus(final String message, final int code) { + this.message = message; + this.code = code; + } + + public String getMessage() { + return message; + } + + public int getCode() { + return code; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java index 29b86b61c0..43d50f25b6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java @@ -1,47 +1,47 @@ -package org.apache.coyote.http11; - -public class RequestLine { - private static final String REQUEST_HEADER_DELIMITER = " "; - private final String httpMethod; - private final RequestURI requestURI; - private final String httpVersion; - - public RequestLine(final String httpMethod, final RequestURI requestURI, final String httpVersion) { - this.httpMethod = httpMethod; - this.requestURI = requestURI; - this.httpVersion = httpVersion; - } - - public static RequestLine from(final String requestHeaderFirstLine) { - final String[] splitedLine = requestHeaderFirstLine.split(REQUEST_HEADER_DELIMITER); - return new RequestLine( - splitedLine[0], - new RequestURI(splitedLine[1]), - splitedLine[2] - ); - } - - public boolean isExistRequestFile() { - return this.requestURI.isExistFile(); - } - - public String getPath() { - return this.requestURI.getPath(); - } - - public String readFile() { - return this.requestURI.readFile(); - } - - public RequestURI getRequestURI() { - return requestURI; - } - - public String getHttpMethod() { - return httpMethod; - } - - public String getHttpVersion() { - return httpVersion; - } -} +package org.apache.coyote.http11; + +public class RequestLine { + private static final String REQUEST_HEADER_DELIMITER = " "; + private final String httpMethod; + private final RequestURI requestURI; + private final String httpVersion; + + public RequestLine(final String httpMethod, final RequestURI requestURI, final String httpVersion) { + this.httpMethod = httpMethod; + this.requestURI = requestURI; + this.httpVersion = httpVersion; + } + + public static RequestLine from(final String requestHeaderFirstLine) { + final String[] splitedLine = requestHeaderFirstLine.split(REQUEST_HEADER_DELIMITER); + return new RequestLine( + splitedLine[0], + new RequestURI(splitedLine[1]), + splitedLine[2] + ); + } + + public boolean isExistRequestFile() { + return this.requestURI.isExistFile(); + } + + public String getPath() { + return this.requestURI.getPath(); + } + + public String readFile() { + return this.requestURI.readFile(); + } + + public RequestURI getRequestURI() { + return requestURI; + } + + public String getHttpMethod() { + return httpMethod; + } + + public String getHttpVersion() { + return httpVersion; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java index 4767dea69f..4a9a5250b4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java @@ -61,18 +61,18 @@ public String readFile() { } } - public int getFileLength() throws IOException { - return new String(Files.readAllBytes(file.toPath())).getBytes().length; - } - public boolean isExistFile() { - return this.file == null; + return this.file.isFile(); } public String getPath() { return this.path; } + public String getFileName() { + return this.file.getName(); + } + public Map getQueryParameter() { return queryParameter.getParameters(); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/Response.java index 9344b1f10c..d03f751970 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Response.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Response.java @@ -1,13 +1,69 @@ -package org.apache.coyote.http11; - -public class Response { - private final String value; - - public Response(final String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} +package org.apache.coyote.http11; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class Response { + private final String httpVersion; + private final HttpStatus httpStatus; + private final ResponseHeaders headers; + private final String responseBody; + + public Response(final String httpVersion, final HttpStatus httpStatus, final ResponseHeaders headers, + final String responseBody) { + this.httpVersion = httpVersion; + this.httpStatus = httpStatus; + this.headers = headers; + this.responseBody = responseBody; + } + + public static Response createByTemplate(final HttpStatus httpStatus, final String templateName) { + final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + final URL resource = classLoader.getResource("static/" + templateName); + final String responseBody; + try { + responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + } catch (final IOException e) { + throw new IllegalArgumentException("파일을 읽을 수 없습니다."); + } + + final ResponseHeaders responseHeaders = new ResponseHeaders(); + final ContentType contentType = ContentType.findByFileName(templateName); + responseHeaders.add("Content-Type", contentType.getHeaderValue() + ";charset=utf-8"); + responseHeaders.add("Content-Length", String.valueOf(responseBody.getBytes().length)); + return new Response("HTTP/1.1", httpStatus, responseHeaders, responseBody); + } + + public static Response createByTemplate(final RequestURI requestURI) { + final String responseBody = requestURI.readFile(); + final ResponseHeaders responseHeaders = new ResponseHeaders(); + final ContentType contentType = ContentType.findByFileName(requestURI.getFileName()); + responseHeaders.add("Content-Type", contentType.getHeaderValue() + ";charset=utf-8"); + responseHeaders.add("Content-Length", String.valueOf(responseBody.getBytes().length)); + return new Response("HTTP/1.1", HttpStatus.OK, responseHeaders, responseBody); + } + + public static Response createByResponseBody(final HttpStatus httpStatus, final String responseBody) { + final ResponseHeaders responseHeaders = new ResponseHeaders(); + responseHeaders.add("Content-Type", "text/html;charset=utf-8"); + responseHeaders.add("Content-Length", String.valueOf(responseBody.getBytes().length)); + return new Response("HTTP/1.1", httpStatus, responseHeaders, responseBody); + } + + public String getResponse() { + final List responseData = new ArrayList<>(); + final String responseLine = httpVersion + " " + httpStatus.getCode() + " " + httpStatus.getMessage() + " "; + responseData.add(responseLine); + + final List responseHeaderLines = headers.getHeaderLines(); + responseData.addAll(responseHeaderLines); + responseData.add(""); + responseData.add(responseBody); + + return String.join("\r\n", responseData); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java new file mode 100644 index 0000000000..2d0847aa2e --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java @@ -0,0 +1,35 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ResponseHeaders { + private final Map headers; + + public ResponseHeaders(final Map headers) { + this.headers = headers; + } + + public ResponseHeaders() { + this.headers = new HashMap<>(); + } + + public void add(final String key, final String value) { + if(headers.putIfAbsent(key, value) != null) { + final String originalValue = headers.get(key); + headers.replace(key, originalValue + "; " + value); + } + } + + public Map getHeaders() { + return headers; + } + + public List getHeaderLines() { + return headers.keySet().stream() + .map(key -> key + ": " + headers.get(key) + " ") + .collect(Collectors.toList()); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 58395874d1..4ce0754b5c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -2,10 +2,6 @@ import static org.reflections.Reflections.log; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -13,12 +9,13 @@ import java.util.function.Function; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; +import org.apache.coyote.http11.HttpStatus; import org.apache.coyote.http11.Request; import org.apache.coyote.http11.RequestLine; import org.apache.coyote.http11.Response; public class HandlerMapper { - private static Map> HANDLERS = new HashMap<>(); + private static final Map> HANDLERS = new HashMap<>(); public HandlerMapper() { init(); @@ -35,91 +32,21 @@ public void init() { } public Response rootHandler(final Request request) { - final var responseBody = "Hello world!"; - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response htmlHandler(final Request request) { - final var responseBody = request.getRequestLine().readFile(); - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - - public Response cssHandler(final Request request) { - final var responseBody = request.getRequestLine().readFile(); - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/css;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); - } - - public Response jsHandler(final Request request) { - final var responseBody = request.getRequestLine().readFile(); - - final String response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/javascript;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); + return Response.createByResponseBody(HttpStatus.OK, "Hello world!"); } public Response loginHandler(final Request request) { - final URL resource = getClass().getClassLoader().getResource("static/login.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("login.html이 존재하지 않습니다."); - } - - // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); + return Response.createByTemplate(HttpStatus.OK, "login.html"); } public Response loginWithQueryParameterHandler(final Request request) { final RequestLine requestLine = request.getRequestLine(); final var queryParameter = requestLine.getRequestURI().getQueryParameter(); final User user = InMemoryUserRepository.findByAccountAndPassword( - queryParameter.get("account"), queryParameter.get("password")) + queryParameter.get("account"), queryParameter.get("password")) .orElseThrow(() -> new IllegalArgumentException("아이디와 비밀번호가 일치하는 사용자가 존재하지 않습니다.")); log.info("user : " + user); - - - final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - final URL resource = classLoader.getResource("static/" + "index.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (final IOException e) { - throw new IllegalArgumentException("index.html" + "이 존재하지 않습니다."); - } - return new Response(responseBody); + return Response.createByTemplate(request.getRequestLine().getRequestURI()); } public Response loginFormHandler(final Request request) { @@ -132,40 +59,11 @@ public Response loginFormHandler(final Request request) { } private Response loginSuccess() { - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("index.html이 존재하지 않습니다."); - } - - // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 - final var response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); + return Response.createByTemplate(HttpStatus.FOUND, "index.html"); } private Response loginFail() { - final URL resource = getClass().getClassLoader().getResource("static/401.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("index.html이 존재하지 않습니다."); - } - - final var response = String.join("\r\n", - "HTTP/1.1 401 Unauthorized ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); + return Response.createByTemplate(HttpStatus.UNAUTHORIZED, "401.html"); } private Optional login(final String account, final String password) { @@ -173,21 +71,7 @@ private Optional login(final String account, final String password) { } public Response registerHandler(final Request request) { - final URL resource = getClass().getClassLoader().getResource("static/register.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("register.html이 존재하지 않습니다."); - } - - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); + return Response.createByTemplate(HttpStatus.OK, "register.html"); } public Response registerFormHandler(final Request request) { @@ -197,22 +81,7 @@ public Response registerFormHandler(final Request request) { final String password = requestForms.get("password"); InMemoryUserRepository.save(new User(account, password, email)); - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (IOException e) { - throw new IllegalArgumentException("index.html이 존재하지 않습니다."); - } - - // request header의 cookie에 세션아이디가 없으면 response에 set-cookie추가 - final var response = String.join("\r\n", - "HTTP/1.1 302 Found ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - return new Response(response); + return Response.createByTemplate(HttpStatus.FOUND, "index.html"); } public Response handle(final Request request) { @@ -228,12 +97,8 @@ public Response handle(final Request request) { return handler.apply(request); } - if (requestLine.getPath().endsWith(".html")) { - return htmlHandler(request); - } else if (requestLine.getPath().endsWith(".css")) { - return cssHandler(request); - } else if (requestLine.getPath().endsWith(".js")) { - return jsHandler(request); + if (requestLine.getRequestURI().isExistFile()) { + return Response.createByTemplate(request.getRequestLine().getRequestURI()); } throw new IllegalArgumentException("매핑되는 핸들러가 존재하지 않습니다."); } From 0aa56d7d8c8953dbd4462179a10e162225e096ba Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 20:37:51 +0900 Subject: [PATCH 13/29] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=8B=9C=20Cookie=EC=97=90=20JSESSIONID=20=EC=84=A4=EC=A0=95?= =?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 --- README.md | 47 ++++---- .../java/org/apache/coyote/http11/Cookie.java | 31 ++++++ .../org/apache/coyote/http11/Request.java | 100 +++++++++--------- .../apache/coyote/http11/RequestHeaders.java | 16 ++- .../org/apache/coyote/http11/Response.java | 13 ++- .../apache/coyote/http11/ResponseHeaders.java | 6 +- .../coyote/http11/handler/HandlerMapper.java | 11 +- 7 files changed, 144 insertions(+), 80 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Cookie.java diff --git a/README.md b/README.md index 1fa808eb28..41dcf018f0 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,24 @@ -# 톰캣 구현하기 - - -### html 파일 응답하기 -* [x] HTTP Request Header에서 필요한 정보를 파싱한다. - * [x] HTTP 메소드를 저장한다. - * [x] Request URI(html 파일)를 저장한다. - * [x] HTTP 버전을 저장한다. -* [x] Request URI에 해당하는 파일을 responseBody로 돌려준다. - -### CSS 지원하기 -* [x] 요청 리소스가 CSS or JS인 경우 Response Header에 적절한 Content-Type을 보낸다. - -### 로그인 -* [x] 로그인 페이지를 응답한다. - * [x] Request URI에서 QueryString을 파싱해 저장한다. - * [x] QueryString에서 유저의 정보를 추출해 콘솔로 출력한다. -* [x] 로그인에 성공하면 index 페이지로 리다이렉트한다. -* [x] 로그인에 실패하면 401 페이지로 리다이렉트한다. - -### 회원가입 -* [x] 회원가입 페이지를 응답한다. -* [x] 회원가입을 수행하면 index 페이지로 리다이렉트한다. +# 톰캣 구현하기 + + +### html 파일 응답하기 +* [x] HTTP Request Header에서 필요한 정보를 파싱한다. + * [x] HTTP 메소드를 저장한다. + * [x] Request URI(html 파일)를 저장한다. + * [x] HTTP 버전을 저장한다. +* [x] Request URI에 해당하는 파일을 responseBody로 돌려준다. + +### CSS 지원하기 +* [x] 요청 리소스가 CSS or JS인 경우 Response Header에 적절한 Content-Type을 보낸다. + +### 로그인 +* [x] 로그인 페이지를 응답한다. + * [x] Request URI에서 QueryString을 파싱해 저장한다. + * [x] QueryString에서 유저의 정보를 추출해 콘솔로 출력한다. +* [x] 로그인에 성공하면 index 페이지로 리다이렉트한다. + * [x] Cookie에 JSESSIONID가 없다면 추가한다. +* [x] 로그인에 실패하면 401 페이지로 리다이렉트한다. + +### 회원가입 +* [x] 회원가입 페이지를 응답한다. +* [x] 회원가입을 수행하면 index 페이지로 리다이렉트한다. diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java b/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java new file mode 100644 index 0000000000..584d1b582b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java @@ -0,0 +1,31 @@ +package org.apache.coyote.http11; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +public class Cookie { + private final Map cookieData; + + private Cookie(final Map cookieData) { + this.cookieData = cookieData; + } + + public static Cookie from(final String value) { + if (value == null) { + return new Cookie(Collections.emptyMap()); + } + + String[] cookies = value.split("; "); + Map cookieData = Arrays.stream(cookies) + .map(cookie -> cookie.split("=")) + .collect(Collectors.toMap(cookiePair -> cookiePair[0], cookiePair -> cookiePair[1])); + + return new Cookie(cookieData); + } + + public String get(final String key) { + return cookieData.get(key); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/Request.java index a35842805f..898ae3831e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Request.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Request.java @@ -1,48 +1,52 @@ -package org.apache.coyote.http11; - -import java.io.BufferedReader; -import java.io.IOException; - -public class Request { - private final RequestLine requestLine; - private final RequestHeaders requestHeaders; - private final RequestForms requestForms; - - public Request(final RequestLine requestLine, final RequestHeaders requestHeaders, - final RequestForms requestForms) { - this.requestLine = requestLine; - this.requestHeaders = requestHeaders; - this.requestForms = requestForms; - } - - public static Request from(final BufferedReader br) throws IOException { - final RequestLine requestLine = RequestLine.from(br.readLine()); - final RequestHeaders requestHeaders = RequestHeaders.from(br); - final RequestForms requestForms = createRequestBody(br, requestHeaders); - return new Request(requestLine, requestHeaders, requestForms); - } - - public RequestLine getRequestLine() { - return requestLine; - } - - private static RequestForms createRequestBody(BufferedReader br, RequestHeaders requestHeaders) - throws IOException { - if (!requestHeaders.hasContentType()) { - return new RequestForms(null); - } - final int contentLength = Integer.parseInt((String) requestHeaders.get("Content-Length")); - final char[] buffer = new char[contentLength]; - br.read(buffer, 0, contentLength); - final String requestBody = new String(buffer); - return RequestForms.from(requestBody); - } - - public RequestHeaders getRequestHeaders() { - return requestHeaders; - } - - public RequestForms getRequestForms() { - return requestForms; - } -} +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; + +public class Request { + private final RequestLine requestLine; + private final RequestHeaders requestHeaders; + private final RequestForms requestForms; + + public Request(final RequestLine requestLine, final RequestHeaders requestHeaders, + final RequestForms requestForms) { + this.requestLine = requestLine; + this.requestHeaders = requestHeaders; + this.requestForms = requestForms; + } + + public static Request from(final BufferedReader br) throws IOException { + final RequestLine requestLine = RequestLine.from(br.readLine()); + final RequestHeaders requestHeaders = RequestHeaders.from(br); + final RequestForms requestForms = createRequestBody(br, requestHeaders); + return new Request(requestLine, requestHeaders, requestForms); + } + + public RequestLine getRequestLine() { + return requestLine; + } + + private static RequestForms createRequestBody(BufferedReader br, RequestHeaders requestHeaders) + throws IOException { + if (!requestHeaders.hasContentType()) { + return new RequestForms(null); + } + final int contentLength = Integer.parseInt((String) requestHeaders.get("Content-Length")); + final char[] buffer = new char[contentLength]; + br.read(buffer, 0, contentLength); + final String requestBody = new String(buffer); + return RequestForms.from(requestBody); + } + + public String getSessionId() { + return requestHeaders.getCookieValue("JSESSIONID"); + } + + public RequestHeaders getRequestHeaders() { + return requestHeaders; + } + + public RequestForms getRequestForms() { + return requestForms; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java index 361a223cd3..65db8b0583 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java @@ -5,19 +5,21 @@ import java.util.stream.Collectors; public class RequestHeaders { - Map headers; + private final Map headers; + private final Cookie cookie; - public RequestHeaders(final Map headers) { + public RequestHeaders(final Map headers, final Cookie cookie) { this.headers = headers; + this.cookie = cookie; } public static RequestHeaders from(final BufferedReader br) { - Map headers = br.lines() + final Map headers = br.lines() .takeWhile(line -> !line.equals("")) .map(line -> line.split(": ")) .collect(Collectors.toMap(line -> line[0], line -> line[1])); - - return new RequestHeaders(headers); + final String cookieValue = (String) headers.get("Cookie"); + return new RequestHeaders(headers, Cookie.from(cookieValue)); } boolean hasContentType() { @@ -27,4 +29,8 @@ boolean hasContentType() { public Object get(final String headerKey) { return headers.get(headerKey); } + + public String getCookieValue(final String cookieKey) { + return cookie.get(cookieKey); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/Response.java index d03f751970..e00d2ea88b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Response.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Response.java @@ -5,7 +5,9 @@ import java.net.URL; import java.nio.file.Files; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class Response { private final String httpVersion; @@ -21,7 +23,11 @@ public Response(final String httpVersion, final HttpStatus httpStatus, final Res this.responseBody = responseBody; } - public static Response createByTemplate(final HttpStatus httpStatus, final String templateName) { + public static Response createByTemplate( + final HttpStatus httpStatus, + final String templateName, + Map headers + ) { final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); final URL resource = classLoader.getResource("static/" + templateName); final String responseBody; @@ -35,9 +41,14 @@ public static Response createByTemplate(final HttpStatus httpStatus, final Strin final ContentType contentType = ContentType.findByFileName(templateName); responseHeaders.add("Content-Type", contentType.getHeaderValue() + ";charset=utf-8"); responseHeaders.add("Content-Length", String.valueOf(responseBody.getBytes().length)); + responseHeaders.addAll(headers); return new Response("HTTP/1.1", httpStatus, responseHeaders, responseBody); } + public static Response createByTemplate(final HttpStatus httpStatus, final String templateName) { + return createByTemplate(httpStatus, templateName, new HashMap<>()); + } + public static Response createByTemplate(final RequestURI requestURI) { final String responseBody = requestURI.readFile(); final ResponseHeaders responseHeaders = new ResponseHeaders(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java index 2d0847aa2e..c5323881a8 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java @@ -17,12 +17,16 @@ public ResponseHeaders() { } public void add(final String key, final String value) { - if(headers.putIfAbsent(key, value) != null) { + if (headers.putIfAbsent(key, value) != null) { final String originalValue = headers.get(key); headers.replace(key, originalValue + "; " + value); } } + public void addAll(Map headers) { + headers.keySet().forEach(key -> add(key, headers.get(key))); + } + public Map getHeaders() { return headers; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 4ce0754b5c..248bd71117 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.function.Function; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; @@ -53,12 +54,18 @@ public Response loginFormHandler(final Request request) { final Map requestForms = request.getRequestForms().getRequestForms(); Optional user = login(requestForms.get("account"), requestForms.get("password")); if (user.isPresent()) { - return loginSuccess(); + return loginSuccess(request); } return loginFail(); } - private Response loginSuccess() { + private Response loginSuccess(final Request request) { + if (request.getSessionId() == null) { + final Map header = new HashMap<>(); + final String sessionId = String.valueOf(UUID.randomUUID()); + header.put("Set-Cookie", "JSESSIONID=" + sessionId); + return Response.createByTemplate(HttpStatus.FOUND, "index.html", header); + } return Response.createByTemplate(HttpStatus.FOUND, "index.html"); } From 48cacb1b790b33c4b883c73c629cc603903a71dc Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 21:18:28 +0900 Subject: [PATCH 14/29] =?UTF-8?q?feat:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=84=B8=EC=85=98=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + .../org/apache/coyote/http11/Request.java | 5 +-- .../org/apache/coyote/http11/Session.java | 34 +++++++++++++++++++ .../apache/coyote/http11/SessionManager.java | 23 +++++++++++++ .../coyote/http11/handler/HandlerMapper.java | 24 +++++++++---- 5 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Session.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java diff --git a/README.md b/README.md index 41dcf018f0..a6b9d0453e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ * [x] 로그인 페이지를 응답한다. * [x] Request URI에서 QueryString을 파싱해 저장한다. * [x] QueryString에서 유저의 정보를 추출해 콘솔로 출력한다. + * [x] 세션 정보가 있다면 index 페이지로 리다이렉트 한다. * [x] 로그인에 성공하면 index 페이지로 리다이렉트한다. * [x] Cookie에 JSESSIONID가 없다면 추가한다. * [x] 로그인에 실패하면 401 페이지로 리다이렉트한다. diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/Request.java index 898ae3831e..e41eedb79a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Request.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Request.java @@ -38,8 +38,9 @@ private static RequestForms createRequestBody(BufferedReader br, RequestHeaders return RequestForms.from(requestBody); } - public String getSessionId() { - return requestHeaders.getCookieValue("JSESSIONID"); + public Session getSession() { + final String sessionId = requestHeaders.getCookieValue("JSESSIONID"); + return SessionManager.findSession(sessionId); } public RequestHeaders getRequestHeaders() { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Session.java b/tomcat/src/main/java/org/apache/coyote/http11/Session.java new file mode 100644 index 0000000000..26f2ad4723 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Session.java @@ -0,0 +1,34 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class Session { + private final String id; + private final Map values = new HashMap<>(); + + public Session() { + this.id = String.valueOf(UUID.randomUUID()); + } + + public String getId() { + return this.id; + } + + public Object getAttribute(final String name) { + return values.get(name); + } + + public void setAttribute(final String name, final Object value) { + values.put(name, value); + } + + public void removeAttribute(final String name) { + values.remove(name); + } + + public void invalidate() { + values.clear(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java new file mode 100644 index 0000000000..a6840c995b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java @@ -0,0 +1,23 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class SessionManager { + private static final Map SESSIONS = new HashMap<>(); + + public static void add(final Session session) { + SESSIONS.put(session.getId(), session); + } + + public static Session findSession(final String id) { + return SESSIONS.get(id); + } + + public static void remove(final String id) { + SESSIONS.remove(id); + } + + private SessionManager() { + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 248bd71117..bdb6f7b557 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -6,7 +6,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.UUID; import java.util.function.Function; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; @@ -14,6 +13,8 @@ import org.apache.coyote.http11.Request; import org.apache.coyote.http11.RequestLine; import org.apache.coyote.http11.Response; +import org.apache.coyote.http11.Session; +import org.apache.coyote.http11.SessionManager; public class HandlerMapper { private static final Map> HANDLERS = new HashMap<>(); @@ -37,10 +38,18 @@ public Response rootHandler(final Request request) { } public Response loginHandler(final Request request) { + if (request.getSession() != null) { + return Response.createByTemplate(HttpStatus.FOUND, "index.html"); + } + return Response.createByTemplate(HttpStatus.OK, "login.html"); } public Response loginWithQueryParameterHandler(final Request request) { + if (request.getSession() != null) { + return Response.createByTemplate(HttpStatus.FOUND, "index.html"); + } + final RequestLine requestLine = request.getRequestLine(); final var queryParameter = requestLine.getRequestURI().getQueryParameter(); final User user = InMemoryUserRepository.findByAccountAndPassword( @@ -54,16 +63,19 @@ public Response loginFormHandler(final Request request) { final Map requestForms = request.getRequestForms().getRequestForms(); Optional user = login(requestForms.get("account"), requestForms.get("password")); if (user.isPresent()) { - return loginSuccess(request); + return loginSuccess(request, user.get()); } return loginFail(); } - private Response loginSuccess(final Request request) { - if (request.getSessionId() == null) { + private Response loginSuccess(final Request request, final User user) { + if (request.getSession() == null) { + final Session session = new Session(); + session.setAttribute("user", user); + SessionManager.add(session); + final Map header = new HashMap<>(); - final String sessionId = String.valueOf(UUID.randomUUID()); - header.put("Set-Cookie", "JSESSIONID=" + sessionId); + header.put("Set-Cookie", "JSESSIONID=" + session.getId()); return Response.createByTemplate(HttpStatus.FOUND, "index.html", header); } return Response.createByTemplate(HttpStatus.FOUND, "index.html"); From c0efe01dc992d1de0005c9ad8d28de92bad93986 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 21:21:48 +0900 Subject: [PATCH 15/29] =?UTF-8?q?feat:=20ico=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/ContentType.java | 3 ++- tomcat/src/main/resources/static/favicon.ico | Bin 0 -> 42626 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tomcat/src/main/resources/static/favicon.ico diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java index f999390f05..a9ce67fe40 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java @@ -5,7 +5,8 @@ public enum ContentType { HTML(".html", "text/html"), CSS(".css", "text/css"), - JS(".js", "text/javascript"); + JS(".js", "text/javascript"), + ICO(".ico", "image/x-icon"); private final String extension; private final String headerValue; diff --git a/tomcat/src/main/resources/static/favicon.ico b/tomcat/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8f80fd22c8957115b76a7ac020a60010631ca186 GIT binary patch literal 42626 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

J<~IP-y$DmY_kWudB6 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 Date: Mon, 4 Sep 2023 21:27:24 +0900 Subject: [PATCH 16/29] =?UTF-8?q?refactor:=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/ResponseHeaders.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java index c5323881a8..6b6812621e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java @@ -1,6 +1,6 @@ package org.apache.coyote.http11; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -8,12 +8,8 @@ public class ResponseHeaders { private final Map headers; - public ResponseHeaders(final Map headers) { - this.headers = headers; - } - public ResponseHeaders() { - this.headers = new HashMap<>(); + this.headers = new LinkedHashMap<>(); } public void add(final String key, final String value) { From 6f9301644e63b26edc7c493894c198ddba227114 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Mon, 4 Sep 2023 21:27:24 +0900 Subject: [PATCH 17/29] =?UTF-8?q?refactor:=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/coyote/http11/Http11Processor.java | 1 + .../coyote/http11/handler/HandlerMapper.java | 6 +-- .../coyote/http11/{ => request}/Cookie.java | 2 +- .../http11/{ => request}/QueryParameter.java | 2 +- .../coyote/http11/{ => request}/Request.java | 4 +- .../http11/{ => request}/RequestForms.java | 54 +++++++++---------- .../http11/{ => request}/RequestHeaders.java | 2 +- .../http11/{ => request}/RequestLine.java | 2 +- .../http11/{ => request}/RequestURI.java | 2 +- .../http11/{ => response}/ContentType.java | 2 +- .../http11/{ => response}/Response.java | 4 +- .../{ => response}/ResponseHeaders.java | 10 ++-- 12 files changed, 46 insertions(+), 45 deletions(-) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/Cookie.java (91%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/QueryParameter.java (92%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/Request.java (90%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/RequestForms.java (91%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/RequestHeaders.java (93%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/RequestLine.java (93%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => request}/RequestURI.java (94%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => response}/ContentType.java (94%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => response}/Response.java (94%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => response}/ResponseHeaders.java (78%) 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 b08f7ab9c2..d879eabbfd 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -7,6 +7,7 @@ import nextstep.jwp.exception.UncheckedServletException; import org.apache.coyote.Processor; import org.apache.coyote.http11.handler.HandlerMapper; +import org.apache.coyote.http11.request.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index bdb6f7b557..ecdbd7fd03 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -10,11 +10,11 @@ import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; import org.apache.coyote.http11.HttpStatus; -import org.apache.coyote.http11.Request; -import org.apache.coyote.http11.RequestLine; -import org.apache.coyote.http11.Response; import org.apache.coyote.http11.Session; import org.apache.coyote.http11.SessionManager; +import org.apache.coyote.http11.request.Request; +import org.apache.coyote.http11.request.RequestLine; +import org.apache.coyote.http11.response.Response; public class HandlerMapper { private static final Map> HANDLERS = new HashMap<>(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java similarity index 91% rename from tomcat/src/main/java/org/apache/coyote/http11/Cookie.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java index 584d1b582b..058c9cde07 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Cookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.request; import java.util.Arrays; import java.util.Collections; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java b/tomcat/src/main/java/org/apache/coyote/http11/request/QueryParameter.java similarity index 92% rename from tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/QueryParameter.java index 96355d3b96..c09a165574 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/QueryParameter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/QueryParameter.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.request; import java.util.HashMap; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java similarity index 90% rename from tomcat/src/main/java/org/apache/coyote/http11/Request.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/Request.java index e41eedb79a..6ab5e0f9cc 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Request.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java @@ -1,7 +1,9 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.request; import java.io.BufferedReader; import java.io.IOException; +import org.apache.coyote.http11.Session; +import org.apache.coyote.http11.SessionManager; public class Request { private final RequestLine requestLine; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestForms.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java similarity index 91% rename from tomcat/src/main/java/org/apache/coyote/http11/RequestForms.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java index 1d2d0f487e..3bf4774ed7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestForms.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java @@ -1,27 +1,27 @@ -package org.apache.coyote.http11; - -import java.util.LinkedHashMap; -import java.util.Map; - -public class RequestForms { - private final Map requestForms; - - public RequestForms(final Map requestForms) { - this.requestForms = requestForms; - } - - public static RequestForms from(final String values) { - final Map requestForms = new LinkedHashMap<>(); - - final String[] splitedValues = values.split("&"); - for (final String value : splitedValues) { - final String[] formPair = value.split("="); - requestForms.put(formPair[0], formPair[1]); - } - return new RequestForms(requestForms); - } - - public Map getRequestForms() { - return requestForms; - } -} +package org.apache.coyote.http11.request; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class RequestForms { + private final Map requestForms; + + public RequestForms(final Map requestForms) { + this.requestForms = requestForms; + } + + public static RequestForms from(final String values) { + final Map requestForms = new LinkedHashMap<>(); + + final String[] splitedValues = values.split("&"); + for (final String value : splitedValues) { + final String[] formPair = value.split("="); + requestForms.put(formPair[0], formPair[1]); + } + return new RequestForms(requestForms); + } + + public Map getRequestForms() { + return requestForms; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java similarity index 93% rename from tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java index 65db8b0583..0213322b7b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.request; import java.io.BufferedReader; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java similarity index 93% rename from tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java index 43d50f25b6..e4a4d59aee 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.request; public class RequestLine { private static final String REQUEST_HEADER_DELIMITER = " "; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java similarity index 94% rename from tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java rename to tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java index 4a9a5250b4..2f71b3c712 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/RequestURI.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.request; import java.io.File; import java.io.IOException; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ContentType.java similarity index 94% rename from tomcat/src/main/java/org/apache/coyote/http11/ContentType.java rename to tomcat/src/main/java/org/apache/coyote/http11/response/ContentType.java index a9ce67fe40..7f8f40aac1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ContentType.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.response; import java.util.Arrays; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java similarity index 94% rename from tomcat/src/main/java/org/apache/coyote/http11/Response.java rename to tomcat/src/main/java/org/apache/coyote/http11/response/Response.java index e00d2ea88b..2ca0a01ae5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Response.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.response; import java.io.File; import java.io.IOException; @@ -8,6 +8,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.coyote.http11.HttpStatus; +import org.apache.coyote.http11.request.RequestURI; public class Response { private final String httpVersion; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java similarity index 78% rename from tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java rename to tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java index c5323881a8..fc0e49ee41 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/ResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java @@ -1,6 +1,6 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.response; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -8,12 +8,8 @@ public class ResponseHeaders { private final Map headers; - public ResponseHeaders(final Map headers) { - this.headers = headers; - } - public ResponseHeaders() { - this.headers = new HashMap<>(); + this.headers = new LinkedHashMap<>(); } public void add(final String key, final String value) { From ef5242ba845c5499eaa79785108ab8fc51e393ab Mon Sep 17 00:00:00 2001 From: hgo641 Date: Tue, 5 Sep 2023 10:37:09 +0900 Subject: [PATCH 18/29] =?UTF-8?q?refactor:=20final=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 | 2 +- .../coyote/http11/handler/HandlerMapper.java | 9 +++---- .../apache/coyote/http11/request/Cookie.java | 4 +-- .../apache/coyote/http11/request/Request.java | 10 +++---- .../coyote/http11/request/RequestURI.java | 10 +++---- .../coyote/http11/response/Response.java | 26 +++++++++++-------- .../http11/response/ResponseHeaders.java | 2 +- 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index d879eabbfd..f29aed24b6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -39,7 +39,7 @@ public void process(final Socket connection) { final String response = getResponse(request); outputStream.write(response.getBytes()); outputStream.flush(); - } catch (IOException | UncheckedServletException e) { + } catch (final IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index ecdbd7fd03..ad973733e7 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -60,12 +60,9 @@ public Response loginWithQueryParameterHandler(final Request request) { } public Response loginFormHandler(final Request request) { - final Map requestForms = request.getRequestForms().getRequestForms(); - Optional user = login(requestForms.get("account"), requestForms.get("password")); - if (user.isPresent()) { - return loginSuccess(request, user.get()); - } - return loginFail(); + final Map requestForms = request.getRequestForms().getFormData(); + final Optional user = login(requestForms.get("account"), requestForms.get("password")); + return user.map(value -> loginSuccess(request, value)).orElseGet(this::loginFail); } private Response loginSuccess(final Request request, final User user) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java index 058c9cde07..0cd33d5ef9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java @@ -17,8 +17,8 @@ public static Cookie from(final String value) { return new Cookie(Collections.emptyMap()); } - String[] cookies = value.split("; "); - Map cookieData = Arrays.stream(cookies) + final String[] cookies = value.split("; "); + final Map cookieData = Arrays.stream(cookies) .map(cookie -> cookie.split("=")) .collect(Collectors.toMap(cookiePair -> cookiePair[0], cookiePair -> cookiePair[1])); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java index 6ab5e0f9cc..0b6f9cf2d3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java @@ -24,11 +24,7 @@ public static Request from(final BufferedReader br) throws IOException { return new Request(requestLine, requestHeaders, requestForms); } - public RequestLine getRequestLine() { - return requestLine; - } - - private static RequestForms createRequestBody(BufferedReader br, RequestHeaders requestHeaders) + private static RequestForms createRequestBody(final BufferedReader br, final RequestHeaders requestHeaders) throws IOException { if (!requestHeaders.hasContentType()) { return new RequestForms(null); @@ -45,6 +41,10 @@ public Session getSession() { return SessionManager.findSession(sessionId); } + public RequestLine getRequestLine() { + return requestLine; + } + public RequestHeaders getRequestHeaders() { return requestHeaders; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java index 2f71b3c712..34b25f6bba 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java @@ -35,8 +35,8 @@ public RequestURI(final String uri) { this.file = null; } - private String parsePath(String uri) { - int index = uri.indexOf("?"); + private String parsePath(final String uri) { + final int index = uri.indexOf("?"); if (index == -1) { return uri; } @@ -44,8 +44,8 @@ private String parsePath(String uri) { return uri.substring(0, index); } - private QueryParameter parseQueryString(String uri) { - int index = uri.indexOf("?"); + private QueryParameter parseQueryString(final String uri) { + final int index = uri.indexOf("?"); if (index == -1) { return QueryParameter.EMPTY; } @@ -56,7 +56,7 @@ private QueryParameter parseQueryString(String uri) { public String readFile() { try { return new String(Files.readAllBytes(file.toPath())); - } catch (IOException e) { + } catch (final IOException e) { throw new IllegalArgumentException("파일을 읽던 중 에러가 발생했습니다."); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java index 2ca0a01ae5..fa7f9ece96 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java @@ -28,25 +28,29 @@ public Response(final String httpVersion, final HttpStatus httpStatus, final Res public static Response createByTemplate( final HttpStatus httpStatus, final String templateName, - Map headers + final Map headers ) { - final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - final URL resource = classLoader.getResource("static/" + templateName); - final String responseBody; - try { - responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - } catch (final IOException e) { - throw new IllegalArgumentException("파일을 읽을 수 없습니다."); - } - final ResponseHeaders responseHeaders = new ResponseHeaders(); final ContentType contentType = ContentType.findByFileName(templateName); + final String responseBody = getTemplateBody(templateName); responseHeaders.add("Content-Type", contentType.getHeaderValue() + ";charset=utf-8"); responseHeaders.add("Content-Length", String.valueOf(responseBody.getBytes().length)); responseHeaders.addAll(headers); return new Response("HTTP/1.1", httpStatus, responseHeaders, responseBody); } + private static String getTemplateBody(final String templateName) { + final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + final URL templateUrl = classLoader.getResource("static/" + templateName); + try { + return new String(Files.readAllBytes(new File(templateUrl.getFile()).toPath())); + } catch (final NullPointerException e) { + throw new IllegalArgumentException("파일이 존재하지 않습니다."); + } catch (final IOException e) { + throw new IllegalArgumentException("파일을 읽을 수 없습니다."); + } + } + public static Response createByTemplate(final HttpStatus httpStatus, final String templateName) { return createByTemplate(httpStatus, templateName, new HashMap<>()); } @@ -75,8 +79,8 @@ public String getResponse() { final List responseHeaderLines = headers.getHeaderLines(); responseData.addAll(responseHeaderLines); responseData.add(""); - responseData.add(responseBody); + responseData.add(responseBody); return String.join("\r\n", responseData); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java index fc0e49ee41..f8366dd960 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java @@ -19,7 +19,7 @@ public void add(final String key, final String value) { } } - public void addAll(Map headers) { + public void addAll(final Map headers) { headers.keySet().forEach(key -> add(key, headers.get(key))); } From c7b9696ec0f54d89f1c0cfa7918b2024aba1ca85 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Tue, 5 Sep 2023 10:37:32 +0900 Subject: [PATCH 19/29] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/Http11Processor.java | 4 ++-- .../apache/coyote/http11/handler/HandlerMapper.java | 2 +- .../org/apache/coyote/http11/request/RequestForms.java | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index f29aed24b6..5d47e81ea8 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -36,7 +36,7 @@ public void process(final Socket connection) { final var outputStream = connection.getOutputStream(); final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { final Request request = Request.from(bufferedReader); - final String response = getResponse(request); + final String response = createResponse(request); outputStream.write(response.getBytes()); outputStream.flush(); } catch (final IOException | UncheckedServletException e) { @@ -44,7 +44,7 @@ public void process(final Socket connection) { } } - private String getResponse(final Request request) { + private String createResponse(final Request request) { return handlerMapper.handle(request).getResponse(); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index ad973733e7..eac30197c1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -91,7 +91,7 @@ public Response registerHandler(final Request request) { } public Response registerFormHandler(final Request request) { - final Map requestForms = request.getRequestForms().getRequestForms(); + final Map requestForms = request.getRequestForms().getFormData(); final String account = requestForms.get("account"); final String email = requestForms.get("email"); final String password = requestForms.get("password"); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java index 3bf4774ed7..8016d106ec 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java @@ -4,10 +4,10 @@ import java.util.Map; public class RequestForms { - private final Map requestForms; + private final Map formData; - public RequestForms(final Map requestForms) { - this.requestForms = requestForms; + public RequestForms(final Map formData) { + this.formData = formData; } public static RequestForms from(final String values) { @@ -21,7 +21,7 @@ public static RequestForms from(final String values) { return new RequestForms(requestForms); } - public Map getRequestForms() { - return requestForms; + public Map getFormData() { + return formData; } } From 3d5f038b7ecc2b3c8245c29388947945fc8fcb5c Mon Sep 17 00:00:00 2001 From: hgo641 Date: Tue, 5 Sep 2023 10:43:16 +0900 Subject: [PATCH 20/29] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/response/ResponseHeaders.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java index f8366dd960..90a5528271 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java @@ -23,10 +23,6 @@ public void addAll(final Map headers) { headers.keySet().forEach(key -> add(key, headers.get(key))); } - public Map getHeaders() { - return headers; - } - public List getHeaderLines() { return headers.keySet().stream() .map(key -> key + ": " + headers.get(key) + " ") From 1a7fd47c9552ae37fb57b0681496d588047b2d23 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Wed, 6 Sep 2023 15:20:15 +0900 Subject: [PATCH 21/29] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=95=B8=EB=93=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/handler/HandlerMapper.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index eac30197c1..2039ae077e 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -26,8 +26,6 @@ public HandlerMapper() { public void init() { HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); HANDLERS.put(new HandlerStatus("GET", "/login"), this::loginHandler); - HANDLERS.put(new HandlerStatus("GET", "/login", Set.of("account", "password")), - this::loginWithQueryParameterHandler); HANDLERS.put(new HandlerStatus("POST", "/login"), this::loginFormHandler); HANDLERS.put(new HandlerStatus("GET", "/register"), this::registerHandler); HANDLERS.put(new HandlerStatus("POST", "/register"), this::registerFormHandler); @@ -45,20 +43,6 @@ public Response loginHandler(final Request request) { return Response.createByTemplate(HttpStatus.OK, "login.html"); } - public Response loginWithQueryParameterHandler(final Request request) { - if (request.getSession() != null) { - return Response.createByTemplate(HttpStatus.FOUND, "index.html"); - } - - final RequestLine requestLine = request.getRequestLine(); - final var queryParameter = requestLine.getRequestURI().getQueryParameter(); - final User user = InMemoryUserRepository.findByAccountAndPassword( - queryParameter.get("account"), queryParameter.get("password")) - .orElseThrow(() -> new IllegalArgumentException("아이디와 비밀번호가 일치하는 사용자가 존재하지 않습니다.")); - log.info("user : " + user); - return Response.createByTemplate(request.getRequestLine().getRequestURI()); - } - public Response loginFormHandler(final Request request) { final Map requestForms = request.getRequestForms().getFormData(); final Optional user = login(requestForms.get("account"), requestForms.get("password")); From b6c22a24a1ceba0538628805c6ceb7fa21bed95e Mon Sep 17 00:00:00 2001 From: hgo641 Date: Wed, 6 Sep 2023 15:50:58 +0900 Subject: [PATCH 22/29] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=8B=9C=20Index=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=ED=95=98=EB=8A=94?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/handler/HandlerMapper.java | 6 ++---- .../org/apache/coyote/http11/request/Request.java | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 2039ae077e..21edf2850f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -1,7 +1,5 @@ package org.apache.coyote.http11.handler; -import static org.reflections.Reflections.log; - import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -36,7 +34,7 @@ public Response rootHandler(final Request request) { } public Response loginHandler(final Request request) { - if (request.getSession() != null) { + if (request.getSessionValue("user") != Optional.empty()) { return Response.createByTemplate(HttpStatus.FOUND, "index.html"); } @@ -50,7 +48,7 @@ public Response loginFormHandler(final Request request) { } private Response loginSuccess(final Request request, final User user) { - if (request.getSession() == null) { + if (request.noSession()) { final Session session = new Session(); session.setAttribute("user", user); SessionManager.add(session); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java index 0b6f9cf2d3..9e04541f4c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java @@ -2,6 +2,7 @@ import java.io.BufferedReader; import java.io.IOException; +import java.util.Optional; import org.apache.coyote.http11.Session; import org.apache.coyote.http11.SessionManager; @@ -36,9 +37,18 @@ private static RequestForms createRequestBody(final BufferedReader br, final Req return RequestForms.from(requestBody); } - public Session getSession() { + public boolean noSession() { final String sessionId = requestHeaders.getCookieValue("JSESSIONID"); - return SessionManager.findSession(sessionId); + return SessionManager.findSession(sessionId) == null; + } + + public Optional getSessionValue(final String key) { + final String sessionId = requestHeaders.getCookieValue("JSESSIONID"); + final Session session = SessionManager.findSession(sessionId); + if (session == null) { + return Optional.empty(); + } + return Optional.of(session.getAttribute(key)); } public RequestLine getRequestLine() { From 1d92ed56deaf224128e78b711b8687af77415397 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Wed, 6 Sep 2023 16:27:14 +0900 Subject: [PATCH 23/29] =?UTF-8?q?refactor:=20ResponseHeaders=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http11/response/ResponseHeaders.java | 4 +- .../coyote/http11/Http11ProcessorTest.java | 124 +++++++++--------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java index 90a5528271..f0b36dfb1c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java @@ -1,6 +1,6 @@ package org.apache.coyote.http11.response; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -9,7 +9,7 @@ public class ResponseHeaders { private final Map headers; public ResponseHeaders() { - this.headers = new LinkedHashMap<>(); + this.headers = new HashMap<>(); } public void add(final String key, final String value) { 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..a5509865f8 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,62 +1,62 @@ -package nextstep.org.apache.coyote.http11; - -import support.StubSocket; -import org.apache.coyote.http11.Http11Processor; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; - -import static org.assertj.core.api.Assertions.assertThat; - -class Http11ProcessorTest { - - @Test - void process() { - // given - final var socket = new StubSocket(); - final var processor = new Http11Processor(socket); - - // when - processor.process(socket); - - // then - var expected = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: 12 ", - "", - "Hello world!"); - - assertThat(socket.output()).isEqualTo(expected); - } - - @Test - void index() throws IOException { - // given - final String httpRequest= String.join("\r\n", - "GET /index.html HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "", - ""); - - final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket); - - // when - processor.process(socket); - - // then - final URL resource = getClass().getClassLoader().getResource("static/index.html"); - var expected = "HTTP/1.1 200 OK \r\n" + - "Content-Type: text/html;charset=utf-8 \r\n" + - "Content-Length: 5564 \r\n" + - "\r\n"+ - new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - - assertThat(socket.output()).isEqualTo(expected); - } -} +package nextstep.org.apache.coyote.http11; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.List; +import org.apache.coyote.http11.Http11Processor; +import org.junit.jupiter.api.Test; +import support.StubSocket; + +class Http11ProcessorTest { + + @Test + void process() { + // given + final var socket = new StubSocket(); + final var processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final List expectedLines = List.of( + "HTTP/1.1 200 OK \r\n", + "Content-Type: text/html;charset=utf-8 \r\n", + "Content-Length: 12 \r\n", + "\r\nHello world!" + ); + + assertThat(socket.output()).contains(expectedLines); + } + + @Test + void index() throws IOException { + // given + final String httpRequest = String.join("\r\n", + "GET /index.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); + + final var socket = new StubSocket(httpRequest); + final Http11Processor processor = new Http11Processor(socket); + + // when + processor.process(socket); + + // then + final URL resource = getClass().getClassLoader().getResource("static/index.html"); + final List expectedLines = List.of( + "HTTP/1.1 200 OK \r\n", + "Content-Type: text/html;charset=utf-8 \r\n", + "Content-Length: 5564 \r\n", + new String(Files.readAllBytes(new File(resource.getFile()).toPath())) + ); + assertThat(socket.output()).contains(expectedLines); + } +} From 3dccee5b1cc70f4097c9d1f870d120a471a03fed Mon Sep 17 00:00:00 2001 From: hgo641 Date: Thu, 7 Sep 2023 18:54:00 +0900 Subject: [PATCH 24/29] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/Http11Processor.java | 2 +- .../apache/coyote/http11/handler/HandlerMapper.java | 8 ++++---- .../http11/{response => message}/ContentType.java | 2 +- .../apache/coyote/http11/{ => message}/HttpStatus.java | 2 +- .../coyote/http11/{ => message}/request/Cookie.java | 2 +- .../http11/{ => message}/request/QueryParameter.java | 4 ++-- .../coyote/http11/{ => message}/request/Request.java | 2 +- .../http11/{ => message}/request/RequestForms.java | 2 +- .../http11/{ => message}/request/RequestHeaders.java | 2 +- .../http11/{ => message}/request/RequestLine.java | 10 +++++----- .../http11/{ => message}/request/RequestURI.java | 2 +- .../coyote/http11/{ => message}/response/Response.java | 7 ++++--- .../http11/{ => message}/response/ResponseHeaders.java | 2 +- 13 files changed, 24 insertions(+), 23 deletions(-) rename tomcat/src/main/java/org/apache/coyote/http11/{response => message}/ContentType.java (94%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/HttpStatus.java (86%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/request/Cookie.java (91%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/request/QueryParameter.java (90%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/request/Request.java (95%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/request/RequestForms.java (90%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/request/RequestHeaders.java (92%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/request/RequestLine.java (92%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/request/RequestURI.java (94%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/response/Response.java (92%) rename tomcat/src/main/java/org/apache/coyote/http11/{ => message}/response/ResponseHeaders.java (91%) 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 5d47e81ea8..f588866978 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -7,7 +7,7 @@ import nextstep.jwp.exception.UncheckedServletException; import org.apache.coyote.Processor; import org.apache.coyote.http11.handler.HandlerMapper; -import org.apache.coyote.http11.request.Request; +import org.apache.coyote.http11.message.request.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 21edf2850f..0229b51ea0 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -7,12 +7,12 @@ import java.util.function.Function; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; -import org.apache.coyote.http11.HttpStatus; +import org.apache.coyote.http11.message.HttpStatus; import org.apache.coyote.http11.Session; import org.apache.coyote.http11.SessionManager; -import org.apache.coyote.http11.request.Request; -import org.apache.coyote.http11.request.RequestLine; -import org.apache.coyote.http11.response.Response; +import org.apache.coyote.http11.message.request.Request; +import org.apache.coyote.http11.message.request.RequestLine; +import org.apache.coyote.http11.message.response.Response; public class HandlerMapper { private static final Map> HANDLERS = new HashMap<>(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/message/ContentType.java similarity index 94% rename from tomcat/src/main/java/org/apache/coyote/http11/response/ContentType.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/ContentType.java index 7f8f40aac1..f5ad90ef7c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/ContentType.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.http11.message; import java.util.Arrays; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/message/HttpStatus.java similarity index 86% rename from tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/HttpStatus.java index 395b4645e1..8da09bf0de 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/HttpStatus.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11; +package org.apache.coyote.http11.message; public enum HttpStatus { OK("OK", 200), diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/Cookie.java similarity index 91% rename from tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/request/Cookie.java index 0cd33d5ef9..d102690605 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Cookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/Cookie.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11.message.request; import java.util.Arrays; import java.util.Collections; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/QueryParameter.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameter.java similarity index 90% rename from tomcat/src/main/java/org/apache/coyote/http11/request/QueryParameter.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameter.java index c09a165574..c25d03e574 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/QueryParameter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/QueryParameter.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11.message.request; import java.util.HashMap; import java.util.Map; @@ -14,7 +14,7 @@ public QueryParameter(final Map parameters) { public QueryParameter(final String uri) { final int index = uri.indexOf("?"); final String queryString = uri.substring(index + 1); - final String[] splitedQueryStrings = queryString.split("&"); // account=gugu + final String[] splitedQueryStrings = queryString.split("&"); for (final String str : splitedQueryStrings) { final int strIndex = str.indexOf("="); parameters.put(str.substring(0, strIndex), str.substring(strIndex + 1)); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/Request.java similarity index 95% rename from tomcat/src/main/java/org/apache/coyote/http11/request/Request.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/request/Request.java index 9e04541f4c..ab43ab12c8 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/Request.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/Request.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11.message.request; import java.io.BufferedReader; import java.io.IOException; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestForms.java similarity index 90% rename from tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestForms.java index 8016d106ec..02d40b0456 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestForms.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestForms.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11.message.request; import java.util.LinkedHashMap; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestHeaders.java similarity index 92% rename from tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestHeaders.java index 0213322b7b..4a34eb678f 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestHeaders.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11.message.request; import java.io.BufferedReader; import java.util.Map; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestLine.java similarity index 92% rename from tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestLine.java index e4a4d59aee..8406210d56 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestLine.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11.message.request; public class RequestLine { private static final String REQUEST_HEADER_DELIMITER = " "; @@ -25,14 +25,14 @@ public boolean isExistRequestFile() { return this.requestURI.isExistFile(); } - public String getPath() { - return this.requestURI.getPath(); - } - public String readFile() { return this.requestURI.readFile(); } + public String getPath() { + return this.requestURI.getPath(); + } + public RequestURI getRequestURI() { return requestURI; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestURI.java similarity index 94% rename from tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestURI.java index 34b25f6bba..44f77744a4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestURI.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestURI.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11.message.request; import java.io.File; import java.io.IOException; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/message/response/Response.java similarity index 92% rename from tomcat/src/main/java/org/apache/coyote/http11/response/Response.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/response/Response.java index fa7f9ece96..5b4842463c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/Response.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/response/Response.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.http11.message.response; import java.io.File; import java.io.IOException; @@ -8,8 +8,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.coyote.http11.HttpStatus; -import org.apache.coyote.http11.request.RequestURI; +import org.apache.coyote.http11.message.ContentType; +import org.apache.coyote.http11.message.HttpStatus; +import org.apache.coyote.http11.message.request.RequestURI; public class Response { private final String httpVersion; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/message/response/ResponseHeaders.java similarity index 91% rename from tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java rename to tomcat/src/main/java/org/apache/coyote/http11/message/response/ResponseHeaders.java index f0b36dfb1c..b89bdbdac4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/response/ResponseHeaders.java @@ -1,4 +1,4 @@ -package org.apache.coyote.http11.response; +package org.apache.coyote.http11.message.response; import java.util.HashMap; import java.util.List; From fdb032ac21ddba8e9e058e6ace6715dbc4123aa7 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Thu, 7 Sep 2023 20:22:58 +0900 Subject: [PATCH 25/29] =?UTF-8?q?refactor:=20handlerMapper=20static=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/handler/HandlerMapper.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 0229b51ea0..1bada2dc85 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -15,18 +15,18 @@ import org.apache.coyote.http11.message.response.Response; public class HandlerMapper { - private static final Map> HANDLERS = new HashMap<>(); + private final Map> handlers = new HashMap<>(); public HandlerMapper() { init(); } - public void init() { - HANDLERS.put(new HandlerStatus("GET", "/"), this::rootHandler); - HANDLERS.put(new HandlerStatus("GET", "/login"), this::loginHandler); - HANDLERS.put(new HandlerStatus("POST", "/login"), this::loginFormHandler); - HANDLERS.put(new HandlerStatus("GET", "/register"), this::registerHandler); - HANDLERS.put(new HandlerStatus("POST", "/register"), this::registerFormHandler); + private void init() { + handlers.put(new HandlerStatus("GET", "/"), this::rootHandler); + handlers.put(new HandlerStatus("GET", "/login"), this::loginHandler); + handlers.put(new HandlerStatus("POST", "/login"), this::loginFormHandler); + handlers.put(new HandlerStatus("GET", "/register"), this::registerHandler); + handlers.put(new HandlerStatus("POST", "/register"), this::registerFormHandler); } public Response rootHandler(final Request request) { @@ -90,7 +90,7 @@ public Response handle(final Request request) { final Set queryParameterKeys = queryParameter.keySet(); final HandlerStatus handlerStatus = new HandlerStatus(httpMethod, path, queryParameterKeys); - final Function handler = HANDLERS.get(handlerStatus); + final Function handler = handlers.get(handlerStatus); if (handler != null) { return handler.apply(request); } From 493d4b2dbe138a136855e9582d70bee1998d19fb Mon Sep 17 00:00:00 2001 From: hgo641 Date: Thu, 7 Sep 2023 20:23:26 +0900 Subject: [PATCH 26/29] =?UTF-8?q?refactor:=20HandlerMapper=EB=A5=BC=20Tomc?= =?UTF-8?q?at=EC=97=90=20=EC=A3=BC=EC=9E=85=ED=95=98=EA=B2=8C=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 --- .../apache/catalina/connector/Connector.java | 193 +++++++++--------- .../org/apache/catalina/startup/Tomcat.java | 57 +++--- .../apache/coyote/http11/Http11Processor.java | 4 +- .../coyote/http11/Http11ProcessorTest.java | 7 +- 4 files changed, 135 insertions(+), 126 deletions(-) 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..cfd020ac8d 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -1,95 +1,98 @@ -package org.apache.catalina.connector; - -import org.apache.coyote.http11.Http11Processor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.ServerSocket; -import java.net.Socket; - -public class Connector implements Runnable { - - private static final Logger log = LoggerFactory.getLogger(Connector.class); - - private static final int DEFAULT_PORT = 8080; - private static final int DEFAULT_ACCEPT_COUNT = 100; - - private final ServerSocket serverSocket; - private boolean stopped; - - public Connector() { - this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); - } - - public Connector(final int port, final int acceptCount) { - this.serverSocket = createServerSocket(port, acceptCount); - this.stopped = false; - } - - private ServerSocket createServerSocket(final int port, final int acceptCount) { - try { - final int checkedPort = checkPort(port); - final int checkedAcceptCount = checkAcceptCount(acceptCount); - return new ServerSocket(checkedPort, checkedAcceptCount); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void start() { - var thread = new Thread(this); - thread.setDaemon(true); - thread.start(); - stopped = false; - log.info("Web Application Server started {} port.", serverSocket.getLocalPort()); - } - - @Override - public void run() { - // 클라이언트가 연결될때까지 대기한다. - while (!stopped) { - connect(); - } - } - - private void connect() { - try { - process(serverSocket.accept()); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - - private void process(final Socket connection) { - if (connection == null) { - return; - } - var processor = new Http11Processor(connection); - new Thread(processor).start(); - } - - public void stop() { - stopped = true; - try { - serverSocket.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - - private int checkPort(final int port) { - final var MIN_PORT = 1; - final var MAX_PORT = 65535; - - if (port < MIN_PORT || MAX_PORT < port) { - return DEFAULT_PORT; - } - return port; - } - - private int checkAcceptCount(final int acceptCount) { - return Math.max(acceptCount, DEFAULT_ACCEPT_COUNT); - } -} +package org.apache.catalina.connector; + +import org.apache.coyote.http11.Http11Processor; +import org.apache.coyote.http11.handler.HandlerMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.ServerSocket; +import java.net.Socket; + +public class Connector implements Runnable { + + private static final Logger log = LoggerFactory.getLogger(Connector.class); + + private static final int DEFAULT_PORT = 8080; + private static final int DEFAULT_ACCEPT_COUNT = 100; + + private final ServerSocket serverSocket; + private final HandlerMapper handlerMapper; + private boolean stopped; + + public Connector(final HandlerMapper handlerMapper) { + this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, handlerMapper); + } + + public Connector(final int port, final int acceptCount, final HandlerMapper handlerMapper) { + this.serverSocket = createServerSocket(port, acceptCount); + this.stopped = false; + this.handlerMapper = handlerMapper; + } + + private ServerSocket createServerSocket(final int port, final int acceptCount) { + try { + final int checkedPort = checkPort(port); + final int checkedAcceptCount = checkAcceptCount(acceptCount); + return new ServerSocket(checkedPort, checkedAcceptCount); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void start() { + var thread = new Thread(this); + thread.setDaemon(true); + thread.start(); + stopped = false; + log.info("Web Application Server started {} port.", serverSocket.getLocalPort()); + } + + @Override + public void run() { + // 클라이언트가 연결될때까지 대기한다. + while (!stopped) { + connect(); + } + } + + private void connect() { + try { + process(serverSocket.accept()); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + private void process(final Socket connection) { + if (connection == null) { + return; + } + var processor = new Http11Processor(connection, handlerMapper); + new Thread(processor).start(); + } + + public void stop() { + stopped = true; + try { + serverSocket.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + private int checkPort(final int port) { + final var MIN_PORT = 1; + final var MAX_PORT = 65535; + + if (port < MIN_PORT || MAX_PORT < port) { + return DEFAULT_PORT; + } + return port; + } + + private int checkAcceptCount(final int acceptCount) { + return Math.max(acceptCount, DEFAULT_ACCEPT_COUNT); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java index 205159e95b..4e6aeb80fe 100644 --- a/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java +++ b/tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java @@ -1,27 +1,30 @@ -package org.apache.catalina.startup; - -import org.apache.catalina.connector.Connector; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class Tomcat { - - private static final Logger log = LoggerFactory.getLogger(Tomcat.class); - - public void start() { - var connector = new Connector(); - connector.start(); - - try { - // make the application wait until we press any key. - System.in.read(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } finally { - log.info("web server stop."); - connector.stop(); - } - } -} +package org.apache.catalina.startup; + +import org.apache.catalina.connector.Connector; +import org.apache.coyote.http11.handler.HandlerMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class Tomcat { + + private static final Logger log = LoggerFactory.getLogger(Tomcat.class); + + private final HandlerMapper handlerMapper = new HandlerMapper(); + + public void start() { + var connector = new Connector(handlerMapper); + connector.start(); + + try { + // make the application wait until we press any key. + System.in.read(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } finally { + log.info("web server stop."); + connector.stop(); + } + } +} 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 f588866978..e2626c7b5a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -19,9 +19,9 @@ public class Http11Processor implements Runnable, Processor { private final HandlerMapper handlerMapper; - public Http11Processor(final Socket connection) { + public Http11Processor(final Socket connection, final HandlerMapper handlerMapper) { this.connection = connection; - this.handlerMapper = new HandlerMapper(); + this.handlerMapper = handlerMapper; } @Override 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 a5509865f8..2efdaa905f 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 @@ -8,6 +8,7 @@ import java.nio.file.Files; import java.util.List; import org.apache.coyote.http11.Http11Processor; +import org.apache.coyote.http11.handler.HandlerMapper; import org.junit.jupiter.api.Test; import support.StubSocket; @@ -17,7 +18,8 @@ class Http11ProcessorTest { void process() { // given final var socket = new StubSocket(); - final var processor = new Http11Processor(socket); + final var handlerMapper = new HandlerMapper(); + final var processor = new Http11Processor(socket, handlerMapper); // when processor.process(socket); @@ -43,8 +45,9 @@ void index() throws IOException { "", ""); + final var handlerMapper = new HandlerMapper(); final var socket = new StubSocket(httpRequest); - final Http11Processor processor = new Http11Processor(socket); + final Http11Processor processor = new Http11Processor(socket, handlerMapper); // when processor.process(socket); From f5078e093c76408d42b5d91a5b4717f694eae52b Mon Sep 17 00:00:00 2001 From: hgo641 Date: Thu, 7 Sep 2023 20:28:55 +0900 Subject: [PATCH 27/29] =?UTF-8?q?refactor:=20QueryParameterKeys=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=A0=95?= =?UTF-8?q?=EC=A0=81=ED=8C=A9=ED=86=A0=EB=A6=AC=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/handler/HandlerMapper.java | 9 +- .../coyote/http11/handler/HandlerStatus.java | 96 +++++++++---------- 2 files changed, 45 insertions(+), 60 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java index 1bada2dc85..b029db6fa2 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapper.java @@ -3,13 +3,12 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; -import org.apache.coyote.http11.message.HttpStatus; import org.apache.coyote.http11.Session; import org.apache.coyote.http11.SessionManager; +import org.apache.coyote.http11.message.HttpStatus; import org.apache.coyote.http11.message.request.Request; import org.apache.coyote.http11.message.request.RequestLine; import org.apache.coyote.http11.message.response.Response; @@ -84,11 +83,7 @@ public Response registerFormHandler(final Request request) { public Response handle(final Request request) { final RequestLine requestLine = request.getRequestLine(); - final String path = requestLine.getPath(); - final String httpMethod = requestLine.getHttpMethod(); - final Map queryParameter = requestLine.getRequestURI().getQueryParameter(); - final Set queryParameterKeys = queryParameter.keySet(); - final HandlerStatus handlerStatus = new HandlerStatus(httpMethod, path, queryParameterKeys); + final HandlerStatus handlerStatus = HandlerStatus.from(requestLine); final Function handler = handlers.get(handlerStatus); if (handler != null) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java index 9da40b22fc..f59c0e4cc9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerStatus.java @@ -1,53 +1,43 @@ -package org.apache.coyote.http11.handler; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -public class HandlerStatus { - private final String httpMethod; - private final String path; - private final Set queryParameterKeys; - - public HandlerStatus(final String httpMethod, final String path) { - this.httpMethod = httpMethod; - this.path = path; - this.queryParameterKeys = new HashSet<>(); - } - - public HandlerStatus(final String httpMethod, final String path, final Set queryParameterKeys) { - this.httpMethod = httpMethod; - this.path = path; - this.queryParameterKeys = queryParameterKeys; - } - - public String getHttpMethod() { - return httpMethod; - } - - public String getPath() { - return path; - } - - public Set getQueryParameterKeys() { - return queryParameterKeys; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (!(o instanceof HandlerStatus)) { - return false; - } - final HandlerStatus that = (HandlerStatus) o; - return Objects.equals(httpMethod, that.httpMethod) && Objects.equals(path, that.path) - && Objects.equals(queryParameterKeys, that.queryParameterKeys); - } - - @Override - public int hashCode() { - return Objects.hash(httpMethod, path, queryParameterKeys); - } -} +package org.apache.coyote.http11.handler; + +import java.util.Objects; +import org.apache.coyote.http11.message.request.RequestLine; + +public class HandlerStatus { + private final String httpMethod; + private final String path; + + public HandlerStatus(final String httpMethod, final String path) { + this.httpMethod = httpMethod; + this.path = path; + } + + public static HandlerStatus from(final RequestLine requestLine) { + return new HandlerStatus(requestLine.getHttpMethod(), requestLine.getPath()); + } + + public String getHttpMethod() { + return httpMethod; + } + + public String getPath() { + return path; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HandlerStatus)) { + return false; + } + final HandlerStatus that = (HandlerStatus) o; + return Objects.equals(httpMethod, that.httpMethod) && Objects.equals(path, that.path); + } + + @Override + public int hashCode() { + return Objects.hash(httpMethod, path); + } +} From 48e118123eeeb4da04c701a8b6094a88691f559e Mon Sep 17 00:00:00 2001 From: hgo641 Date: Thu, 7 Sep 2023 20:48:01 +0900 Subject: [PATCH 28/29] =?UTF-8?q?refactor:=20RequestURI=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=9D=BD=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coyote/http11/message/request/RequestLine.java | 4 ---- .../coyote/http11/message/request/RequestURI.java | 14 -------------- .../coyote/http11/message/response/Response.java | 9 +++++---- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestLine.java index 8406210d56..894580bf7d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestLine.java @@ -25,10 +25,6 @@ public boolean isExistRequestFile() { return this.requestURI.isExistFile(); } - public String readFile() { - return this.requestURI.readFile(); - } - public String getPath() { return this.requestURI.getPath(); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestURI.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestURI.java index 44f77744a4..e683f9c467 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestURI.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestURI.java @@ -1,9 +1,7 @@ package org.apache.coyote.http11.message.request; import java.io.File; -import java.io.IOException; import java.net.URL; -import java.nio.file.Files; import java.util.Map; public class RequestURI { @@ -53,14 +51,6 @@ private QueryParameter parseQueryString(final String uri) { return new QueryParameter(uri.substring(index + 1)); } - public String readFile() { - try { - return new String(Files.readAllBytes(file.toPath())); - } catch (final IOException e) { - throw new IllegalArgumentException("파일을 읽던 중 에러가 발생했습니다."); - } - } - public boolean isExistFile() { return this.file.isFile(); } @@ -69,10 +59,6 @@ public String getPath() { return this.path; } - public String getFileName() { - return this.file.getName(); - } - public Map getQueryParameter() { return queryParameter.getParameters(); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/response/Response.java b/tomcat/src/main/java/org/apache/coyote/http11/message/response/Response.java index 5b4842463c..e2bae35ade 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/response/Response.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/response/Response.java @@ -40,9 +40,9 @@ public static Response createByTemplate( return new Response("HTTP/1.1", httpStatus, responseHeaders, responseBody); } - private static String getTemplateBody(final String templateName) { + private static String getTemplateBody(final String templatePath) { final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - final URL templateUrl = classLoader.getResource("static/" + templateName); + final URL templateUrl = classLoader.getResource("static/" + templatePath); try { return new String(Files.readAllBytes(new File(templateUrl.getFile()).toPath())); } catch (final NullPointerException e) { @@ -57,9 +57,10 @@ public static Response createByTemplate(final HttpStatus httpStatus, final Strin } public static Response createByTemplate(final RequestURI requestURI) { - final String responseBody = requestURI.readFile(); + final String templatePath = requestURI.getPath(); + final String responseBody = getTemplateBody(templatePath); final ResponseHeaders responseHeaders = new ResponseHeaders(); - final ContentType contentType = ContentType.findByFileName(requestURI.getFileName()); + final ContentType contentType = ContentType.findByFileName(requestURI.getPath()); responseHeaders.add("Content-Type", contentType.getHeaderValue() + ";charset=utf-8"); responseHeaders.add("Content-Length", String.valueOf(responseBody.getBytes().length)); return new Response("HTTP/1.1", HttpStatus.OK, responseHeaders, responseBody); From c9b48c8eae9249f19dd12a04aee78f9a4a275724 Mon Sep 17 00:00:00 2001 From: hgo641 Date: Thu, 7 Sep 2023 22:14:00 +0900 Subject: [PATCH 29/29] =?UTF-8?q?refactor:=20=ED=97=A4=EB=8D=94=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=BF=A0=ED=82=A4=EA=B0=92=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/message/request/RequestHeaders.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestHeaders.java index 4a34eb678f..f5b3513912 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/message/request/RequestHeaders.java @@ -19,6 +19,7 @@ public static RequestHeaders from(final BufferedReader br) { .map(line -> line.split(": ")) .collect(Collectors.toMap(line -> line[0], line -> line[1])); final String cookieValue = (String) headers.get("Cookie"); + headers.remove("Cookie"); return new RequestHeaders(headers, Cookie.from(cookieValue)); }