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단계] 루카(백경환) 미션 제출합니다. #489

Merged
merged 22 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
53bc8ba
feat: RequestUri 구현
dooboocookie Sep 9, 2023
6af43a0
test: RequestLine 테스트 추가
dooboocookie Sep 9, 2023
16b4559
test: Request 테스트 추가
dooboocookie Sep 9, 2023
d8321b2
test: RequestParameters 테스트 추가
dooboocookie Sep 9, 2023
b0f0ae3
test: Headers 테스트 추가
dooboocookie Sep 9, 2023
dfd1221
test: StatusLine 테스트 추가
dooboocookie Sep 9, 2023
3e93477
test: Response 테스트 보완 및 리팩토링
dooboocookie Sep 10, 2023
b364094
feat: Response가 Cookie 추가하는 기능 갖도록 수정
dooboocookie Sep 10, 2023
b1db534
chore: 코드 스타일 정리 및 임포트문 정리
dooboocookie Sep 10, 2023
be4949d
refactor: 리퀘스트 파라미터 Optional로 반환하도록 변경
dooboocookie Sep 10, 2023
5328934
refactor: Controller 구현
dooboocookie Sep 10, 2023
11656cd
feat: ThreadPool 설정 추가
dooboocookie Sep 11, 2023
5224c13
feat: ConcurrentHashMap 변경
dooboocookie Sep 11, 2023
0a74fe5
chore: 코드 컨벤션 추가
dooboocookie Sep 11, 2023
1f109f6
chore: 리다이렉트 리턴 안하던 과정 추가
dooboocookie Sep 11, 2023
8153bdc
chore: 코드 스멜 제거
dooboocookie Sep 11, 2023
b05c414
fix: POST / 요청 405 응답으로 수정
dooboocookie Sep 12, 2023
73616d0
refactor: Response에서 401, 404 응답 세팅하는 메서드 삭제
dooboocookie Sep 12, 2023
549b696
refactor: 없는 정보는 404응답 주도록 수정
dooboocookie Sep 12, 2023
941a808
fix: 이미 요청이 SessionId를 포함하고 있고 그 SessionId를 갱신하지 않았다면 SessionId를 갱신하지…
dooboocookie Sep 12, 2023
de558a1
test: SoftAssertions로 수정
dooboocookie Sep 12, 2023
b16b372
chore: 컨벤션 수정
dooboocookie 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
9 changes: 9 additions & 0 deletions tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package nextstep;

import nextstep.jwp.controller.HomeController;
import nextstep.jwp.controller.LoginController;
import nextstep.jwp.controller.RegisterController;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.controller.ControllerMapper;

public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();

ControllerMapper.register("/", new HomeController());
ControllerMapper.register("/login", new LoginController());
ControllerMapper.register("/register", new RegisterController());

tomcat.start();
}
}
20 changes: 20 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/HomeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package nextstep.jwp.controller;

import org.apache.coyote.controller.AbstractController;
import org.apache.coyote.http11.request.Request;
import org.apache.coyote.http11.response.Response;

import static org.apache.coyote.http11.response.StatusCode.METHOD_NOT_ALLOWED;

public class HomeController extends AbstractController {

@Override
protected void doPost(final Request request, final Response response) {
response.setStatusCode(METHOD_NOT_ALLOWED);
}

@Override
protected void doGet(final Request request, final Response response) {
response.writeBody("Hello world!");
}
}
62 changes: 62 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.controller.AbstractController;
import org.apache.coyote.http11.request.Request;
import org.apache.coyote.http11.request.Session;
import org.apache.coyote.http11.response.Response;
import org.apache.coyote.http11.response.StatusCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;

import static org.apache.coyote.http11.response.StatusCode.UNAUTHORIZED;

public class LoginController extends AbstractController {

private static final Logger log = LoggerFactory.getLogger(LoginController.class);

@Override
protected void doPost(final Request request, final Response response) {
final Session session = request.getSession();
final Optional<String> account = request.getParameter("account");

if (account.isEmpty()) {
response.redirect("/login.html");
return;
}

final Optional<User> maybeUser = InMemoryUserRepository.findByAccount(account.get());
if (maybeUser.isEmpty()) {
response.setStatusCode(UNAUTHORIZED);
response.writeStaticResource("/401.html");
return;
}
final User findUser = maybeUser.get();
final Optional<String> password = request.getParameter("password");
if (password.isEmpty() || !findUser.checkPassword(password.get())) {
response.setStatusCode(UNAUTHORIZED);
response.writeStaticResource("/401.html");
return;
}
log.info("user: {}", findUser);

session.setAttribute("user", findUser);

response.redirect("/index.html");
}

@Override
protected void doGet(final Request request, final Response response) {
final Session session = request.getSession();
final User user = (User) session.getAttribute("user");
if (user != null) {
response.redirect("/index.html");
return;
}
response.setStatusCode(StatusCode.CREATED);
response.writeStaticResource("/login.html");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.controller.AbstractController;
import org.apache.coyote.http11.request.Request;
import org.apache.coyote.http11.response.Response;

public class RegisterController extends AbstractController {

@Override
protected void doPost(final Request request, final Response response) {
final String account = request.getParameter("account")
.orElseThrow(() -> new IllegalArgumentException("계정 입력이 잘못되었습니다."));
final String password = request.getParameter("password")
.orElseThrow(() -> new IllegalArgumentException("비밀번호 입력이 잘못되었습니다."));
final String email = request.getParameter("email")
.orElseThrow(() -> new IllegalArgumentException("이메일 입력이 잘못되었습니다."));
Comment on lines +13 to +18
Copy link

Choose a reason for hiding this comment

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

꼼꼼한 예외처리 👍
LoginController에서는 다르게 처리하신 이유가 있으실까요?

Copy link
Author

Choose a reason for hiding this comment

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

        final Optional<User> maybeUser = InMemoryUserRepository.findByAccount(account.get());
        if (maybeUser.isEmpty()) {
            response.setStatusCode(UNAUTHORIZED);
            response.writeStaticResource("/401.html");
            return;
        }
        final User findUser = maybeUser.get();
        final Optional<String> password = request.getParameter("password");
        if (password.isEmpty() || !findUser.checkPassword(password.get())) {
            response.setStatusCode(UNAUTHORIZED);
            response.writeStaticResource("/401.html");
            return;
        }

LoginController는 해당 파라미터가 있냐 없냐에 따라서 이 인증 처리의 어떻게 분기 처리할지 갈리게 되므로 그 자체를 판단하기 위해서 Optional 값이 존재하는지 확인합니다.

하지만 위의 회원가입에서는 그 입력값이 들어오지 않았던 것이므로 예외처리를 하게됩니다.

만약 스프링으로 옮긴다면
LoginController에서 리퀘스트 바디를 바인딩하다 생긴 에러는 익셉션 핸들러에 의해서 401응답을 내려주는 과정을 거쳤을 것 같습니다


final User user = new User(account, password, email);
InMemoryUserRepository.save(user);

response.redirect("/login");
}

@Override
protected void doGet(final Request request, final Response response) {
response.writeStaticResource("/register.html");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}

private InMemoryUserRepository() {}
private InMemoryUserRepository() {
}
}
12 changes: 5 additions & 7 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,12 @@ public interface Manager {
* specified session id (if any); otherwise return <code>null</code>.
*
* @param id The session id for the session to be returned
*
* @exception IllegalStateException if a new session cannot be
* instantiated for any reason
* @exception IOException if an input/output error occurs while
* processing this request
*
* @return the request session or {@code null} if a session with the
* requested ID could not be found
* requested ID could not be found
* @throws IllegalStateException if a new session cannot be
* instantiated for any reason
* @throws IOException if an input/output error occurs while
* processing this request
*/
HttpSession findSession(String id) throws IOException;

Expand Down
21 changes: 18 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,22 +8,36 @@
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

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 long DEFAULT_KEEP_ALIVE_ALIVE = 60;
public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;

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

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

public Connector(final int port, final int acceptCount) {
this.executorService = Executors.newCachedThreadPool();
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
}

public Connector(final int port, final int acceptCount, final int maxThreads) {
final int coreThreadCount = Runtime.getRuntime().availableProcessors() * 2;
final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(DEFAULT_ACCEPT_COUNT);

this.executorService = new ThreadPoolExecutor(coreThreadCount, maxThreads, DEFAULT_KEEP_ALIVE_ALIVE, DEFAULT_TIME_UNIT, workQueue);
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
}
Expand All @@ -39,7 +53,7 @@ private ServerSocket createServerSocket(final int port, final int acceptCount) {
}

public void start() {
var thread = new Thread(this);
final var thread = new Thread(this);
thread.setDaemon(true);
thread.start();
stopped = false;
Expand All @@ -66,14 +80,15 @@ private void process(final Socket connection) {
if (connection == null) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
final var processor = new Http11Processor(connection);
executorService.submit(processor);
}

public void stop() {
stopped = true;
try {
serverSocket.close();
executorService.shutdown();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class Tomcat {
private static final Logger log = LoggerFactory.getLogger(Tomcat.class);

public void start() {
var connector = new Connector();
var connector = new Connector(8080, 100, 250);
connector.start();

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.apache.coyote.controller;

import org.apache.coyote.http11.request.Request;
import org.apache.coyote.http11.response.Response;

import static org.apache.coyote.http11.request.RequestMethod.GET;
import static org.apache.coyote.http11.request.RequestMethod.POST;
import static org.apache.coyote.http11.response.StatusCode.METHOD_NOT_ALLOWED;

public abstract class AbstractController implements Controller {

@Override
public void service(final Request request,
final Response response) {
if (request.getRequestLine().getRequestMethod() == GET) {
doGet(request, response);
return;
}
if (request.getRequestLine().getRequestMethod() == POST) {
doPost(request, response);
return;
}
response.setStatusCode(METHOD_NOT_ALLOWED);
Copy link

Choose a reason for hiding this comment

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

405 에러까지 처리하셨네요 👍👍

}

protected abstract void doPost(final Request request,
final Response response);

protected abstract void doGet(final Request request,
final Response response);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.apache.coyote.controller;

import org.apache.coyote.http11.request.Request;
import org.apache.coyote.http11.response.Response;

public interface Controller {

void service(final Request request, final Response response);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.apache.coyote.controller;

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

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

public class ControllerMapper {

private static final Controller STATIC_RESOURCE_CONTROLLER = new StaticResourceController();

private static final Map<String, Controller> CONTROLLER_MAP = new HashMap<>();

private ControllerMapper() {
}

public static void register(final String path,
final Controller controller) {
CONTROLLER_MAP.put(path, controller);
}

public static Controller getController(final Request request) {
final String path = request.getRequestLine().getRequestPath();
if (CONTROLLER_MAP.containsKey(path)) {
return CONTROLLER_MAP.get(path);
}
return STATIC_RESOURCE_CONTROLLER;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.apache.coyote.controller;

import org.apache.coyote.http11.request.Request;
import org.apache.coyote.http11.response.Response;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;

import static org.apache.coyote.http11.response.StatusCode.*;

public class StaticResourceController extends AbstractController {

@Override
protected void doPost(final Request request, final Response response) {
response.setStatusCode(METHOD_NOT_ALLOWED);
}

@Override
protected void doGet(final Request request, final Response response) {
final ClassLoader classLoader = getClass().getClassLoader();
final String name = "static" + request.getRequestLine().getRequestPath();
final URL fileURL = classLoader.getResource(name);

if (fileURL == null) {
response.setStatusCode(NOT_FOUND);
response.writeStaticResource("/404.html");
return;
}

final URI fileURI;
try {
fileURI = fileURL.toURI();
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}

final StringBuilder stringBuilder = new StringBuilder();
try (final InputStream inputStream = new FileInputStream(Paths.get(fileURI).toFile());
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {

String nextLine;
while ((nextLine = bufferedReader.readLine()) != null) {
stringBuilder.append(nextLine)
.append(System.lineSeparator());
}
} catch (IOException e) {
throw new IllegalStateException(e);
}

response.writeBody(stringBuilder.toString());
}
}
Loading