Skip to content

Commit

Permalink
[톰캣 구현하기 1, 2단계] 조이썬(이영수) 미션 제출합니다. (#506)
Browse files Browse the repository at this point in the history
* fix: remove implementation logback-classic on gradle (#501)

* fix: add threads min-spare configuration on properties (#502)

* feat: Http Method 구현

* feat: Http Path 구현

* feat: Http QueryParams 구현

* feat: Http RequestLine 구현

* feat: Http Headers 구현

* feat: Http Request 구현

* feat: 파일 읽는 ResourcesReader 구현

* feat: Http Response 구현

* feat: Http 11 Process 구현

* chore: 충돌 해결

* feat: 헤더 포멧 메시지 내 공백 추가

* 1단계 완료

* refactor: 파일 패키지로 이동

* feat: 정적 메소드로 변경

* feat: 로그인 기능 구현

* feat: 리소스 조회 Executor 구현

* feat: Http Cookie 구현

* feat: Http Body 구현

* refactor: 함수명 변경

* feat: Http Session 구현

* feat: 쿠기 추가하는 기능 구현

* feat: Request 내 Body,Cookies,Session 추가

* feat: Process 내 Session 받아오는 기능 추가

* feat: GET 요청 분리 및 구현

* feat: POST 요청 구현, Redirect 구현

* 2단계 완료

* refactor: 전체적인 코드 검사, 상수화

* refactor: 피드백 반영

* feat: 세션 만료 기능 구현

* feat: 만료된 세션 정리하는 기능 구현

* feat: 프로세서 내 세션 정리 기능 추가

---------

Co-authored-by: Gyeongho Yang <[email protected]>
  • Loading branch information
youngsu5582 and geoje authored Sep 8, 2024
1 parent 0b698a2 commit 187717f
Show file tree
Hide file tree
Showing 63 changed files with 1,590 additions and 95 deletions.
1 change: 0 additions & 1 deletion study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'ch.qos.logback:logback-classic:1.5.7'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1'
Expand Down
1 change: 1 addition & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ server:
accept-count: 1
max-connections: 1
threads:
min-spare: 2
max: 2
37 changes: 26 additions & 11 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
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;
Expand All @@ -18,36 +22,47 @@ class FileTest {

/**
* resource 디렉터리 경로 찾기
*
* <p>
* File 객체를 생성하려면 파일의 경로를 알아야 한다.
* 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다.
* resource 디렉터리의 경로는 어떻게 알아낼 수 있을까?
*/
@Test
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
void resource_디렉터리에_있는_파일의_경로를_찾는다() throws IOException {
final String fileName = "nextstep.txt";

// todo
final String actual = "";
// out resources 경로 찾음
final Path path = Files.walk(Paths.get(""))
.filter(p -> p.getFileName()
.toString()
.equals("resources"))
.findFirst()
.get();

final File file = Paths.get(String.valueOf(path), fileName)
.toFile();

final String actual = file.toPath()
.toString();

assertThat(actual).endsWith(fileName);
}

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

// todo
final Path path = null;
final Path path = Paths.get(getClass().getClassLoader()
.getResource(fileName)
.toURI());

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
94 changes: 55 additions & 39 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
import org.junit.jupiter.api.Test;

import java.io.*;
import java.nio.charset.StandardCharsets;

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

/**
* 자바는 스트림(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 +27,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 +40,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 @@ -49,11 +50,13 @@ class OutputStream_학습_테스트 {
final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112};
final OutputStream outputStream = new ByteArrayOutputStream(bytes.length);

/**
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

// ByteArrayOutputStream 은 synchronized 를 사용해서 write 시 일관성을 보장한다.
// FileOutputStream 은 synchronized 를 사용하지 않는다.

// > 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
// 하나씩 넣는것보다, byte[] 배열로 넣는게 이득이라는 의미.
outputStream.write(bytes);
final String actual = outputStream.toString();

assertThat(actual).isEqualTo("nextstep");
Expand All @@ -63,26 +66,38 @@ class OutputStream_학습_테스트 {
/**
* 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
* BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
*
* <p>
* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
* flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
* Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
* 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다.
*/
@Test
void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException {
final OutputStream outputStream = mock(BufferedOutputStream.class);
final BufferedOutputStream outputStream = mock(BufferedOutputStream.class);

/**
* todo
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
// buffer 는 데이터를 버퍼에 임시 저장 + 조건 만족시 데이터 실제 출력 스트림에 씀
// byteArray 는 데이터를 메모리 내 바이트 배열에 직접 작성

//flush 시, buf 에 있는 값을 out 으로 이동 -> out 에서 Flush
outputStream.flush();
verify(outputStream, atLeastOnce()).flush();
outputStream.close();
}

@Test
void OutputStream_들은_서로_연결_가능하다() throws IOException {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream);

bufferedOutputStream.write("Hello World".getBytes());

// buffer -> byteArray
bufferedOutputStream.flush();

assertThat(byteArrayOutputStream.toString()).hasToString("Hello World");
}

/**
* 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
* 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
Expand All @@ -91,24 +106,23 @@ class OutputStream_학습_테스트 {
void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
final OutputStream outputStream = mock(OutputStream.class);

/**
* todo
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/

//public abstract class OutputStream implements Closeable, Flushable
// Closeable 지원
try (OutputStream outputStream1 = outputStream) {
outputStream1.flush();
}
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 @@ -121,17 +135,15 @@ class InputStream_학습_테스트 {
*/
@Test
void InputStream은_데이터를_바이트로_읽는다() throws IOException {
byte[] bytes = {-16, -97, -92, -87};
final byte[] bytes = {-16, -97, -92, -87};
final InputStream inputStream = new ByteArrayInputStream(bytes);

/**
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final String actual = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);

assertThat(new String(bytes)).isEqualTo("🤩");
assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);

inputStream.close();
}

Expand All @@ -143,19 +155,16 @@ class InputStream_학습_테스트 {
void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
final InputStream inputStream = mock(InputStream.class);

/**
* todo
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try (final InputStream inputStream1 = inputStream) {
}

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

/**
* FilterStream 학습하기
*
* <p>
* 필터는 필터 스트림, reader, writer로 나뉜다.
* 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
* reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.
Expand All @@ -169,12 +178,14 @@ class FilterStream_학습_테스트 {
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@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];
//DEFAULT BUFFER SIZE 는 8192
final InputStream bufferedInputStream = new BufferedInputStream(inputStream);

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

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand All @@ -197,16 +208,21 @@ 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 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final StringBuilder actual = new StringBuilder();

while (bufferedReader.ready()) {
actual.append(bufferedReader.readLine()).append("\r\n");
}

assertThat(actual).hasToString(emoji);
}
}
Expand Down
2 changes: 1 addition & 1 deletion tomcat/src/main/java/com/techcourse/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

public class Application {

public static void main(String[] args) {
public static void main(final String[] args) {
final var tomcat = new Tomcat();
tomcat.start();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public class InMemoryUserRepository {
database.put(user.getAccount(), user);
}

public static void save(User user) {
public static void save(final User user) {
database.put(user.getAccount(), user);
}

public static Optional<User> findByAccount(String account) {
public static Optional<User> findByAccount(final String account) {
return Optional.ofNullable(database.get(account));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class UncheckedServletException extends RuntimeException {

public UncheckedServletException(Exception e) {
public UncheckedServletException(final Exception e) {
super(e);
}
}
23 changes: 23 additions & 0 deletions tomcat/src/main/java/com/techcourse/executor/LoginGetExecutor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.techcourse.executor;

import org.apache.coyote.file.ResourcesReader;
import org.apache.coyote.http11.executor.Executor;
import org.apache.coyote.http11.HttpStatusCode;
import org.apache.coyote.http11.path.Path;
import org.apache.coyote.http11.ResourceToResponseConverter;
import org.apache.coyote.http11.method.HttpMethod;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public class LoginGetExecutor implements Executor {
@Override
public HttpResponse execute(final HttpRequest request) {
return ResourceToResponseConverter.convert(HttpStatusCode.OK, ResourcesReader.read(Path.from("login.html")));
}

@Override
public boolean isMatch(final HttpRequest request) {
return request.getMethod() == HttpMethod.GET && request.getPath()
.equals("/login");
}
}
Loading

0 comments on commit 187717f

Please sign in to comment.