Skip to content

Commit

Permalink
[1 - 2 단계 Tomcat 구현하기] 호티(윤주호) 미션 제출합니다. (#563)
Browse files Browse the repository at this point in the history
* test: 학습 테스트 수행

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

* refactor: CSS 지원하기 전 페이지 응답 uri 수정

* refactor: RequestLine 추출 메서드 분리

* docs: Tomcat 구현하기 1단계 기능 명세 정리

* feat: CSS 지원하기

* feat: Query String 파싱

* refactor: Servlet Container 구성

* docs: Tomcat 구현하기 2단계 기능 명세 정리

* feat: HTTP Status Code 302

* feat: POST 방식으로 회원가입

* feat: Cookie에 JSESSIONID 값 저장하기

* feat: Session 구현하기

* test: HTTP 활용하기 학습 테스트

* fix: remove implementation logback-classic on gradle (#501)

(cherry picked from commit fed02f6)

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

(cherry picked from commit 7e91356)

* refactor: http 패키지 분리

* refactor: 메서드 분리

* refactor: 패키지 분리

* refactor: 헤더 추출 로직 가독성 향상 도모

* fix:  페이지 요청 시 화면 깨짐 수정

* refactor: 불필요한 정적 팩토리 메서드 삭제

* refactor: 정적 페이지 호출 메서드 분리

* fix: 이전 쿠키 삭제 후 재발행 로직 추가

* refactor: 정적 리소스 URL 반환 메서드 분리

* refactor: 중복 로직 메서드 분리

---------

Co-authored-by: Gyeongho Yang <[email protected]>
  • Loading branch information
Ho-Tea and geoje authored Sep 11, 2024
1 parent 7e91356 commit d72a631
Show file tree
Hide file tree
Showing 22 changed files with 653 additions and 83 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,43 @@
1. [File, I/O Stream](study/src/test/java/study)
2. [HTTP Cache](study/src/test/java/cache)
3. [Thread](study/src/test/java/thread)

---

## 🚀 1단계 - HTTP 서버 구현하기

### 1. GET /index.html 응답하기
- [x] 인덱스 페이지(http://localhost:8080/index.html)에 접근할 수 있도록 만들자.
- [x] `Http11ProcessorTest` 테스트 클래스의 모든 테스트를 통과해야 한다.

### 2. CSS 지원하기
- [x] 사용자가 페이지를 열었을 때 CSS 파일도 호출하도록 기능을 추가하자.


### 3. Query String 파싱
- [x] http://localhost:8080/login?account=gugu&password=password으로 접속하면 로그인 페이지(login.html)를 보여주도록 만들자.
- [x] 로그인 페이지에 접속했을 때 Query String을 파싱해서 아이디, 비밀번호가 일치하면 콘솔창에 로그로 회원을 조회한 결과가 나오도록 만들자.


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

### 1. HTTP Status Code 302
- [x] 로그인 여부에 따라 다른 페이지로 이동
- [x] 성공하면 응답 헤더에 http status code를 302로 반환하고 /index.html로 리다이렉트 한다.
- [x] 실패하면 401.html로 리다이렉트한다.

### 2. POST 방식으로 회원가입
- [x] http://localhost:8080/register으로 접속하면 회원가입 페이지(register.html)를 보여준다.
- [x] 회원가입 페이지를 보여줄 때는 GET을 사용한다.
- [x] 회원가입을 버튼을 누르면 HTTP method를 GET이 아닌 POST를 사용한다.
- [x] 회원가입을 완료하면 index.html로 리다이렉트한다.
- [x] 로그인 페이지도 버튼을 눌렀을 때 GET 방식에서 POST 방식으로 전송하도록 변경하자.

### 3. Cookie에 JSESSIONID 값 저장하기
- [x] 서버에서 HTTP 응답을 전달할 때 응답 헤더에 Set-Cookie를 추가하고 `JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46` 형태로 값을 전달한다.
- [x] Cookie 클래스를 추가하고 HTTP Request Header의 Cookie에 `JSESSIONID`가 없으면 HTTP Response Header에 Set-Cookie를 반환해주는 기능을 구현한다.

### 4. Session 구현하기
- [x] 쿠키에서 전달 받은 `JSESSIONID`의 값으로 로그인 여부를 체크할 수 있어야 한다.
- [x] 로그인에 성공하면 Session 객체의 값으로 User 객체를 저장해보자.
- [x] 로그인된 상태에서 /login 페이지에 HTTP GET method로 접근하면 이미 로그인한 상태니 index.html 페이지로 리다이렉트 처리한다.
2 changes: 1 addition & 1 deletion study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies {
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'

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.assertj:assertj-core:3.26.0'
testImplementation 'org.mockito:mockito-core:5.12.0'
Expand Down
2 changes: 2 additions & 0 deletions study/src/main/java/cache/com/example/GreetingController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cache.com.example;

import jakarta.servlet.http.HttpServletResponse;

import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cache.com.example.cachecontrol;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class CacheControlInterceptor implements HandlerInterceptor {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
response.setHeader("Cache-Control", "no-cache, private");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cache.com.example.cachecontrol;

import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

Expand All @@ -9,5 +11,8 @@ public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(new CacheControlInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/resources/**");
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
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,10 @@
package cache.com.example.version;

import java.time.Duration;

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 +23,7 @@ public CacheBustingWebConfig(ResourceVersion version) {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
.addResourceLocations("classpath:/static/");
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic()); // Cache-Control: public, max-age=31536000 설정;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import jakarta.annotation.PostConstruct;

@Component
public class ResourceVersion {

Expand Down
3 changes: 3 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ server:
threads:
min-spare: 2
max: 2
compression:
enabled: true
min-response-size: 10
42 changes: 29 additions & 13 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package study;

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

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

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
Expand All @@ -18,7 +23,7 @@ class FileTest {

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

// todo
final String actual = "";
// ClassLoader를 사용해 리소스를 가져옵니다.
URL resource = getClass().getClassLoader().getResource(fileName);

assertThat(actual).endsWith(fileName);
// 리소스가 존재하는지 확인 후 File 객체 생성
final File file = new File(Objects.requireNonNull(resource).getFile());

// 파일의 경로가 올바른지 검증
assertThat(file.getPath()).endsWith(fileName);
}

/**
* 파일 내용 읽기
*
* <p>
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
Expand All @@ -44,11 +53,18 @@ class FileTest {
final String fileName = "nextstep.txt";

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

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

assertThat(actual).containsOnly("nextstep");
try {
expected = Files.readAllLines(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
assertAll(
() -> assertThat(expected).containsOnly("nextstep"),
() -> assertThat(expected.size()).isEqualTo(1)
);
}
}
Loading

0 comments on commit d72a631

Please sign in to comment.