Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[톰캣 구현하기 - 3, 4단계] 허브(방대의) 미션 제출합니다. #431

Merged
merged 42 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8f80ba8
feat: HTTP 활용 테스트 진행
greeng00se Sep 4, 2023
5cde353
feat: HttpVersion 추가
greeng00se Sep 4, 2023
d36d48d
feat: Http11Exception을 제외한 CustomException 제거
greeng00se Sep 4, 2023
2aa52b6
feat: RequestLine에 HttpVersion 적용
greeng00se Sep 4, 2023
99c9efb
feat: Path 클래스 추가 및 적용
greeng00se Sep 4, 2023
c425310
feat: HttpRequest, HttpRequestParser 추가
greeng00se Sep 4, 2023
ef65b92
feat: Thread 실습 내용 추가
greeng00se Sep 7, 2023
680320e
refactor: RequestHeader Headers로 변경
greeng00se Sep 7, 2023
1feabc5
feat: Controller와 AbstractController 추가
greeng00se Sep 8, 2023
9d2d82b
refactor: Session catalina 패키지로 변경
greeng00se Sep 8, 2023
6048c94
feat: HttpResponse 객체 추가
greeng00se Sep 8, 2023
3ea8e9c
refactor: 컨트롤러 throws Exception 제거
greeng00se Sep 8, 2023
7096104
feat: HttpRequest parseCookie 추가
greeng00se Sep 8, 2023
cadc5bc
feat: RequestBody 기본 생성자 추가
greeng00se Sep 8, 2023
e46a70e
feat: 세션 clear 메서드 추가
greeng00se Sep 8, 2023
978aebf
feat: LoginController 추가
greeng00se Sep 8, 2023
858e10e
feat: HomeController 추가
greeng00se Sep 8, 2023
94504e8
feat: RegisterController 추가
greeng00se Sep 8, 2023
ac71648
feat: Static Controller 추가
greeng00se Sep 8, 2023
4df9960
feat: RequestMapper 추가
greeng00se Sep 8, 2023
a159ff0
feat: Tomcat에 Mapper 적용
greeng00se Sep 8, 2023
96098be
feat: HttpResponseGenerator HttpResponse 받아서 반환값 생성하는 기능 추가
greeng00se Sep 9, 2023
e6e0a9e
feat: 알 수 없는 예외가 발생하는 경우 Internal Server Error 반환하는 기능 추가
greeng00se Sep 9, 2023
ff4a5c3
refactor: 톰캣 실행 부분 수정
greeng00se Sep 9, 2023
4458086
test: redirect에 대한 검증부 수정
greeng00se Sep 9, 2023
d7bd953
docs: README 수정
greeng00se Sep 9, 2023
fd7a306
feat: 최대 ThreadPool 크기 250으로 적용
greeng00se Sep 9, 2023
c0c6e36
feat: ConcurrentHashMap 사용
greeng00se Sep 9, 2023
990d183
fix: JSESSION_ID 가져올 때 getOrDefault 사용하도록 수정
greeng00se Sep 9, 2023
4d6dea6
feat: SessionManager를 RequestMapper가 의존하도록 수정
greeng00se Sep 9, 2023
69b0f73
chore: 빌드 설정 수정
greeng00se Sep 9, 2023
801ddc8
remove: 사용하지 않는 필드 제거
greeng00se Sep 9, 2023
2330205
fix: finally 후 space 추가
greeng00se Sep 11, 2023
67f7dd2
refactor: 상수명 일관성 있게 수정
greeng00se Sep 11, 2023
139bef6
refactor: Http11Processor 중복 제거
greeng00se Sep 11, 2023
812e5f8
refactor: isEmpty -> isBlank로 수정
greeng00se Sep 11, 2023
9254604
feat: 사용자 생성시 검증하는 부분 추가
greeng00se Sep 11, 2023
643c045
refactor: session null check 부분 수정
greeng00se Sep 11, 2023
1c49621
refactor: final 빠진 부분 추가
greeng00se Sep 11, 2023
351882a
feat: Adapter 추가
greeng00se Sep 11, 2023
5825bae
test: 세션 관련 테스트 수정
greeng00se Sep 11, 2023
da4e1f6
fix: 불필요한 구문 제거
greeng00se Sep 11, 2023
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,11 @@
- [x] 회원가입 페이지의 경우 GET을 사용하여 보여준다.
- [x] 회원가입의 경우 POST를 사용한다.
- [x] 회원가입을 완료하는 경우 index.html로 리다이렉트한다.

- [x] 리팩터링을 진행한다.
- [x] HttpRequest 클래스를 구현한다.
- [x] HttpResponse 클래스를 구현한다.
- [x] 알 수 없는 예외 발생시 Internal Server Error 페이지 반환한다.
- [x] Controller 인터페이스를 추가한다.
- [x] Controller를 매핑해주는 클래스를 구현한다.
- [x] ThreadPoolExecutor를 사용해서 스레드 풀(thread pool) 기능을 추가한다.
- [x] 세션의 경우 동시성 컬렉션을 사용한다.
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package cache.com.example.cachecontrol;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.WebContentInterceptor;

@Configuration
public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
final CacheControl cacheControl = CacheControl
.noCache()
.cachePrivate();

WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.addCacheMapping(cacheControl, "/**");

registry.addInterceptor(webContentInterceptor);
}
}
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() {
final var filterRegistrationBean = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/etag");
filterRegistrationBean.addUrlPatterns("/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,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(365, TimeUnit.DAYS).cachePublic());
}
}
10 changes: 8 additions & 2 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ handlebars:

server:
tomcat:
accept-count: 1
max-connections: 1
# 모든 쓰레드가 사용 중 일 때 들어온 요청이 대기하는 최대 큐의 길이
accept-count: 2
# 서버가 유지할 수 있는 최대 Connection의 수
max-connections: 2
# 최대 실행 가능 Thread 수
threads:
max: 2
compression:
enabled: true
min-response-size: 10
25 changes: 10 additions & 15 deletions study/src/test/java/thread/stage0/SynchronizationTest.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
package thread.stage0;

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

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

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

/**
* 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다.
* 자바는 공유 데이터에 대한 스레드 접근을 동기화(synchronization)하여 경쟁 조건을 방지한다.
* 동기화된 블록은 하나의 스레드만 접근하여 실행할 수 있다.
*
* Synchronization
* https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
* 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다. 자바는 공유 데이터에 대한 스레드 접근을
* 동기화(synchronization)하여 경쟁 조건을 방지한다. 동기화된 블록은 하나의 스레드만 접근하여 실행할 수 있다.
* <p>
* Synchronization https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
*/
class SynchronizationTest {

/**
* 테스트가 성공하도록 SynchronizedMethods 클래스에 동기화를 적용해보자.
* synchronized 키워드에 대하여 찾아보고 적용하면 된다.
*
* Guide to the Synchronized Keyword in Java
* https://www.baeldung.com/java-synchronized
* 테스트가 성공하도록 SynchronizedMethods 클래스에 동기화를 적용해보자. synchronized 키워드에 대하여 찾아보고 적용하면 된다.
* <p>
* Guide to the Synchronized Keyword in Java https://www.baeldung.com/java-synchronized
*/
@Test
void testSynchronized() throws InterruptedException {
Expand All @@ -41,7 +36,7 @@ private static final class SynchronizedMethods {

private int sum = 0;

public void calculate() {
public synchronized void calculate() {
setSum(getSum() + 1);
}

Expand Down
28 changes: 12 additions & 16 deletions study/src/test/java/thread/stage0/ThreadPoolsTest.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
package thread.stage0;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

Expand All @@ -31,8 +27,8 @@ void testNewFixedThreadPool() {
executor.submit(logWithSleep("hello fixed thread pools"));

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

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

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

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
Expand Down
10 changes: 9 additions & 1 deletion tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package nextstep;

import java.util.Map;
import nextstep.jwp.controller.HomeController;
import nextstep.jwp.controller.LoginController;
import nextstep.jwp.controller.RegisterController;
import org.apache.catalina.startup.Tomcat;

public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();
final Tomcat tomcat = new Tomcat(Map.of(
"/", new HomeController(),
"/login", new LoginController(),
"/register", new RegisterController()
));
tomcat.start();
}
}
17 changes: 17 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/HomeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package nextstep.jwp.controller;

import org.apache.catalina.controller.AbstractController;
import org.apache.coyote.http11.common.HttpStatus;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public class HomeController extends AbstractController {

private static final String HOME_PAGE = "/home.html";

@Override
protected void doGet(final HttpRequest request, final HttpResponse response) {
response.setHttpStatus(HttpStatus.OK)
.sendRedirect(HOME_PAGE);
}
}
50 changes: 50 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.catalina.controller.AbstractController;
import org.apache.coyote.http11.common.HttpStatus;
import org.apache.coyote.http11.common.Session;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.RequestBody;
import org.apache.coyote.http11.response.HttpResponse;

public class LoginController extends AbstractController {

private static final String INDEX_PAGE = "/index.html";
private static final String LOGIN_PAGE = "/login.html";
private static final String UNAUTHORIZED_PAGE = "/401.html";

@Override
protected void doPost(final HttpRequest request, final HttpResponse response) {
final RequestBody requestBody = request.getRequestBody();
final String account = requestBody.get("account");
final String password = requestBody.get("password");

final User user = InMemoryUserRepository.findByAccount(account).orElseThrow();
if (!user.checkPassword(password)) {
response.setHttpStatus(HttpStatus.UNAUTHORIZED).sendRedirect(UNAUTHORIZED_PAGE);
return;
}

final Session session = request.getSession();
session.setAttribute("user", user);
response.setHttpStatus(HttpStatus.FOUND)
.setCookie("JSESSIONID", session.getId())
.setSession(session)
.addHeader("Location", INDEX_PAGE)
.sendRedirect(INDEX_PAGE);
}

@Override
protected void doGet(final HttpRequest request, final HttpResponse response) {
final Session session = request.getSession();
if (session.getAttribute("user") == null) {
response.setHttpStatus(HttpStatus.OK).sendRedirect(LOGIN_PAGE);
return;
}
response.setHttpStatus(HttpStatus.FOUND)
.addHeader("Location", INDEX_PAGE)
.sendRedirect(INDEX_PAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.catalina.controller.AbstractController;
import org.apache.coyote.http11.common.HttpStatus;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.RequestBody;
import org.apache.coyote.http11.response.HttpResponse;

public class RegisterController extends AbstractController {

private static final String INDEX_PAGE = "/index.html";
private static final String REGISTER_PAGE = "/register.html";
private static final String CONFLICT_PAGE = "/409.html";

@Override
protected void doPost(final HttpRequest request, final HttpResponse response) {
final RequestBody requestBody = request.getRequestBody();
final String account = requestBody.get("account");
if (InMemoryUserRepository.findByAccount(account).isPresent()) {
response.setHttpStatus(HttpStatus.CONFLICT)
.sendRedirect(CONFLICT_PAGE);
return;
}
InMemoryUserRepository.save(new User(account, requestBody.get("password"), requestBody.get("email")));
response.setHttpStatus(HttpStatus.FOUND)
.addHeader("Location", INDEX_PAGE)
.sendRedirect(INDEX_PAGE);
}

@Override
protected void doGet(final HttpRequest request, final HttpResponse response) {
response.setHttpStatus(HttpStatus.OK)
.sendRedirect(REGISTER_PAGE);
}
}
18 changes: 13 additions & 5 deletions tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
package nextstep.jwp.db;

import nextstep.jwp.model.User;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import nextstep.jwp.model.User;

public class InMemoryUserRepository {

private static final Map<String, User> database = new ConcurrentHashMap<>();
private static final long INITIAL_KEY_VALUE = 1L;
private static final AtomicLong id = new AtomicLong(INITIAL_KEY_VALUE);

static {
final User user = new User(1L, "gugu", "password", "hkkang@woowahan.com");
final User user = new User(id.getAndIncrement(), "gugu", "password", "hkkang@woowahan.com");
database.put(user.getAccount(), user);
}

public static void save(User user) {
database.put(user.getAccount(), user);
final User savedUser = new User(id.getAndIncrement(), user.getAccount(), user.getPassword(), user.getEmail());
database.put(savedUser.getAccount(), savedUser);
}

public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}

private InMemoryUserRepository() {}
public static void clear() {
database.clear();
}

private InMemoryUserRepository() {
}
}
Loading