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단계] 베베(최원용) 미션 제출합니다. #417

Merged
merged 25 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
38bce8f
refactor: request 객체 분리
wonyongChoi05 Sep 8, 2023
a5ed403
refactor: 매직넘버 상수화
wonyongChoi05 Sep 8, 2023
63b77a4
refactor: 패키지 구조 변경
wonyongChoi05 Sep 8, 2023
89ebd54
refactor: Status Code와 Redirect URL Enum으로 분리
wonyongChoi05 Sep 8, 2023
fb57afd
refactor: AuthService 메서드 분리
wonyongChoi05 Sep 8, 2023
221dae9
refactor: ResponsePage 패키지 변경
wonyongChoi05 Sep 8, 2023
9789bb4
refactor: Login, Register 서비스, 컨트롤러 분리
wonyongChoi05 Sep 8, 2023
2f58f2a
refactor: 스레드 풀 설정
wonyongChoi05 Sep 8, 2023
2d40278
refactor: null이면 ConcurrentHashMap에 데이터를 넣지 않는다.
wonyongChoi05 Sep 8, 2023
cb615f0
chore: 사용하지 않는 import 제거
wonyongChoi05 Sep 8, 2023
ae1289a
refactor: request, response add prefix http
wonyongChoi05 Sep 10, 2023
353872e
refactor: 로그인이 되어있다면 인덱스 페이지로 리다이렉트
wonyongChoi05 Sep 10, 2023
c4e6719
refactor: controller 추상화
wonyongChoi05 Sep 10, 2023
e03eb85
fix: 테스트케이스 수정
wonyongChoi05 Sep 10, 2023
01f3cbe
refactor: service layer에서는 request와 response의 연관관계를 끊는다.
wonyongChoi05 Sep 10, 2023
19edeb8
chore: 사용하지 않는 import 제거
wonyongChoi05 Sep 10, 2023
b2f16b6
refactor: HttpRequestGenerator 필드 제거
wonyongChoi05 Sep 11, 2023
00e56ac
refactor: 다중 cookie header 설정 지원
wonyongChoi05 Sep 11, 2023
c7d1c44
refactor: RequestHeader 추상화
wonyongChoi05 Sep 11, 2023
ded5f7a
refactor: Session에서 null 대신 Optional 반환
wonyongChoi05 Sep 11, 2023
a871936
refactor: null 검증 로직 제거
wonyongChoi05 Sep 11, 2023
ae90174
fix: 테스트 코드 수정
wonyongChoi05 Sep 11, 2023
255ced3
refactor: cookie 값 Optional로 반환
wonyongChoi05 Sep 12, 2023
ad61f36
refactor: 여러개의 cookie 값 처리
wonyongChoi05 Sep 12, 2023
1b8ab94
chore: 사용하지 않는 메서드 제거
wonyongChoi05 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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -14,8 +22,12 @@ public class Connector implements Runnable {

private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int MAX_THREAD_POOL_SIZE = 250;
private static final int CORE_POOL_SIZE = 10;
private static final Long KEEP_ALIVE_TIME = 60L;
wonyongChoi05 marked this conversation as resolved.
Show resolved Hide resolved

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

public Connector() {
Expand All @@ -24,6 +36,12 @@ public Connector() {

public Connector(final int port, final int acceptCount) {
Copy link
Member

Choose a reason for hiding this comment

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

리뷰라기보단, 저도 궁금했던 부분이라 베베에게도 여쭤보고 싶은 것이 있습니다!

// maxThreads를 추가했다.
public Connector(final Container container, final int port, final int acceptCount, final int maxThreads) {
    // 생성자에서 스레드 풀 생성
}

4단계 미션 요구사항을 보면, Connector 생성자를 다음과 같은 시그니처로 정의하고 있습니다.
여기서 Container 객체는 과연 어떤 역할을 하는 객체일까요?
스레드 관련하여 지식이 거의 없기 때문에 감이 안 잡히네요 ㅎㅎ.. 혹시 베베는 짐작이 가시나요?!

Copy link
Member Author

Choose a reason for hiding this comment

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

Connector 클래스의 생성자에서 파라미터로 받는 Container는 아마 웹 서버에서 사용되는 컨테이너를 나타내는 객체일 것입니다.(서블릿 컨테이너일듯?)

Connector 클래스의 생성자에서 받는 Container 객체는 뭔가 컨테이너와 관련된 설정이나 동작을 구성하고 초기화하는 데 사용될 것 같네요. 구체적인 내용은 코드의 나머지 부분이나 설명을 확인해봐야 알 것 같은데 별도로 제공되지는 않나보군요 ㅠ

Copy link
Member

Choose a reason for hiding this comment

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

아하 .. 이 부분에 대해서는 따로 공부가 필요한 것 같네요 ㅋㅋㅋ 저도 잘 몰라서 사용하지 않았습니다.. 의견 알려주셔서 감사합니다~!

this.serverSocket = createServerSocket(port, acceptCount);
this.threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_THREAD_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(DEFAULT_ACCEPT_COUNT));
this.stopped = false;
}

Expand Down Expand Up @@ -66,13 +84,14 @@ private void process(final Socket connection) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
threadPoolExecutor.execute(processor);
}

public void stop() {
stopped = true;
try {
serverSocket.close();
threadPoolExecutor.shutdown();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
Expand Down
52 changes: 11 additions & 41 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import nextstep.jwp.exception.UncheckedServletException;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.request.body.RequestBody;
import org.apache.coyote.http11.request.handler.RequestHandler;
import org.apache.coyote.http11.request.header.RequestHeader;
import org.apache.coyote.http11.request.line.RequestLine;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.HttpRequestGenerator;
import org.apache.coyote.http11.request.RequestHandler;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpResponseGenerator;
import org.apache.coyote.http11.response.ResponseEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -24,7 +21,7 @@ public class Http11Processor implements Runnable, Processor {
private static final Logger LOG = LoggerFactory.getLogger(Http11Processor.class);

private final Socket connection;
private final HttpResponseGenerator httpResponseGenerator = new HttpResponseGenerator();
private final HttpResponseGenerator responseGenerator = new HttpResponseGenerator();
private final RequestHandler requestHandler = new RequestHandler();

public Http11Processor(Socket connection) {
Expand All @@ -42,44 +39,17 @@ public void process(final Socket connection) {
try (final InputStream inputStream = connection.getInputStream();
final OutputStream outputStream = connection.getOutputStream();
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
final String firstLine = bufferedReader.readLine();
if (firstLine == null) {
return;
}
final String response = getResponse(bufferedReader, firstLine);

final HttpRequest httpRequest = HttpRequestGenerator.generate(bufferedReader);
final HttpResponse httpResponse = requestHandler.getResponse(httpRequest);
final String response = responseGenerator.generate(httpResponse);
System.out.println("response = " + response);

outputStream.write(response.getBytes());
outputStream.flush();
} catch (IOException | UncheckedServletException e) {
LOG.error(e.getMessage(), e);
}
}

private String getResponse(BufferedReader bufferedReader, String firstLine) throws IOException {
final RequestLine requestLine = RequestLine.from(firstLine);
final RequestHeader requestHeader = getHeader(bufferedReader);
final RequestBody requestBody = getBody(bufferedReader, requestHeader);
final ResponseEntity responseEntity = requestHandler.getResponse(requestLine, requestHeader, requestBody);
return httpResponseGenerator.generate(responseEntity);
}

private RequestHeader getHeader(final BufferedReader bufferedReader) throws IOException {
List<String> requestHeaders = new ArrayList<>();
for (String line = bufferedReader.readLine(); !"".equals(line); line = bufferedReader.readLine()) {
requestHeaders.add(line);
}
return RequestHeader.from(requestHeaders);
}

private RequestBody getBody(final BufferedReader bufferedReader, final RequestHeader requestHeader)
throws IOException {
List<String> contentLengths = requestHeader.headers().get("Content-Length");
if (contentLengths == null) {
return RequestBody.from(null);
}
int contentLength = Integer.parseInt(contentLengths.get(0));
char[] buffer = new char[contentLength];
bufferedReader.read(buffer, 0, contentLength);
return RequestBody.from(new String(buffer));
}

}

This file was deleted.

15 changes: 13 additions & 2 deletions tomcat/src/main/java/org/apache/coyote/http11/auth/Cookie.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static java.util.Collections.EMPTY_MAP;
import static java.util.stream.Collectors.collectingAndThen;
Expand Down Expand Up @@ -32,12 +33,22 @@ public static Cookie from(final List<String> elements) {
));
}

public Cookie() {
}

public void setSession(Session session) {
elements.put("JSESSIONID", session.getId());
}

public void put(final String key, final String value) {
elements.put(key, value);
}

public String get(final String key) {
return elements.get(key);
public Optional<String> get(final String key) {
if (key == null) {
return Optional.empty();
}
return Optional.ofNullable(elements.get(key));
}

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

import java.util.Optional;
import java.util.UUID;
import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.http11.request.line.Protocol;
import org.apache.coyote.http11.response.HttpResponse;

import static org.apache.coyote.http11.response.ResponsePage.INDEX_PAGE;
import static org.apache.coyote.http11.response.ResponsePage.LOGIN_PAGE;
import static org.apache.coyote.http11.response.ResponsePage.UNAUTHORIZED_PAGE;

public class LoginService {

public static final String COOKIE_KEY = "JSESSIONID";

private final SessionRepository sessionRepository = new SessionRepository();

public HttpResponse getLoginViewResponse(Cookie cookie, Protocol protocol) {
Optional<String> cookieOption = cookie.get(COOKIE_KEY);
if (cookieOption.isEmpty()) {
return HttpResponse.getCookieNullResponseEntity(protocol, LOGIN_PAGE);
}
final Optional<Session> session = sessionRepository.getSession(cookieOption.get());
if (session.isEmpty()) {
return HttpResponse.getCookieNullResponseEntity(protocol, LOGIN_PAGE);
}
return HttpResponse.getCookieNullResponseEntity(protocol, INDEX_PAGE);
}

public HttpResponse getLoginOrElseUnAuthorizedResponse(Protocol protocol, String account, String password) {
return InMemoryUserRepository.findByAccount(account)
.filter(user -> user.checkPassword(password))
.map(user -> getSuccessLoginResponse(user, protocol))
.orElseGet(() -> HttpResponse.getCookieNullResponseEntity(protocol, UNAUTHORIZED_PAGE));
}

private HttpResponse getSuccessLoginResponse(final User user, final Protocol protocol) {
final String uuid = UUID.randomUUID().toString();
final Session session = Session.from(uuid);
session.setAttribute("user", user);
sessionRepository.create(session);
Cookie cookie = new Cookie();
cookie.setSession(session);
return HttpResponse.getResponseEntity(protocol, cookie, INDEX_PAGE);
}

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

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.http11.request.line.Protocol;
import org.apache.coyote.http11.response.HttpResponse;

import static org.apache.coyote.http11.response.ResponsePage.CONFLICT_PAGE;
import static org.apache.coyote.http11.response.ResponsePage.INDEX_PAGE;

public class RegisterService {

public HttpResponse getIndexOrConflictResponse(String account, String password, String email, Protocol protocol) {
if (InMemoryUserRepository.findByAccount(account).isPresent()) {
return HttpResponse.getCookieNullResponseEntity(protocol, CONFLICT_PAGE);
}

InMemoryUserRepository.save(new User(account, password, email));
return HttpResponse.getCookieNullResponseEntity(protocol, INDEX_PAGE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import java.util.HashMap;
import java.util.Map;
import nextstep.jwp.model.User;

public class Session {

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

private Session(final String id) {
this.id = id;
Expand All @@ -16,7 +17,7 @@ public static Session from(String id) {
return new Session(id);
}

public void setAttribute(final String key, final Object value) {
public void setAttribute(final String key, final User value) {
items.put(key, value);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package org.apache.coyote.http11.auth;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

public class SessionRepository {

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

public static void create(Session session) {
SESSIONS.put(session.getId(), session);
}

public Session getSession(String id) {
return SESSIONS.get(id);
}

public static void clearSessions() {
SESSIONS.clear();
public Optional<Session> getSession(String id) {
if (id == null) {
return Optional.ofNullable(null);
}
return Optional.ofNullable(SESSIONS.get(id));
}

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

import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.line.HttpMethod;
import org.apache.coyote.http11.response.HttpResponse;

public abstract class AbstractController implements Controller {

@Override
public HttpResponse service(final HttpRequest request, final HttpResponse response) {
if (request.methodIsEqualTo(HttpMethod.GET)) {
return doGet(request, response);
}
if (request.methodIsEqualTo(HttpMethod.POST)) {
return doPost(request, response);
}
return null;
}

protected abstract HttpResponse doPost(final HttpRequest request, final HttpResponse response);

protected abstract HttpResponse doGet(final HttpRequest request, final HttpResponse response);

}
Loading