Skip to content

Commit

Permalink
[톰캣 구현하기 - 3, 4단계] 히이로(문제웅) 미션 제출합니다. (#455)
Browse files Browse the repository at this point in the history
* refactor: httpRequest 객체 분리

* refactor: InputStream의 보조 스트림은 request 객체 내부로 캡슐화

* refactor: httpRequest 에서 cookies 반환 시 방어적 복사 기능 구현

* refactor: Http11Response 객체 분리

* refactor: HttpRequest 객체명 Http11Request로 수정

* feat: 501 status enum 추가

* feat: Servlet 인터페이스 및 AbstractServlet 클래스 구현

* feat: LoginServlet 기능 분리 및 구현

* feat: RegisterServlet 기능 분리 및 구현

* feat: 서블릿 컨테이너 Context 클래스 구현 및 DefaultServlet 구현

* feat: 응답 메세지 바디 생성 메서드 공통 Util 클래스로 분리

* style: catalina, coyote 패키지 역할 별 분리

* test: thread 관련 학습 테스트 진행

* feat: tomcat connector 쓰레드 풀 설정 추가

* feat: 로그인 session 동시성 관리를 위한 ConcurrentHashMap 사용

* feat: HttpHeader enum 작성

* feat: Connector 상수 분리

* feat: Connector 쓰레드 풀 재설정

* refactor: Servlet 메서드 순서 변경

* refactor: AbstractServlet 상수 분리

* refactor: LoginServlet 객체 static 선언 추가

* refactor: LoginServlet 객체 내 SessionManager 전역으로 관리하도록 수정

* refactor: context를 Tomcat 객체에서 생성 후 의존성 주입하는 방향으로 수정

* refactor: startLine 객체 내 queryString을 queryParams로 수정
  • Loading branch information
MoonJeWoong authored Sep 13, 2023
1 parent d712003 commit aa584ed
Show file tree
Hide file tree
Showing 32 changed files with 653 additions and 357 deletions.
4 changes: 2 additions & 2 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ handlebars:
server:
tomcat:
accept-count: 1
max-connections: 1
max-connections: 10
threads:
max: 2
max: 10
compression:
enabled: true
min-response-size: 10
2 changes: 1 addition & 1 deletion study/src/test/java/thread/stage0/SynchronizationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private static final class SynchronizedMethods {

private int sum = 0;

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

Expand Down
6 changes: 3 additions & 3 deletions study/src/test/java/thread/stage0/ThreadPoolsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,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 +46,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
4 changes: 2 additions & 2 deletions study/src/test/java/thread/stage1/ConcurrencyTest.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package thread.stage1;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

/**
* 스레드를 다룰 때 어떤 상황을 조심해야 할까?
* - 상태를 가진 한 객체를 여러 스레드에서 동시에 접근할 경우
Expand Down
2 changes: 1 addition & 1 deletion study/src/test/java/thread/stage1/UserServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public void service(final User user) {
join(user);
}

private void join(final User user) {
private synchronized void join(final User user) {
if (!users.contains(user)) {
users.add(user);
}
Expand Down
2 changes: 1 addition & 1 deletion study/src/test/java/thread/stage2/AppTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void test() throws Exception {
thread.join();
}

assertThat(count.intValue()).isEqualTo(2);
assertThat(count.intValue()).isEqualTo(10);
}

private static void incrementIfOk(final HttpResponse<String> response) {
Expand Down
28 changes: 0 additions & 28 deletions tomcat/src/main/java/nextstep/jwp/controller/LoginController.java

This file was deleted.

14 changes: 0 additions & 14 deletions tomcat/src/main/java/nextstep/jwp/dto/LoginResponseDto.java

This file was deleted.

37 changes: 0 additions & 37 deletions tomcat/src/main/java/nextstep/jwp/service/LoginService.java

This file was deleted.

36 changes: 36 additions & 0 deletions tomcat/src/main/java/nextstep/org/apache/catalina/Context.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package nextstep.org.apache.catalina;

import java.util.HashMap;
import java.util.Map;
import nextstep.org.apache.catalina.servlet.DefaultServlet;
import nextstep.org.apache.catalina.servlet.LoginServlet;
import nextstep.org.apache.catalina.servlet.RegisterServlet;
import nextstep.org.apache.catalina.servlet.Servlet;

public class Context {

private static final Map<String, Servlet> servletMappings;
private static final String EXTENSION_DELIMITER = ".";

static {
servletMappings = new HashMap<>();
servletMappings.put("/login", new LoginServlet());
servletMappings.put("/register", new RegisterServlet());
servletMappings.put("default", new DefaultServlet());
}

public Servlet getServlet(String pathInfo) {
if (servletMappings.containsKey(removeExtension(pathInfo))) {
return servletMappings.get(removeExtension(pathInfo));
}
return servletMappings.get("default");
}

private String removeExtension(String pathInfo) {
int idx = pathInfo.indexOf(EXTENSION_DELIMITER);
if (idx == -1) {
return pathInfo;
}
return pathInfo.substring(0,idx);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nextstep.org.apache.catalina;

import java.io.IOException;
import nextstep.org.apache.coyote.http11.Session;
import nextstep.org.apache.catalina.session.Session;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
package nextstep.org.apache.catalina.connector;

import nextstep.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.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import nextstep.org.apache.catalina.Context;
import nextstep.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 MAX_THREADS_COUNT = 250;

private final Context context;
private final ThreadPoolExecutor threadPoolExecutor;
private final ServerSocket serverSocket;
private boolean stopped;

public Connector() {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
public Connector(Context context) {
this(context, DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, MAX_THREADS_COUNT);
}

public Connector(final int port, final int acceptCount) {
public Connector(Context context, final int port, final int acceptCount, final int maxThreads) {
this.context = context;
this.threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(maxThreads);
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
}
Expand Down Expand Up @@ -66,8 +73,8 @@ private void process(final Socket connection) {
if (connection == null) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
var processor = new Http11Processor(context, connection);
this.threadPoolExecutor.submit(processor);
}

public void stop() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package nextstep.org.apache.coyote.http11;
package nextstep.org.apache.catalina.cookie;

import static nextstep.org.apache.coyote.http11.HttpUtil.parseMultipleValues;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import nextstep.org.apache.coyote.http11.HttpHeader;

public class HttpCookie {
public class Cookies {

private static final String SET_COOKIE_HEADER = "Set-Cookie: %s \r\n";
private static final String SET_COOKIE_HEADER_FORMAT = "%s: %s \r\n";
private static final String COOKIE_VALUES_DELIMITER = "; ";
private static final String COOKIE_KEY_VALUE_DELIMITER = "=";

Expand Down Expand Up @@ -36,9 +37,17 @@ public boolean hasCookie(String key) {
}

public String createSetCookieHeader() {
String cookies = cookie.entrySet().stream()
return cookie.entrySet().stream()
.map(entry -> entry.getKey() + COOKIE_KEY_VALUE_DELIMITER + entry.getValue())
.collect(Collectors.joining(COOKIE_VALUES_DELIMITER));
return String.format(SET_COOKIE_HEADER, cookies);
.map(value -> String.format(
SET_COOKIE_HEADER_FORMAT, HttpHeader.SET_COOKIE.getValue(), value)
)
.collect(Collectors.joining());
}

public Cookies defensiveCopy() {
Cookies newCookies = new Cookies();
cookie.forEach(newCookies::set);
return newCookies;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package nextstep.org.apache.catalina.servlet;

import static nextstep.org.apache.coyote.http11.HttpUtil.selectFirstContentTypeOrDefault;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Objects;
import java.util.Optional;
import nextstep.org.apache.coyote.http11.HttpHeader;
import nextstep.org.apache.coyote.http11.request.Http11Request;
import nextstep.org.apache.coyote.http11.response.Http11Response;
import nextstep.org.apache.coyote.http11.Status;

public abstract class AbstractServlet implements Servlet{

private static final String RESOURCES_PATH_PREFIX = "static";
protected static final String NOT_FOUND_DEFAULT_MESSAGE = "404 Not Found";
private static final String CHARSET_UTF_8 = ";charset=utf-8";
private static final String DEFAULT_EXTENSION = ".html";
private static final String NOT_FOUND_PAGE = "/404.html";
private static final String HTTP_GET_METHOD = "GET";
private static final String HTTP_POST_METHOD = "POST";

@Override
public void service(Http11Request request, Http11Response response) throws Exception {
String method = request.getMethod();

if (method.equals(HTTP_GET_METHOD)) {
doGet(request, response);
} else if (method.equals(HTTP_POST_METHOD)) {
doPost(request, response);
} else {
response.setStatus(Status.NOT_IMPLEMENTED);
}
}

protected abstract void doGet(Http11Request request, Http11Response response) throws Exception;

protected abstract void doPost(Http11Request request, Http11Response response) throws Exception;

protected Optional<String> createResponseBody(String requestPath) throws IOException {
if (requestPath.equals("/")) {
return Optional.of("Hello world!");
}

String resourceName = RESOURCES_PATH_PREFIX + requestPath;
if (!resourceName.contains(".")) {
resourceName += DEFAULT_EXTENSION;
}
URL resource = getClass().getClassLoader().getResource(resourceName);

if (Objects.isNull(resource)) {
return Optional.empty();
}
return Optional.of(new String(Files.readAllBytes(new File(resource.getFile()).toPath())));
}

protected void responseWithBody(Http11Request request, Http11Response response) throws IOException {
Optional<String> responseBody = createResponseBody(request.getPathInfo());
String contentType = selectFirstContentTypeOrDefault(request.getHeader(HttpHeader.ACCEPT));

if (responseBody.isEmpty()) {
responseWithNotFound(request, response);
return;
}

response.setStatus(Status.OK)
.setHeader(HttpHeader.CONTENT_TYPE, contentType + CHARSET_UTF_8)
.setHeader(HttpHeader.CONTENT_LENGTH, String.valueOf(
responseBody.get().getBytes(StandardCharsets.UTF_8).length))
.setBody(responseBody.get());
}

private void responseWithNotFound(Http11Request request, Http11Response response) throws IOException {
String notFoundPageBody = createResponseBody(NOT_FOUND_PAGE)
.orElse(NOT_FOUND_DEFAULT_MESSAGE);
String contentType = selectFirstContentTypeOrDefault(request.getHeader(HttpHeader.ACCEPT));

response.setStatus(Status.NOT_FOUND)
.setHeader(HttpHeader.CONTENT_TYPE, contentType + CHARSET_UTF_8)
.setHeader(HttpHeader.CONTENT_LENGTH, String.valueOf(
notFoundPageBody.getBytes(StandardCharsets.UTF_8).length))
.setBody(notFoundPageBody);
}
}
Loading

0 comments on commit aa584ed

Please sign in to comment.