Skip to content

Commit

Permalink
[톰캣 구현하기 1 & 2단계] 오션(김동해) 미션 제출합니다 (#347)
Browse files Browse the repository at this point in the history
* test: File 경로 찾기 및 파일 내용 읽기 학습 테스트

* test: I/O Stream 학습 테스트

* feat: GET /index.html 응답하기 추가

* feat: CSS 지원 기능 추가

* docs: 요구사항 명세서 추가

* feat: 파비콘 파일 추가

* build: junit 추가

* feat: 쿼리 스트링 파싱 추가

* refactor: 1단계 코드 리팩토링

* docs: 2단계 요구사항 추가

* feat: HTTP Status Code 302 기능 추가

* feat: Post 방식 회원가입 및 Session 기능 추가

* refactor: 쿠키 파싱 로직 수정

* refactor: 절대 경로 조회 로직 수정

* refactor: 개행 수정

* refactor: BufferedReader HttpRequest 로직 수정

* refactor: 존재하지 않는 리소스 404.html 리다이렉트

* refactor: 로그인 세션 생성 로직 수정

* refactor: 코드 리팩토링

* refactor: findSession Optional 반환
  • Loading branch information
e-astsea authored Sep 8, 2023
1 parent 68db530 commit da04259
Show file tree
Hide file tree
Showing 31 changed files with 1,140 additions and 53 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# 톰캣 구현하기

1단계 HTTP 서버 구현하기
-[x] HTTP 서버 구현하기
- [x] GET /index.html 응답하기
- [x] CSS 지원하기
- [x] Query String 파싱

2단계 로그인 구현하기
- [x] HTTP Status Code 302
- [x] 로그인 성공시 302반환 및 /index.html로 리다이렉트
- [x] 로그인 실패 시 401.html로 리다이렉트
- [x] POST 방식으로 회원가입
- [x] 회원가입 완료 시 index.html로 리다이렉트
- [x] 로그인 페이지도 POST 방식
- [x] Cookie에 JSESSIONID 값 저장
- [x] Session 구현
17 changes: 10 additions & 7 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -18,7 +20,7 @@ class FileTest {

/**
* resource 디렉터리 경로 찾기
*
* <p>
* File 객체를 생성하려면 파일의 경로를 알아야 한다.
* 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다.
* resource 디렉터리의 경로는 어떻게 알아낼 수 있을까?
Expand All @@ -28,26 +30,27 @@ class FileTest {
final String fileName = "nextstep.txt";

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

assertThat(actual).endsWith(fileName);
}

/**
* 파일 내용 읽기
*
* <p>
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
void 파일의_내용을_읽는다() {
void 파일의_내용을_읽는다() throws IOException {
final String fileName = "nextstep.txt";

// todo
final Path path = null;
final Path path = Path.of(getClass().getClassLoader().getResource(fileName).getPath());

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
59 changes: 43 additions & 16 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.io.*;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

/**
* 자바는 스트림(Stream)으로부터 I/O를 사용한다.
* 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
*
* <p>
* InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다.
* FilterStream은 InputStream이나 OutputStream에 연결될 수 있다.
* FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환)
*
* <p>
* Stream은 데이터를 바이트로 읽고 쓴다.
* 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다.
* Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다.
Expand All @@ -26,7 +37,7 @@ class IOStreamTest {

/**
* OutputStream 학습하기
*
* <p>
* 자바의 기본 출력 클래스는 java.io.OutputStream이다.
* OutputStream의 write(int b) 메서드는 기반 메서드이다.
* <code>public abstract void write(int b) throws IOException;</code>
Expand All @@ -39,7 +50,7 @@ class OutputStream_학습_테스트 {
* OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.
* 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
* 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.
*
* <p>
* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다.
* <code>write(byte[] data)</code>와 <code>write(byte b[], int off, int len)</code> 메서드는
* 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
Expand All @@ -53,6 +64,7 @@ class OutputStream_학습_테스트 {
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
outputStream.write(bytes);

final String actual = outputStream.toString();

Expand All @@ -63,7 +75,7 @@ class OutputStream_학습_테스트 {
/**
* 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
* BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
*
* <p>
* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
* flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
* Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
Expand All @@ -78,6 +90,7 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
outputStream.flush();

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

verify(outputStream, atLeastOnce()).close();
}
}

/**
* InputStream 학습하기
*
* <p>
* 자바의 기본 입력 클래스는 java.io.InputStream이다.
* InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
* InputStream의 read() 메서드는 기반 메서드이다.
* <code>public abstract int read() throws IOException;</code>
*
* <p>
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
*/
@Nested
Expand All @@ -128,7 +143,7 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final String actual = new String(inputStream.readAllBytes());

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

verify(inputStream, atLeastOnce()).close();
}
}

/**
* FilterStream 학습하기
*
* <p>
* 필터는 필터 스트림, reader, writer로 나뉜다.
* 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
* reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.
Expand All @@ -169,12 +186,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 Down Expand Up @@ -203,9 +220,19 @@ class InputStreamReader_학습_테스트 {
"😇🙂🙃😉😌😍🥰😘😗😙😚",
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());

final StringBuilder actual = new StringBuilder();
try (
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
) {
String line;
while ((line = bufferedReader.readLine()) != null) {
actual.append(line).append("\r\n");
}
} catch (IOException e){

}

assertThat(actual).hasToString(emoji);
}
Expand Down
2 changes: 1 addition & 1 deletion tomcat/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ dependencies {

testImplementation "org.assertj:assertj-core:3.24.2"
testImplementation "org.mockito:mockito-core:5.4.0"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.2"
testImplementation "org.junit.jupiter:junit-jupiter:5.7.2"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.2"
}
21 changes: 10 additions & 11 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;
import org.apache.catalina.session.Session;

import java.io.IOException;
import java.util.Optional;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
Expand All @@ -29,28 +30,26 @@ 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>.
*
* @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
* 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;
Optional<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);
}
26 changes: 26 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/session/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.apache.catalina.session;

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

public class Session {
private final String id;
private final Map<String, Object> sessions;

public Session(final String id) {
this.id = id;
this.sessions = new HashMap<>();
}

public String getId() {
return id;
}

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

public void setAttribute(final String name, final Object value){
sessions.put(name,value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.apache.catalina.session;

import org.apache.catalina.Manager;

import javax.swing.text.html.Option;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class SessionManager implements Manager {

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

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

@Override
public Optional<Session> findSession(final String id) {
return Optional.ofNullable(SESSIONS.get(id));
}

@Override
public void remove(final Session session) {
SESSIONS.remove(session.getId());
}
}
Loading

0 comments on commit da04259

Please sign in to comment.