Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
649a603
test: 학습 테스트 수행
Ho-Tea Sep 3, 2024
8dc6457
feat: GET /index.html 응답하기
Ho-Tea Sep 3, 2024
54b285c
refactor: CSS 지원하기 전 페이지 응답 uri 수정
Ho-Tea Sep 3, 2024
877bdb5
refactor: RequestLine 추출 메서드 분리
Ho-Tea Sep 4, 2024
e52df7d
docs: Tomcat 구현하기 1단계 기능 명세 정리
Ho-Tea Sep 4, 2024
b7b0e30
feat: CSS 지원하기
Ho-Tea Sep 4, 2024
ae46265
feat: Query String 파싱
Ho-Tea Sep 5, 2024
10738d0
refactor: Servlet Container 구성
Ho-Tea Sep 5, 2024
5defcc2
docs: Tomcat 구현하기 2단계 기능 명세 정리
Ho-Tea Sep 5, 2024
bdcbc1e
feat: HTTP Status Code 302
Ho-Tea Sep 5, 2024
265bff3
feat: POST 방식으로 회원가입
Ho-Tea Sep 5, 2024
cac6364
feat: Cookie에 JSESSIONID 값 저장하기
Ho-Tea Sep 6, 2024
d500656
feat: Session 구현하기
Ho-Tea Sep 6, 2024
452b9d1
test: HTTP 활용하기 학습 테스트
Ho-Tea Sep 6, 2024
9606571
fix: remove implementation logback-classic on gradle (#501)
geoje Sep 5, 2024
7806662
fix: add threads min-spare configuration on properties (#502)
geoje Sep 5, 2024
a9d964f
Merge branch 'ho-tea' into step1
Ho-Tea Sep 6, 2024
7131bd5
refactor: http 패키지 분리
Ho-Tea Sep 7, 2024
1e92216
refactor: 메서드 분리
Ho-Tea Sep 7, 2024
f0ee1e3
refactor: 패키지 분리
Ho-Tea Sep 10, 2024
7472550
refactor: 헤더 추출 로직 가독성 향상 도모
Ho-Tea Sep 10, 2024
d81f691
fix: 페이지 요청 시 화면 깨짐 수정
Ho-Tea Sep 10, 2024
af465a5
refactor: 불필요한 정적 팩토리 메서드 삭제
Ho-Tea Sep 10, 2024
5d604e2
refactor: 정적 페이지 호출 메서드 분리
Ho-Tea Sep 10, 2024
f1f6d6c
fix: 이전 쿠키 삭제 후 재발행 로직 추가
Ho-Tea Sep 10, 2024
3df5686
refactor: 정적 리소스 URL 반환 메서드 분리
Ho-Tea Sep 10, 2024
331ff15
refactor: 중복 로직 메서드 분리
Ho-Tea Sep 10, 2024
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
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 페이지로 리다이렉트 처리한다.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 기능 잘 동작하고 있나요?
로그인 하고 /login 에 GET으로 요청하니 login 페이지가 그대로 보이는 것 같네요 ㅠㅠ

Copy link
Member Author

@Ho-Tea Ho-Tea Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

실제로 테스트할 때 항상 쿠키를 모두 삭제하고 실행했기에 (쿠키가 없는 상태에서의 접근만 생각)
애플리케이션을 재실행 했을 시 전에 저장되어있는 쿠키가 있어 발생하는 에러였습니다ㅠ
수정하였습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 기능과 더불어 페이지 로딩 제대로 되는 부분까지 확인 되었습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

페이지 로딩은 ready() 메서드 때문이었군요
메서드를 부를 때 도착해있는 게 없으면 false가 뜨고 if문 안의 로직을 수행시키지 못하는 것으로 이해했는데 맞나요?!

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