Skip to content

Commit 87807d2

Browse files
authored
[톰캣 구현하기 3, 4단계] 로이 미션 제출합니다. (#494)
* refactor: 불필요한 필드 값 삭제 * refactor: QueryParamsParser 로직 수정 * refactor: HttpResponse 클래스에 빌더 패턴 적용 * feat: 동시성 구현 * refactor: PathFinder 내 null값 예외 처리 로직 추가 * refactor: 불필요한 스레드 생성 코드 제거 * refactor: 불필요한 Concurrent 자료 구조 제거 * feat: 400에러 페이지 추가 * fix: default max threads 설정 변경
1 parent 112d989 commit 87807d2

File tree

13 files changed

+148
-55
lines changed

13 files changed

+148
-55
lines changed

tomcat/src/main/java/nextstep/jwp/controller/AbstractController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ protected HttpResponse doPost(HttpRequest request) throws Exception {
3333
private HttpResponse defaultInternalServerErrorPage() throws URISyntaxException, IOException {
3434
final Path path = PathFinder.findPath("/500.html");
3535
final String responseBody = new String(Files.readAllBytes(path));
36-
return new HttpResponse(HttpStatus.INTERNAL_SERVER_ERROR, responseBody, ContentType.HTML);
36+
37+
return new HttpResponse.Builder(HttpStatus.INTERNAL_SERVER_ERROR, responseBody, ContentType.HTML).build();
3738
}
3839
}

tomcat/src/main/java/nextstep/jwp/controller/FileController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ protected HttpResponse doGet(HttpRequest request) throws Exception {
2222
var responseBody = new String(Files.readAllBytes(path));
2323
ContentType contentType = ContentType.findContentType(extension);
2424

25-
return new HttpResponse(HttpStatus.OK, responseBody, contentType);
25+
return new HttpResponse.Builder(HttpStatus.OK, responseBody, contentType).build();
2626
}
2727
}

tomcat/src/main/java/nextstep/jwp/controller/HelloController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ public class HelloController extends AbstractController {
1010
@Override
1111
protected HttpResponse doGet(HttpRequest request) {
1212
String responseBody = "Hello world!";
13-
return new HttpResponse(HttpStatus.OK, responseBody, ContentType.HTML);
13+
return new HttpResponse.Builder(HttpStatus.OK, responseBody, ContentType.HTML).build();
1414
}
1515
}

tomcat/src/main/java/nextstep/jwp/controller/LoginController.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@ protected HttpResponse doGet(HttpRequest request) throws Exception {
3131
if (loginUser.isPresent()) {
3232
return redirectByAlreadyLogin(responseBody);
3333
}
34-
return new HttpResponse(HttpStatus.OK, responseBody, ContentType.HTML);
34+
return new HttpResponse.Builder(HttpStatus.OK, responseBody, ContentType.HTML)
35+
.build();
36+
3537
}
3638

3739
private HttpResponse redirectByAlreadyLogin(String responseBody) {
38-
return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/index.html");
40+
return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML)
41+
.redirect("/index.html")
42+
.build();
43+
3944
}
4045

4146
@Override
@@ -58,18 +63,25 @@ private HttpResponse makeLoginResponse(Map<String, String> loginData, final Stri
5863
}
5964

6065
private HttpResponse successLoginResponse(String responseBody, User user) {
61-
HttpResponse httpResponse =
62-
new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/index.html");
6366
Session session = new Session();
6467
session.setAttribute("user", user);
6568
SessionManager.add(session);
66-
httpResponse.addJSessionId(session);
69+
HttpResponse httpResponse =
70+
new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML)
71+
.redirect("index.html")
72+
.addJSessionId(session)
73+
.build();
74+
75+
6776
return httpResponse;
6877
}
6978

7079
private HttpResponse failLoginResponse(String responseBody) {
7180
log.info("로그인 계정 정보가 이상합니다. responseBody={}", responseBody);
72-
return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/401.html");
81+
return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML)
82+
.redirect("/401.html")
83+
.build();
84+
7385
}
7486

7587
private boolean isSuccessLogin(String account, String password) {

tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ protected HttpResponse doGet(HttpRequest request) throws Exception {
3030
String requestUrl = request.getRequestUrl();
3131
Path path = PathFinder.findPath(requestUrl + ".html");
3232
var responseBody = new String(Files.readAllBytes(path));
33-
return new HttpResponse(HttpStatus.OK, responseBody, ContentType.HTML);
33+
return new HttpResponse.Builder(HttpStatus.OK, responseBody, ContentType.HTML)
34+
.build();
35+
3436
}
3537

3638
@Override
@@ -44,7 +46,11 @@ protected HttpResponse doPost(HttpRequest request) throws Exception {
4446
saveUser(registerData);
4547
Path path = PathFinder.findPath(requestUrl + ".html");
4648
String responseBody = new String(Files.readAllBytes(path));
47-
return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/index.html");
49+
return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML)
50+
.redirect("/index.html")
51+
.build();
52+
53+
4854
}
4955

5056
private boolean checkInputForm(final HashMap<String, String> registerData) {
@@ -56,7 +62,10 @@ private boolean checkInputForm(final HashMap<String, String> registerData) {
5662
private HttpResponse generateBadRequestResponse() throws URISyntaxException, IOException {
5763
Path path = PathFinder.findPath("/400.html");
5864
String responseBody = new String(Files.readAllBytes(path));
59-
return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/400.html");
65+
return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML)
66+
.redirect("/400.html")
67+
.build();
68+
6069
}
6170

6271
private void saveUser(HashMap<String, String> registerData) {

tomcat/src/main/java/org/apache/catalina/connector/Connector.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,32 @@
88
import java.io.UncheckedIOException;
99
import java.net.ServerSocket;
1010
import java.net.Socket;
11+
import java.util.concurrent.ExecutorService;
12+
import java.util.concurrent.Executors;
13+
import java.util.concurrent.TimeUnit;
1114

1215
public class Connector implements Runnable {
1316

1417
private static final Logger log = LoggerFactory.getLogger(Connector.class);
1518

1619
private static final int DEFAULT_PORT = 8080;
1720
private static final int DEFAULT_ACCEPT_COUNT = 100;
21+
private static final int DEFAULT_MAX_THREADS = 200;
22+
23+
private static final int TERMINATION_LIMIT_SECONDS = 60;
1824

1925
private final ServerSocket serverSocket;
26+
private final ExecutorService executorService;
2027
private boolean stopped;
2128

2229
public Connector() {
23-
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
30+
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_MAX_THREADS);
2431
}
2532

26-
public Connector(final int port, final int acceptCount) {
33+
public Connector(final int port, final int acceptCount, final int maxThreads) {
2734
this.serverSocket = createServerSocket(port, acceptCount);
2835
this.stopped = false;
36+
this.executorService = Executors.newFixedThreadPool(maxThreads);
2937
}
3038

3139
private ServerSocket createServerSocket(final int port, final int acceptCount) {
@@ -67,15 +75,27 @@ private void process(final Socket connection) {
6775
return;
6876
}
6977
var processor = new Http11Processor(connection);
70-
new Thread(processor).start();
78+
executorService.execute(processor);
7179
}
7280

7381
public void stop() {
7482
stopped = true;
7583
try {
84+
executorService.shutdown();
85+
terminateWhenTimeLimitExceed();
7686
serverSocket.close();
7787
} catch (IOException e) {
7888
log.error(e.getMessage(), e);
89+
} catch (InterruptedException e) {
90+
executorService.shutdownNow();
91+
log.error(e.getMessage(), e);
92+
Thread.currentThread().interrupt();
93+
}
94+
}
95+
96+
private void terminateWhenTimeLimitExceed() throws InterruptedException {
97+
if (!executorService.awaitTermination(TERMINATION_LIMIT_SECONDS, TimeUnit.SECONDS)) {
98+
executorService.shutdownNow();
7999
}
80100
}
81101

tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,6 @@
2121
public class Http11Processor implements Runnable, Processor {
2222

2323
private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);
24-
private static final String ACCOUNT_KEY = "account";
25-
private static final String PASSWORD_KEY = "password";
26-
private static final String EMAIL_KEY = "email";
27-
private static final String INDEX_PAGE = "/index.html";
28-
private static final String LOGIN_PAGE = "/login.html";
29-
private static final String UNAUTHORIZED_PAGE = "/401.html";
30-
private static final String REGISTER_PAGE = "/register.html";
3124

3225
private final Socket connection;
3326

@@ -64,7 +57,10 @@ private HttpResponse makeResponse(HttpRequest httpRequest) throws Exception {
6457
} catch (IllegalArgumentException e) {
6558
Path path = PathFinder.findPath("/400.html");
6659
String responseBody = new String(Files.readAllBytes(path));
67-
return new HttpResponse(HttpStatus.FOUND, responseBody, ContentType.HTML, "/400.html");
60+
return new HttpResponse.Builder(HttpStatus.FOUND, responseBody, ContentType.HTML)
61+
.redirect("/400.html")
62+
.build();
6863
}
64+
6965
}
7066
}

tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,41 @@ public class HttpResponse {
1313
private final Map<String, String> headers;
1414
private final String responseBody;
1515

16-
public HttpResponse(HttpStatus httpStatus, String responseBody,
17-
ContentType contentType, String redirectUrl) {
18-
this.startLine = new ResponseLine(httpStatus);
19-
this.headers = new LinkedHashMap<>();
20-
initHeader(contentType, responseBody, redirectUrl);
21-
this.responseBody = responseBody;
16+
private HttpResponse(Builder builder) {
17+
this.startLine = builder.startLine;
18+
this.headers = builder.headers;
19+
this.responseBody = builder.responseBody;
2220
}
2321

22+
public static class Builder {
23+
private ResponseLine startLine;
24+
private Map<String, String> headers = new LinkedHashMap<>();
25+
private String responseBody;
2426

25-
public HttpResponse(final HttpStatus httpStatus, final String responseBody, final ContentType contentType) {
26-
this.startLine = new ResponseLine(httpStatus);
27-
this.headers = new LinkedHashMap<>();
28-
initHeader(contentType, responseBody);
29-
this.responseBody = responseBody;
30-
}
27+
public Builder(HttpStatus httpStatus, String responseBody, ContentType contentType) {
28+
this.startLine = new ResponseLine(httpStatus);
29+
this.responseBody = responseBody;
30+
initHeader(contentType, responseBody);
31+
}
3132

32-
private void initHeader(final ContentType contentType, final String responseBody, final String redirectUrl) {
33-
initHeader(contentType, responseBody);
34-
headers.put("Location", redirectUrl);
35-
}
33+
public Builder redirect(String redirectUrl) {
34+
headers.put("Location", redirectUrl);
35+
return this;
36+
}
3637

37-
private void initHeader(ContentType contentType, String responseBody) {
38-
headers.put("Content-Type", contentType.getContentType() + ";charset=utf-8");
39-
headers.put("Content-Length", String.valueOf(responseBody.getBytes(StandardCharsets.UTF_8).length));
40-
}
38+
public Builder addJSessionId(Session session) {
39+
headers.put("Set-Cookie", "JSESSIONID=" + session.getId());
40+
return this;
41+
}
42+
43+
public HttpResponse build() {
44+
return new HttpResponse(this);
45+
}
4146

42-
public void addJSessionId(final Session session) {
43-
headers.put("Set-Cookie", "JSESSIONID=" + session.getId());
47+
private void initHeader(ContentType contentType, String responseBody) {
48+
headers.put("Content-Type", contentType.getContentType() + ";charset=utf-8");
49+
headers.put("Content-Length", String.valueOf(responseBody.getBytes(StandardCharsets.UTF_8).length));
50+
}
4451
}
4552

4653
@Override

tomcat/src/main/java/org/apache/coyote/http11/session/Session.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package org.apache.coyote.http11.session;
22

3-
import java.util.HashMap;
43
import java.util.Map;
54
import java.util.UUID;
5+
import java.util.concurrent.ConcurrentHashMap;
66

77
public class Session {
88

99
private final String id;
10-
private final Map<String, Object> values = new HashMap<>();
10+
private final Map<String, Object> values = new ConcurrentHashMap<>();
1111

1212
public Session() {
1313
this.id = UUID.randomUUID().toString();

tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
55

6-
import java.util.HashMap;
76
import java.util.Map;
7+
import java.util.concurrent.ConcurrentHashMap;
88

99
public class SessionManager {
1010

1111
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
1212

13-
private static final Map<String, Session> SESSIONS = new HashMap<>();
13+
private static final Map<String, Session> SESSIONS = new ConcurrentHashMap<>();
1414

1515
public static void add(Session session) {
1616
log.info("session add 완료: {}", session.getId());

0 commit comments

Comments
 (0)