diff --git a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java index 94fbf34b85..8671455159 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java @@ -33,6 +33,7 @@ protected HttpResponse doPost(HttpRequest request) throws Exception { private HttpResponse defaultInternalServerErrorPage() throws URISyntaxException, IOException { final Path path = PathFinder.findPath("/500.html"); final String responseBody = new String(Files.readAllBytes(path)); - return new HttpResponse(HttpStatus.INTERNAL_SERVER_ERROR, responseBody, ContentType.HTML); + + return new HttpResponse.Builder(HttpStatus.INTERNAL_SERVER_ERROR, responseBody, ContentType.HTML).build(); } } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/FileController.java b/tomcat/src/main/java/nextstep/jwp/controller/FileController.java index ae6175e433..e0458cfb3a 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/FileController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/FileController.java @@ -22,6 +22,6 @@ protected HttpResponse doGet(HttpRequest request) throws Exception { var responseBody = new String(Files.readAllBytes(path)); ContentType contentType = ContentType.findContentType(extension); - return new HttpResponse(HttpStatus.OK, responseBody, contentType); + return new HttpResponse.Builder(HttpStatus.OK, responseBody, contentType).build(); } } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/HelloController.java b/tomcat/src/main/java/nextstep/jwp/controller/HelloController.java index ca50e0defd..d64973ab62 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/HelloController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/HelloController.java @@ -10,6 +10,6 @@ public class HelloController extends AbstractController { @Override protected HttpResponse doGet(HttpRequest request) { String responseBody = "Hello world!"; - return new HttpResponse(HttpStatus.OK, responseBody, ContentType.HTML); + return new HttpResponse.Builder(HttpStatus.OK, responseBody, ContentType.HTML).build(); } } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java index 5360d81f0a..93f8cb1253 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -31,11 +31,16 @@ protected HttpResponse doGet(HttpRequest request) throws Exception { if (loginUser.isPresent()) { return redirectByAlreadyLogin(responseBody); } - return new HttpResponse(HttpStatus.OK, responseBody, ContentType.HTML); + return new HttpResponse.Builder(HttpStatus.OK, responseBody, ContentType.HTML) + .build(); + } private HttpResponse redirectByAlreadyLogin(String responseBody) { - return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/index.html"); + return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML) + .redirect("/index.html") + .build(); + } @Override @@ -58,18 +63,25 @@ private HttpResponse makeLoginResponse(Map loginData, final Stri } private HttpResponse successLoginResponse(String responseBody, User user) { - HttpResponse httpResponse = - new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/index.html"); Session session = new Session(); session.setAttribute("user", user); SessionManager.add(session); - httpResponse.addJSessionId(session); + HttpResponse httpResponse = + new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML) + .redirect("index.html") + .addJSessionId(session) + .build(); + + return httpResponse; } private HttpResponse failLoginResponse(String responseBody) { log.info("로그인 계정 정보가 이상합니다. responseBody={}", responseBody); - return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/401.html"); + return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML) + .redirect("/401.html") + .build(); + } private boolean isSuccessLogin(String account, String password) { diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java index 525ac122d2..d1f365af25 100644 --- a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -30,7 +30,9 @@ protected HttpResponse doGet(HttpRequest request) throws Exception { String requestUrl = request.getRequestUrl(); Path path = PathFinder.findPath(requestUrl + ".html"); var responseBody = new String(Files.readAllBytes(path)); - return new HttpResponse(HttpStatus.OK, responseBody, ContentType.HTML); + return new HttpResponse.Builder(HttpStatus.OK, responseBody, ContentType.HTML) + .build(); + } @Override @@ -44,7 +46,11 @@ protected HttpResponse doPost(HttpRequest request) throws Exception { saveUser(registerData); Path path = PathFinder.findPath(requestUrl + ".html"); String responseBody = new String(Files.readAllBytes(path)); - return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/index.html"); + return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML) + .redirect("/index.html") + .build(); + + } private boolean checkInputForm(final HashMap registerData) { @@ -56,7 +62,10 @@ private boolean checkInputForm(final HashMap registerData) { private HttpResponse generateBadRequestResponse() throws URISyntaxException, IOException { Path path = PathFinder.findPath("/400.html"); String responseBody = new String(Files.readAllBytes(path)); - return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/400.html"); + return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML) + .redirect("/400.html") + .build(); + } private void saveUser(HashMap registerData) { diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java index 3b2c4dda7c..7c5f8e379f 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -8,6 +8,9 @@ import java.io.UncheckedIOException; import java.net.ServerSocket; import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; public class Connector implements Runnable { @@ -15,17 +18,22 @@ public class Connector implements Runnable { private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_ACCEPT_COUNT = 100; + private static final int DEFAULT_MAX_THREADS = 200; + + private static final int TERMINATION_LIMIT_SECONDS = 60; 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_THREADS); } - public Connector(final int port, final int acceptCount) { + public Connector(final int port, final int acceptCount, final int maxThreads) { this.serverSocket = createServerSocket(port, acceptCount); this.stopped = false; + this.executorService = Executors.newFixedThreadPool(maxThreads); } private ServerSocket createServerSocket(final int port, final int acceptCount) { @@ -67,15 +75,27 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + executorService.execute(processor); } public void stop() { stopped = true; try { + executorService.shutdown(); + terminateWhenTimeLimitExceed(); serverSocket.close(); } catch (IOException e) { log.error(e.getMessage(), e); + } catch (InterruptedException e) { + executorService.shutdownNow(); + log.error(e.getMessage(), e); + Thread.currentThread().interrupt(); + } + } + + private void terminateWhenTimeLimitExceed() throws InterruptedException { + if (!executorService.awaitTermination(TERMINATION_LIMIT_SECONDS, TimeUnit.SECONDS)) { + executorService.shutdownNow(); } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 7b3d6f9033..bbe55775f1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -21,13 +21,6 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final String ACCOUNT_KEY = "account"; - private static final String PASSWORD_KEY = "password"; - private static final String EMAIL_KEY = "email"; - private static final String INDEX_PAGE = "/index.html"; - private static final String LOGIN_PAGE = "/login.html"; - private static final String UNAUTHORIZED_PAGE = "/401.html"; - private static final String REGISTER_PAGE = "/register.html"; private final Socket connection; @@ -64,7 +57,10 @@ private HttpResponse makeResponse(HttpRequest httpRequest) throws Exception { } catch (IllegalArgumentException e) { Path path = PathFinder.findPath("/400.html"); String responseBody = new String(Files.readAllBytes(path)); - return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/400.html"); + return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML) + .redirect("/400.html") + .build(); } + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index e4318eef7e..f1e05c6d2d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -13,34 +13,41 @@ public class HttpResponse { private final Map headers; private final String responseBody; - public HttpResponse(HttpStatus httpStatus, String responseBody, - ContentType contentType, String redirectUrl) { - this.startLine = new ResponseLine(httpStatus); - this.headers = new LinkedHashMap<>(); - initHeader(contentType, responseBody, redirectUrl); - this.responseBody = responseBody; + private HttpResponse(Builder builder) { + this.startLine = builder.startLine; + this.headers = builder.headers; + this.responseBody = builder.responseBody; } + public static class Builder { + private ResponseLine startLine; + private Map headers = new LinkedHashMap<>(); + private String responseBody; - public HttpResponse(final HttpStatus httpStatus, final String responseBody, final ContentType contentType) { - this.startLine = new ResponseLine(httpStatus); - this.headers = new LinkedHashMap<>(); - initHeader(contentType, responseBody); - this.responseBody = responseBody; - } + public Builder(HttpStatus httpStatus, String responseBody, ContentType contentType) { + this.startLine = new ResponseLine(httpStatus); + this.responseBody = responseBody; + initHeader(contentType, responseBody); + } - private void initHeader(final ContentType contentType, final String responseBody, final String redirectUrl) { - initHeader(contentType, responseBody); - headers.put("Location", redirectUrl); - } + public Builder redirect(String redirectUrl) { + headers.put("Location", redirectUrl); + return this; + } - private void initHeader(ContentType contentType, String responseBody) { - headers.put("Content-Type", contentType.getContentType() + ";charset=utf-8"); - headers.put("Content-Length", String.valueOf(responseBody.getBytes(StandardCharsets.UTF_8).length)); - } + public Builder addJSessionId(Session session) { + headers.put("Set-Cookie", "JSESSIONID=" + session.getId()); + return this; + } + + public HttpResponse build() { + return new HttpResponse(this); + } - public void addJSessionId(final Session session) { - headers.put("Set-Cookie", "JSESSIONID=" + session.getId()); + private void initHeader(ContentType contentType, String responseBody) { + headers.put("Content-Type", contentType.getContentType() + ";charset=utf-8"); + headers.put("Content-Length", String.valueOf(responseBody.getBytes(StandardCharsets.UTF_8).length)); + } } @Override diff --git a/tomcat/src/main/java/org/apache/coyote/http11/session/Session.java b/tomcat/src/main/java/org/apache/coyote/http11/session/Session.java index 6cf0f95bb9..ceb0789e59 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/session/Session.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/session/Session.java @@ -1,13 +1,13 @@ package org.apache.coyote.http11.session; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; public class Session { private final String id; - private final Map values = new HashMap<>(); + private final Map values = new ConcurrentHashMap<>(); public Session() { this.id = UUID.randomUUID().toString(); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java index 0d9034a98c..9ed11fffb9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java @@ -3,14 +3,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class SessionManager { private static final Logger log = LoggerFactory.getLogger(SessionManager.class); - private static final Map SESSIONS = new HashMap<>(); + private static final Map SESSIONS = new ConcurrentHashMap<>(); public static void add(Session session) { log.info("session add 완료: {}", session.getId()); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/PathFinder.java b/tomcat/src/main/java/org/apache/coyote/http11/util/PathFinder.java index 5f4624abf5..206e453e79 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/PathFinder.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/PathFinder.java @@ -9,9 +9,17 @@ public class PathFinder { private static final String RESOURCE_ROOT_DIRECTORY_PATH = "static"; - public static Path findPath(String resourceName) throws URISyntaxException { - final URL resource = - PathFinder.class.getClassLoader().getResource(RESOURCE_ROOT_DIRECTORY_PATH + resourceName); - return Paths.get(resource.toURI()); + public static Path findPath(String resourceName) { + try { + URL resource = + PathFinder.class.getClassLoader().getResource(RESOURCE_ROOT_DIRECTORY_PATH + resourceName); + + if (resource == null) { + throw new IllegalArgumentException("Resource Not Found: " + resourceName); + } + return Paths.get(resource.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException("URI Syntax Exception : " + resourceName); + } } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/util/QueryParamsParser.java b/tomcat/src/main/java/org/apache/coyote/http11/util/QueryParamsParser.java index a15d5a0f55..0204fa802c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/util/QueryParamsParser.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/util/QueryParamsParser.java @@ -21,8 +21,9 @@ public static HashMap parseByBody(final String requestBody) { private static void initData(final Map data, final String[] params) { for (final String param : params) { - final String paramInfo = param.split("=")[PARAM_INFO_INDEX]; - final String paramValue = param.split("=")[PARAM_VALUE_INDEX]; + String[] split = param.split("="); + final String paramInfo = split[PARAM_INFO_INDEX]; + final String paramValue = split[PARAM_VALUE_INDEX]; data.put(paramInfo, paramValue); } } diff --git a/tomcat/src/main/resources/static/400.html b/tomcat/src/main/resources/static/400.html new file mode 100644 index 0000000000..9e46139a97 --- /dev/null +++ b/tomcat/src/main/resources/static/400.html @@ -0,0 +1,39 @@ + + + + + + Error 400 - Bad Request + + + +
+

Error 400 - Bad Request

+

The request you made was malformed or invalid.

+
+ +