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단계] 로이스(원태연) 미션 제출합니다. #429

Merged
merged 14 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cache.com.example.cachecontrol;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.web.servlet.HandlerInterceptor;

public class CacheControlInterceptor implements HandlerInterceptor {

@Override
public void afterCompletion(
final HttpServletRequest request,
final HttpServletResponse response,
final Object handler,
final Exception ex
) {
final String cacheControl = CacheControl
.noCache()
.cachePrivate()
Copy link

Choose a reason for hiding this comment

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

no-cache와 no-store
private과 public 각각 어떤 차이가 있었나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

모두 응답 헤더 Cache-control에 설정하여 캐시에 대한 정책에 대해 명시하는 것 입니다.
no-cache는 캐시를 저장은 해도 되지만, 매 요청마다 revalidate(재검증..?)가 필요하다는 정책이고 no-store는 캐시를 아예 저장하지 말라는 뜻입니다.

privatepublic의 차이점은 캐싱의 범위입니다. CDN이나 다른 proxy 서버에 캐싱을 저장할 수 있는 public과 달리, private은 클라이언트에서만 캐싱할 수 있습니다.

이미지나 로고 같이 모든 사용자에게 보여지는 민감하지 않은 정보는 public, 사용자와 관련된 데이터는 private으로 설정하는 식으로 목적에 맞게 관리 할 수 있을 것 같습니다!

.getHeaderValue();
response.setHeader(HttpHeaders.CACHE_CONTROL, cacheControl);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(new CacheControlInterceptor())
.excludePathPatterns("/resources/**");
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
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());
Copy link

Choose a reason for hiding this comment

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

ShallowEtagHeaderFilter의 역할은 무엇인가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

ETag를 생성한 뒤 응답에 포함시켜 반환합니다!

filterRegistrationBean.addUrlPatterns("/etag");
filterRegistrationBean.addUrlPatterns("/resources/*");
return filterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package cache.com.example.version;

import org.springframework.beans.factory.annotation.Autowired;
import java.time.Duration;
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 @@ -12,14 +13,14 @@ public class CacheBustingWebConfig implements WebMvcConfigurer {

private final ResourceVersion version;

@Autowired
public CacheBustingWebConfig(ResourceVersion version) {
this.version = 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.github.jknack.handlebars.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper;

@HandlebarsHelper
Expand All @@ -13,7 +12,6 @@ public class VersionHandlebarsHelper {

private final ResourceVersion version;

@Autowired
public VersionHandlebarsHelper(ResourceVersion version) {
this.version = version;
}
Expand Down
6 changes: 5 additions & 1 deletion study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ handlebars:
server:
tomcat:
accept-count: 1
max-connections: 1
max-connections: 2
threads:
max: 2
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
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
30 changes: 14 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,11 +27,12 @@ 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());
executor.shutdown();
}

@Test
Expand All @@ -46,11 +43,12 @@ 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());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
executor.shutdown();
}

private Runnable logWithSleep(final String message) {
Expand Down
7 changes: 3 additions & 4 deletions tomcat/src/main/java/nextstep/web/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package nextstep.web;

import org.apache.coyote.http11.mvc.AbstractController;
import org.apache.coyote.http11.mvc.view.ResponseEntity;
import org.apache.coyote.http11.controller.AbstractController;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public class HelloController extends AbstractController {

@Override
public ResponseEntity handleGetRequest(final HttpRequest request, final HttpResponse response) {
return ResponseEntity.fromSimpleStringData("Hello world!");
public void doGet(final HttpRequest request, final HttpResponse response) {
response.textPlain("Hello world!");
}
}
7 changes: 3 additions & 4 deletions tomcat/src/main/java/nextstep/web/IndexController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package nextstep.web;

import org.apache.coyote.http11.mvc.AbstractController;
import org.apache.coyote.http11.mvc.view.ResponseEntity;
import org.apache.coyote.http11.controller.AbstractController;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public class IndexController extends AbstractController {

@Override
public ResponseEntity handleGetRequest(final HttpRequest request, final HttpResponse response) {
return ResponseEntity.forwardTo("/index.html");
public void doGet(final HttpRequest request, final HttpResponse response) {
response.forwardTo("/index.html");
}
}
21 changes: 11 additions & 10 deletions tomcat/src/main/java/nextstep/web/LoginController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import org.apache.coyote.http11.common.HttpStatus;
import org.apache.coyote.http11.common.Session;
import org.apache.coyote.http11.common.SessionManager;
import org.apache.coyote.http11.mvc.AbstractController;
import org.apache.coyote.http11.mvc.view.ResponseEntity;
import org.apache.coyote.http11.controller.AbstractController;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.slf4j.Logger;
Expand All @@ -20,29 +19,31 @@ public class LoginController extends AbstractController {
private final UserService userService = new UserService();

@Override
public ResponseEntity handleGetRequest(final HttpRequest request, final HttpResponse response) {
public void doGet(final HttpRequest request, final HttpResponse response) {
final Session session = SessionManager.findSession(request.getCookie("JSESSIONID"));
if (session != null) {
final User user = (User) session.getAttribute("user");
log.info("already login: {}", user);
return ResponseEntity.redirectTo("/index.html");
response.redirectTo("/index.html");
return;
}

return ResponseEntity.forwardTo("/login.html");
response.forwardTo("/login.html");
}

@Override
public ResponseEntity handlePostRequest(final HttpRequest request, final HttpResponse response) {
public void doPost(final HttpRequest request, final HttpResponse response) {
final String account = request.getPayloadValue("account");
final String password = request.getPayloadValue("password");
if (userService.validateLogin(account, password)) {
return successLogin(response, account);
successLogin(response, account);
return;
}

return ResponseEntity.forwardTo(HttpStatus.UNAUTHORIZED, "/401.html");
response.forwardTo(HttpStatus.UNAUTHORIZED, "/401.html");
}

private ResponseEntity successLogin(final HttpResponse response, final String account) {
private void successLogin(final HttpResponse response, final String account) {
final User user = userService.getUserByAccount(account);
log.info("login success: {}", user);

Expand All @@ -51,6 +52,6 @@ private ResponseEntity successLogin(final HttpResponse response, final String ac
final Session session = new Session(uuid);
session.setAttribute("user", user);
SessionManager.add(session);
return ResponseEntity.redirectTo("/index.html");
response.redirectTo("/index.html");
}
}
11 changes: 5 additions & 6 deletions tomcat/src/main/java/nextstep/web/RegisterController.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package nextstep.web;

import nextstep.jwp.application.RegisterService;
import org.apache.coyote.http11.mvc.AbstractController;
import org.apache.coyote.http11.mvc.view.ResponseEntity;
import org.apache.coyote.http11.controller.AbstractController;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

Expand All @@ -11,18 +10,18 @@ public class RegisterController extends AbstractController {
private final RegisterService registerService = new RegisterService();

@Override
public ResponseEntity handleGetRequest(final HttpRequest request, final HttpResponse response) {
return ResponseEntity.forwardTo("/register.html");
public void doGet(final HttpRequest request, final HttpResponse response) {
response.forwardTo("/register.html");
}

@Override
public ResponseEntity handlePostRequest(final HttpRequest request, final HttpResponse response) {
public void doPost(final HttpRequest request, final HttpResponse response) {
final String account = request.getPayloadValue("account");
final String password = request.getPayloadValue("password");
final String email = request.getPayloadValue("email");

registerService.register(account, password, email);

return ResponseEntity.redirectTo("/index.html");
response.redirectTo("/index.html");
}
}
18 changes: 11 additions & 7 deletions tomcat/src/main/java/org/apache/catalina/connector/Connector.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
package org.apache.catalina.connector;

import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Connector implements Runnable {

private static final Logger log = LoggerFactory.getLogger(Connector.class);

private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int DEFAULT_MAX_THREAD_COUNT = 250;

private final ServerSocket serverSocket;
private final Executor executor;
private boolean stopped;

public Connector() {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_MAX_THREAD_COUNT);
}

public Connector(final int port, final int acceptCount) {
public Connector(final int port, final int acceptCount, final int maxThreadCount) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.executor = Executors.newFixedThreadPool(maxThreadCount);
Copy link

Choose a reason for hiding this comment

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

이렇게 설정하면 내부적으로 스레드가 어떻게 관리되나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

설정한 개수(250) 만큼의 Thread pool의 크기를 가지는(corePoolSize, maxPoolSize) ExecutorService를 생성합니다.
만약 250개의로 설정하고 생성한다면, 250개의 Thread가 미리 생성되어 대기 하다가 work가 실행되면 생성된 Thread들이 해당 work를 수행합니다.
모든 thread가 활성화(active) 된 상태이면 내부적으로 설정된 LinkedBlockingQueuework가 적재되어 대기하다가 available(사용가능?)한 Thread가 작업을 실행하는 구조로 관리됩니다!

}

private ServerSocket createServerSocket(final int port, final int acceptCount) {
Expand Down Expand Up @@ -67,7 +71,7 @@ private void process(final Socket connection) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
executor.execute(processor);
}

public void stop() {
Expand Down
Loading