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단계] 제나(위예나) 미션 제출합니다. #355

Merged
merged 35 commits into from
Sep 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8094af4
docs: readme 작성
yenawee Sep 4, 2023
9b07f65
feat: FileTest 학습
yenawee Sep 4, 2023
2879085
feat: IOStreamTest 학습
yenawee Sep 4, 2023
0b1d778
feat: /index.html 응답하기
yenawee Sep 4, 2023
a935129
feat: 웹 요청 시 소켓 close err 로 헤더 처리 추가
yenawee Sep 4, 2023
d872e10
feat: CSS 지원하기
yenawee Sep 4, 2023
6da0d85
feat: QueryString 파싱
yenawee Sep 4, 2023
f44f9fb
feat: 로그인 시 HTTP StatusCode 302
yenawee Sep 4, 2023
3f015b7
feat: POST 방식으로 회원가입
yenawee Sep 4, 2023
2cb2540
feat: Login 도 POST 로 변경
yenawee Sep 4, 2023
2590d48
feat: Cookie 에 JSESSIONID 값 저장하기
yenawee Sep 4, 2023
5fe897e
feat: Session 관리를 위한 객체들 생성
yenawee Sep 4, 2023
54de2b3
feat: Session 구현하기
yenawee Sep 4, 2023
a098957
refactor: session 패키지 변경
yenawee Sep 4, 2023
6760207
refactor: HttpRequest 객체 생성
yenawee Sep 4, 2023
ea5aa94
refactor: HttpResponse 객체 생성
yenawee Sep 4, 2023
79228ee
fix: test 실패 수정
yenawee Sep 4, 2023
d92c8e5
feat: cache 학습 테스트
yenawee Sep 5, 2023
ceb3a9c
feat: Thread 학습 테스트
yenawee Sep 8, 2023
e01ff4f
fix: 회원가입 로직 수정
yenawee Sep 8, 2023
7ae92a8
fix: ContentType enum 추가
yenawee Sep 8, 2023
4cb4253
feat: HttpResponse 추가
yenawee Sep 9, 2023
d11169c
refactor : / 일 때 hello world 반환 리팩터링
yenawee Sep 9, 2023
77aad2e
refactor : /index.html 반환 리팩터링
yenawee Sep 9, 2023
4e53e95
refactor : /login GET 요청 리팩터링
yenawee Sep 9, 2023
47d0f4c
refactor : /login POST 요청 리팩터링
yenawee Sep 9, 2023
00e13b7
refactor : /register 리팩터링
yenawee Sep 9, 2023
e1445aa
refactor : 메서드 순서 변경
yenawee Sep 9, 2023
a2a9e1b
refactor : package 변경
yenawee Sep 9, 2023
0928ec8
refactor : FileUtils 객체 생성
yenawee Sep 9, 2023
096f77c
refactor : java convention
yenawee Sep 9, 2023
14cfde1
test: 로그인 Get 요청 테스트 추가
yenawee Sep 9, 2023
6fe6cd8
feat: 회원가입 예외처리
yenawee Sep 9, 2023
2b0effc
feat: 리뷰 반영
yenawee Sep 9, 2023
20bbf6d
fix: null 반환 오류 수정
yenawee Sep 9, 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: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# 톰캣 구현하기

## CheckList

70825 marked this conversation as resolved.
Show resolved Hide resolved
### 1단계 - HTTP 서버 구현하기

- [x] GET /index.html 응답하기
- [x] CSS 지원하기
- [x] Query String 파싱

### 2단계 - 로그인 구현하기

- [x] HTTP Status Code 302
- [x] POST 방식으로 회원가입
- [x] Login 도 POST 로 변경
- [x] Cookie에 JSESSIONID 값 저장하기
- [x] Session 구현하기
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package cache.com.example.cachecontrol;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.WebContentInterceptor;

@Configuration
public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
CacheControl cacheControl = CacheControl.noCache().cachePrivate();

WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.addCacheMapping(cacheControl, "/**");

registry.addInterceptor(webContentInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package cache.com.example.etag;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;

import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES;

@Configuration
public class EtagFilterConfiguration {

// @Bean
// public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
// return null;
// }
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> filterFilterRegistrationBean =
new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());

filterFilterRegistrationBean.addUrlPatterns("/etag", PREFIX_STATIC_RESOURCES + "/*");
return filterFilterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.Duration;

@Configuration
public class CacheBustingWebConfig implements WebMvcConfigurer {

Expand All @@ -20,6 +23,9 @@ public CacheBustingWebConfig(ResourceVersion version) {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
.addResourceLocations("classpath:/static/");
.setUseLastModified(true)
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic());
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper;

import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES;

@HandlebarsHelper
public class VersionHandlebarsHelper {

Expand All @@ -20,6 +22,6 @@ public VersionHandlebarsHelper(ResourceVersion version) {

public String staticUrls(String path, Options options) {
log.debug("static url : {}", path);
return String.format("/resources/%s%s", version.getVersion(), path);
return String.format(PREFIX_STATIC_RESOURCES + "/%s%s", version.getVersion(), path);
}
}
3 changes: 3 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ server:
max-connections: 1
threads:
max: 2
compression:
enabled: true
min-response-size: 10
16 changes: 9 additions & 7 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

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,26 @@ class FileTest {
final String fileName = "nextstep.txt";

// todo
final String actual = "";
final String actual = Objects.requireNonNull(getClass().getClassLoader().getResource(fileName)).getPath();

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(Objects.requireNonNull(getClass().getClassLoader().getResource(fileName)).getPath());

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
43 changes: 30 additions & 13 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 @@ -54,6 +55,8 @@ class OutputStream_학습_테스트 {
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

outputStream.write(bytes);

final String actual = outputStream.toString();

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

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -96,19 +100,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 +134,8 @@ 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 @@ -149,13 +156,17 @@ class InputStream_학습_테스트 {
* 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 +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,7 +208,7 @@ class InputStreamReader_학습_테스트 {
* 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
*/
@Test
void BufferedReader를_사용하여_문자열을_읽어온다() {
void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException {
final String emoji = String.join("\r\n",
"😀😃😄😁😆😅😂🤣🥲☺️😊",
"😇🙂🙃😉😌😍🥰😘😗😙😚",
Expand All @@ -206,6 +217,12 @@ class InputStreamReader_학습_테스트 {
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());

final StringBuilder actual = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
actual.append(line).append("\r\n");
}
}

assertThat(actual).hasToString(emoji);
}
Expand Down
2 changes: 1 addition & 1 deletion study/src/test/java/thread/stage0/SynchronizationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private static final class SynchronizedMethods {

private int sum = 0;

public void calculate() {
public synchronized void calculate() {
setSum(getSum() + 1);
}

Expand Down
6 changes: 3 additions & 3 deletions study/src/test/java/thread/stage0/ThreadPoolsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ void testNewFixedThreadPool() {
executor.submit(logWithSleep("hello fixed thread pools"));

// 올바른 값으로 바꿔서 테스트를 통과시키자.
final int expectedPoolSize = 0;
final int expectedQueueSize = 0;
final int expectedPoolSize = 2;
final int expectedQueueSize = 1;

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
Expand All @@ -46,7 +46,7 @@ void testNewCachedThreadPool() {
executor.submit(logWithSleep("hello cached thread pools"));

// 올바른 값으로 바꿔서 테스트를 통과시키자.
final int expectedPoolSize = 0;
final int expectedPoolSize = 3;
final int expectedQueueSize = 0;

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.apache.catalina.manager;

import org.apache.coyote.common.Session;

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 void remove(final String id) {
SESSIONS.remove(id);
}
}
Loading