Skip to content

Commit 94761d0

Browse files
authored
[톰캣 구현하기 - 3, 4단계] 준팍(박준현) 미션 제출합니다. (#407)
* test: IOStreamTest 완료 * test: FileTest 완료 * test: FileTest path에서 getResource로 방법 변경 * docs : 요구사항 정리 * feat: GET /index.html 응답 기능 구현 * feat: css 지원 기능 구현 * refactor: 클래스 분리 * feat: QueryString 파싱 기능 구현 * refactor: FileExtension 클래스 분리 * refactor: HttpStatus 클래스 분리 * refactor: RequestHandler 클래스 분리 * refactor: RequestHandler Path 로직 수정 * feat: favicon 추가 * feat: HTTP 302 기능 구현 * refactor: 개행 문자 lineSeparator() * refactor: 클래스 분리 * feat: Post method 기능 구현 * feat: 회원 가입 후 리다이렉트 완료 * refacotr: lineSeperator() "\r\n"로 수정 * test: cache 학습테스트 완료 * test: cache 학습테스트 완료 * refactor: 피드백 반영 * refactor: QueryStrings 일급 컬렉션 구현 * feat: HttpCookie 구현 * refactor: 네이밍 변경 * chore: 패키지 분리 * feat: RequestHeaders에 HttpCookie 추가 * refactor: 메서드 분리 * refactor: HandlerMapping 상수화 * feat: 쿠키 값을 스트링으로 반환하는 메서드 구현 * refactor: 메서드 이동 * refactor: 클래스별 역할과 책임 재분배 * refactor: Handler 추가 * feat: Cookie에 JSESSIONID 값 저장 기능 구현 * feat: Session 및 SessionManager 객체 구현 * feat: Session 기능 구현 완료 * feat: User ID AutoIncrement 기능 구현 * fix: 로그인 실패시 401.html 리다이렉트 * refactor: SessionManager 싱글턴 적용 * fix: 401 리다이렉트 오류 수정 * refactor: SessionManager Enum Singleton 적용 * chore: 코드 커버리지 확인을 위한 build.yml 수정 * refactor: 네이밍 변경, 싱글턴 적용, collection 변경 * fix: GET 요청 시 Login 및 Register 기능 삭제 * refactor: 불필요 메서드 삭제 * refactor: statusCode 타입 변경 * refactor: 불필요 객체 삭제 * refactor: ResponseEntity 빌더패턴 적용 * feat: 동시성 확장하기 * test: 학습테스트 Thread 학습 테스트 완료 * feat: Connector Thread pool 적용 * chore: 코드 커버리지 체크 제거 * refactor: ResponseEntity 제거 및 Controller 인터페이스 service 메서드 void 적용, 싱글턴 제거 * feat: HttpResponseParser 객체 구현 * refactor: 코드 스멜 제거 * refactor: 피드백 반영 * refactor: 피드백 반영 * fix: 파일을 찾을 수 없을 시 NotFound 연결
1 parent 3453b3d commit 94761d0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+667
-624
lines changed

README.md

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
11
# 톰캣 구현하기
22

3-
## 1단계 - HTTP 서버 구현하기
4-
- [x] GET /index.html 응답하기
5-
- [x] CSS 지원하기
6-
- [x] Query String 파싱
3+
## 3단계 - 리팩터링
4+
- [x] HttpRequest 클래스 구현하기
5+
- [x] HttpResponse 클래스 구현하기
6+
- [x] Controller 인터페이스 추가하기
7+
- [x] AbstractController를 상속한 구현체 만들기
78

8-
## 2단계 - 로그인 구현하기
9-
- [x] HTTP Status Code 302
10-
- [x] 로그인 성공 시, 응답 헤더에 http status code를 302로 반환하고 `/index.html`로 리다이렉트
11-
- [x] 로그인 실패 시, `401.html`로 리다이렉트
12-
- [x] POST 방식으로 회원가입
13-
- [x] `http://localhost:8080/register` 으로 접속하면 회원가입 페이지(`register.html`)를 보여준다.
14-
- [x] 회원가입 페이지를 보여줄 때는 GET을 사용한다.
15-
- [x] 회원가입을 버튼을 누르면 HTTP method를 GET이 아닌 POST를 사용한다.
16-
- [x] 회원가입을 완료하면 `index.html`로 리다이렉트한다.
17-
- [x] 로그인 페이지도 버튼을 눌렀을 때 GET 방식에서 POST 방식으로 전송하도록 변경하자.
18-
- [x] Cookie에 JSESSIONID 값 저장하기
19-
- [x] 서버에서 HTTP 응답을 전달할 때 응답 헤더에 `Set-Cookie`를 추가하고 `JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46` 형태로 값을 전달하면
20-
클라이언트 요청 헤더의 `Cookie` 필드에 값이 추가된다.
21-
- [x] Cookie 클래스를 추가하고 HTTP Request Header의 Cookie에 `JSESSIONID`가 없으면 HTTP Response Header에 `Set-Cookie`를 반환해주는 기능을
22-
구현한다.
23-
- [x] Session 구현하기
24-
- [x] 쿠키에서 전달 받은 JSESSIONID의 값으로 로그인 여부를 체크할 수 있어야 한다.
25-
- [x] 로그인에 성공하면 Session 객체의 값으로 User 객체를 저장해보자.
26-
- [x] 로그인 상태에서 `/login` 페이지에 HTTP GET method로 접근하면 `index.html` 페이지로 리다이렉트 처리한다
9+
## 4단계 - 동시성 확장하기
10+
- [x] Executors로 Thread Pool 적용하기
11+
- [x] Connector 클래스에서 Executors 클래스를 사용해서 ExecutorService 객체 생성하기
12+
- [x] maxThreads라는 변수로 스레드 갯수 지정하기
13+
- [x] 동시성 컬렉션 사용하기
14+
- [x] SessionManager 클래스에서 Session 컬렉션에 동시성 컬렉션 적용하기
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
package cache.com.example.cachecontrol;
22

33
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.http.CacheControl;
45
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
56
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7+
import org.springframework.web.servlet.mvc.WebContentInterceptor;
68

79
@Configuration
810
public class CacheWebConfig implements WebMvcConfigurer {
911

1012
@Override
1113
public void addInterceptors(final InterceptorRegistry registry) {
14+
CacheControl cacheControl = CacheControl
15+
.noCache()
16+
.cachePrivate();
17+
18+
WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
19+
webContentInterceptor.addCacheMapping(cacheControl, "/");
20+
21+
registry.addInterceptor(webContentInterceptor);
1222
}
1323
}
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
package cache.com.example.etag;
22

3+
import cache.com.example.version.CacheBustingWebConfig;
4+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
5+
import org.springframework.context.annotation.Bean;
36
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.web.filter.ShallowEtagHeaderFilter;
48

59
@Configuration
610
public class EtagFilterConfiguration {
711

8-
// @Bean
9-
// public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
10-
// return null;
11-
// }
12+
@Bean
13+
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
14+
FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<>(
15+
new ShallowEtagHeaderFilter());
16+
filterRegistrationBean.addUrlPatterns(
17+
"/etag",
18+
CacheBustingWebConfig.PREFIX_STATIC_RESOURCES + "/*"
19+
);
20+
21+
return filterRegistrationBean;
22+
}
1223
}

study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package cache.com.example.version;
22

3+
import java.time.Duration;
34
import org.springframework.beans.factory.annotation.Autowired;
45
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.http.CacheControl;
57
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
68
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
79

@@ -20,6 +22,13 @@ public CacheBustingWebConfig(ResourceVersion version) {
2022
@Override
2123
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
2224
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
23-
.addResourceLocations("classpath:/static/");
25+
.addResourceLocations("classpath:/static/")
26+
.setUseLastModified(true)
27+
.setCacheControl(
28+
CacheControl.maxAge(Duration.ofDays(365))
29+
.cachePublic()
30+
)
31+
;
2432
}
33+
2534
}

study/src/main/resources/application.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ handlebars:
33

44
server:
55
tomcat:
6-
accept-count: 1
7-
max-connections: 1
6+
accept-count: 8 # 연결된 요청이 max-connections에 도달했을 경우, 추가로 연결 대기할 수 있는 요청의 갯수
7+
max-connections: 2 # 서버가 동시에 처리할 수 있는 클라이언트 연결의 최대 개수,
8+
#max-connections이 thread의 최대 갯수보다 많으면 추가 연결이 처리 대기열에 배치된다.
89
threads:
9-
max: 2
10+
max: 2 # 요청을 처리하는 Thread의 최대 갯수, AppTest의 count.intValue()는 해당 값을 의미한다.
11+
compression:
12+
enabled: true
13+
min-response-size: 10

study/src/test/java/thread/stage0/SynchronizationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ private static final class SynchronizedMethods {
4141

4242
private int sum = 0;
4343

44-
public void calculate() {
44+
public synchronized void calculate() {
4545
setSum(getSum() + 1);
4646
}
4747

study/src/test/java/thread/stage0/ThreadPoolsTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ void testNewFixedThreadPool() {
3131
executor.submit(logWithSleep("hello fixed thread pools"));
3232

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

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

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

5252
assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());

study/src/test/java/thread/stage0/ThreadTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ void testExtendedThread() throws InterruptedException {
3030
Thread thread = new ExtendedThread("hello thread");
3131

3232
// 생성한 thread 객체를 시작한다.
33-
thread.start();
33+
thread.start();
3434

3535
// thread의 작업이 완료될 때까지 기다린다.
36-
thread.join();
36+
thread.join();
3737
}
3838

3939
/**
@@ -46,10 +46,10 @@ void testRunnableThread() throws InterruptedException {
4646
Thread thread = new Thread(new RunnableThread("hello thread"));
4747

4848
// 생성한 thread 객체를 시작한다.
49-
thread.start();
49+
thread.start();
5050

5151
// thread의 작업이 완료될 때까지 기다린다.
52-
thread.join();
52+
thread.join();
5353
}
5454

5555
private static final class ExtendedThread extends Thread {

study/src/test/java/thread/stage2/AppTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,30 @@ class AppTest {
2424
@Test
2525
void test() throws Exception {
2626
final var NUMBER_OF_THREAD = 10;
27-
var threads = new Thread[NUMBER_OF_THREAD];
2827

28+
// Thread 객체 배열 생성 및 10개의 Thread 객체 생성하여 배열에 할당한다.
29+
var threads = new Thread[NUMBER_OF_THREAD];
2930
for (int i = 0; i < NUMBER_OF_THREAD; i++) {
3031
threads[i] = new Thread(() -> incrementIfOk(TestHttpUtils.send("/test")));
3132
}
3233

34+
// Thread 객체의 start() 메서드를 호출하면, Thread를 생성하고, 해당 스레드에서 run()메서드를 실행한다.
3335
for (final var thread : threads) {
3436
thread.start();
3537
Thread.sleep(50);
3638
}
3739

40+
// join() 메서드를 호출한 스레드가 join() 메서드를 실행한 스레드의 종료를 기다린다.
3841
for (final var thread : threads) {
3942
thread.join();
4043
}
4144

45+
// count.intValue()는 실제 생성된 스레드의 갯수이다.
4246
assertThat(count.intValue()).isEqualTo(2);
4347
}
4448

4549
private static void incrementIfOk(final HttpResponse<String> response) {
50+
// 요청이 200일때 AtomicInteger의 수량 증가
4651
if (response.statusCode() == 200) {
4752
count.incrementAndGet();
4853
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package nextstep.jwp.controller;
2+
3+
import org.apache.coyote.http11.common.HttpStatus;
4+
import org.apache.coyote.http11.controller.Controller;
5+
import org.apache.coyote.http11.request.HttpRequest;
6+
import org.apache.coyote.http11.response.HttpResponse;
7+
8+
public class DefaultController implements Controller {
9+
10+
@Override
11+
public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
12+
httpResponse.setHttpStatus(HttpStatus.OK)
13+
.setPath("/default");
14+
}
15+
16+
}

0 commit comments

Comments
 (0)