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단계] 히이로(문제웅) 미션 제출합니다. #330

Merged
merged 31 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
21e98c9
feat: 컨텐츠 협상을 통해 Content-Type 선택 기능 구현
MoonJeWoong Sep 2, 2023
ffa9d52
test: css 파일 요청에 대한 Content-Type 작성 기능 테스트 구현
MoonJeWoong Sep 2, 2023
00e7d90
feat: login 페이지 요청 시 쿼리파라미터 파싱 기능 구현
MoonJeWoong Sep 2, 2023
9f47185
feat: 잘못된 정보로 login 요청 시 예외 처리 클래스 구현
MoonJeWoong Sep 3, 2023
3dab630
feat: login 요청 시 handler 매핑 기능 구현
MoonJeWoong Sep 3, 2023
ae2356b
test: 학습 테스트 일부 구현
MoonJeWoong Sep 3, 2023
d2b3169
test: 누락된 윈도우 개행문자 이슈 반영
MoonJeWoong Sep 3, 2023
d679ed3
chore: 패키지 구조 정상화
MoonJeWoong Sep 3, 2023
3096d44
feat: handlerMapper를 통한 loginController 매핑 기능 구현
MoonJeWoong Sep 3, 2023
2ada182
feat: Login 관련 컨트롤러 및 서비스 기능 구현
MoonJeWoong Sep 3, 2023
1739dba
feat: 로그인 성공 여부에 따른 리다이렉션 기능 구현
MoonJeWoong Sep 3, 2023
bd79358
feat: 기존 login 기능에 대한 get, post 매핑 분리
MoonJeWoong Sep 3, 2023
99cb964
feat: 회원 가입 후 리다이렉션 기능 구현
MoonJeWoong Sep 3, 2023
ce1acc1
feat: HttpCookie 클래스 구현
MoonJeWoong Sep 3, 2023
71492a6
feat: 로그인 성공 시 cookie에 JSESSIONID 발급 기능 구현
MoonJeWoong Sep 3, 2023
34ead5a
feat: session, sessionManager 기능 구현
MoonJeWoong Sep 4, 2023
46767eb
feat: 로그인 성공 시 session에 user객체 등록 기능 구현
MoonJeWoong Sep 4, 2023
f03a1a7
feat: 로그인된 상태로 login 페이지 접근 시 index.html로 리다이렉션 기능 구현
MoonJeWoong Sep 4, 2023
6a30330
feat: 운영체제 파일 시스템 차이로 인한 테스트 실패 부분 수정
MoonJeWoong Sep 4, 2023
cb877f6
refactor: httpRequest 메세지 startLine 파싱 객체 책임 분리
MoonJeWoong Sep 5, 2023
d89f258
test: cache 학습 테스트 진행 완료
MoonJeWoong Sep 5, 2023
f2a5b44
style: 상수 논리적 구분을 위한 공백 라인 추가
MoonJeWoong Sep 6, 2023
53ba652
style: 직관성 향상을 위한 상수명 개선
MoonJeWoong Sep 6, 2023
e42d489
refactor: 접근제어자가 잘못 지정된 상수들을 전부 private으로 수정
MoonJeWoong Sep 6, 2023
3bbf659
refactor: startLine에서 url 변수명을 path로 변경
MoonJeWoong Sep 6, 2023
b87e00d
style: 메서드 시그니처 가독성 개선
MoonJeWoong Sep 6, 2023
e879d0f
refactor: contentType 선택 메서드 명 수정
MoonJeWoong Sep 6, 2023
2840ed4
refactor: 적절하지 않은 변수명 수정
MoonJeWoong Sep 6, 2023
301ad15
refactor: 존재하지 않는 리소스 요청 시 404 status와 페이지를 반환하도록 수정
MoonJeWoong Sep 6, 2023
b27ca88
refactor: 중복되는 values 파싱 로직 util 클래스로 분리
MoonJeWoong Sep 6, 2023
3c0a310
refactor: 404페이지 반환 로직 수행 시 NPE 발생위험 Optional 적용
MoonJeWoong 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cache.com.example.cachecontrol;

import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.HandlerInterceptor;

public class CacheInterceptor implements HandlerInterceptor {

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
if (Objects.isNull(response.getHeader("Cache-Control"))) {
response.addHeader("Cache-Control",
CacheControl.noCache().cachePrivate().getHeaderValue());
}
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(new CacheInterceptor());
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
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;

@Configuration
public class EtagFilterConfiguration {

// @Bean
// public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
// return null;
// }
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean =
new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/etag", "/resources/*");
return filterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cache.com.example.version;

import java.util.concurrent.TimeUnit;
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;

Expand All @@ -20,6 +22,12 @@ public CacheBustingWebConfig(ResourceVersion version) {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
.addResourceLocations("classpath:/static/");
.addResourceLocations("classpath:/static/")
// ETag를 사용하니까 Last-Modified는 불필요하지 않을까?
// Last-Modified를 추가하면 캐싱 외에도 크롤러에게 마지막 수정 시간을 알려주어 크롤링 빈도를 조정할 수 있다.
// 자세한 내용은 아래 링크를 참고하자.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#sect1
.setUseLastModified(true)
.setCacheControl(CacheControl.maxAge(365L, TimeUnit.DAYS).cachePublic());
}
}
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
2 changes: 1 addition & 1 deletion study/src/main/resources/static/js/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
console.log('hello world');
console.log('hello world');
54 changes: 31 additions & 23 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,54 +1,62 @@
package study;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.net.URL;
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;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
* File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
*/
@DisplayName("File 클래스 학습 테스트")
class FileTest {

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

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

//then
assertThat(actual).endsWith(fileName);
}

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

// todo
final Path path = null;

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

URL resource = getClass().getClassLoader().getResource(fileName);
Path path = Paths.get(resource.getPath().substring(1));

//when
final List<String> actual;
try {
actual = Files.readAllLines(path);
} catch (IOException e) {
throw new RuntimeException(e);
}

//then
assertThat(actual).containsOnly("nextstep");
}
}
124 changes: 67 additions & 57 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
package study;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

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 java.util.Objects;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.io.*;

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

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

/**
* OutputStream 학습하기
*
* 자바의 기본 출력 클래스는 java.io.OutputStream이다.
* OutputStream의 write(int b) 메서드는 기반 메서드이다.
* <p>
* 자바의 기본 출력 클래스는 java.io.OutputStream이다. OutputStream의 write(int b) 메서드는 기반 메서드이다.
* <code>public abstract void write(int b) throws IOException;</code>
*/
@Nested
class OutputStream_학습_테스트 {

/**
* OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다.
* OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.
* 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
* 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.
*
* 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 @@ -61,13 +66,11 @@ class OutputStream_학습_테스트 {
}

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

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

/**
* InputStream 학습하기
*
* 자바의 기본 입력 클래스는 java.io.InputStream이다.
* InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
* InputStream의 read() 메서드는 기반 메서드이다.
* <p>
* 자바의 기본 입력 클래스는 java.io.InputStream이다. InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다. InputStream의
* read() 메서드는 기반 메서드이다.
* <code>public abstract int read() throws IOException;</code>
*
* <p>
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
*/
@Nested
class InputStream_학습_테스트 {

/**
* read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다.
* int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다.
* 그리고 Stream 끝에 도달하면 -1을 반환한다.
* read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다. int 값을 byte 타입으로 변환하면 -128부터
* 127 사이의 값으로 변환된다. 그리고 Stream 끝에 도달하면 -1을 반환한다.
*/
@Test
void InputStream_데이터를_바이트로_읽는다() throws IOException {
Expand All @@ -128,16 +129,30 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
StringBuilder sb = new StringBuilder();

try (
InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader)
) {
String readLine = "";
while (Objects.nonNull(readLine = bufferedReader.readLine())) {
sb.append(readLine);
}
}

String actual = sb.toString();

// String actual = new String(inputStream.readAllBytes(), "UTF-8");

assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
inputStream.close();
}

/**
* 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
* 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
* 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가
* 발생한다.
*/
@Test
void InputStream_사용하고_나서_close_처리를_해준다() throws IOException {
Expand All @@ -155,18 +170,16 @@ class InputStream_학습_테스트 {

/**
* FilterStream 학습하기
*
* 필터는 필터 스트림, reader, writer로 나뉜다.
* 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
* reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.
* <p>
* 필터는 필터 스트림, reader, writer로 나뉜다. 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다. reader, writer는 UTF-8, ISO
* 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.
*/
@Nested
class FilterStream_학습_테스트 {

/**
* BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다.
* InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다.
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
* BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다. InputStream 객체를 생성하고 필터 생성자에 전달하면
* 필터에 연결된다. 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@Test
void 필터인_BufferedInputStream_사용해보자() {
Expand All @@ -182,19 +195,16 @@ class FilterStream_학습_테스트 {
}

/**
* 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다.
* 문자열이 아닌 바이트 단위로 처리하려니 불편하다.
* 그리고 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다.
* reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다.
* 그리고 InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다.
* 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. 문자열이 아닌 바이트 단위로 처리하려니 불편하다. 그리고 바이트를 문자(char)로 처리하려면 인코딩을
* 신경 써야 한다. reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. 그리고 InputStreamReader를
* 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다.
*/
@Nested
class InputStreamReader_학습_테스트 {

/**
* InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다.
* 읽어온 문자(char)를 문자열(String)로 처리하자.
* 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
* InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다. 읽어온 문자(char)를 문자열(String)로 처리하자. 필터인
* BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
*/
@Test
void BufferedReader_사용하여_문자열을_읽어온다() {
Expand Down
2 changes: 1 addition & 1 deletion tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package nextstep;

import org.apache.catalina.startup.Tomcat;
import nextstep.org.apache.catalina.startup.Tomcat;

public class Application {

Expand Down
Loading