Skip to content

Commit b381530

Browse files
authored
[톰캣 구현하기 3, 4단계] 블랙캣(송우석) 미션 제출합니다. (#477)
* test: 쓰레드 및 톰캣 설정 테스트 코드 작성 * refactor: AbstractController 추상클래스, Controller 인터페이스 적용 * feat: 최대 요청 처리 가능 개수 250개 및 요청 대기 수 100개 설정 * refactor: 멀티 쓰레드를 고려한 ConcurrentHashMap 적용 * docs: 변경 사항 반영 * refactor: 메서드 분리 * refactor: 에러 코드 - 메시지 순서 변경 * feat: 테스트 분리 및 서블릿 매핑 테스트 추가 * feat: 500 에러 페이지 반환 기능 구현 * refactor: 지원하는 컨트롤러가 없을 시 404 페이지 반환 * refactor: 코드 정렬 * refactor: 사용하지 않는 코드 삭제 * fix: Request에 Content-Length 가 0일 때 body 파싱시 NPE 문제 해결 * feat: 지원하지 않는 메서드에 대해서 405 에러 반환 구현 * refactor: code smell 제거 * refactor: code smell 제거
1 parent 4fbcb0a commit b381530

Some content is hidden

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

46 files changed

+1014
-767
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
- `POST /login`
1515
- 요청 바디: `account`, `password`
1616
- 로그인 성공 이후 `/index.html` 리다이렉트
17-
- 로그인 실패 이후 `/401.html` 리다이렉트
17+
- 로그인 실패 이후 `401 Unauthorized`-`/401.html` 반환
1818

1919
### ✔ 회원가입 요청 페이지
2020
- `GET /register`
2121

2222
### ✔ 회원가입 요청
2323
- `POST /register`
2424
- 요청 바디: `account`, `email`, `password`
25-
- 회원 가입 성공 이후 `/index.html`
26-
- 아이디 중복인 경우 `400 Bad Request` 반환
25+
- 회원 가입 성공 이후 `/login` 리다이렉트
26+
- 아이디 중복인 경우 `400 Bad Request` - `/400.html` 반환
2727

2828
### ✔ 존재하지 않는 경로
2929
- `404.html` 반환

study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package cache.com.example.cachecontrol;
22

3+
import java.io.File;
34
import org.springframework.context.annotation.Configuration;
45
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
56
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -9,5 +10,6 @@ public class CacheWebConfig implements WebMvcConfigurer {
910

1011
@Override
1112
public void addInterceptors(final InterceptorRegistry registry) {
13+
registry.addInterceptor(new NoCacheInterceptor());
1214
}
1315
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cache.com.example.cachecontrol;
2+
3+
import javax.servlet.http.HttpServletRequest;
4+
import javax.servlet.http.HttpServletResponse;
5+
import org.springframework.http.HttpHeaders;
6+
import org.springframework.web.servlet.HandlerInterceptor;
7+
import org.springframework.web.servlet.ModelAndView;
8+
9+
public class NoCacheInterceptor implements HandlerInterceptor {
10+
11+
@Override
12+
public void postHandle(
13+
final HttpServletRequest request,
14+
final HttpServletResponse response,
15+
final Object handler,
16+
final ModelAndView modelAndView
17+
) throws Exception {
18+
19+
response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache, private");
20+
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
21+
}
22+
}

study/src/main/resources/application.yml

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

44
server:
55
tomcat:
6-
accept-count: 1
7-
max-connections: 1
6+
accept-count: 0
7+
max-connections: 2
88
threads:
99
max: 2

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: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,61 @@
11
package thread.stage0;
22

3-
import org.junit.jupiter.api.Test;
4-
import org.slf4j.Logger;
5-
import org.slf4j.LoggerFactory;
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.SoftAssertions.assertSoftly;
65

76
import java.util.concurrent.Executors;
87
import java.util.concurrent.ThreadPoolExecutor;
9-
10-
import static org.assertj.core.api.Assertions.assertThat;
8+
import org.junit.jupiter.api.Test;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
1111

1212
/**
13-
* 스레드 풀은 무엇이고 어떻게 동작할까?
14-
* 테스트를 통과시키고 왜 해당 결과가 나왔는지 생각해보자.
15-
*
16-
* Thread Pools
17-
* https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
18-
*
19-
* Introduction to Thread Pools in Java
20-
* https://www.baeldung.com/thread-pool-java-and-guava
13+
* 스레드 풀은 무엇이고 어떻게 동작할까? 테스트를 통과시키고 왜 해당 결과가 나왔는지 생각해보자.
14+
* <p>
15+
* Thread Pools https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
16+
* <p>
17+
* Introduction to Thread Pools in Java https://www.baeldung.com/thread-pool-java-and-guava
2118
*/
2219
class ThreadPoolsTest {
2320

2421
private static final Logger log = LoggerFactory.getLogger(ThreadPoolsTest.class);
2522

2623
@Test
2724
void testNewFixedThreadPool() {
25+
// 코어 쓰레드와 최대 쓰레드가 2이다.
2826
final var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
2927
executor.submit(logWithSleep("hello fixed thread pools"));
3028
executor.submit(logWithSleep("hello fixed thread pools"));
3129
executor.submit(logWithSleep("hello fixed thread pools"));
30+
executor.submit(logWithSleep("hello fixed thread pools"));
3231

3332
// 올바른 값으로 바꿔서 테스트를 통과시키자.
34-
final int expectedPoolSize = 0;
35-
final int expectedQueueSize = 0;
33+
final int expectedPoolSize = 2;
34+
// 2개의 작업이 쓰레드에 할당되어서 실행되고 있기 때문에 남은 4-2
35+
final int expectedQueueSize = 2;
3636

37-
assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
38-
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
37+
assertSoftly(softly -> {
38+
softly.assertThat(executor.getPoolSize()).isEqualTo(expectedPoolSize);
39+
softly.assertThat(executor.getQueue().size()).isEqualTo(expectedQueueSize);
40+
});
3941
}
4042

4143
@Test
4244
void testNewCachedThreadPool() {
45+
// 코어 쓰레드와 최대 쓰레드가 최대 int 값 까지이다.
4346
final var executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
4447
executor.submit(logWithSleep("hello cached thread pools"));
4548
executor.submit(logWithSleep("hello cached thread pools"));
4649
executor.submit(logWithSleep("hello cached thread pools"));
4750

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

52-
assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
53-
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
55+
assertSoftly(softly -> {
56+
softly.assertThat(executor.getPoolSize()).isEqualTo(expectedPoolSize);
57+
softly.assertThat(executor.getQueue().size()).isEqualTo(expectedQueueSize);
58+
});
5459
}
5560

5661
private Runnable logWithSleep(final String message) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class AppTest {
2020
* - 스레드명(nio-8080-exec-x)으로 생성된 스레드 갯수를 파악
2121
* - http call count
2222
* - 테스트 결과값
23+
accept count: backlog 크기 만큼 생성된 운영체제 단의 accept 큐 사이즈 3-way 핸드셰이크가 끝난 커넥션이 대기
24+
max-connections: socket.accept() 로 받을 수 있는 커넥션 수 accept큐에서 대기 중인 커넥션을 꺼내서 accpet() 받을 수 있는 최대 커넥션 수
25+
threads: 한번에 처리할 수 있는 최대 요청 개수 (즉 실제 생성되는 스레드 개수)
2326
*/
2427
@Test
2528
void test() throws Exception {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package nextstep.jwp.handler;
2+
3+
import java.util.List;
4+
import org.apache.catalina.exception.NotSupportedRequestException;
5+
import org.apache.catalina.servlet.Controller;
6+
import org.apache.coyote.http.vo.HttpRequest;
7+
8+
public class ControllerMapping {
9+
10+
private static final List<Controller> controllers = List.of(
11+
new DefaultPageController(),
12+
new LoginController(),
13+
new RegisterController()
14+
);
15+
16+
private ControllerMapping() {
17+
}
18+
19+
public static boolean hasSupportedController(final HttpRequest request) {
20+
return controllers.stream()
21+
.anyMatch(it -> it.isSupported(request));
22+
}
23+
24+
public static Controller getSupportedController(final HttpRequest request) {
25+
return controllers.stream()
26+
.filter(it -> it.isSupported(request))
27+
.findFirst()
28+
.orElseThrow(NotSupportedRequestException::new);
29+
}
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package nextstep.jwp.handler;
2+
3+
import org.apache.catalina.servlet.AbstractController;
4+
import org.apache.coyote.http.HttpHeader;
5+
import org.apache.coyote.http.HttpMethod;
6+
import org.apache.coyote.http.HttpStatus;
7+
import org.apache.coyote.http.SupportFile;
8+
import org.apache.coyote.http.vo.HttpRequest;
9+
import org.apache.coyote.http.vo.HttpResponse;
10+
import org.apache.coyote.http.vo.Url;
11+
12+
public class DefaultPageController extends AbstractController {
13+
14+
@Override
15+
protected void doGet(final HttpRequest request, final HttpResponse response) {
16+
response.setStatus(HttpStatus.OK)
17+
.setHeader(HttpHeader.CONTENT_TYPE, SupportFile.HTML.getContentType())
18+
.setBody("Hello world!");
19+
}
20+
21+
@Override
22+
public boolean isSupported(final HttpRequest request) {
23+
return request.isRequestMethodOf(HttpMethod.GET) &&
24+
request.isUrl(Url.from("/"));
25+
}
26+
}

tomcat/src/main/java/nextstep/jwp/handler/DefaultPageHandler.java

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)