Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[톰캣 구현하기 - 1,2단계] 오잉(이하늘) 미션 제출합니다. #341

Merged
merged 51 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
dc73464
feat: FileTest 완료
hanueleee Sep 3, 2023
588ed60
feat: IOStreamTest 완료
hanueleee Sep 3, 2023
2053156
feat: input 데이터를 파싱하여 HttpRequest를 만드는 기능 구현
hanueleee Sep 3, 2023
9c48078
feat(Http11Processor): 기능요구사항1 (/index.html 응답 기능) 구현
hanueleee Sep 3, 2023
efe09b2
feat: HttpResponse 관련 객체들 만들기
hanueleee Sep 3, 2023
7399c9e
feat: 요청을 처리하는 Handler 인터페이스 구현
hanueleee Sep 3, 2023
feb42bd
feat: 요청을 처리할 수 있는 Handler를 찾아주는 HandlerFinder 구현
hanueleee Sep 3, 2023
d8976e6
feat(Http11Processor): 기능요구사항2 (CSS 지원하기) 구현
hanueleee Sep 3, 2023
b2f9db4
feat: HttpRequest의 StartLine에 QueryParams 추가
hanueleee Sep 3, 2023
5b664f0
refactor: handler의 IOException 처리를 try-catch에서 throws로 변경
hanueleee Sep 3, 2023
f4099f8
feat: 기능요구사항3 (Query String 파싱) 구현
hanueleee Sep 3, 2023
0af54a8
feat: 기능요구사항4 (리다이렉트) 구현
hanueleee Sep 3, 2023
7ab8aa5
refactor: 정적파일 읽어오는 기능을 StaticFileLoader로 이동
hanueleee Sep 3, 2023
ec0252e
refactor: QueryParams를 파싱하는 기능을 QueryParser로 이동
hanueleee Sep 3, 2023
cfa7655
feat: 기능요구사항5 (POST 방식으로 회원가입) 구현
hanueleee Sep 3, 2023
e403c1f
refactor: login 페이지 버튼 눌렀을 때 POST로 전송하도록 수정
hanueleee Sep 3, 2023
1dfd767
feat: Cookie 기능 추가
hanueleee Sep 4, 2023
91cdd76
feat: 기능요구사항6 (Cookie에 JSESSIONID 값 저장하기) 구현
hanueleee Sep 4, 2023
05105cb
feat: Session 기능 추가
hanueleee Sep 4, 2023
03f7d62
feat: 기능요구사항7 (Session을 사용한 로그인) 구
hanueleee Sep 4, 2023
30fd52c
chore: 컨벤션 정리
hanueleee Sep 4, 2023
141587c
test: Parser test 추가
hanueleee Sep 4, 2023
ef8460d
test: Http11Processor test 추가
hanueleee Sep 4, 2023
556a8cf
test: RequestUri test 추가
hanueleee Sep 4, 2023
15d5bae
test: HttpHeaders test 추가
hanueleee Sep 4, 2023
eac2089
test: StartLine test 추가
hanueleee Sep 4, 2023
4732705
test: HttpResponse test 추가
hanueleee Sep 4, 2023
5aaa6b5
test: ContentType test 추가
hanueleee Sep 4, 2023
e7e3769
refactor: QueryParams의 사용하지 않는 메소드 삭제
hanueleee Sep 4, 2023
258c1b6
refactor: 상수 추출
hanueleee Sep 4, 2023
78603da
refactor: 클래스 명칭을 Handler에서 Servlet으로 수정
hanueleee Sep 6, 2023
a8f4a22
refactor: Cookie의 Map을 LinkedHashMap에서 HashMap으로 수정
hanueleee Sep 6, 2023
1b83e41
chore: 클래스 이름 오타 수정
hanueleee Sep 6, 2023
bf1e124
feat: Method Not Allowed 관련 로직 추가
hanueleee Sep 6, 2023
6b847a9
feat: Not Found 관련 로직 추가
hanueleee Sep 6, 2023
4af7eff
refactor: 자주 사용되는 html 파일명을 Page라는 enum으로 묶기
hanueleee Sep 6, 2023
b52dc77
refactor: HttpMethod enum의 valueOf에 대한 방어 코드
hanueleee Sep 6, 2023
fd340ec
refactor: QueryParams의 getParam의 반환값을 Optional로 수정
hanueleee Sep 7, 2023
9f56a42
refactor: Manager의 findSession 반환값을 Optional로 수정
hanueleee Sep 7, 2023
7e6cad4
refactor: Session의 getAttribute 반환값을 Optional로 수정
hanueleee Sep 7, 2023
f5a04fe
refactor: HttpHeaders의 getHeader 반환값을 Optional로 수정
hanueleee Sep 7, 2023
9f1721b
refactor: Cookies의 getCookie 반환값을 Optional로 수정
hanueleee Sep 7, 2023
26e0f06
refactor: 정적 팩토리 메소드 empty에서 null대신 빈 Map으로 생성하도록 수정
hanueleee Sep 7, 2023
5341e8e
refactor: HttpHeaders에 존재하던 getCookies로직을 HttpRequest로 이동
hanueleee Sep 7, 2023
4a449c0
refactor: HttpRequest의 getSession메소드 시그니처 수정 및 null 핸들링 추가
hanueleee Sep 7, 2023
484f3b7
refactor: LoginServlet의 isLoggedIn에서 사용자 정보 불일치할 경우 session에서 user 어트…
hanueleee Sep 7, 2023
dfa78da
chore: code smell 일부 제거
hanueleee Sep 7, 2023
f6dc456
refactor: BadRequest와 MethodNotAllowed관련 response에 대한 정적 팩토리 메소드 생성
hanueleee Sep 7, 2023
13be23e
refactor: Optional<String>을 반환하는 메소드의 반환값을 String으로 수정
hanueleee Sep 7, 2023
66ad3d9
chore: StartLine에서 사용하지 않는 필드 삭제
hanueleee Sep 7, 2023
7d2ed1d
test: HttpResponse의 create 메소드에 대한 검증부 추가
hanueleee Sep 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package study;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -27,8 +31,9 @@ class FileTest {
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
final String fileName = "nextstep.txt";

// todo
final String actual = "";
// TODO
URL resource = getClass().getClassLoader().getResource(fileName);
String actual = resource.getFile();

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

// todo
final Path path = null;
URL resource = getClass().getClassLoader().getResource(fileName);
Path path = new File(resource.getPath()).toPath();

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
33 changes: 24 additions & 9 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.*;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.in;
import static org.mockito.Mockito.*;

/**
Expand Down Expand Up @@ -53,8 +54,9 @@ class OutputStream_학습_테스트 {
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

outputStream.write(bytes);
final String actual = outputStream.toString();
// byte[] info = actual.getBytes("utf-8"); <- info = bytes

assertThat(actual).isEqualTo("nextstep");
outputStream.close();
Expand All @@ -72,12 +74,12 @@ class OutputStream_학습_테스트 {
@Test
void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException {
final OutputStream outputStream = mock(BufferedOutputStream.class);

/**
* todo
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
outputStream.flush();

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

try(outputStream) {
}
verify(outputStream, atLeastOnce()).close();
}
}
Expand Down Expand Up @@ -128,7 +131,13 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
Reader reader = new InputStreamReader(inputStream);
StringBuilder result = new StringBuilder();
for (int data = reader.read(); data != -1; data = reader.read()) {
result.append((char)data);
}

final String actual = result.toString();

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

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

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

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand All @@ -197,15 +208,19 @@ class InputStreamReader_학습_테스트 {
* 필터인 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 InputStreamReader reader = new InputStreamReader(inputStream);

final StringBuilder actual = new StringBuilder();
StringBuilder actual = new StringBuilder();
for (int data = reader.read(); data != -1; data = reader.read()) {
actual.append((char)data);
}

assertThat(actual).hasToString(emoji);
}
Expand Down
4 changes: 4 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public String getAccount() {
return account;
}

public String getPassword() {
return password;
}

@Override
public String toString() {
return "User{" +
Expand Down
36 changes: 14 additions & 22 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import org.apache.coyote.http11.common.Session;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
* particular Container. Different Manager implementations may support
* value-added features such as the persistent storage of session data,
* as well as migrating sessions for distributable web applications.
* A <b>Manager</b> manages the pool of Sessions that are associated with a particular Container. Different Manager
* implementations may support value-added features such as the persistent storage of session data, as well as migrating
* sessions for distributable web applications.
* <p>
* In order for a <code>Manager</code> implementation to successfully operate
* with a <code>Context</code> implementation that implements reloading, it
* must obey the following constraints:
* In order for a <code>Manager</code> implementation to successfully operate with a <code>Context</code> implementation
* that implements reloading, it must obey the following constraints:
* <ul>
* <li>Must implement <code>Lifecycle</code> so that the Context can indicate
* that a restart is required.
Expand All @@ -29,28 +26,23 @@ public interface Manager {
*
* @param session Session to be added
*/
void add(HttpSession session);
void add(Session session);

/**
* Return the active Session, associated with this Manager, with the
* specified session id (if any); otherwise return <code>null</code>.
* Return the active Session, associated with this Manager, with the specified session id (if any); otherwise return
* <code>null</code>.
*
* @param id The session id for the session to be returned
*
* @exception IllegalStateException if a new session cannot be
* instantiated for any reason
* @exception IOException if an input/output error occurs while
* processing this request
*
* @return the request session or {@code null} if a session with the
* requested ID could not be found
* @return the request session or {@code null} if a session with the requested ID could not be found
* @throws IllegalStateException if a new session cannot be instantiated for any reason
* @throws IOException if an input/output error occurs while processing this request
*/
HttpSession findSession(String id) throws IOException;
Session findSession(String id) throws IOException;

/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
*/
void remove(HttpSession session);
void remove(Session session);
}
23 changes: 12 additions & 11 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package org.apache.coyote.http11;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import nextstep.jwp.exception.UncheckedServletException;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.common.request.HttpRequest;
import org.apache.coyote.http11.common.response.HttpResponse;
import org.apache.coyote.http11.handler.Handler;
import org.apache.coyote.http11.handler.HandlerFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.Socket;

public class Http11Processor implements Runnable, Processor {

private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);
Expand All @@ -28,15 +33,11 @@ public void run() {
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {
final var bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
hanueleee marked this conversation as resolved.
Show resolved Hide resolved

final var responseBody = "Hello world!";

final var response = String.join("\r\n",
"HTTP/1.1 200 OK ",
"Content-Type: text/html;charset=utf-8 ",
"Content-Length: " + responseBody.getBytes().length + " ",
"",
responseBody);
HttpRequest request = HttpRequest.create(bufferedReader);
Handler handler = HandlerFinder.find(request);
HttpResponse response = handler.handle(request);

jundonghyuk marked this conversation as resolved.
Show resolved Hide resolved
outputStream.write(response.getBytes());
outputStream.flush();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.apache.coyote.http11.common;

import java.util.Arrays;

public enum ContentType {
TEXT_PLAIN("", "text/plain;charset=utf-8"),
TEXT_HTML(".html", "text/html;charset=utf-8"),
TEXT_CSS(".css", "text/css"),
APPLICATION_JAVASCRIPT(".js", "application/javascript");

private String extension;
private String detail;

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

public String getDetail() {
return detail;
}

public static String getDetailfromExtension(String extension) {
return Arrays.stream(ContentType.values())
.filter(it -> it.extension.equals(extension))
.findFirst()
.map(ContentType::getDetail)
.orElseThrow(() -> new IllegalArgumentException("No enum constant with extension: " + extension));
}
}
25 changes: 25 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/http11/common/Cookies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.apache.coyote.http11.common;

import java.util.Map;
import org.apache.coyote.http11.util.Parser;

public class Cookies {

private Map<String, String> values;

public Cookies(final Map<String, String> values) {
this.values = values;
}

public static Cookies create(String line) {
return Parser.parseToCookie(line);
}

public static Cookies empty() {
return new Cookies(null);
}

public String getCookie(String name) {
return values.get(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.apache.coyote.http11.common;

public enum HttpHeaderName {
CONTENT_TYPE("Content-Type"),
CONTENT_LENGTH("Content-Length"),
LOCATION("Location"),
SET_COOKIE("Set-Cookie");

private String name;

HttpHeaderName(final String name) {
this.name = name;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.apache.coyote.http11.common;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class HttpHeaders {
public static final String DELIMITER = ": ";
public static final String COOKIE = "Cookie";
private Map<String, String> headers;

public HttpHeaders() {
headers = new LinkedHashMap<>();
}

private HttpHeaders(final Map<String, String> headers) {
this.headers = headers;
}

public static HttpHeaders create(final List<String> lines) {
Map<String, String> headers = new LinkedHashMap<>();
for (String line : lines) {
String[] split = line.split(DELIMITER);
headers.put(split[0], split[1]);
}

return new HttpHeaders(headers);
}

public static HttpHeaders create(final Map<String, String> headers) {
return new HttpHeaders(new LinkedHashMap<>(headers));
}

public void addHeader(HttpHeaderName header, String value) {
headers.put(header.getName(), value);
}

public String getHeader(String name) {
return headers.get(name);
}

public Cookies getCookies() {
String cookieLine = headers.get(COOKIE);
return Cookies.create(cookieLine);
}

@Override
public String toString() {
return headers.entrySet().stream()
.map(entry -> entry.getKey() + DELIMITER + entry.getValue() + " ")
.collect(Collectors.joining(System.lineSeparator()));
}
}
Loading