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

[톰캣 구현하기 - 2, 3, 4단계] 밀리(김미성) 미션 제출합니다. #464

Merged
merged 16 commits into from
Sep 13, 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
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,18 @@
- [x] 회원가입 페이지는 GET으로 요청한다.
- [x] 회원가입을 완료하면 index.html로 리다이렉트한다.
- [x] 로그인 페이지도 버튼을 눌렀을 때 POST 방식으로 전송하도록 변경한다.
- [ ] 서버에서 HTTP 응답을 전달할 때 응답 헤더에 Set-Cookie를 추가한다.
- [ ] Cookie에 JSESSIONID가 없으면 응답 헤더에 Set-Cookie를 반환해준다.
- [ ] 쿠키에서 전달 받은 JSESSIONID의 값으로 로그인 여부를 체크한다.
- [ ] 로그인된 상태에서 /login 페이지에 접근하면 index.html 페이지로 리다이렉트 처리한다.
- [x] 서버에서 HTTP 응답을 전달할 때 응답 헤더에 Set-Cookie를 추가한다.
- [x] Cookie에 JSESSIONID가 없으면 응답 헤더에 Set-Cookie를 반환해준다.
- [x] 쿠키에서 전달 받은 JSESSIONID의 값으로 로그인 여부를 체크한다.
- [x] 로그인된 상태에서 /login 페이지에 접근하면 index.html 페이지로 리다이렉트 처리한다.

### 3단계

- [x] HttpRequest 클래스 구현하기
- [x] HttpResponse 클래스 구현하기
- [x] Controller 인터페이스 추가하기

### 4단계

- [x] Executors로 Thread Pool 적용
- [x] 동시성 컬렉션 사용하기
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
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 WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/*");
registry.addInterceptor(interceptor);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package cache.com.example.etag;

import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES;

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 FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<>(
new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns(
"/etag",
PREFIX_STATIC_RESOURCES + "/*"
);
return filterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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 @@ -13,13 +15,14 @@ public class CacheBustingWebConfig implements WebMvcConfigurer {
private final ResourceVersion version;

@Autowired
public CacheBustingWebConfig(ResourceVersion version) {
public CacheBustingWebConfig(final 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());
}
}
3 changes: 3 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ server:
max-connections: 1
threads:
max: 2
compression:
enabled: true
min-response-size: 10
31 changes: 13 additions & 18 deletions study/src/test/java/thread/stage0/SynchronizationTest.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
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 {
var executorService = Executors.newFixedThreadPool(3);
var synchronizedMethods = new SynchronizedMethods();
final var executorService = Executors.newFixedThreadPool(3);
final var synchronizedMethods = new SynchronizedMethods();

IntStream.range(0, 1000)
.forEach(count -> executorService.submit(synchronizedMethods::calculate));
Expand All @@ -41,15 +36,15 @@ private static final class SynchronizedMethods {

private int sum = 0;

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

public int getSum() {
return sum;
}

public void setSum(int sum) {
public void setSum(final int sum) {
this.sum = sum;
}
}
Expand Down
30 changes: 13 additions & 17 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 All @@ -57,7 +53,7 @@ private Runnable logWithSleep(final String message) {
return () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
log.info(message);
Expand Down
6 changes: 0 additions & 6 deletions tomcat/src/main/java/handler/RequestHandler.java

This file was deleted.

23 changes: 0 additions & 23 deletions tomcat/src/main/java/handler/RequestHandlerMapping.java

This file was deleted.

8 changes: 6 additions & 2 deletions tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package nextstep;

import nextstep.jwp.JwpRequestMapping;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.handler.RequestMapping;

public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();
private static final RequestMapping requestMapping = new JwpRequestMapping();

public static void main(final String[] args) {
final var tomcat = new Tomcat(requestMapping);
tomcat.start();
}
}
37 changes: 37 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/JwpRequestMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nextstep.jwp;

import java.util.HashMap;
import java.util.Map;
import nextstep.jwp.controller.IndexController;
import nextstep.jwp.controller.LoginController;
import nextstep.jwp.controller.RegisterController;
import nextstep.jwp.controller.StaticController;
import org.apache.coyote.handler.Controller;
import org.apache.coyote.handler.RequestMapping;

public class JwpRequestMapping implements RequestMapping {

private static final String STATIC_RESOURCE_KEY = "static";
private static final String EXTENSION_DELIMITER = ".";

private final Map<String, Controller> handlerMapping = new HashMap<>();

public JwpRequestMapping() {
handlerMapping.put(STATIC_RESOURCE_KEY, new StaticController());
handlerMapping.put("/", new IndexController());
handlerMapping.put("/login", new LoginController());
handlerMapping.put("/register", new RegisterController());
}

@Override
public Controller getHandler(final String requestUri) {
if (isFileRequest(requestUri)) {
return handlerMapping.get(STATIC_RESOURCE_KEY);
}
return handlerMapping.get(requestUri);
}

private boolean isFileRequest(final String requestUri) {
return requestUri.contains(EXTENSION_DELIMITER);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package nextstep.jwp.controller;

import handler.Controller;
import org.apache.coyote.handler.AbstractController;
import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpResponse;
import org.apache.coyote.http11.HttpStatusCode;
import org.apache.coyote.http11.ViewResolver;

public class IndexController implements Controller {
public class IndexController extends AbstractController {

@Override
public String run(final HttpRequest httpRequest, final HttpResponse httpResponse) {
httpResponse.setStatusCode(HttpStatusCode.OK);
return "/index.html";
public void doGet(final HttpRequest request, final HttpResponse response) {
final String responseBody = ViewResolver.read("/index.html");
response.setResponseBody(responseBody);
response.setContentLength();

Choose a reason for hiding this comment

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

한가지만 더 질문을 드리고 싶은데요,
ContentLength는 ResponseBody가 정해짐에 따라 항상 함께 정해져야 하는 것 아닐까요?
즉 이렇게 setResponseBody 이후에 setContentLength를 해주는 게 아니라 setResponseBody 내부에서 setContentLength를 처리해 주는 것은 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

오!! 저도 이 부분에 대해서 고민을 했었어요!
항상 함께 정해져야 하는게 맞는 것 같다가도, responseBody를 set하는데 Content-Length가 지정될 지 예측을 할 수 있을까? 하는 생각에 그냥 분리했습니다! 당연히 http에 대해서 잘 안다면 content-length가 지정될 수 있겠구나 생각은 할 수 있겠지만요.
내부 코드를 보지 않으면 content-length가 두 번 지정될 수도 있을 것도 같네요.
그런데 해당 메서드를 사용하는 사용자 입장에서 무엇이 더 편할지는 또 고민이 되네요..
말랑은 어떻게 생각하시나요?!

response.setStatusCode(HttpStatusCode.OK);
}
}
Loading