Skip to content

Commit

Permalink
[톰캣 구현하기 - 1, 2단계] 다즐(최우창) 미션 제출합니다. (#316)
Browse files Browse the repository at this point in the history
* test: 학습 테스트 작성

* feat: GET /index.html 응답하기 기능 구현

* feat: HttpException 커스텀 예외 구현

* feat: http 공통 클래스 구현

* feat: http request 클래스 구현

* feat: http response 클래스 구현

* feat: 요청 매핑을 위한 클래스 구현

* refactor: ACCEPT 헤더 처리 수정

* feat: Handler 클래스 기능 구현

* feat: Processor 클래스 기능 구현

* test: GET 요청 본문 제거

* refactor(ViewResolver): 존재하지 않는 페이지 예외 처리

* refactor: 요청 헤더 파싱 기능 수정
  • Loading branch information
woo-chang authored Sep 5, 2023
1 parent 68db530 commit 22a9cc3
Show file tree
Hide file tree
Showing 68 changed files with 2,833 additions and 38 deletions.
12 changes: 7 additions & 5 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package study;

import java.nio.file.Files;
import java.nio.file.Paths;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.springframework.core.io.ClassPathResource;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -28,7 +30,7 @@ class FileTest {
final String fileName = "nextstep.txt";

// todo
final String actual = "";
final String actual = new ClassPathResource(fileName).getPath();

assertThat(actual).endsWith(fileName);
}
Expand All @@ -40,14 +42,14 @@ class FileTest {
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
void 파일의_내용을_읽는다() {
void 파일의_내용을_읽는다() throws Exception {
final String fileName = "nextstep.txt";

// todo
final Path path = null;
final Path path = Paths.get(new ClassPathResource(fileName).getURI());

// todo
final List<String> actual = Collections.emptyList();
final List<String> actual = Files.readAllLines(path);

assertThat(actual).containsOnly("nextstep");
}
Expand Down
21 changes: 16 additions & 5 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package study;

import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -53,6 +54,7 @@ class OutputStream_학습_테스트 {
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
outputStream.write(bytes);

final String actual = outputStream.toString();

Expand All @@ -78,6 +80,7 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
outputStream.flush();

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -96,6 +99,7 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
outputStream.close();

verify(outputStream, atLeastOnce()).close();
}
Expand Down Expand Up @@ -128,7 +132,7 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final String actual = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);

assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
Expand All @@ -148,6 +152,7 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
inputStream.close();

verify(inputStream, atLeastOnce()).close();
}
Expand All @@ -169,12 +174,12 @@ class FilterStream_학습_테스트 {
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@Test
void 필터인_BufferedInputStream_사용해보자() {
void 필터인_BufferedInputStream_사용해보자() throws Exception {
final String text = "필터에 연결해보자.";
final InputStream inputStream = new ByteArrayInputStream(text.getBytes());
final InputStream bufferedInputStream = null;
final InputStream bufferedInputStream = new BufferedInputStream(inputStream);

final byte[] actual = new byte[0];
final byte[] actual = bufferedInputStream.readAllBytes();

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand All @@ -197,15 +202,21 @@ class InputStreamReader_학습_테스트 {
* 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
*/
@Test
void BufferedReader_사용하여_문자열을_읽어온다() {
void BufferedReader_사용하여_문자열을_읽어온다() throws Exception {
final String emoji = String.join("\r\n",
"😀😃😄😁😆😅😂🤣🥲☺️😊",
"😇🙂🙃😉😌😍🥰😘😗😙😚",
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

final StringBuilder actual = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
actual.append(line);
actual.append("\r\n");
}

assertThat(actual).hasToString(emoji);
}
Expand Down
64 changes: 64 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/common/ContentType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.apache.coyote.common;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.apache.coyote.request.Accept;

public enum ContentType {

APPLICATION_XML("application/xml", "xml"),
APPLICATION_JAVASCRIPT("application/javascript", "js"),
APPLICATION_JSON("application/json", "json"),
APPLICATION_OCTET_STREAM("application/octet-stream", ""),
APPLICATION_PDF("application/pdf", "pdf"),
TEXT_PLAIN("text/plain", "text"),
TEXT_HTML("text/html;charset=utf-8", "html"),
TEXT_CSS("text/css", "css"),
TEXT_JAVASCRIPT("text/javascript", "js"),
TEXT_XML("text/xml", "xml"),
IMAGE_JPEG("image/jpeg", "jpeg"),
IMAGE_PNG("image/png", "png"),
IMAGE_GIF("image/gif", "gif"),
IMAGE_SVG("image/svg+xml", "svg"),
AUDIO_MPEG("audio/mpeg", "mpeg"),
AUDIO_OGG("audio/ogg", "ogg"),
VIDEO_MP4("video/mp4", "mp4"),
MULTIPART_FOR_DATA("multipart/form-data", "");

private final String type;
private final String extension;

ContentType(final String type, final String extension) {
this.type = type;
this.extension = extension;
}

public static String getTypeFrom(final List<Accept> accepts) {
return accepts.stream()
.map(ContentType::from)
.filter(Objects::nonNull)
.findFirst()
.map(ContentType::getType)
.orElse(null);
}

private static ContentType from(final Accept accept) {
return Arrays.stream(ContentType.values())
.filter(contentType -> contentType.type.contains(accept.getAcceptType()))
.findFirst()
.orElse(null);
}

public static String getTypeFrom(final String extension) {
return Arrays.stream(ContentType.values())
.filter(contentType -> Objects.equals(contentType.extension, extension))
.findAny()
.map(ContentType::getType)
.orElse(null);
}

public String getType() {
return type;
}
}
25 changes: 25 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/common/HttpMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.apache.coyote.common;

import java.util.Arrays;
import java.util.Objects;
import org.apache.coyote.exception.http.HttpMethodNotMatchException;

public enum HttpMethod {

GET,
POST,
PUT,
DELETE,
HEAD,
OPTIONS,
PATCH,
CONNECT,
TRACE;

public static HttpMethod from(final String method) {
return Arrays.stream(HttpMethod.values())
.filter(httpMethod -> Objects.equals(httpMethod.name(), method))
.findFirst()
.orElseThrow(HttpMethodNotMatchException::new);
}
}
40 changes: 40 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/common/HttpStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.apache.coyote.common;

public enum HttpStatus {

CONTINUE(100, "Continue"),
SWITCHING_PROTOCOLS(101, "Switching Protocols"),
OK(200, "OK"),
CREATED(201, "Created"),
ACCEPTED(202, "Accepted"),
NO_CONTENT(204, "No Content"),
MULTIPLE_CHOICES(300, "Multiple Choices"),
MOVED_PERMANENTLY(301, "Moved Permanently"),
FOUND(302, "Found"),
NOT_MODIFIED(304, "Not Modified"),
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
NOT_IMPLEMENTED(501, "Not Implemented"),
BAD_GATEWAY(502, "Bad Gateway"),
SERVICE_UNAVAILABLE(503, "Service Unavailable");

private final int code;
private final String message;

HttpStatus(final int code, final String message) {
this.code = code;
this.message = message;
}

public int getCode() {
return code;
}

public String getMessage() {
return message;
}
}
31 changes: 31 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/common/HttpVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.apache.coyote.common;

import java.util.Arrays;
import java.util.Objects;
import org.apache.coyote.exception.http.HttpVersionNotMatchException;

public enum HttpVersion {

HTTP_09("HTTP/0.9"),
HTTP_10("HTTP/1.0"),
HTTP_11("HTTP/1.1"),
HTTP_20("HTTP/2"),
HTTP_30("HTTP/3");

private final String version;

HttpVersion(final String version) {
this.version = version;
}

public static HttpVersion from(final String version) {
return Arrays.stream(HttpVersion.values())
.filter(httpVersion -> Objects.equals(httpVersion.version, version))
.findFirst()
.orElseThrow(HttpVersionNotMatchException::new);
}

public String getVersion() {
return version;
}
}
35 changes: 35 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/common/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.apache.coyote.common;

import java.util.HashMap;
import java.util.Map;

public class Session {

private final String id;
private final Map<String, Object> values = new HashMap<>();

public Session(final String id) {
this.id = id;
}

public String getId() {
return id;
}

public Object getAttribute(final String name) {
return values.get(name);
}

public void setAttribute(final String name, final Object value) {
values.put(name, value);
}

public void removeAttribute(final String name) {
values.remove(name);
}

public void invalidate() {
values.clear();
}
}

24 changes: 24 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/common/SessionManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.apache.coyote.common;

import java.util.HashMap;
import java.util.Map;

public class SessionManager {

private static final Map<String, Session> SESSIONS = new HashMap<>();

private SessionManager() {
}

public static void add(final Session session) {
SESSIONS.put(session.getId(), session);
}

public static Session findSession(final String id) {
return SESSIONS.get(id);
}

public static void remove(final String id) {
SESSIONS.remove(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.apache.coyote.exception.http;

public class HttpException extends RuntimeException {

public HttpException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.apache.coyote.exception.http;

public class HttpMethodNotMatchException extends HttpException {

public HttpMethodNotMatchException() {
super("Http Method Not Matched");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.apache.coyote.exception.http;

public class HttpVersionNotMatchException extends HttpException {

public HttpVersionNotMatchException() {
super("Http Version Not Matched");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.apache.coyote.exception.http;

public class InvalidHeaderException extends HttpException {

public InvalidHeaderException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.apache.coyote.exception.http;

public class InvalidRequestPathException extends HttpException {

public InvalidRequestPathException(final String message) {
super(message);
}
}
Loading

0 comments on commit 22a9cc3

Please sign in to comment.