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단계] 채채(신채원) 미션 제출합니다 #485

Merged
merged 12 commits into from
Sep 14, 2023
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@
- [x] Session 구현하기
- [x] 로그인된 상태에서 /login 페이지에 HTTP GET method로 접근하면 이미 로그인한 상태니 index.html 페이지로 리다이렉트 처리한다.
- [x] 로그인에 성공하면 Session 객체의 값으로 User 객체를 저장해보자.

## step3
- [x] 요청 응답의 구조를 공부하여 코드를 리팩터링 한다.
- [x] 스레드풀을 적용시킨다.
11 changes: 5 additions & 6 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import org.apache.catalina.session.HttpSession;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
Expand All @@ -27,9 +26,9 @@ public interface Manager {
/**
* Add this Session to the set of active Sessions for this Manager.
*
* @param session Session to be added
* @param httpSession Session to be added
*/
void add(HttpSession session);
void add(HttpSession httpSession);

/**
* Return the active Session, associated with this Manager, with the
Expand All @@ -50,7 +49,7 @@ public interface Manager {
/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
* @param httpSession Session to be removed
*/
void remove(HttpSession session);
void remove(HttpSession httpSession);
}
46 changes: 43 additions & 3 deletions tomcat/src/main/java/org/apache/catalina/connector/Connector.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,39 @@
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

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 = 250;

private final ServerSocket serverSocket;
private final ExecutorService executorService;
private boolean stopped;

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

public Connector(final int port, final int acceptCount) {
public Connector(final int port,
final int acceptCount,
final int maxTread) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.executorService = new ThreadPoolExecutor(
checkMaxThread(maxTread),
checkMaxThread(maxTread),
0,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(acceptCount)
);
}

private ServerSocket createServerSocket(final int port, final int acceptCount) {
Expand All @@ -38,6 +53,10 @@ private ServerSocket createServerSocket(final int port, final int acceptCount) {
}
}

private int checkMaxThread(int maxThread) {
return Math.max(maxThread, DEFAULT_MAX_THREAD);
}

public void start() {
var thread = new Thread(this);
thread.setDaemon(true);
Expand Down Expand Up @@ -67,11 +86,12 @@ private void process(final Socket connection) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
executorService.execute(processor);
}

public void stop() {
stopped = true;
gracefulShutdown();

Choose a reason for hiding this comment

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

👍

try {
serverSocket.close();
} catch (IOException e) {
Expand All @@ -92,4 +112,24 @@ private int checkPort(final int port) {
private int checkAcceptCount(final int acceptCount) {
return Math.max(acceptCount, DEFAULT_ACCEPT_COUNT);
}

private void gracefulShutdown() {
executorService.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
log.error("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
executorService.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}

}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package org.apache.coyote.http11.session;
package org.apache.catalina.session;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class Session {
public class HttpSession {

private final String id;
private final Map<String, Object> values = new HashMap<>();
private final Map<String, Object> values;

public Session() {
public HttpSession(final String name, final Object value) {
this.id = UUID.randomUUID().toString();
values = new HashMap<>();
values.put(name, value);

Choose a reason for hiding this comment

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

생성자에서 값을 추가하는 이유가 무엇인가요?
밑에 작성해둔 addAttribute를 사용해도 좋을것같아요

Copy link
Author

Choose a reason for hiding this comment

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

생성자를 새로 생성할때마다 새로운 UUID를 주고 그외의 상황에서는 id를 바꾸거나 생성하지 않도록 하기 위해서 생성자에서 진행을 하였습니다! 만들어둔 매서드를 사용하는것이 좋겠네요..!

}

public String getId() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.apache.catalina.session;

import org.apache.catalina.Manager;

import java.util.HashMap;
import java.util.Map;

public class SessionManager implements Manager {

// static!
private static final Map<String, HttpSession> sessions = new HashMap<>();

Choose a reason for hiding this comment

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

  1. 동시성 컬렉션 사용하기

라는 요구사항이 있었는데 한번 확인 해보시면 좋을것같아요 🤔


@Override
public void add(HttpSession httpSession) {
sessions.put(httpSession.getId(), httpSession);
}

@Override
public HttpSession findSession(final String id) {
return sessions.get(id);
}

@Override
public void remove(HttpSession httpSession) {
sessions.remove(httpSession.getId());
}

public static boolean isExist(final String id) {
return sessions.containsKey(id);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ private boolean isSameExtension(String extension) {
public String getContentType() {
return contentType;
}

}
14 changes: 10 additions & 4 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import nextstep.jwp.exception.UncheckedServletException;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.ResponseMaker;
import org.apache.coyote.http11.response.Controller;
import org.apache.coyote.http11.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -35,11 +36,12 @@ public void process(final Socket connection) {
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

final HttpRequest httpRequest = new HttpRequest(reader);
final HttpResponse httpResponse = new HttpResponse();

final ResponseMaker responseMaker = RequestMappingHandler.findResponseMaker(httpRequest);
final String response = responseMaker.createResponse(httpRequest);
final Controller controller = RequestMappingHandler.findController(httpRequest);
controller.service(httpRequest, httpResponse);

outputStream.write(response.getBytes());
outputStream.write(httpResponse.getResponse().getBytes());
outputStream.flush();

} catch (IOException | UncheckedServletException e) {
Expand All @@ -48,4 +50,8 @@ public void process(final Socket connection) {
throw new RuntimeException(e);
}
}

// 쿠키값 빼내올때 문제점
// doPost, doGet 추가수정
// 세션 전략 세우기
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,32 @@

public enum RequestMappingHandler {

STRING(RequestMappingHandler::isStringGetUrl, new HelloResponseMaker()),
FILE(RequestMappingHandler::isFileGetUrl, new FileGetResponseMaker()),
LOGIN_GET(RequestMappingHandler::isLoginGetUrl, new LoginGetResponseMaker()),
LOGIN_POST(RequestMappingHandler::isLoginPostUrl, new LoginPostResponseMaker()),
REGISTER_GET(RequestMappingHandler::isRegisterGetUrl, new RegisterGetResponseMaker()),
REGISTER_POST(RequestMappingHandler::isRegisterPostUrl, new RegisterPostResponseMaker());
STRING(RequestMappingHandler::isStringGetUrl, new HelloController()),
FILE(RequestMappingHandler::isFileGetUrl, new FileGetController()),
LOGIN_GET(RequestMappingHandler::isLoginGetUrl, new LoginGetController()),
LOGIN_POST(RequestMappingHandler::isLoginPostUrl, new LoginPostController()),
REGISTER_GET(RequestMappingHandler::isRegisterGetUrl, new RegisterGetController()),
REGISTER_POST(RequestMappingHandler::isRegisterPostUrl, new RegisterPostController());

private static final Pattern FILE_REGEX = Pattern.compile(".+\\.(html|css|js|ico)");

private final BiPredicate<String, HttpMethod> condition;
private final ResponseMaker responseMaker;
private final Controller controller;

RequestMappingHandler(final BiPredicate<String, HttpMethod> condition, final ResponseMaker responseMaker) {
RequestMappingHandler(final BiPredicate<String, HttpMethod> condition, final Controller controller) {
this.condition = condition;
this.responseMaker = responseMaker;
this.controller = controller;
}

public static ResponseMaker findResponseMaker(final HttpRequest request) {
public static Controller findController(final HttpRequest request) {
String resourcePath = request.getRequestLine().getRequestUrl();
HttpMethod requestMethod = request.getRequestLine().getHttpMethod();

return Arrays.stream(values())
.filter(value -> value.condition.test(resourcePath, requestMethod))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("잘못된 url 요청입니다."))
.getResponseMaker();
.getController();
}

public static boolean isFileGetUrl(final String resourcePath, final HttpMethod requestMethod) {
Expand All @@ -62,8 +62,8 @@ public static boolean isRegisterPostUrl(final String requestUrl, final HttpMetho
return requestUrl.startsWith("/register") && requestMethod == HttpMethod.POST;
}

public ResponseMaker getResponseMaker() {
return responseMaker;
public Controller getController() {
return controller;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.apache.coyote.http11.response;

import org.apache.coyote.http11.request.HttpRequest;

import java.io.IOException;

public interface Controller {
void service(final HttpRequest request,
final HttpResponse response) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.apache.coyote.http11.response;

import org.apache.coyote.http11.ContentType;
import org.apache.coyote.http11.request.HttpRequest;

public class FileGetController implements Controller {

@Override
public void service(final HttpRequest request,
final HttpResponse response) {
final String resourcePath = request.getRequestLine().getRequestUrl();
final String responseBody = ResourceResolver.resolve(resourcePath);

response.setStatusCode(StatusCode.OK);
response.setResponseBody(ResourceResolver.resolve(resourcePath));
response.addHeader("Content-Type", ContentType.from(resourcePath).getContentType() + ";charset=utf-8");
response.addHeader("Content-Length",String.valueOf(responseBody.getBytes().length));
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.coyote.http11.response;

import org.apache.coyote.http11.ContentType;
import org.apache.coyote.http11.request.HttpRequest;

public class HelloController implements Controller {

@Override
public void service(final HttpRequest request,
final HttpResponse response) {
final String responseBody = "Hello world!";

response.setStatusCode(StatusCode.OK);
response.setResponseBody(responseBody);
response.addHeader("Content-Type", ContentType.HTML.getContentType() + ";charset=utf-8");
response.addHeader("Content-Length", String.valueOf(responseBody.getBytes().length));
}

}

This file was deleted.

Loading