Skip to content

Commit

Permalink
[톰캣 구현하기 - 3, 4단계] 우르(김현우) 미션 제출합니다. (#481)
Browse files Browse the repository at this point in the history
* feat : 학습 테스트

* feat : Cookie 클래스 생성

* feat : Session 클래스 생성

* feat : HTTP 요청을 파싱하는 유틸리티 클래스 생성

* feat : HttpRequestLine 클래스 추가

* feat : RequestBody 클래스 추가

* feat : QueryString 클래스 추가

* refactor : queryString, body, cookie, httpRequestLine 추가

* feat : 회원 가입 및 로그인 기능 추가

* refactor : path -> uri 로 변수 변경

* feat : handler 추가

* refactor : handle 반환값 void 로 변경

* refactor : handler composite 반환값 변경

* feat : Http 응답 관련 header, body, status line 객체 추가

* feat : Handler 반환값을 void로 변경하여 response에 값을 넣도록 수정

* feat : HttpRequest, HttpResponse 공통적으로 Cookie 사용하도록 수정

* feat : 매 요청마다 세션이 있으면 세션 매니저에 넣어주고, 없으면 생성하여 넣어주기

* feat : 로그인 관련 로직 수정

* refactor : 세션 추가할 때 기존 세션이 가지고 있는 id로 저장

* refactor : LoginPageHandler는 정적 Handler 로 수정

* refactor : Http11Processor의 HandlerComposite 타입을 Handler 인터페이스로 수정

* refactor : 동시성 보장을 위한 ConcurrentHashMap 사용

* refactor : max thread 설정

* test : 테스트를 위한 getter 추가

* feat : 학습 테스트

* refactor : 공백 추가

* refactor : 코드 스멜 제거

* refactor : redirect URL 상대 경로로 설정

* refactor : HttpResponse에 redirect 행위 캡술화

* refactor : 핸들러가 존재하지 않을 때 exception 추가
  • Loading branch information
java-saeng authored Sep 13, 2023
1 parent f8050ff commit 92f744a
Show file tree
Hide file tree
Showing 39 changed files with 1,092 additions and 285 deletions.
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,29 @@
package cache.com.example.etag;

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

import java.util.Collections;
import java.util.List;
import javax.servlet.Filter;
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> bean = new FilterRegistrationBean<>();
final ShallowEtagHeaderFilter filter = new ShallowEtagHeaderFilter();
bean.setFilter(filter);
bean.setUrlPatterns(List.of(
"/etag",
PREFIX_STATIC_RESOURCES + "/*"
));

return bean;
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
package cache.com.example.version;

import java.time.Duration;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CacheBustingWebConfig implements WebMvcConfigurer {

public static final String PREFIX_STATIC_RESOURCES = "/resources";
public static final String PREFIX_STATIC_RESOURCES = "/resources";

private final ResourceVersion version;
private final ResourceVersion version;

@Autowired
public CacheBustingWebConfig(ResourceVersion version) {
this.version = 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/");
}
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
.addResourceLocations("classpath:/static/")
.setUseLastModified(true)
.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 @@ -2,6 +2,9 @@ handlebars:
suffix: .html

server:
compression:
enabled: true
min-response-size: 10
tomcat:
accept-count: 1
max-connections: 1
Expand Down
106 changes: 51 additions & 55 deletions study/src/test/java/thread/stage0/ThreadPoolsTest.java
Original file line number Diff line number Diff line change
@@ -1,66 +1,62 @@
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 {

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

@Test
void testNewFixedThreadPool() {
final var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
executor.submit(logWithSleep("hello fixed thread pools"));
executor.submit(logWithSleep("hello fixed thread pools"));
executor.submit(logWithSleep("hello fixed thread pools"));

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

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
}

@Test
void testNewCachedThreadPool() {
final var executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
executor.submit(logWithSleep("hello cached thread pools"));
executor.submit(logWithSleep("hello cached thread pools"));
executor.submit(logWithSleep("hello cached thread pools"));

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

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
}

private Runnable logWithSleep(final String message) {
return () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info(message);
};
}
private static final Logger log = LoggerFactory.getLogger(ThreadPoolsTest.class);

@Test
void testNewFixedThreadPool() {
final ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
executor.submit(logWithSleep("hello fixed thread pools"));
executor.submit(logWithSleep("hello fixed thread pools"));
executor.submit(logWithSleep("hello fixed thread pools"));

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

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
}

@Test
void testNewCachedThreadPool() {
final var executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
executor.submit(logWithSleep("hello cached thread pools"));
executor.submit(logWithSleep("hello cached thread pools"));
executor.submit(logWithSleep("hello cached thread pools"));

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

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
}

private Runnable logWithSleep(final String message) {
return () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info(message);
};
}
}
28 changes: 14 additions & 14 deletions study/src/test/java/thread/stage0/ThreadTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ void testExtendedThread() throws InterruptedException {
thread.join();
}

private static final class ExtendedThread extends Thread {

private String message;

public ExtendedThread(final String message) {
this.message = message;
}

@Override
public void run() {
log.info(message);
}
}

/**
* Runnable 인터페이스를 사용하는 방법도 있다.
* 주석을 참고하여 테스트 코드를 작성하고, 테스트를 실행시켜서 메시지가 잘 나오는지 확인한다.
Expand All @@ -52,20 +66,6 @@ void testRunnableThread() throws InterruptedException {
thread.join();
}

private static final class ExtendedThread extends Thread {

private String message;

public ExtendedThread(final String message) {
this.message = message;
}

@Override
public void run() {
log.info(message);
}
}

private static final class RunnableThread implements Runnable {

private String message;
Expand Down
7 changes: 4 additions & 3 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import org.apache.coyote.request.Session;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
Expand All @@ -29,7 +30,7 @@ public interface Manager {
*
* @param session Session to be added
*/
void add(HttpSession session);
void add(Session session);

/**
* Return the active Session, associated with this Manager, with the
Expand All @@ -45,12 +46,12 @@ public interface Manager {
* @return the request session or {@code null} if a session with the
* requested ID could not be found
*/
HttpSession findSession(String id) throws IOException;
Session findSession(String id) throws IOException;

/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
*/
void remove(HttpSession session);
void remove(Session session);
}
19 changes: 15 additions & 4 deletions tomcat/src/main/java/org/apache/catalina/connector/Connector.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.apache.catalina.connector;

import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.apache.coyote.handler.Handler;
import org.apache.coyote.handler.HandlerComposite;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -17,19 +20,27 @@ public class Connector implements Runnable {

private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int MAX_THREADS = 200;

private final ServerSocket serverSocket;
private boolean stopped;
private final List<Handler> handlers;
private final Executor executor;

public Connector(final List<Handler> handlers) {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, handlers);
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, handlers, MAX_THREADS);
}

public Connector(final int port, final int acceptCount, final List<Handler> handlers) {
public Connector(
final int port,
final int acceptCount,
final List<Handler> handlers,
final int maxThreads
) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.handlers = handlers;
executor = Executors.newFixedThreadPool(maxThreads);
}

private ServerSocket createServerSocket(final int port, final int acceptCount) {
Expand Down Expand Up @@ -70,8 +81,8 @@ private void process(final Socket connection) {
if (connection == null) {
return;
}
var processor = new Http11Processor(connection, handlers);
new Thread(processor).start();
var processor = new Http11Processor(connection, new HandlerComposite(handlers));
executor.execute(processor);
}

public void stop() {
Expand Down
8 changes: 7 additions & 1 deletion tomcat/src/main/java/org/apache/catalina/startup/Tomcat.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import org.apache.catalina.connector.Connector;
import org.apache.coyote.handler.Handler;
import org.apache.coyote.handler.LoginHandler;
import org.apache.coyote.handler.LoginPageHandler;
import org.apache.coyote.handler.RegisterHandler;
import org.apache.coyote.handler.RegisterPageHandler;
import org.apache.coyote.handler.StaticFileHandler;
import org.apache.coyote.handler.WelcomePageHandler;
import org.slf4j.Logger;
Expand Down Expand Up @@ -34,7 +37,10 @@ public List<Handler> getContext() {
return List.of(
new LoginHandler(),
new StaticFileHandler(),
new WelcomePageHandler()
new LoginPageHandler(),
new WelcomePageHandler(),
new RegisterHandler(),
new RegisterPageHandler()
);
}
}
7 changes: 4 additions & 3 deletions tomcat/src/main/java/org/apache/coyote/handler/Handler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

import java.io.IOException;
import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.response.HttpResponse;

public interface Handler {

boolean canHandle(final HttpRequest httpRequest);

String handle(final HttpRequest httpRequest) throws IOException;
void handle(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException;

default String safeHandle(final HttpRequest httpRequest) {
default void safeHandle(final HttpRequest httpRequest, final HttpResponse httpResponse) {
try {
return handle(httpRequest);
handle(httpRequest, httpResponse);
} catch (IOException e) {
throw new IllegalArgumentException("I/O 작업 관련 에러가 발생했습니다.");
}
Expand Down
Loading

0 comments on commit 92f744a

Please sign in to comment.