Skip to content

Commit

Permalink
[톰캣 구현하기 1, 2단계] 히이로(문제웅) 미션 제출합니다. (#330)
Browse files Browse the repository at this point in the history
* feat: 컨텐츠 협상을 통해 Content-Type 선택 기능 구현

* test: css 파일 요청에 대한 Content-Type 작성 기능 테스트 구현

* feat: login 페이지 요청 시 쿼리파라미터 파싱 기능 구현

* feat: 잘못된 정보로 login 요청 시 예외 처리 클래스 구현

* feat: login 요청 시 handler 매핑 기능 구현

* test: 학습 테스트 일부 구현

* test: 누락된 윈도우 개행문자 이슈 반영

* chore: 패키지 구조 정상화

* feat: handlerMapper를 통한 loginController 매핑 기능 구현

* feat: Login 관련 컨트롤러 및 서비스 기능 구현

* feat: 로그인 성공 여부에 따른 리다이렉션 기능 구현

* feat: 기존 login 기능에 대한 get, post 매핑 분리

* feat: 회원 가입 후 리다이렉션 기능 구현

* feat: HttpCookie 클래스 구현

* feat: 로그인 성공 시 cookie에 JSESSIONID 발급 기능 구현

* feat: session, sessionManager 기능 구현

* feat: 로그인 성공 시 session에 user객체 등록 기능 구현

* feat: 로그인된 상태로 login 페이지 접근 시 index.html로 리다이렉션 기능 구현

* feat: 운영체제 파일 시스템 차이로 인한 테스트 실패 부분 수정

* refactor: httpRequest 메세지 startLine 파싱 객체 책임 분리

* test: cache 학습 테스트 진행 완료

* style: 상수 논리적 구분을 위한 공백 라인 추가

* style: 직관성 향상을 위한 상수명 개선

* refactor: 접근제어자가 잘못 지정된 상수들을 전부 private으로 수정

* refactor: startLine에서 url 변수명을 path로 변경

* style: 메서드 시그니처 가독성 개선

* refactor: contentType 선택 메서드 명 수정

* refactor: 적절하지 않은 변수명 수정

* refactor: 존재하지 않는 리소스 요청 시 404 status와 페이지를 반환하도록 수정

* refactor: 중복되는 values 파싱 로직 util 클래스로 분리

* refactor: 404페이지 반환 로직 수행 시 NPE 발생위험 Optional 적용
  • Loading branch information
MoonJeWoong authored Sep 7, 2023
1 parent 68db530 commit d712003
Show file tree
Hide file tree
Showing 29 changed files with 973 additions and 154 deletions.
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

0 comments on commit d712003

Please sign in to comment.