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단계] 히이로(문제웅) 미션 제출합니다. #455

Merged
merged 24 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5248e44
refactor: httpRequest 객체 분리
MoonJeWoong Sep 7, 2023
e6cf263
refactor: InputStream의 보조 스트림은 request 객체 내부로 캡슐화
MoonJeWoong Sep 7, 2023
e9be59d
refactor: httpRequest 에서 cookies 반환 시 방어적 복사 기능 구현
MoonJeWoong Sep 7, 2023
51291fa
refactor: Http11Response 객체 분리
MoonJeWoong Sep 8, 2023
c6cf4b2
refactor: HttpRequest 객체명 Http11Request로 수정
MoonJeWoong Sep 8, 2023
6b3ed16
feat: 501 status enum 추가
MoonJeWoong Sep 8, 2023
e4c9fb5
feat: Servlet 인터페이스 및 AbstractServlet 클래스 구현
MoonJeWoong Sep 8, 2023
23c3c39
feat: LoginServlet 기능 분리 및 구현
MoonJeWoong Sep 8, 2023
384b755
feat: RegisterServlet 기능 분리 및 구현
MoonJeWoong Sep 8, 2023
ae3ac59
feat: 서블릿 컨테이너 Context 클래스 구현 및 DefaultServlet 구현
MoonJeWoong Sep 9, 2023
4aa7b93
feat: 응답 메세지 바디 생성 메서드 공통 Util 클래스로 분리
MoonJeWoong Sep 10, 2023
24ec969
style: catalina, coyote 패키지 역할 별 분리
MoonJeWoong Sep 10, 2023
84bb261
test: thread 관련 학습 테스트 진행
MoonJeWoong Sep 10, 2023
7f81abc
feat: tomcat connector 쓰레드 풀 설정 추가
MoonJeWoong Sep 10, 2023
7d922b6
feat: 로그인 session 동시성 관리를 위한 ConcurrentHashMap 사용
MoonJeWoong Sep 10, 2023
77de1e6
feat: HttpHeader enum 작성
MoonJeWoong Sep 10, 2023
5016190
feat: Connector 상수 분리
MoonJeWoong Sep 12, 2023
055e620
feat: Connector 쓰레드 풀 재설정
MoonJeWoong Sep 12, 2023
881b66d
refactor: Servlet 메서드 순서 변경
MoonJeWoong Sep 12, 2023
ad63ded
refactor: AbstractServlet 상수 분리
MoonJeWoong Sep 12, 2023
25a5754
refactor: LoginServlet 객체 static 선언 추가
MoonJeWoong Sep 12, 2023
8c99b09
refactor: LoginServlet 객체 내 SessionManager 전역으로 관리하도록 수정
MoonJeWoong Sep 12, 2023
681e7fb
refactor: context를 Tomcat 객체에서 생성 후 의존성 주입하는 방향으로 수정
MoonJeWoong Sep 12, 2023
af65667
refactor: startLine 객체 내 queryString을 queryParams로 수정
MoonJeWoong Sep 12, 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
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 {

Choose a reason for hiding this comment

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

ServletContext를 의미하는 클래스명인가요?? 톰켓에서 Servlet과 Context는 약간 다른 의미로 파악을 하고 있거든요.

Copy link
Author

Choose a reason for hiding this comment

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

catalina 패키지 내에서 servlet context 역할을 수행하는 객체 클래스 네이밍을 context라고 사용하더라구요! 그래서 해당 부분을 반영해서 작성해본 네이밍이었습니다 ㅎㅎ...

Choose a reason for hiding this comment

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

실제 톰캣 구조도 공부하신 것 같은데, catalina 패키지 내의 Context, Container, ContainerServlet, Servlet 이 네 객체의 차이에 대해 여쭤봐도 될까요?? 정말 간단하게라도 답변해주시면 감사하겠습니다 🙇‍♂️


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);
kdkdhoho marked this conversation as resolved.
Show resolved Hide resolved
}
}

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