From 9303da4fc3278b2457c69df6b4f4e50af768afaa Mon Sep 17 00:00:00 2001 From: aak2075 Date: Sun, 3 Sep 2023 18:24:03 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=201=EB=8B=A8=EA=B3=84=20-=20HTTP=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EA=B5=AC=ED=98=84=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/study/FileTest.java | 37 +++-- study/src/test/java/study/IOStreamTest.java | 138 +++++++++--------- .../apache/coyote/http11/Http11Processor.java | 105 +++++++++++-- 3 files changed, 187 insertions(+), 93 deletions(-) diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..f054af8a00 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,53 +1,52 @@ package study; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; +import java.nio.file.Paths; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; /** - * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. - * File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. + * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. */ @DisplayName("File 클래스 학습 테스트") class FileTest { /** * resource 디렉터리 경로 찾기 - * - * File 객체를 생성하려면 파일의 경로를 알아야 한다. - * 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. - * resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? + *

+ * File 객체를 생성하려면 파일의 경로를 알아야 한다. 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. resource + * 디렉터리의 경로는 어떻게 알아낼 수 있을까? */ @Test void resource_디렉터리에_있는_파일의_경로를_찾는다() { final String fileName = "nextstep.txt"; // todo - final String actual = ""; + final URL resource = getClass().getClassLoader().getResource(fileName); + final String actual = resource.getFile(); 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; - + final URL resource = getClass().getClassLoader().getResource(fileName); // todo - final List actual = Collections.emptyList(); + final List actual = Files.readAllLines(Paths.get(resource.getFile())); assertThat(actual).containsOnly("nextstep"); } diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..fc5d4d94c7 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -1,45 +1,50 @@ 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 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에 연결될 수 있다. + * 자바는 스트림(Stream)으로부터 I/O를 사용한다. 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. + *

+ * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다. FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. * FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환) - * - * Stream은 데이터를 바이트로 읽고 쓴다. - * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. - * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다. + *

+ * 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 +58,7 @@ class OutputStream_학습_테스트 { * todo * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 */ + outputStream.write(bytes); final String actual = outputStream.toString(); @@ -61,13 +67,11 @@ class OutputStream_학습_테스트 { } /** - * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. - * BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. - * - * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. - * flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다. - * Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 - * 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다. + * 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다. BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다. + *

+ * 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자. flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 + * 전송한다. Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락(deadlock) 상태가 되기 때문에 flush로 + * 해제해야 한다. */ @Test void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException { @@ -78,19 +82,23 @@ 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); - + try (outputStream) { + } catch (IOException e) { + throw e; + } /** * todo * try-with-resources를 사용한다. @@ -103,21 +111,19 @@ 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 사이의 값으로 변환된다. - * 그리고 Stream 끝에 도달하면 -1을 반환한다. + * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. int 값을 byte 타입으로 변환하면 -128부터 + * 127 사이의 값으로 변환된다. 그리고 Stream 끝에 도달하면 -1을 반환한다. */ @Test void InputStream은_데이터를_바이트로_읽는다() throws IOException { @@ -128,7 +134,7 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + final String actual = new String(inputStream.readAllBytes()); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -136,8 +142,8 @@ class InputStream_학습_테스트 { } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. - * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 + * 발생한다. */ @Test void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -148,6 +154,9 @@ class InputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (inputStream) { + + } verify(inputStream, atLeastOnce()).close(); } @@ -155,26 +164,23 @@ class InputStream_학습_테스트 { /** * 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 객체를 생성하고 필터 생성자에 전달하면 + * 필터에 연결된다. 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? DEFAULT_BUFFER_SIZE = 8192; */ @Test - void 필터인_BufferedInputStream를_사용해보자() { + void 필터인_BufferedInputStream를_사용해보자() throws IOException { final String text = "필터에 연결해보자."; final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; - - final byte[] actual = new byte[0]; + final InputStream bufferedInputStream = new BufferedInputStream(inputStream); + final byte[] actual = bufferedInputStream.readAllBytes(); assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); @@ -182,31 +188,33 @@ 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()); final StringBuilder actual = new StringBuilder(); - + final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); + final InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + final BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + while (bufferedReader.ready()) { + actual.append(bufferedReader.readLine()); + actual.append("\r\n"); + } assertThat(actual).hasToString(emoji); } } 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..94b7dda045 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,17 +1,28 @@ package org.apache.coyote.http11; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; +import nextstep.jwp.model.User; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.Socket; - public class Http11Processor implements Runnable, Processor { + public static final String STATIC_RESOURCE_PATH = "static"; + public static final String HTTP_REQUEST_LINE = "Request-Line"; + public static final String HTTP_HEADER_ACCEPT = "Accept"; private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private final Socket connection; public Http11Processor(final Socket connection) { @@ -26,14 +37,26 @@ 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!"; + try ( + final var inputStream = connection.getInputStream(); + final var inputStreamReader = new InputStreamReader(inputStream); + final var bufferedReader = new BufferedReader(inputStreamReader); + final var outputStream = connection.getOutputStream() + ) { + final Map request = parseRequest(bufferedReader); + final var requestLine = request.get(HTTP_REQUEST_LINE); + final var uri = requestLine.split(" ")[1]; + if (uri.contains("?")) { + final var queryStartIndex = uri.indexOf("?"); + final var queryString = uri.substring(queryStartIndex + 1); + final var parameters = parseParameters(queryString); + logUser(parameters); + } + final var responseBody = readFile(uri); final var response = String.join("\r\n", "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", + request.get(HTTP_HEADER_ACCEPT), "Content-Length: " + responseBody.getBytes().length + " ", "", responseBody); @@ -44,4 +67,68 @@ public void process(final Socket connection) { log.error(e.getMessage(), e); } } + + private Map parseRequest(BufferedReader bufferedReader) + throws IOException { + final Map request = new HashMap<>(); + final var requestLine = bufferedReader.readLine(); + if (requestLine == null) { + throw new RuntimeException(); + } + request.put(HTTP_REQUEST_LINE, requestLine); + + var requestHeader = bufferedReader.readLine(); + while (!requestHeader.isEmpty()) { + String[] keyValuePair = requestHeader.split(": "); + request.put(keyValuePair[0], keyValuePair[1]); + requestHeader = bufferedReader.readLine(); + } + return request; + } + + private Map parseParameters(String queryString) { + Map parameters = new HashMap<>(); + String[] parametersArray = queryString.split("&"); + for (String parameter : parametersArray) { + String[] split = parameter.split("="); + parameters.put(split[0], split[1]); + } + return parameters; + } + + private void logUser(Map parameters) { + if (parameters.containsKey("account") && parameters.containsKey("password")) { + User user = InMemoryUserRepository.findByAccount(parameters.get("account")) + .orElseThrow(); + if (user.checkPassword(parameters.get("password"))) { + log.info(user.toString()); + } + } + } + + private String readFile(String uri) throws IOException { + if (uri.equals("/")) { + return "Hello world!"; + } + final String filePath = getFilePath(uri); + URL resource = findResource(filePath); + Path path = Paths.get(resource.getFile()); + return new String(Files.readAllBytes(path)); + } + + private String getFilePath(String uri) { + String filePath = uri.split("\\?")[0]; + if (!filePath.matches(".+\\.[a-zA-Z]+$")) { + return filePath + ".html"; + } + return filePath; + } + + private URL findResource(String fileName) { + URL resource = getClass().getClassLoader().getResource(STATIC_RESOURCE_PATH + fileName); + if (resource == null) { + return getClass().getClassLoader().getResource(STATIC_RESOURCE_PATH + "/404.html"); + } + return resource; + } } From a9f593d93977d49405972dfa0bd1c4ee93d08711 Mon Sep 17 00:00:00 2001 From: aak2075 Date: Mon, 4 Sep 2023 09:39:12 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=202=EB=8B=A8=EA=B3=84=20-=201.HTTP?= =?UTF-8?q?=20Status=20Code=20302?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/src/main/java/common/FileReader.java | 36 ++++++ .../main/java/nextstep/handler/Handler.java | 83 ++++++++++++++ .../apache/coyote/http11/Http11Processor.java | 106 ++---------------- .../org/apache/coyote/http11/HttpHeaders.java | 30 +++++ .../org/apache/coyote/http11/HttpMethod.java | 6 + .../org/apache/coyote/http11/HttpRequest.java | 91 +++++++++++++++ .../apache/coyote/http11/HttpResponse.java | 46 ++++++++ .../org/apache/coyote/http11/HttpStatus.java | 41 +++++++ .../exception/InvalidHttpFormException.java | 5 + .../coyote/http11/util/HttpRequestReader.java | 82 ++++++++++++++ .../http11/util/HttpResponseWriter.java | 62 ++++++++++ 11 files changed, 489 insertions(+), 99 deletions(-) create mode 100644 tomcat/src/main/java/common/FileReader.java create mode 100644 tomcat/src/main/java/nextstep/handler/Handler.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpMethod.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.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/exception/InvalidHttpFormException.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java diff --git a/tomcat/src/main/java/common/FileReader.java b/tomcat/src/main/java/common/FileReader.java new file mode 100644 index 0000000000..e78f678caa --- /dev/null +++ b/tomcat/src/main/java/common/FileReader.java @@ -0,0 +1,36 @@ +package common; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FileReader { + + private static final String STATIC_RESOURCE_PATH = "static"; + + private FileReader() { + } + + public static String readFile(String uri) throws IOException { + URL resource = findResource(uri); + Path path = Paths.get(resource.getFile()); + return new String(Files.readAllBytes(path)); + } + + private static URL findResource(String fileName) { + URL resource = FileReader.class.getClassLoader() + .getResource(STATIC_RESOURCE_PATH + fileName); + if (resource == null) { + fileName = fileName + ".html"; + resource = FileReader.class.getClassLoader() + .getResource(STATIC_RESOURCE_PATH + fileName); + } + if (resource == null) { + return FileReader.class.getClassLoader() + .getResource(STATIC_RESOURCE_PATH + "/404.html"); + } + return resource; + } +} diff --git a/tomcat/src/main/java/nextstep/handler/Handler.java b/tomcat/src/main/java/nextstep/handler/Handler.java new file mode 100644 index 0000000000..0b8e30d854 --- /dev/null +++ b/tomcat/src/main/java/nextstep/handler/Handler.java @@ -0,0 +1,83 @@ +package nextstep.handler; + +import common.FileReader; +import java.io.IOException; +import java.util.Map; +import nextstep.jwp.db.InMemoryUserRepository; +import org.apache.coyote.http11.HttpHeaders; +import org.apache.coyote.http11.HttpMethod; +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Handler { + + private static final String TEXT_HTML = "text/html;charset=utf-8"; + private static final Logger log = LoggerFactory.getLogger(Handler.class); + + private Handler() { + } + + public static void handle(HttpRequest httpRequest, HttpResponse httpResponse) + throws IOException { + final var path = httpRequest.getPath(); + HttpMethod method = httpRequest.getHttpMethod(); + if (method == HttpMethod.GET && path.equals("/")) { + httpResponse.setHttpStatus(HttpStatus.OK); + httpResponse.setBody("Hello world!"); + return; + } + if (method == HttpMethod.GET && path.equals("/index.html")) { + httpResponse.setHttpStatus(HttpStatus.OK); + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); + httpResponse.setBody(FileReader.readFile(path)); + return; + } + if (method == HttpMethod.GET && (path.equals("/login") || path.equals("/login.html"))) { + final var parameters = httpRequest.getParameters(); + if (parameters.containsKey("account") && parameters.containsKey("password")) { + login(httpResponse, parameters); + return; + } + final var responseBody = FileReader.readFile(httpRequest.getUri()); + httpResponse.setHttpStatus(HttpStatus.OK); + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); + httpResponse.setBody(responseBody); + return; + } + if (method == HttpMethod.GET && path.endsWith(".html")) { + final var responseBody = FileReader.readFile(httpRequest.getUri()); + httpResponse.setHttpStatus(HttpStatus.OK); + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); + httpResponse.setBody(responseBody); + return; + } + if (method == HttpMethod.GET) { + final var responseBody = FileReader.readFile(httpRequest.getUri()); + httpResponse.setHttpStatus(HttpStatus.OK); + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, + httpRequest.getHeader(HttpHeaders.ACCEPT)); + httpResponse.setBody(responseBody); + } + } + + private static void login(HttpResponse httpResponse, Map parameters) { + InMemoryUserRepository.findByAccount(parameters.get("account")) + .filter(user -> user.checkPassword(parameters.get("password"))) + .ifPresentOrElse(user -> success(httpResponse), () -> fail(httpResponse)); + } + + private static void success(HttpResponse httpResponse) { + httpResponse.setHttpStatus(HttpStatus.FOUND); + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); + httpResponse.addHeader(HttpHeaders.LOCATION, "/index.html"); + } + + private static void fail(HttpResponse httpResponse) { + httpResponse.setHttpStatus(HttpStatus.FOUND); + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); + httpResponse.addHeader(HttpHeaders.LOCATION, "/401.html"); + } +} 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 94b7dda045..04110a5488 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,27 +1,17 @@ package org.apache.coyote.http11; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.net.Socket; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.handler.Handler; import nextstep.jwp.exception.UncheckedServletException; -import nextstep.jwp.model.User; import org.apache.coyote.Processor; +import org.apache.coyote.http11.util.HttpRequestReader; +import org.apache.coyote.http11.util.HttpResponseWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Http11Processor implements Runnable, Processor { - public static final String STATIC_RESOURCE_PATH = "static"; - public static final String HTTP_REQUEST_LINE = "Request-Line"; - public static final String HTTP_HEADER_ACCEPT = "Accept"; private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); private final Socket connection; @@ -39,96 +29,14 @@ public void run() { public void process(final Socket connection) { try ( final var inputStream = connection.getInputStream(); - final var inputStreamReader = new InputStreamReader(inputStream); - final var bufferedReader = new BufferedReader(inputStreamReader); final var outputStream = connection.getOutputStream() ) { - final Map request = parseRequest(bufferedReader); - final var requestLine = request.get(HTTP_REQUEST_LINE); - final var uri = requestLine.split(" ")[1]; - if (uri.contains("?")) { - final var queryStartIndex = uri.indexOf("?"); - final var queryString = uri.substring(queryStartIndex + 1); - final var parameters = parseParameters(queryString); - logUser(parameters); - } - - final var responseBody = readFile(uri); - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - request.get(HTTP_HEADER_ACCEPT), - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); - - outputStream.write(response.getBytes()); - outputStream.flush(); + HttpRequest httpRequest = HttpRequestReader.read(inputStream); + final var httpResponse = new HttpResponse(); + Handler.handle(httpRequest, httpResponse); + HttpResponseWriter.write(outputStream, httpResponse); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } - - private Map parseRequest(BufferedReader bufferedReader) - throws IOException { - final Map request = new HashMap<>(); - final var requestLine = bufferedReader.readLine(); - if (requestLine == null) { - throw new RuntimeException(); - } - request.put(HTTP_REQUEST_LINE, requestLine); - - var requestHeader = bufferedReader.readLine(); - while (!requestHeader.isEmpty()) { - String[] keyValuePair = requestHeader.split(": "); - request.put(keyValuePair[0], keyValuePair[1]); - requestHeader = bufferedReader.readLine(); - } - return request; - } - - private Map parseParameters(String queryString) { - Map parameters = new HashMap<>(); - String[] parametersArray = queryString.split("&"); - for (String parameter : parametersArray) { - String[] split = parameter.split("="); - parameters.put(split[0], split[1]); - } - return parameters; - } - - private void logUser(Map parameters) { - if (parameters.containsKey("account") && parameters.containsKey("password")) { - User user = InMemoryUserRepository.findByAccount(parameters.get("account")) - .orElseThrow(); - if (user.checkPassword(parameters.get("password"))) { - log.info(user.toString()); - } - } - } - - private String readFile(String uri) throws IOException { - if (uri.equals("/")) { - return "Hello world!"; - } - final String filePath = getFilePath(uri); - URL resource = findResource(filePath); - Path path = Paths.get(resource.getFile()); - return new String(Files.readAllBytes(path)); - } - - private String getFilePath(String uri) { - String filePath = uri.split("\\?")[0]; - if (!filePath.matches(".+\\.[a-zA-Z]+$")) { - return filePath + ".html"; - } - return filePath; - } - - private URL findResource(String fileName) { - URL resource = getClass().getClassLoader().getResource(STATIC_RESOURCE_PATH + fileName); - if (resource == null) { - return getClass().getClassLoader().getResource(STATIC_RESOURCE_PATH + "/404.html"); - } - return resource; - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java new file mode 100644 index 0000000000..9ec4e63d3f --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class HttpHeaders { + + public static final String ACCEPT = "Accept"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String LOCATION = "Location"; + + private final Map headers; + + public HttpHeaders() { + this.headers = new HashMap<>(); + } + + public HttpHeaders(Map headers) { + this.headers = headers; + } + + public void put(String key, String value) { + headers.put(key, value); + } + + public String get(String headerName) { + return headers.get(headerName); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpMethod.java new file mode 100644 index 0000000000..7ad5a1709b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpMethod.java @@ -0,0 +1,6 @@ +package org.apache.coyote.http11; + +public enum HttpMethod { + GET, + POST +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java new file mode 100644 index 0000000000..19a8b49c46 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -0,0 +1,91 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class HttpRequest { + + private HttpMethod httpMethod; + private String uri; + private String path; + private Map parameters = new HashMap<>(); + private String protocol; + private HttpHeaders headers; + private String body; + + public HttpRequest() { + this.headers = new HttpHeaders(); + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getParameter(String parameterName) { + return this.parameters.get(parameterName); + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + public HttpMethod getHttpMethod() { + return httpMethod; + } + + public void setHttpMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + public void addHeader(String headerName, String value) { + this.headers.put(headerName, value); + } + + public String getHeader(String headerName) { + return this.headers.get(headerName); + } + + public void addParameter(String parameterName, String value) { + this.parameters.put(parameterName, value); + } + + public void addParameters(Map parameters) { + this.parameters.putAll(parameters); + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public void setHeaders(HttpHeaders headers) { + this.headers = headers; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Map getParameters() { + return parameters; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java new file mode 100644 index 0000000000..9ea7f58218 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -0,0 +1,46 @@ +package org.apache.coyote.http11; + +public class HttpResponse { + + private static final String protocol = "HTTP/1.1"; + private HttpStatus httpStatus; + private HttpHeaders headers; + private String body; + + public HttpResponse() { + this.headers = new HttpHeaders(); + } + + public void addHeader(String headerName, String value) { + this.headers.put(headerName, value); + } + + public String getProtocol() { + return protocol; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public void setHttpStatus(HttpStatus httpStatus) { + this.httpStatus = httpStatus; + } + + public HttpHeaders getHeaders() { + return headers; + } + + public String getHeader(String headerName) { + return this.headers.get(headerName); + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(body.getBytes().length)); + } +} 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..8f32e99238 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java @@ -0,0 +1,41 @@ +package org.apache.coyote.http11; + +public enum HttpStatus { + OK(200, "OK"), + FOUND(302, "Found"), + UNAUTHORIZED(401, "Unauthorized"), + NOT_FOUND(404, "Not Found"); + + private final int value; + private final String reasonPhrase; + + HttpStatus(int value, String reasonPhrase) { + this.value = value; + this.reasonPhrase = reasonPhrase; + } + + public static HttpStatus valueOf(int statusCode) { + HttpStatus status = resolve(statusCode); + if (status == null) { + throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); + } + return null; + } + + private static HttpStatus resolve(int statusCode) { + for (HttpStatus status : values()) { + if (status.value == statusCode) { + return status; + } + } + return null; + } + + public String getReasonPhrase() { + return this.reasonPhrase; + } + + public int getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/exception/InvalidHttpFormException.java b/tomcat/src/main/java/org/apache/coyote/http11/exception/InvalidHttpFormException.java new file mode 100644 index 0000000000..6fb7b081dd --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/exception/InvalidHttpFormException.java @@ -0,0 +1,5 @@ +package org.apache.coyote.http11.exception; + +public class InvalidHttpFormException extends RuntimeException { + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java new file mode 100644 index 0000000000..db956abb5b --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java @@ -0,0 +1,82 @@ +package org.apache.coyote.http11.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; +import org.apache.coyote.http11.HttpMethod; +import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.exception.InvalidHttpFormException; + +public class HttpRequestReader { + + private HttpRequestReader() { + } + + public static HttpRequest read(InputStream inputStream) throws IOException { + final var bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + final var httpRequest = new HttpRequest(); + + readRequestLine(httpRequest, bufferedReader); + readRequestHeaders(httpRequest, bufferedReader); + + return httpRequest; + } + + private static void readRequestLine(HttpRequest httpRequest, BufferedReader bufferedReader) + throws IOException { + final var requestLine = bufferedReader.readLine(); + if (requestLine == null) { + throw new InvalidHttpFormException(); + } + final var splitLine = requestLine.split(" "); + httpRequest.setHttpMethod(HttpMethod.valueOf(splitLine[0])); + parseUri(httpRequest, splitLine[1]); + httpRequest.setProtocol(splitLine[2]); + } + + private static void readRequestHeaders(HttpRequest httpRequest, BufferedReader bufferedReader) + throws IOException { + while (bufferedReader.ready()) { + final var header = bufferedReader.readLine(); + if (header.isEmpty()) { + break; + } + final var keyValuePair = header.split(": "); + httpRequest.addHeader(keyValuePair[0], keyValuePair[1]); + } + } + + private static void parseUri(HttpRequest httpRequest, String uri) { + if (uri.contains("?")) { + final var queryStartIndex = uri.indexOf("?"); + final var queryString = uri.substring(queryStartIndex + 1); + final var parameters = parseParameters(queryString); + httpRequest.addParameters(parameters); + } + + final var path = getFilePath(uri); + httpRequest.setUri(uri); + httpRequest.setPath(path); + } + + private static Map parseParameters(String queryString) { + Map parameters = new HashMap<>(); + String[] parametersArray = queryString.split("&"); + for (String parameter : parametersArray) { + String[] split = parameter.split("="); + parameters.put(split[0], split[1]); + } + return parameters; + } + + private static String getFilePath(String uri) { + String filePath = uri.split("\\?")[0]; + if (!filePath.matches(".+\\.[a-zA-Z]+$")) { + return filePath + ".html"; + } + return filePath; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java new file mode 100644 index 0000000000..749559f8af --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java @@ -0,0 +1,62 @@ +package org.apache.coyote.http11.util; + +import java.io.IOException; +import java.io.OutputStream; +import org.apache.coyote.http11.HttpHeaders; +import org.apache.coyote.http11.HttpResponse; +import org.apache.coyote.http11.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpResponseWriter { + + private HttpResponseWriter() { + } + + private static final Logger log = LoggerFactory.getLogger(HttpResponseWriter.class); + private static final String HEADER_DELIMITER = ": "; + + public static void write(OutputStream outputStream, HttpResponse httpResponse) + throws IOException { + final var headers = httpResponse.getHeaders(); + var contentType = headers.get(HttpHeaders.CONTENT_TYPE); + if (contentType == null) { + contentType = "text/html;charset=utf-8"; + } + + String response = ""; + if (httpResponse.getHttpStatus() == HttpStatus.OK) { + response = createOkResponse(httpResponse, contentType); + } + if (httpResponse.getHttpStatus() == HttpStatus.FOUND) { + response = createFoundResponse(httpResponse); + } + + log.info(response); + outputStream.write(response.getBytes()); + outputStream.flush(); + } + + private static String createOkResponse(HttpResponse httpResponse, String contentType) { + return String.join("\r\n", + createStatusLine(httpResponse) + " ", + String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_TYPE, contentType) + " ", + String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_LENGTH, String.valueOf(httpResponse.getBody().getBytes().length)) + " ", + "", + httpResponse.getBody()); + } + + private static String createFoundResponse(HttpResponse httpResponse) { + return String.join("\r\n", + createStatusLine(httpResponse) + " ", + String.join(HEADER_DELIMITER, HttpHeaders.LOCATION, httpResponse.getHeader("Location")) + " ", + "", + httpResponse.getBody()); + } + + private static String createStatusLine(HttpResponse httpResponse) { + return String.join(" ", httpResponse.getProtocol(), + String.valueOf(httpResponse.getHttpStatus().getValue()), + httpResponse.getHttpStatus().getReasonPhrase()); + } +} From f64db47cc2143ed0e51948db648e63680bbab501 Mon Sep 17 00:00:00 2001 From: aak2075 Date: Mon, 4 Sep 2023 10:31:15 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=202=EB=8B=A8=EA=B3=84=20-=202.=20PO?= =?UTF-8?q?ST=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/nextstep/handler/Handler.java | 90 +++++++++++++++---- .../org/apache/coyote/http11/HttpHeaders.java | 4 + .../org/apache/coyote/http11/HttpRequest.java | 4 + .../org/apache/coyote/http11/HttpStatus.java | 3 +- .../coyote/http11/util/HttpRequestReader.java | 12 +++ tomcat/src/main/resources/static/login.html | 2 +- 6 files changed, 97 insertions(+), 18 deletions(-) diff --git a/tomcat/src/main/java/nextstep/handler/Handler.java b/tomcat/src/main/java/nextstep/handler/Handler.java index 0b8e30d854..398eda077d 100644 --- a/tomcat/src/main/java/nextstep/handler/Handler.java +++ b/tomcat/src/main/java/nextstep/handler/Handler.java @@ -2,8 +2,8 @@ import common.FileReader; import java.io.IOException; -import java.util.Map; import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpMethod; import org.apache.coyote.http11.HttpRequest; @@ -14,8 +14,9 @@ public class Handler { - private static final String TEXT_HTML = "text/html;charset=utf-8"; + public static final String INDEX_HTML = "/index.html"; private static final Logger log = LoggerFactory.getLogger(Handler.class); + private static final String TEXT_HTML = "text/html;charset=utf-8"; private Handler() { } @@ -29,18 +30,13 @@ public static void handle(HttpRequest httpRequest, HttpResponse httpResponse) httpResponse.setBody("Hello world!"); return; } - if (method == HttpMethod.GET && path.equals("/index.html")) { + if (method == HttpMethod.GET && path.isEmpty()) { httpResponse.setHttpStatus(HttpStatus.OK); httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); - httpResponse.setBody(FileReader.readFile(path)); + httpResponse.setBody(FileReader.readFile(INDEX_HTML)); return; } if (method == HttpMethod.GET && (path.equals("/login") || path.equals("/login.html"))) { - final var parameters = httpRequest.getParameters(); - if (parameters.containsKey("account") && parameters.containsKey("password")) { - login(httpResponse, parameters); - return; - } final var responseBody = FileReader.readFile(httpRequest.getUri()); httpResponse.setHttpStatus(HttpStatus.OK); httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); @@ -60,22 +56,84 @@ public static void handle(HttpRequest httpRequest, HttpResponse httpResponse) httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, httpRequest.getHeader(HttpHeaders.ACCEPT)); httpResponse.setBody(responseBody); + return; + } + if (method == HttpMethod.POST && (path.equals("/login") || path.equals("/login.html"))) { + login(httpRequest, httpResponse); + return; + } + if (method == HttpMethod.POST && path.equals("/register.html")) { + register(httpRequest, httpResponse); + } + } + + private static void login(HttpRequest httpRequest, HttpResponse httpResponse) { + final var body = httpRequest.getBody(); + String[] parameters = body.split("&"); + + String account = ""; + String password = ""; + for (String parameter : parameters) { + String[] keyValuePair = parameter.split("="); + if (keyValuePair[0].equals("account")) { + account = keyValuePair[1]; + } + if (keyValuePair[0].equals("password")) { + password = keyValuePair[1]; + } + } + + if (account.isEmpty() || password.isEmpty()) { + httpResponse.setHttpStatus(HttpStatus.OK); + httpResponse.addHeader(HttpHeaders.LOCATION, "/500.html"); + return; + } + checkUser(httpResponse, account, password); + } + + private static void register(HttpRequest httpRequest, HttpResponse httpResponse) { + final var body = httpRequest.getBody(); + String[] parameters = body.split("&"); + + String account = ""; + String password = ""; + String email = ""; + for (String parameter : parameters) { + String[] keyValuePair = parameter.split("="); + if (keyValuePair[0].equals("account")) { + account = keyValuePair[1]; + } + if (keyValuePair[0].equals("password")) { + password = keyValuePair[1]; + } + if (keyValuePair[0].equals("email")) { + email = keyValuePair[1]; + } } + if (account.isEmpty() || password.isEmpty() || email.isEmpty()) { + httpResponse.setHttpStatus(HttpStatus.OK); + httpResponse.addHeader(HttpHeaders.LOCATION, "/500.html"); + return; + } + InMemoryUserRepository.save(new User(account, password, email)); + httpResponse.setHttpStatus(HttpStatus.FOUND); + httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); } - private static void login(HttpResponse httpResponse, Map parameters) { - InMemoryUserRepository.findByAccount(parameters.get("account")) - .filter(user -> user.checkPassword(parameters.get("password"))) - .ifPresentOrElse(user -> success(httpResponse), () -> fail(httpResponse)); + private static void checkUser(HttpResponse httpResponse, String account, String password) { + InMemoryUserRepository.findByAccount(account) + .filter(user -> user.checkPassword(password)) + .ifPresentOrElse(user -> loginSuccess(httpResponse), + () -> loginFailed(httpResponse)); } - private static void success(HttpResponse httpResponse) { + private static void loginSuccess(HttpResponse httpResponse) { httpResponse.setHttpStatus(HttpStatus.FOUND); httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); - httpResponse.addHeader(HttpHeaders.LOCATION, "/index.html"); + httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); } - private static void fail(HttpResponse httpResponse) { + private static void loginFailed(HttpResponse httpResponse) { httpResponse.setHttpStatus(HttpStatus.FOUND); httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); httpResponse.addHeader(HttpHeaders.LOCATION, "/401.html"); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index 9ec4e63d3f..51cf6da8bc 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -20,6 +20,10 @@ public HttpHeaders(Map headers) { this.headers = headers; } + public boolean containsHeader(String headerName) { + return this.headers.containsKey(headerName); + } + public void put(String key, String value) { headers.put(key, value); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index 19a8b49c46..223785bbb5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -49,6 +49,10 @@ public String getHeader(String headerName) { return this.headers.get(headerName); } + public boolean containsHeader(String headerName) { + return this.headers.containsHeader(headerName); + } + public void addParameter(String parameterName, String value) { this.parameters.put(parameterName, value); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java index 8f32e99238..5507e95863 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java @@ -4,7 +4,8 @@ public enum HttpStatus { OK(200, "OK"), FOUND(302, "Found"), UNAUTHORIZED(401, "Unauthorized"), - NOT_FOUND(404, "Not Found"); + NOT_FOUND(404, "Not Found"), + INTER_SERVER_ERROR(500, "Internal Server Error"); private final int value; private final String reasonPhrase; diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java index db956abb5b..7ec4b54536 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java @@ -6,6 +6,7 @@ import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; +import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpMethod; import org.apache.coyote.http11.HttpRequest; import org.apache.coyote.http11.exception.InvalidHttpFormException; @@ -21,6 +22,7 @@ public static HttpRequest read(InputStream inputStream) throws IOException { readRequestLine(httpRequest, bufferedReader); readRequestHeaders(httpRequest, bufferedReader); + readRequestBody(httpRequest, bufferedReader); return httpRequest; } @@ -49,6 +51,16 @@ private static void readRequestHeaders(HttpRequest httpRequest, BufferedReader b } } + private static void readRequestBody(HttpRequest httpRequest, BufferedReader bufferedReader) throws IOException { + if (!httpRequest.containsHeader(HttpHeaders.CONTENT_LENGTH)) { + return; + } + int contentLength = Integer.parseInt(httpRequest.getHeader(HttpHeaders.CONTENT_LENGTH)); + char[] buffer = new char[contentLength]; + bufferedReader.read(buffer, 0, contentLength); + String requestBody = new String(buffer); + httpRequest.setBody(requestBody); + } private static void parseUri(HttpRequest httpRequest, String uri) { if (uri.contains("?")) { final var queryStartIndex = uri.indexOf("?"); 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 2b05c7cf649d958d5dbe739bb45746d5fb5706e6 Mon Sep 17 00:00:00 2001 From: aak2075 Date: Mon, 4 Sep 2023 14:15:03 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=202=EB=8B=A8=EA=B3=84=20-=203,4=20C?= =?UTF-8?q?ookie=EC=97=90=20JSESSIONID=20=EA=B0=92=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EB=B0=8F=20Session=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/nextstep/handler/Handler.java | 18 ++++++++-- .../catalina/manager/SessionManager.java | 25 +++++++++++++ .../org/apache/coyote/http11/HttpCookie.java | 22 ++++++++++++ .../org/apache/coyote/http11/HttpHeaders.java | 9 ++--- .../org/apache/coyote/http11/HttpRequest.java | 36 +++++++++---------- .../apache/coyote/http11/HttpResponse.java | 27 +++++++++++--- .../org/apache/coyote/http11/Session.java | 30 ++++++++++++++++ .../exception/SessionNotFoundException.java | 5 +++ .../coyote/http11/util/HttpRequestReader.java | 24 ++++++++++++- .../http11/util/HttpResponseWriter.java | 24 ++++++++++--- 10 files changed, 182 insertions(+), 38 deletions(-) create mode 100644 tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/Session.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java diff --git a/tomcat/src/main/java/nextstep/handler/Handler.java b/tomcat/src/main/java/nextstep/handler/Handler.java index 398eda077d..bd5e05daf8 100644 --- a/tomcat/src/main/java/nextstep/handler/Handler.java +++ b/tomcat/src/main/java/nextstep/handler/Handler.java @@ -2,13 +2,17 @@ import common.FileReader; import java.io.IOException; +import java.util.UUID; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; +import org.apache.catalina.manager.SessionManager; +import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpMethod; import org.apache.coyote.http11.HttpRequest; import org.apache.coyote.http11.HttpResponse; import org.apache.coyote.http11.HttpStatus; +import org.apache.coyote.http11.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +41,12 @@ public static void handle(HttpRequest httpRequest, HttpResponse httpResponse) return; } if (method == HttpMethod.GET && (path.equals("/login") || path.equals("/login.html"))) { + Session session = httpRequest.getSession(); + if (session != null) { + httpResponse.setHttpStatus(HttpStatus.FOUND); + httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); + return; + } final var responseBody = FileReader.readFile(httpRequest.getUri()); httpResponse.setHttpStatus(HttpStatus.OK); httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); @@ -123,14 +133,18 @@ private static void register(HttpRequest httpRequest, HttpResponse httpResponse) private static void checkUser(HttpResponse httpResponse, String account, String password) { InMemoryUserRepository.findByAccount(account) .filter(user -> user.checkPassword(password)) - .ifPresentOrElse(user -> loginSuccess(httpResponse), + .ifPresentOrElse(user -> loginSuccess(httpResponse, user), () -> loginFailed(httpResponse)); } - private static void loginSuccess(HttpResponse httpResponse) { + private static void loginSuccess(HttpResponse httpResponse, User user) { httpResponse.setHttpStatus(HttpStatus.FOUND); httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); + final var session = new Session(UUID.randomUUID().toString()); + session.setAttribute("user", user); + SessionManager.add(session); + httpResponse.addCookie(new HttpCookie(HttpCookie.JSESSIONID, session.getId())); } private static void loginFailed(HttpResponse httpResponse) { diff --git a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java new file mode 100644 index 0000000000..49bd560edc --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -0,0 +1,25 @@ +package org.apache.catalina.manager; + +import java.util.HashMap; +import java.util.Map; +import org.apache.coyote.http11.Session; + +public class SessionManager { + + private static final Map SESSIONS = new HashMap<>(); + + private SessionManager() { + } + + public static void add(Session session) { + SESSIONS.put(session.getId(), session); + } + + public static void remove(Session session) { + SESSIONS.remove(session.getId()); + } + + public static Session findSession(String id) { + return SESSIONS.get(id); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java new file mode 100644 index 0000000000..7a8d58c0ff --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -0,0 +1,22 @@ +package org.apache.coyote.http11; + +public class HttpCookie { + + public static final String JSESSIONID = "JSESSIONID"; + + private final String name; + private final String value; + + public HttpCookie(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index 51cf6da8bc..1247603b38 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -9,15 +9,12 @@ public class HttpHeaders { public static final String CONTENT_LENGTH = "Content-Length"; public static final String CONTENT_TYPE = "Content-Type"; public static final String LOCATION = "Location"; + public static final String SET_COOKIE = "Set-Cookie"; + public static final String COOKIE = "Cookie"; - private final Map headers; + private final Map headers = new HashMap<>(); public HttpHeaders() { - this.headers = new HashMap<>(); - } - - public HttpHeaders(Map headers) { - this.headers = headers; } public boolean containsHeader(String headerName) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index 223785bbb5..bc2f1321d4 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -5,18 +5,24 @@ public class HttpRequest { + private final HttpHeaders headers; + private final Map cookies = new HashMap<>(); private HttpMethod httpMethod; private String uri; private String path; private Map parameters = new HashMap<>(); private String protocol; - private HttpHeaders headers; + private Session session; private String body; public HttpRequest() { this.headers = new HttpHeaders(); } + public void addCookie(HttpCookie cookie) { + this.cookies.put(cookie.getName(), cookie); + } + public String getPath() { return path; } @@ -25,12 +31,16 @@ public void setPath(String path) { this.path = path; } - public String getParameter(String parameterName) { - return this.parameters.get(parameterName); + public HttpCookie getCookie(String name) { + return cookies.get(name); } - public void setParameters(Map parameters) { - this.parameters = parameters; + public Session getSession() { + return this.session; + } + + public void setSession(Session session) { + this.session = session; } public HttpMethod getHttpMethod() { @@ -53,8 +63,8 @@ public boolean containsHeader(String headerName) { return this.headers.containsHeader(headerName); } - public void addParameter(String parameterName, String value) { - this.parameters.put(parameterName, value); + public boolean hasCookie(String name) { + return this.cookies.containsKey(name); } public void addParameters(Map parameters) { @@ -69,18 +79,10 @@ public void setUri(String uri) { this.uri = uri; } - public String getProtocol() { - return protocol; - } - public void setProtocol(String protocol) { this.protocol = protocol; } - public void setHeaders(HttpHeaders headers) { - this.headers = headers; - } - public String getBody() { return body; } @@ -88,8 +90,4 @@ public String getBody() { public void setBody(String body) { this.body = body; } - - public Map getParameters() { - return parameters; - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java index 9ea7f58218..f93b4cdfe3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -1,20 +1,21 @@ package org.apache.coyote.http11; +import java.util.HashMap; +import java.util.Map; + public class HttpResponse { private static final String protocol = "HTTP/1.1"; + + private final HttpHeaders headers; + private final Map cookies = new HashMap<>(); private HttpStatus httpStatus; - private HttpHeaders headers; private String body; public HttpResponse() { this.headers = new HttpHeaders(); } - public void addHeader(String headerName, String value) { - this.headers.put(headerName, value); - } - public String getProtocol() { return protocol; } @@ -43,4 +44,20 @@ public void setBody(String body) { this.body = body; addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(body.getBytes().length)); } + + public void addHeader(String headerName, String value) { + this.headers.put(headerName, value); + } + + public void addCookie(HttpCookie cookie) { + this.cookies.put(cookie.getName(), cookie); + } + + public HttpCookie getCookie(String name) { + return this.cookies.get(name); + } + + public Map getCookies() { + return this.cookies; + } } 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..041f02927c --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Session.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class Session { + + private final String id; + private final Map values = new HashMap<>(); + + public Session(final String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public Object getAttribute(final String name) { + return this.values.get(name); + } + + public void setAttribute(final String name, final Object value) { + this.values.put(name, value); + } + + public void removeAttribute(final String name) { + this.values.remove(name); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java b/tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java new file mode 100644 index 0000000000..74a70c5fa5 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java @@ -0,0 +1,5 @@ +package org.apache.coyote.http11.exception; + +public class SessionNotFoundException extends RuntimeException { + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java index 7ec4b54536..00c26c67b3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java @@ -6,10 +6,14 @@ import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; +import org.apache.catalina.manager.SessionManager; +import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpMethod; import org.apache.coyote.http11.HttpRequest; +import org.apache.coyote.http11.Session; import org.apache.coyote.http11.exception.InvalidHttpFormException; +import org.apache.coyote.http11.exception.SessionNotFoundException; public class HttpRequestReader { @@ -49,9 +53,26 @@ private static void readRequestHeaders(HttpRequest httpRequest, BufferedReader b final var keyValuePair = header.split(": "); httpRequest.addHeader(keyValuePair[0], keyValuePair[1]); } + if (httpRequest.containsHeader(HttpHeaders.COOKIE)) { + + String[] cookies = httpRequest.getHeader(HttpHeaders.COOKIE).split("; "); + for (String cookie : cookies) { + String[] keyValuePair = cookie.split("="); + httpRequest.addCookie(new HttpCookie(keyValuePair[0], keyValuePair[1])); + } + } + if (httpRequest.hasCookie(HttpCookie.JSESSIONID)) { + HttpCookie sessionCookie = httpRequest.getCookie(HttpCookie.JSESSIONID); + Session session = SessionManager.findSession(sessionCookie.getValue()); + if (session == null) { + throw new SessionNotFoundException(); + } + httpRequest.setSession(session); + } } - private static void readRequestBody(HttpRequest httpRequest, BufferedReader bufferedReader) throws IOException { + private static void readRequestBody(HttpRequest httpRequest, BufferedReader bufferedReader) + throws IOException { if (!httpRequest.containsHeader(HttpHeaders.CONTENT_LENGTH)) { return; } @@ -61,6 +82,7 @@ private static void readRequestBody(HttpRequest httpRequest, BufferedReader buff String requestBody = new String(buffer); httpRequest.setBody(requestBody); } + private static void parseUri(HttpRequest httpRequest, String uri) { if (uri.contains("?")) { final var queryStartIndex = uri.indexOf("?"); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java index 749559f8af..52870fdb58 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.stream.Collectors; +import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpResponse; import org.apache.coyote.http11.HttpStatus; @@ -10,12 +12,12 @@ public class HttpResponseWriter { - private HttpResponseWriter() { - } - private static final Logger log = LoggerFactory.getLogger(HttpResponseWriter.class); private static final String HEADER_DELIMITER = ": "; + private HttpResponseWriter() { + } + public static void write(OutputStream outputStream, HttpResponse httpResponse) throws IOException { final var headers = httpResponse.getHeaders(); @@ -39,9 +41,10 @@ public static void write(OutputStream outputStream, HttpResponse httpResponse) private static String createOkResponse(HttpResponse httpResponse, String contentType) { return String.join("\r\n", - createStatusLine(httpResponse) + " ", + createStatusLine(httpResponse), String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_TYPE, contentType) + " ", - String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_LENGTH, String.valueOf(httpResponse.getBody().getBytes().length)) + " ", + String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_LENGTH, + String.valueOf(httpResponse.getBody().getBytes().length)) + " ", "", httpResponse.getBody()); } @@ -49,6 +52,7 @@ private static String createOkResponse(HttpResponse httpResponse, String content private static String createFoundResponse(HttpResponse httpResponse) { return String.join("\r\n", createStatusLine(httpResponse) + " ", + setCookie(httpResponse) + " ", String.join(HEADER_DELIMITER, HttpHeaders.LOCATION, httpResponse.getHeader("Location")) + " ", "", httpResponse.getBody()); @@ -59,4 +63,14 @@ private static String createStatusLine(HttpResponse httpResponse) { String.valueOf(httpResponse.getHttpStatus().getValue()), httpResponse.getHttpStatus().getReasonPhrase()); } + + private static String setCookie(HttpResponse httpResponse) { + if (httpResponse.getCookie(HttpCookie.JSESSIONID) == null) { + return ""; + } + String cookieValues = httpResponse.getCookies().values().stream() + .map(cookie -> cookie.getName() + "=" + cookie.getValue()) + .collect(Collectors.joining("; ")); + return String.join(HEADER_DELIMITER, HttpHeaders.SET_COOKIE, cookieValues); + } } From 3069ea7889d0b9f7c5326b0b24723a2e56d78116 Mon Sep 17 00:00:00 2001 From: aak2075 Date: Mon, 4 Sep 2023 15:27:22 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor:=20=EA=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/nextstep/handler/Handler.java | 3 --- .../org/apache/coyote/http11/Http11Processor.java | 1 + .../org/apache/coyote/http11/HttpHeaders.java | 3 --- .../java/org/apache/coyote/http11/Session.java | 8 -------- .../coyote/http11/util/HttpRequestReader.java | 6 +++--- .../coyote/http11/util/HttpResponseWriter.java | 15 +++++---------- 6 files changed, 9 insertions(+), 27 deletions(-) diff --git a/tomcat/src/main/java/nextstep/handler/Handler.java b/tomcat/src/main/java/nextstep/handler/Handler.java index bd5e05daf8..d7da695197 100644 --- a/tomcat/src/main/java/nextstep/handler/Handler.java +++ b/tomcat/src/main/java/nextstep/handler/Handler.java @@ -13,13 +13,10 @@ import org.apache.coyote.http11.HttpResponse; import org.apache.coyote.http11.HttpStatus; import org.apache.coyote.http11.Session; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class Handler { public static final String INDEX_HTML = "/index.html"; - private static final Logger log = LoggerFactory.getLogger(Handler.class); private static final String TEXT_HTML = "text/html;charset=utf-8"; private Handler() { 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 04110a5488..82b975bd04 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -13,6 +13,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); + private final Socket connection; public Http11Processor(final Socket connection) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index 1247603b38..559ad65b9a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -14,9 +14,6 @@ public class HttpHeaders { private final Map headers = new HashMap<>(); - public HttpHeaders() { - } - public boolean containsHeader(String headerName) { return this.headers.containsKey(headerName); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Session.java b/tomcat/src/main/java/org/apache/coyote/http11/Session.java index 041f02927c..72aae4610c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Session.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Session.java @@ -16,15 +16,7 @@ public String getId() { return id; } - public Object getAttribute(final String name) { - return this.values.get(name); - } - public void setAttribute(final String name, final Object value) { this.values.put(name, value); } - - public void removeAttribute(final String name) { - this.values.remove(name); - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java index 00c26c67b3..5c546ac5ef 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java @@ -108,9 +108,9 @@ private static Map parseParameters(String queryString) { private static String getFilePath(String uri) { String filePath = uri.split("\\?")[0]; - if (!filePath.matches(".+\\.[a-zA-Z]+$")) { - return filePath + ".html"; + if (filePath.matches(".+\\.[a-zA-Z]+$") || filePath.equals("/")) { + return filePath; } - return filePath; + return filePath + ".html"; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java index 52870fdb58..4c2a2d54ca 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java @@ -40,22 +40,17 @@ public static void write(OutputStream outputStream, HttpResponse httpResponse) } private static String createOkResponse(HttpResponse httpResponse, String contentType) { - return String.join("\r\n", - createStatusLine(httpResponse), + return String.join("\r\n", createStatusLine(httpResponse) + " ", String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_TYPE, contentType) + " ", String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_LENGTH, - String.valueOf(httpResponse.getBody().getBytes().length)) + " ", - "", + String.valueOf(httpResponse.getBody().getBytes().length)) + " ", "", httpResponse.getBody()); } private static String createFoundResponse(HttpResponse httpResponse) { - return String.join("\r\n", - createStatusLine(httpResponse) + " ", - setCookie(httpResponse) + " ", - String.join(HEADER_DELIMITER, HttpHeaders.LOCATION, httpResponse.getHeader("Location")) + " ", - "", - httpResponse.getBody()); + return String.join("\r\n", createStatusLine(httpResponse) + " ", + setCookie(httpResponse) + " ", String.join(HEADER_DELIMITER, HttpHeaders.LOCATION, + httpResponse.getHeader("Location")) + " ", "", httpResponse.getBody()); } private static String createStatusLine(HttpResponse httpResponse) { From dc503035ee466e6b829e05075bcd1e75ea1bfee6 Mon Sep 17 00:00:00 2001 From: aak2075 Date: Mon, 4 Sep 2023 16:02:00 +0900 Subject: [PATCH 06/10] =?UTF-8?q?refactor:=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EB=8C=80=EB=AC=B8=EC=9E=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/apache/coyote/http11/HttpResponse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java index f93b4cdfe3..ff85529e57 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -5,7 +5,7 @@ public class HttpResponse { - private static final String protocol = "HTTP/1.1"; + private static final String DEFAULT_PROTOCOL = "HTTP/1.1"; private final HttpHeaders headers; private final Map cookies = new HashMap<>(); @@ -17,7 +17,7 @@ public HttpResponse() { } public String getProtocol() { - return protocol; + return DEFAULT_PROTOCOL; } public HttpStatus getHttpStatus() { From 00417a7142149d94957074c0d215e772df47e507 Mon Sep 17 00:00:00 2001 From: aak2075 Date: Tue, 5 Sep 2023 19:59:59 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20=EB=B7=B0=20=EB=A6=AC?= =?UTF-8?q?=EC=A1=B8=EB=B2=84=20=EB=B6=84=EB=A6=AC=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/src/main/java/common/FileReader.java | 3 +- .../src/main/java/nextstep/ModelAndView.java | 30 +++ .../java/nextstep/SupportContentType.java | 26 +++ tomcat/src/main/java/nextstep/View.java | 41 ++++ .../src/main/java/nextstep/ViewResolver.java | 14 ++ .../main/java/nextstep/handler/Handler.java | 175 +++++++++--------- .../catalina/manager/SessionManager.java | 9 +- .../apache/coyote/http11/Http11Processor.java | 5 +- .../org/apache/coyote/http11/HttpHeaders.java | 13 +- .../org/apache/coyote/http11/HttpRequest.java | 37 +++- .../org/apache/coyote/http11/HttpStatus.java | 1 + .../exception/SessionNotFoundException.java | 5 - .../apache/coyote/http11/util/HttpParser.java | 56 ++++++ .../coyote/http11/util/HttpRequestReader.java | 72 ++----- .../http11/util/HttpResponseWriter.java | 41 ++-- tomcat/src/main/resources/static/400.html | 52 ++++++ tomcat/src/main/resources/static/401.html | 2 +- tomcat/src/main/resources/static/500.html | 2 +- 18 files changed, 388 insertions(+), 196 deletions(-) create mode 100644 tomcat/src/main/java/nextstep/ModelAndView.java create mode 100644 tomcat/src/main/java/nextstep/SupportContentType.java create mode 100644 tomcat/src/main/java/nextstep/View.java create mode 100644 tomcat/src/main/java/nextstep/ViewResolver.java delete mode 100644 tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java create mode 100644 tomcat/src/main/java/org/apache/coyote/http11/util/HttpParser.java create mode 100644 tomcat/src/main/resources/static/400.html diff --git a/tomcat/src/main/java/common/FileReader.java b/tomcat/src/main/java/common/FileReader.java index e78f678caa..48f8cbe58d 100644 --- a/tomcat/src/main/java/common/FileReader.java +++ b/tomcat/src/main/java/common/FileReader.java @@ -9,6 +9,7 @@ public class FileReader { private static final String STATIC_RESOURCE_PATH = "static"; + private static final String EXTENSION_HTML = ".html"; private FileReader() { } @@ -23,7 +24,7 @@ private static URL findResource(String fileName) { URL resource = FileReader.class.getClassLoader() .getResource(STATIC_RESOURCE_PATH + fileName); if (resource == null) { - fileName = fileName + ".html"; + fileName = fileName + EXTENSION_HTML; resource = FileReader.class.getClassLoader() .getResource(STATIC_RESOURCE_PATH + fileName); } diff --git a/tomcat/src/main/java/nextstep/ModelAndView.java b/tomcat/src/main/java/nextstep/ModelAndView.java new file mode 100644 index 0000000000..00981a935a --- /dev/null +++ b/tomcat/src/main/java/nextstep/ModelAndView.java @@ -0,0 +1,30 @@ +package nextstep; + +import java.util.HashMap; +import java.util.Map; + +public class ModelAndView { + + private final String viewName; + private final Map model = new HashMap<>(); + + public ModelAndView(final String viewName) { + this.viewName = viewName; + } + + public ModelAndView() { + this(null); + } + + public void setAttribute(String key, String value) { + model.put(key, value); + } + + public String getViewName() { + return viewName; + } + + public Map getModel() { + return model; + } +} diff --git a/tomcat/src/main/java/nextstep/SupportContentType.java b/tomcat/src/main/java/nextstep/SupportContentType.java new file mode 100644 index 0000000000..1b5994151d --- /dev/null +++ b/tomcat/src/main/java/nextstep/SupportContentType.java @@ -0,0 +1,26 @@ +package nextstep; + +public enum SupportContentType { + TEXT_HTML(".html", "text/html;charset=utf-8"), + CSS(".css", "text/css"), + SCRIPT(".js", "text/javascript"), + ICON(".ico", "image/x-icon"), + SVG(".svg", "image/svg+xml"); + + private final String endWith; + private final String contentType; + + SupportContentType(final String endWith, final String contentType) { + this.endWith = endWith; + this.contentType = contentType; + } + + public static String getContentType(String fileName) { + for (final SupportContentType value : values()) { + if (fileName.endsWith(value.endWith)) { + return value.contentType; + } + } + return TEXT_HTML.contentType; + } +} diff --git a/tomcat/src/main/java/nextstep/View.java b/tomcat/src/main/java/nextstep/View.java new file mode 100644 index 0000000000..280c1b7e00 --- /dev/null +++ b/tomcat/src/main/java/nextstep/View.java @@ -0,0 +1,41 @@ +package nextstep; + +import java.util.Map; +import org.apache.coyote.http11.HttpHeaders; +import org.apache.coyote.http11.HttpResponse; + +public class View { + + private static final String DEFAULT_CHAR_SET = "text/html;charset=utf-8"; + + private String content; + private final String contentType; + + public View() { + this.contentType = DEFAULT_CHAR_SET; + } + + public View(final String content, final String contentType) { + this.content = content; + this.contentType = contentType; + } + + public void render(Map model, HttpResponse httpResponse) { + if (content == null) { + StringBuilder sb = new StringBuilder(); + for (final var value : model.values()) { + sb.append(value); + } + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, DEFAULT_CHAR_SET); + httpResponse.addHeader(HttpHeaders.CONTENT_LENGTH, + String.valueOf(sb.toString().getBytes().length)); + httpResponse.setBody(sb.toString()); + return; + } + + httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, contentType); + httpResponse.addHeader(HttpHeaders.CONTENT_LENGTH, + String.valueOf(content.getBytes().length)); + httpResponse.setBody(content); + } +} diff --git a/tomcat/src/main/java/nextstep/ViewResolver.java b/tomcat/src/main/java/nextstep/ViewResolver.java new file mode 100644 index 0000000000..d237a76025 --- /dev/null +++ b/tomcat/src/main/java/nextstep/ViewResolver.java @@ -0,0 +1,14 @@ +package nextstep; + +import common.FileReader; +import java.io.IOException; + +public class ViewResolver { + + private ViewResolver() { + } + + public static View resolve(final String viewName) throws IOException { + return new View(FileReader.readFile(viewName), SupportContentType.getContentType(viewName)); + } +} diff --git a/tomcat/src/main/java/nextstep/handler/Handler.java b/tomcat/src/main/java/nextstep/handler/Handler.java index d7da695197..029f6694b6 100644 --- a/tomcat/src/main/java/nextstep/handler/Handler.java +++ b/tomcat/src/main/java/nextstep/handler/Handler.java @@ -1,8 +1,11 @@ package nextstep.handler; -import common.FileReader; import java.io.IOException; +import java.util.Map; import java.util.UUID; +import nextstep.ModelAndView; +import nextstep.View; +import nextstep.ViewResolver; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; import org.apache.catalina.manager.SessionManager; @@ -13,140 +16,144 @@ import org.apache.coyote.http11.HttpResponse; import org.apache.coyote.http11.HttpStatus; import org.apache.coyote.http11.Session; +import org.apache.coyote.http11.util.HttpParser; public class Handler { - public static final String INDEX_HTML = "/index.html"; - private static final String TEXT_HTML = "text/html;charset=utf-8"; + private static final String INDEX_HTML = "/index.html"; + private static final String BAD_REQUEST_HTML = "/400.html"; + private static final String UNAUTHORIZED_HTML = "/401.html"; private Handler() { } public static void handle(HttpRequest httpRequest, HttpResponse httpResponse) throws IOException { + ModelAndView modelAndView = handler(httpRequest, httpResponse); + View view = new View(); + if (modelAndView.getViewName() != null) { + view = ViewResolver.resolve(modelAndView.getViewName()); + } + view.render(modelAndView.getModel(), httpResponse); + } + + private static ModelAndView handler(final HttpRequest httpRequest, + final HttpResponse httpResponse) { final var path = httpRequest.getPath(); HttpMethod method = httpRequest.getHttpMethod(); if (method == HttpMethod.GET && path.equals("/")) { httpResponse.setHttpStatus(HttpStatus.OK); - httpResponse.setBody("Hello world!"); - return; + ModelAndView modelAndView = new ModelAndView(); + modelAndView.setAttribute("message", "Hello world!"); + return modelAndView; } if (method == HttpMethod.GET && path.isEmpty()) { httpResponse.setHttpStatus(HttpStatus.OK); - httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); - httpResponse.setBody(FileReader.readFile(INDEX_HTML)); - return; + return new ModelAndView(INDEX_HTML); } if (method == HttpMethod.GET && (path.equals("/login") || path.equals("/login.html"))) { - Session session = httpRequest.getSession(); - if (session != null) { + if (isAlreadyLogin(httpRequest)) { httpResponse.setHttpStatus(HttpStatus.FOUND); httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); - return; + return new ModelAndView(INDEX_HTML); } - final var responseBody = FileReader.readFile(httpRequest.getUri()); httpResponse.setHttpStatus(HttpStatus.OK); - httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); - httpResponse.setBody(responseBody); - return; + return new ModelAndView(httpRequest.getPath()); } if (method == HttpMethod.GET && path.endsWith(".html")) { - final var responseBody = FileReader.readFile(httpRequest.getUri()); httpResponse.setHttpStatus(HttpStatus.OK); - httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); - httpResponse.setBody(responseBody); - return; + return new ModelAndView(httpRequest.getPath()); } if (method == HttpMethod.GET) { - final var responseBody = FileReader.readFile(httpRequest.getUri()); httpResponse.setHttpStatus(HttpStatus.OK); - httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, - httpRequest.getHeader(HttpHeaders.ACCEPT)); - httpResponse.setBody(responseBody); - return; + return new ModelAndView(httpRequest.getPath()); } if (method == HttpMethod.POST && (path.equals("/login") || path.equals("/login.html"))) { - login(httpRequest, httpResponse); - return; + final var viewName = login(httpRequest, httpResponse); + return new ModelAndView(viewName); } - if (method == HttpMethod.POST && path.equals("/register.html")) { - register(httpRequest, httpResponse); + if (method == HttpMethod.POST && (path.equals("/register") || path.equals( + "/register.html"))) { + final var viewName = register(httpRequest, httpResponse); + return new ModelAndView(viewName); } + httpResponse.setHttpStatus(HttpStatus.BAD_REQUEST); + return new ModelAndView(BAD_REQUEST_HTML); } - private static void login(HttpRequest httpRequest, HttpResponse httpResponse) { - final var body = httpRequest.getBody(); - String[] parameters = body.split("&"); - - String account = ""; - String password = ""; - for (String parameter : parameters) { - String[] keyValuePair = parameter.split("="); - if (keyValuePair[0].equals("account")) { - account = keyValuePair[1]; - } - if (keyValuePair[0].equals("password")) { - password = keyValuePair[1]; - } - } - - if (account.isEmpty() || password.isEmpty()) { - httpResponse.setHttpStatus(HttpStatus.OK); - httpResponse.addHeader(HttpHeaders.LOCATION, "/500.html"); - return; + private static boolean isAlreadyLogin(final HttpRequest httpRequest) { + Session session = httpRequest.getSession(); + if (session == null) { + return false; } - checkUser(httpResponse, account, password); + return SessionManager.findSession(session.getId()).isPresent(); } - private static void register(HttpRequest httpRequest, HttpResponse httpResponse) { + private static String login(HttpRequest httpRequest, HttpResponse httpResponse) { final var body = httpRequest.getBody(); - String[] parameters = body.split("&"); - - String account = ""; - String password = ""; - String email = ""; - for (String parameter : parameters) { - String[] keyValuePair = parameter.split("="); - if (keyValuePair[0].equals("account")) { - account = keyValuePair[1]; - } - if (keyValuePair[0].equals("password")) { - password = keyValuePair[1]; - } - if (keyValuePair[0].equals("email")) { - email = keyValuePair[1]; - } + Map parameters = HttpParser.parseFormData(body); + if (parameters.containsKey("account") && parameters.containsKey("password")) { + return checkUser(httpResponse, parameters.get("account"), parameters.get("password")); } - if (account.isEmpty() || password.isEmpty() || email.isEmpty()) { - httpResponse.setHttpStatus(HttpStatus.OK); - httpResponse.addHeader(HttpHeaders.LOCATION, "/500.html"); - return; - } - InMemoryUserRepository.save(new User(account, password, email)); - httpResponse.setHttpStatus(HttpStatus.FOUND); - httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); + httpResponse.setHttpStatus(HttpStatus.BAD_REQUEST); + httpResponse.addHeader(HttpHeaders.LOCATION, BAD_REQUEST_HTML); + return BAD_REQUEST_HTML; } - private static void checkUser(HttpResponse httpResponse, String account, String password) { - InMemoryUserRepository.findByAccount(account) + private static String checkUser(HttpResponse httpResponse, String account, String password) { + return InMemoryUserRepository.findByAccount(account) .filter(user -> user.checkPassword(password)) - .ifPresentOrElse(user -> loginSuccess(httpResponse, user), - () -> loginFailed(httpResponse)); + .map(user -> loginSuccess(httpResponse, user)) + .orElseGet(() -> loginFailed(httpResponse)); } - private static void loginSuccess(HttpResponse httpResponse, User user) { + private static String loginSuccess(HttpResponse httpResponse, User user) { httpResponse.setHttpStatus(HttpStatus.FOUND); - httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); final var session = new Session(UUID.randomUUID().toString()); session.setAttribute("user", user); SessionManager.add(session); httpResponse.addCookie(new HttpCookie(HttpCookie.JSESSIONID, session.getId())); + return INDEX_HTML; } - private static void loginFailed(HttpResponse httpResponse) { - httpResponse.setHttpStatus(HttpStatus.FOUND); - httpResponse.addHeader(HttpHeaders.CONTENT_TYPE, TEXT_HTML); - httpResponse.addHeader(HttpHeaders.LOCATION, "/401.html"); + private static String loginFailed(HttpResponse httpResponse) { + httpResponse.setHttpStatus(HttpStatus.UNAUTHORIZED); + httpResponse.addHeader(HttpHeaders.LOCATION, UNAUTHORIZED_HTML); + return UNAUTHORIZED_HTML; + } + + private static String register(HttpRequest httpRequest, HttpResponse httpResponse) { + final String account = "account"; + final String password = "password"; + final String email = "email"; + + final var body = httpRequest.getBody(); + Map parameters = HttpParser.parseFormData(body); + if (parameters.containsKey(account) && + parameters.containsKey(password) && + parameters.containsKey(email) + ) { + if (InMemoryUserRepository.findByAccount(parameters.get(account)).isPresent()) { + httpResponse.setHttpStatus(HttpStatus.BAD_REQUEST); + httpResponse.addHeader(HttpHeaders.LOCATION, BAD_REQUEST_HTML); + return BAD_REQUEST_HTML; + } + InMemoryUserRepository.save( + new User( + parameters.get(account), + parameters.get(password), + parameters.get(email) + ) + ); + + httpResponse.setHttpStatus(HttpStatus.FOUND); + httpResponse.addHeader(HttpHeaders.LOCATION, INDEX_HTML); + return INDEX_HTML; + } + + httpResponse.setHttpStatus(HttpStatus.BAD_REQUEST); + httpResponse.addHeader(HttpHeaders.LOCATION, BAD_REQUEST_HTML); + return BAD_REQUEST_HTML; } } diff --git a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java index 49bd560edc..0f99b27f1d 100644 --- a/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java +++ b/tomcat/src/main/java/org/apache/catalina/manager/SessionManager.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.apache.coyote.http11.Session; public class SessionManager { @@ -19,7 +20,11 @@ public static void remove(Session session) { SESSIONS.remove(session.getId()); } - public static Session findSession(String id) { - return SESSIONS.get(id); + public static Optional findSession(String id) { + try { + return Optional.of(SESSIONS.get(id)); + } catch (NullPointerException e) { + return Optional.empty(); + } } } 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 82b975bd04..d5bf8e1192 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,6 +1,8 @@ package org.apache.coyote.http11; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.net.Socket; import nextstep.handler.Handler; import nextstep.jwp.exception.UncheckedServletException; @@ -30,9 +32,10 @@ public void run() { public void process(final Socket connection) { try ( final var inputStream = connection.getInputStream(); + final var bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); final var outputStream = connection.getOutputStream() ) { - HttpRequest httpRequest = HttpRequestReader.read(inputStream); + HttpRequest httpRequest = HttpRequestReader.read(bufferedReader); final var httpResponse = new HttpResponse(); Handler.handle(httpRequest, httpResponse); HttpResponseWriter.write(outputStream, httpResponse); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index 559ad65b9a..f1f66756c2 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -1,18 +1,17 @@ package org.apache.coyote.http11; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; public class HttpHeaders { - public static final String ACCEPT = "Accept"; public static final String CONTENT_LENGTH = "Content-Length"; public static final String CONTENT_TYPE = "Content-Type"; public static final String LOCATION = "Location"; public static final String SET_COOKIE = "Set-Cookie"; public static final String COOKIE = "Cookie"; - private final Map headers = new HashMap<>(); + private final Map headers = new LinkedHashMap<>(); public boolean containsHeader(String headerName) { return this.headers.containsKey(headerName); @@ -25,4 +24,12 @@ public void put(String key, String value) { public String get(String headerName) { return headers.get(headerName); } + + public void putAll(final Map headers) { + this.headers.putAll(headers); + } + + public Map getHeaders() { + return headers; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index bc2f1321d4..13aa432f08 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -1,7 +1,11 @@ package org.apache.coyote.http11; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.coyote.http11.util.HttpParser; public class HttpRequest { @@ -39,8 +43,29 @@ public Session getSession() { return this.session; } - public void setSession(Session session) { - this.session = session; + public void addHeaders(final Map headers) { + this.headers.putAll(headers); + addCookies(); + addSession(); + } + + private void addCookies() { + if (!this.headers.containsHeader(HttpHeaders.COOKIE)) { + return; + } + List parsedCookies = HttpParser.parseCookies( + this.headers.get(HttpHeaders.COOKIE)); + final Map cookieMap = parsedCookies.stream() + .collect(Collectors.toMap(HttpCookie::getName, Function.identity())); + this.cookies.putAll(cookieMap); + } + + private void addSession() { + if (!this.cookies.containsKey(HttpCookie.JSESSIONID)) { + return; + } + final var sessionCookie = this.cookies.get(HttpCookie.JSESSIONID); + this.session = new Session(sessionCookie.getValue()); } public HttpMethod getHttpMethod() { @@ -51,10 +76,6 @@ public void setHttpMethod(HttpMethod httpMethod) { this.httpMethod = httpMethod; } - public void addHeader(String headerName, String value) { - this.headers.put(headerName, value); - } - public String getHeader(String headerName) { return this.headers.get(headerName); } @@ -63,10 +84,6 @@ public boolean containsHeader(String headerName) { return this.headers.containsHeader(headerName); } - public boolean hasCookie(String name) { - return this.cookies.containsKey(name); - } - public void addParameters(Map parameters) { this.parameters.putAll(parameters); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java index 5507e95863..11485b506d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatus.java @@ -3,6 +3,7 @@ public enum HttpStatus { OK(200, "OK"), FOUND(302, "Found"), + BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401, "Unauthorized"), NOT_FOUND(404, "Not Found"), INTER_SERVER_ERROR(500, "Internal Server Error"); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java b/tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java deleted file mode 100644 index 74a70c5fa5..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/exception/SessionNotFoundException.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.apache.coyote.http11.exception; - -public class SessionNotFoundException extends RuntimeException { - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpParser.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpParser.java new file mode 100644 index 0000000000..7ab0e7bddd --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpParser.java @@ -0,0 +1,56 @@ +package org.apache.coyote.http11.util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.coyote.http11.HttpCookie; + +public class HttpParser { + + private static final String QUERY_STRING_START_SYMBOL = "?"; + private static final String PARAMETER_SEPARATOR = "&"; + private static final String KEY_VALUE_DELIMITER = "="; + private static final String COOKIE_SEPARATOR = "; "; + + private HttpParser() { + } + + public static Map parseQueryParameters(String uri) { + if (uri.contains(QUERY_STRING_START_SYMBOL)) { + final var queryStartIndex = uri.indexOf(QUERY_STRING_START_SYMBOL); + final var queryString = uri.substring(queryStartIndex + 1); + return parseParameters(queryString); + } + return Collections.emptyMap(); + } + + private static Map parseParameters(String queryString) { + return Arrays.stream(queryString.split(PARAMETER_SEPARATOR)) + .map(parameter -> parameter.split(KEY_VALUE_DELIMITER)) + .filter(keyValuePair -> keyValuePair.length == 2) + .collect(Collectors.toMap(keyValuePair -> keyValuePair[0], + keyValuePair -> keyValuePair[1])); + } + + public static String parsePath(String uri) { + return uri.split("\\?")[0]; + } + + public static List parseCookies(String cookieLine) { + final var cookies = cookieLine.split(COOKIE_SEPARATOR); + return Arrays.stream(cookies) + .map(cookie -> cookie.split(KEY_VALUE_DELIMITER)) + .map(pair -> new HttpCookie(pair[0], pair[1])) + .collect(Collectors.toList()); + } + + public static Map parseFormData(String formData) { + return Arrays.stream(formData.split(PARAMETER_SEPARATOR)) + .map(parameter -> parameter.split(KEY_VALUE_DELIMITER)) + .filter(keyValuePair -> keyValuePair.length == 2) + .collect(Collectors.toMap(keyValuePair -> keyValuePair[0], + keyValuePair -> keyValuePair[1])); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java index 5c546ac5ef..c3560d06d5 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java @@ -2,28 +2,23 @@ import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; -import org.apache.catalina.manager.SessionManager; -import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpMethod; import org.apache.coyote.http11.HttpRequest; -import org.apache.coyote.http11.Session; import org.apache.coyote.http11.exception.InvalidHttpFormException; -import org.apache.coyote.http11.exception.SessionNotFoundException; public class HttpRequestReader { + private static final String REQUEST_LINE_DELIMITER = " "; + private static final String REQUEST_HEADER_DELIMITER = ": "; + private HttpRequestReader() { } - public static HttpRequest read(InputStream inputStream) throws IOException { - final var bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + public static HttpRequest read(final BufferedReader bufferedReader) throws IOException { final var httpRequest = new HttpRequest(); - readRequestLine(httpRequest, bufferedReader); readRequestHeaders(httpRequest, bufferedReader); readRequestBody(httpRequest, bufferedReader); @@ -37,38 +32,26 @@ private static void readRequestLine(HttpRequest httpRequest, BufferedReader buff if (requestLine == null) { throw new InvalidHttpFormException(); } - final var splitLine = requestLine.split(" "); + final var splitLine = requestLine.split(REQUEST_LINE_DELIMITER); httpRequest.setHttpMethod(HttpMethod.valueOf(splitLine[0])); - parseUri(httpRequest, splitLine[1]); + httpRequest.setUri(splitLine[1]); + httpRequest.setPath(HttpParser.parsePath(splitLine[1])); + httpRequest.addParameters(HttpParser.parseQueryParameters(splitLine[1])); httpRequest.setProtocol(splitLine[2]); } private static void readRequestHeaders(HttpRequest httpRequest, BufferedReader bufferedReader) throws IOException { + final Map headers = new HashMap<>(); while (bufferedReader.ready()) { final var header = bufferedReader.readLine(); if (header.isEmpty()) { break; } - final var keyValuePair = header.split(": "); - httpRequest.addHeader(keyValuePair[0], keyValuePair[1]); - } - if (httpRequest.containsHeader(HttpHeaders.COOKIE)) { - - String[] cookies = httpRequest.getHeader(HttpHeaders.COOKIE).split("; "); - for (String cookie : cookies) { - String[] keyValuePair = cookie.split("="); - httpRequest.addCookie(new HttpCookie(keyValuePair[0], keyValuePair[1])); - } - } - if (httpRequest.hasCookie(HttpCookie.JSESSIONID)) { - HttpCookie sessionCookie = httpRequest.getCookie(HttpCookie.JSESSIONID); - Session session = SessionManager.findSession(sessionCookie.getValue()); - if (session == null) { - throw new SessionNotFoundException(); - } - httpRequest.setSession(session); + final var keyValuePair = header.split(REQUEST_HEADER_DELIMITER); + headers.put(keyValuePair[0], keyValuePair[1]); } + httpRequest.addHeaders(headers); } private static void readRequestBody(HttpRequest httpRequest, BufferedReader bufferedReader) @@ -82,35 +65,4 @@ private static void readRequestBody(HttpRequest httpRequest, BufferedReader buff String requestBody = new String(buffer); httpRequest.setBody(requestBody); } - - private static void parseUri(HttpRequest httpRequest, String uri) { - if (uri.contains("?")) { - final var queryStartIndex = uri.indexOf("?"); - final var queryString = uri.substring(queryStartIndex + 1); - final var parameters = parseParameters(queryString); - httpRequest.addParameters(parameters); - } - - final var path = getFilePath(uri); - httpRequest.setUri(uri); - httpRequest.setPath(path); - } - - private static Map parseParameters(String queryString) { - Map parameters = new HashMap<>(); - String[] parametersArray = queryString.split("&"); - for (String parameter : parametersArray) { - String[] split = parameter.split("="); - parameters.put(split[0], split[1]); - } - return parameters; - } - - private static String getFilePath(String uri) { - String filePath = uri.split("\\?")[0]; - if (filePath.matches(".+\\.[a-zA-Z]+$") || filePath.equals("/")) { - return filePath; - } - return filePath + ".html"; - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java index 4c2a2d54ca..9b5a8f6307 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java @@ -2,17 +2,17 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.Map.Entry; +import java.util.StringJoiner; import java.util.stream.Collectors; import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpResponse; -import org.apache.coyote.http11.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpResponseWriter { - private static final Logger log = LoggerFactory.getLogger(HttpResponseWriter.class); private static final String HEADER_DELIMITER = ": "; private HttpResponseWriter() { @@ -20,46 +20,31 @@ private HttpResponseWriter() { public static void write(OutputStream outputStream, HttpResponse httpResponse) throws IOException { - final var headers = httpResponse.getHeaders(); - var contentType = headers.get(HttpHeaders.CONTENT_TYPE); - if (contentType == null) { - contentType = "text/html;charset=utf-8"; - } - String response = ""; - if (httpResponse.getHttpStatus() == HttpStatus.OK) { - response = createOkResponse(httpResponse, contentType); + StringJoiner stringJoiner = new StringJoiner(" \r\n"); + stringJoiner.add(createStatusLine(httpResponse)); + HttpHeaders headers = httpResponse.getHeaders(); + for (final Entry entry : headers.getHeaders().entrySet()) { + stringJoiner.add(String.join(HEADER_DELIMITER, entry.getKey(), entry.getValue())); } - if (httpResponse.getHttpStatus() == HttpStatus.FOUND) { - response = createFoundResponse(httpResponse); + String cookieLine = getCookieLine(httpResponse); + if (!cookieLine.isEmpty()) { + stringJoiner.add(cookieLine); } + stringJoiner.add("\r\n" + httpResponse.getBody()); - log.info(response); + String response = stringJoiner.toString(); outputStream.write(response.getBytes()); outputStream.flush(); } - private static String createOkResponse(HttpResponse httpResponse, String contentType) { - return String.join("\r\n", createStatusLine(httpResponse) + " ", - String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_TYPE, contentType) + " ", - String.join(HEADER_DELIMITER, HttpHeaders.CONTENT_LENGTH, - String.valueOf(httpResponse.getBody().getBytes().length)) + " ", "", - httpResponse.getBody()); - } - - private static String createFoundResponse(HttpResponse httpResponse) { - return String.join("\r\n", createStatusLine(httpResponse) + " ", - setCookie(httpResponse) + " ", String.join(HEADER_DELIMITER, HttpHeaders.LOCATION, - httpResponse.getHeader("Location")) + " ", "", httpResponse.getBody()); - } - private static String createStatusLine(HttpResponse httpResponse) { return String.join(" ", httpResponse.getProtocol(), String.valueOf(httpResponse.getHttpStatus().getValue()), httpResponse.getHttpStatus().getReasonPhrase()); } - private static String setCookie(HttpResponse httpResponse) { + private static String getCookieLine(HttpResponse httpResponse) { if (httpResponse.getCookie(HttpCookie.JSESSIONID) == null) { return ""; } diff --git a/tomcat/src/main/resources/static/400.html b/tomcat/src/main/resources/static/400.html new file mode 100644 index 0000000000..a0095232e9 --- /dev/null +++ b/tomcat/src/main/resources/static/400.html @@ -0,0 +1,52 @@ + + + + + + + + + 400 Error - SB Admin + + + + +
+
+
+
+
+
+
+

400

+

Bad Request

+

Access to this resource is denied.

+ + + Return to Dashboard + +
+
+
+
+
+
+ +
+ + + + diff --git a/tomcat/src/main/resources/static/401.html b/tomcat/src/main/resources/static/401.html index 444019ac4e..30c64673cd 100644 --- a/tomcat/src/main/resources/static/401.html +++ b/tomcat/src/main/resources/static/401.html @@ -6,7 +6,7 @@ - 404 Error - SB Admin + 401 Error - SB Admin diff --git a/tomcat/src/main/resources/static/500.html b/tomcat/src/main/resources/static/500.html index f786af3509..ec60ad5642 100644 --- a/tomcat/src/main/resources/static/500.html +++ b/tomcat/src/main/resources/static/500.html @@ -6,7 +6,7 @@ - 404 Error - SB Admin + 500 Error - SB Admin From 7bc374c939b6e3195b0a533d15bca6d3848e98cc Mon Sep 17 00:00:00 2001 From: aak2075 Date: Tue, 5 Sep 2023 20:43:53 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20RequestBuilder=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/coyote/http11/HttpHeaders.java | 10 +- .../org/apache/coyote/http11/HttpRequest.java | 157 +++++++++++------- .../coyote/http11/util/HttpRequestReader.java | 48 +++--- 3 files changed, 131 insertions(+), 84 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java index f1f66756c2..2f8b22dc10 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -11,7 +11,15 @@ public class HttpHeaders { public static final String SET_COOKIE = "Set-Cookie"; public static final String COOKIE = "Cookie"; - private final Map headers = new LinkedHashMap<>(); + private final Map headers; + + public HttpHeaders() { + this.headers = new LinkedHashMap<>(); + } + + public HttpHeaders(final Map headers) { + this.headers = headers; + } public boolean containsHeader(String headerName) { return this.headers.containsKey(headerName); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java index 13aa432f08..d403a1d3df 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -1,6 +1,6 @@ package org.apache.coyote.http11; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -9,30 +9,52 @@ public class HttpRequest { + private final String protocol; private final HttpHeaders headers; - private final Map cookies = new HashMap<>(); - private HttpMethod httpMethod; - private String uri; - private String path; - private Map parameters = new HashMap<>(); - private String protocol; - private Session session; - private String body; - - public HttpRequest() { - this.headers = new HttpHeaders(); + private final Map cookies; + private final HttpMethod httpMethod; + private final String uri; + private final String path; + private final Map parameters; + private final Session session; + private final String body; + + private HttpRequest(final String protocol, + final HttpHeaders headers, + final HttpMethod httpMethod, + final String uri, final String path, final Map parameters, + final String body) { + this.protocol = protocol; + this.headers = headers; + this.httpMethod = httpMethod; + this.uri = uri; + this.path = path; + this.parameters = parameters; + this.body = body; + this.cookies = getCookiesFromHeader(); + this.session = getSessionFromHeader(); } - public void addCookie(HttpCookie cookie) { - this.cookies.put(cookie.getName(), cookie); + private Map getCookiesFromHeader() { + if (!this.headers.containsHeader(HttpHeaders.COOKIE)) { + return Collections.emptyMap(); + } + List parsedCookies = HttpParser.parseCookies( + this.headers.get(HttpHeaders.COOKIE)); + return parsedCookies.stream() + .collect(Collectors.toMap(HttpCookie::getName, Function.identity())); } - public String getPath() { - return path; + private Session getSessionFromHeader() { + if (!this.cookies.containsKey(HttpCookie.JSESSIONID)) { + return null; + } + final var sessionCookie = this.cookies.get(HttpCookie.JSESSIONID); + return new Session(sessionCookie.getValue()); } - public void setPath(String path) { - this.path = path; + public String getPath() { + return path; } public HttpCookie getCookie(String name) { @@ -43,39 +65,10 @@ public Session getSession() { return this.session; } - public void addHeaders(final Map headers) { - this.headers.putAll(headers); - addCookies(); - addSession(); - } - - private void addCookies() { - if (!this.headers.containsHeader(HttpHeaders.COOKIE)) { - return; - } - List parsedCookies = HttpParser.parseCookies( - this.headers.get(HttpHeaders.COOKIE)); - final Map cookieMap = parsedCookies.stream() - .collect(Collectors.toMap(HttpCookie::getName, Function.identity())); - this.cookies.putAll(cookieMap); - } - - private void addSession() { - if (!this.cookies.containsKey(HttpCookie.JSESSIONID)) { - return; - } - final var sessionCookie = this.cookies.get(HttpCookie.JSESSIONID); - this.session = new Session(sessionCookie.getValue()); - } - public HttpMethod getHttpMethod() { return httpMethod; } - public void setHttpMethod(HttpMethod httpMethod) { - this.httpMethod = httpMethod; - } - public String getHeader(String headerName) { return this.headers.get(headerName); } @@ -92,19 +85,69 @@ public String getUri() { return uri; } - public void setUri(String uri) { - this.uri = uri; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - public String getBody() { return body; } - public void setBody(String body) { - this.body = body; + public static class Builder { + + private String protocol; + private HttpHeaders headers; + private HttpMethod httpMethod; + private String uri; + private String path; + private Map parameters; + private String body; + + public static Builder builder() { + return new Builder(); + } + + public Builder protocol(final String protocol) { + this.protocol = protocol; + return this; + } + + public Builder httpMethod(final HttpMethod httpMethod) { + this.httpMethod = httpMethod; + return this; + } + + public Builder headers(final HttpHeaders headers) { + this.headers = headers; + return this; + } + + public Builder uri(final String uri) { + this.uri = uri; + return this; + } + + public Builder path(final String path) { + this.path = path; + return this; + } + + public Builder parameters(final Map parameters) { + this.parameters = parameters; + return this; + } + + public Builder body(final String body) { + this.body = body; + return this; + } + + public HttpRequest build() { + return new HttpRequest( + protocol, + headers, + httpMethod, + uri, + path, + parameters, + body + ); + } } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java index c3560d06d5..45311607ac 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpRequestReader.java @@ -2,7 +2,7 @@ import java.io.BufferedReader; import java.io.IOException; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpMethod; @@ -18,31 +18,27 @@ private HttpRequestReader() { } public static HttpRequest read(final BufferedReader bufferedReader) throws IOException { - final var httpRequest = new HttpRequest(); - readRequestLine(httpRequest, bufferedReader); - readRequestHeaders(httpRequest, bufferedReader); - readRequestBody(httpRequest, bufferedReader); - - return httpRequest; - } - - private static void readRequestLine(HttpRequest httpRequest, BufferedReader bufferedReader) - throws IOException { final var requestLine = bufferedReader.readLine(); if (requestLine == null) { throw new InvalidHttpFormException(); } - final var splitLine = requestLine.split(REQUEST_LINE_DELIMITER); - httpRequest.setHttpMethod(HttpMethod.valueOf(splitLine[0])); - httpRequest.setUri(splitLine[1]); - httpRequest.setPath(HttpParser.parsePath(splitLine[1])); - httpRequest.addParameters(HttpParser.parseQueryParameters(splitLine[1])); - httpRequest.setProtocol(splitLine[2]); + final var requestLineAttributes = requestLine.split(REQUEST_LINE_DELIMITER); + Map headers = readRequestHeaders(bufferedReader); + String body = readRequestBody(headers, bufferedReader); + return HttpRequest.Builder.builder() + .httpMethod(HttpMethod.valueOf(requestLineAttributes[0])) + .uri(requestLineAttributes[1]) + .path(HttpParser.parsePath(requestLineAttributes[1])) + .parameters(HttpParser.parseQueryParameters(requestLineAttributes[1])) + .protocol(requestLineAttributes[2]) + .headers(new HttpHeaders(headers)) + .body(body) + .build(); } - private static void readRequestHeaders(HttpRequest httpRequest, BufferedReader bufferedReader) + private static Map readRequestHeaders(BufferedReader bufferedReader) throws IOException { - final Map headers = new HashMap<>(); + final Map headers = new LinkedHashMap<>(); while (bufferedReader.ready()) { final var header = bufferedReader.readLine(); if (header.isEmpty()) { @@ -51,18 +47,18 @@ private static void readRequestHeaders(HttpRequest httpRequest, BufferedReader b final var keyValuePair = header.split(REQUEST_HEADER_DELIMITER); headers.put(keyValuePair[0], keyValuePair[1]); } - httpRequest.addHeaders(headers); + return headers; } - private static void readRequestBody(HttpRequest httpRequest, BufferedReader bufferedReader) + private static String readRequestBody(Map headers, + BufferedReader bufferedReader) throws IOException { - if (!httpRequest.containsHeader(HttpHeaders.CONTENT_LENGTH)) { - return; + if (!headers.containsKey(HttpHeaders.CONTENT_LENGTH)) { + return null; } - int contentLength = Integer.parseInt(httpRequest.getHeader(HttpHeaders.CONTENT_LENGTH)); + int contentLength = Integer.parseInt(headers.get(HttpHeaders.CONTENT_LENGTH)); char[] buffer = new char[contentLength]; bufferedReader.read(buffer, 0, contentLength); - String requestBody = new String(buffer); - httpRequest.setBody(requestBody); + return new String(buffer); } } From 91004b213c4d79db01df72a782ab631da8644a3d Mon Sep 17 00:00:00 2001 From: aak2075 Date: Tue, 5 Sep 2023 20:45:40 +0900 Subject: [PATCH 09/10] =?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=20import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/coyote/http11/util/HttpResponseWriter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java index 9b5a8f6307..4439fc626b 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/HttpResponseWriter.java @@ -8,8 +8,6 @@ import org.apache.coyote.http11.HttpCookie; import org.apache.coyote.http11.HttpHeaders; import org.apache.coyote.http11.HttpResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class HttpResponseWriter { From d24f44f65f22de95749ba9a75a712e8e8d8f0144 Mon Sep 17 00:00:00 2001 From: aak2075 Date: Tue, 5 Sep 2023 20:52:32 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tomcat/src/main/java/nextstep/{ => mvc}/ModelAndView.java | 2 +- tomcat/src/main/java/nextstep/{ => mvc}/View.java | 2 +- tomcat/src/main/java/nextstep/{ => mvc}/ViewResolver.java | 3 ++- .../src/main/java/nextstep/{ => mvc}/handler/Handler.java | 8 ++++---- .../java/org/apache/coyote/http11/Http11Processor.java | 2 +- .../apache/coyote/http11}/SupportContentType.java | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) rename tomcat/src/main/java/nextstep/{ => mvc}/ModelAndView.java (96%) rename tomcat/src/main/java/nextstep/{ => mvc}/View.java (98%) rename tomcat/src/main/java/nextstep/{ => mvc}/ViewResolver.java (80%) rename tomcat/src/main/java/nextstep/{ => mvc}/handler/Handler.java (98%) rename tomcat/src/main/java/{nextstep => org/apache/coyote/http11}/SupportContentType.java (95%) diff --git a/tomcat/src/main/java/nextstep/ModelAndView.java b/tomcat/src/main/java/nextstep/mvc/ModelAndView.java similarity index 96% rename from tomcat/src/main/java/nextstep/ModelAndView.java rename to tomcat/src/main/java/nextstep/mvc/ModelAndView.java index 00981a935a..4c93cd00ef 100644 --- a/tomcat/src/main/java/nextstep/ModelAndView.java +++ b/tomcat/src/main/java/nextstep/mvc/ModelAndView.java @@ -1,4 +1,4 @@ -package nextstep; +package nextstep.mvc; import java.util.HashMap; import java.util.Map; diff --git a/tomcat/src/main/java/nextstep/View.java b/tomcat/src/main/java/nextstep/mvc/View.java similarity index 98% rename from tomcat/src/main/java/nextstep/View.java rename to tomcat/src/main/java/nextstep/mvc/View.java index 280c1b7e00..2be22ad97c 100644 --- a/tomcat/src/main/java/nextstep/View.java +++ b/tomcat/src/main/java/nextstep/mvc/View.java @@ -1,4 +1,4 @@ -package nextstep; +package nextstep.mvc; import java.util.Map; import org.apache.coyote.http11.HttpHeaders; diff --git a/tomcat/src/main/java/nextstep/ViewResolver.java b/tomcat/src/main/java/nextstep/mvc/ViewResolver.java similarity index 80% rename from tomcat/src/main/java/nextstep/ViewResolver.java rename to tomcat/src/main/java/nextstep/mvc/ViewResolver.java index d237a76025..67cbf05ce5 100644 --- a/tomcat/src/main/java/nextstep/ViewResolver.java +++ b/tomcat/src/main/java/nextstep/mvc/ViewResolver.java @@ -1,7 +1,8 @@ -package nextstep; +package nextstep.mvc; import common.FileReader; import java.io.IOException; +import org.apache.coyote.http11.SupportContentType; public class ViewResolver { diff --git a/tomcat/src/main/java/nextstep/handler/Handler.java b/tomcat/src/main/java/nextstep/mvc/handler/Handler.java similarity index 98% rename from tomcat/src/main/java/nextstep/handler/Handler.java rename to tomcat/src/main/java/nextstep/mvc/handler/Handler.java index 029f6694b6..3d2a8a9433 100644 --- a/tomcat/src/main/java/nextstep/handler/Handler.java +++ b/tomcat/src/main/java/nextstep/mvc/handler/Handler.java @@ -1,11 +1,11 @@ -package nextstep.handler; +package nextstep.mvc.handler; import java.io.IOException; import java.util.Map; import java.util.UUID; -import nextstep.ModelAndView; -import nextstep.View; -import nextstep.ViewResolver; +import nextstep.mvc.ModelAndView; +import nextstep.mvc.View; +import nextstep.mvc.ViewResolver; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.model.User; import org.apache.catalina.manager.SessionManager; 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 d5bf8e1192..04ce70f50c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; -import nextstep.handler.Handler; +import nextstep.mvc.handler.Handler; import nextstep.jwp.exception.UncheckedServletException; import org.apache.coyote.Processor; import org.apache.coyote.http11.util.HttpRequestReader; diff --git a/tomcat/src/main/java/nextstep/SupportContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/SupportContentType.java similarity index 95% rename from tomcat/src/main/java/nextstep/SupportContentType.java rename to tomcat/src/main/java/org/apache/coyote/http11/SupportContentType.java index 1b5994151d..9be15347f6 100644 --- a/tomcat/src/main/java/nextstep/SupportContentType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/SupportContentType.java @@ -1,4 +1,4 @@ -package nextstep; +package org.apache.coyote.http11; public enum SupportContentType { TEXT_HTML(".html", "text/html;charset=utf-8"),