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

[톰캣 구현하기 1, 2단계] 애쉬(정설희) 미션 제출합니다. #338

Merged
merged 26 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ace4a6f
style: 코드 포매팅
xxeol2 Sep 3, 2023
d52fc20
feat: http 서버 구현
xxeol2 Sep 3, 2023
e0e14af
refactor: HttpRequest / HttpResponse 도입
xxeol2 Sep 3, 2023
2cd41e2
feat: login시 redirect 기능 구현
xxeol2 Sep 3, 2023
323f043
feat: login 메서드 post로 수정
xxeol2 Sep 3, 2023
d2ede1b
refactor: 응답/요청 객체 이름에서 Http 제거
xxeol2 Sep 3, 2023
f436723
refactor: buffer 읽어오는 방식 수정
xxeol2 Sep 3, 2023
8a4dbf8
refactor: 메서드 추출
xxeol2 Sep 3, 2023
688b5b2
feat: IllegalArgumentException 발생시 NotFound 응답 return
xxeol2 Sep 3, 2023
7679807
feat: 회원가입 기능 구현
xxeol2 Sep 3, 2023
c6f0672
feat: 로그인시 쿠키 set
xxeol2 Sep 3, 2023
8148e2d
feat: 세션 활용
xxeol2 Sep 3, 2023
cdfbf0d
refactor: Cookies 클래스 네이밍 수정
xxeol2 Sep 3, 2023
4cc32ec
refactor: HashMap 생성자에서 초기화
xxeol2 Sep 3, 2023
7b2e322
refactor: session attribute 삭제
xxeol2 Sep 3, 2023
e1d25ef
refactor: session의 attribute로 user저장
xxeol2 Sep 3, 2023
dd7b3cc
refactor: Handler 추상클래스로 변경
xxeol2 Sep 4, 2023
817fa61
feat: 400 에러 페이지 추가
xxeol2 Sep 4, 2023
7c7b720
refactor: addCookie 메서드 추가
xxeol2 Sep 4, 2023
7776940
refactor: 메서드 추출
xxeol2 Sep 4, 2023
3ac5300
refactor: HttpMethod from을 활용하도록 수정
xxeol2 Sep 4, 2023
57d1cd9
refactor: InMemoryUserRepository에서 id 저장 로직 추가
xxeol2 Sep 4, 2023
0109872
refactor: defaultHandler requestPath 수정
xxeol2 Sep 6, 2023
f2d5a75
refactor: HandlerMapping 방식 Map으로 수정
xxeol2 Sep 7, 2023
1c30838
refactor: tomcat 구조에 맞게 리팩토링
xxeol2 Sep 7, 2023
f17aa67
refactor: 스트림을 활용한 리팩토링
xxeol2 Sep 7, 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
25 changes: 21 additions & 4 deletions tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
package nextstep;

import java.util.Set;

import org.apache.catalina.HandlerMapping;
import org.apache.catalina.startup.Tomcat;

import nextstep.jwp.handler.BaseRequestHandler;
import nextstep.jwp.handler.LoginRequestHandler;
import nextstep.jwp.handler.RegisterRequestHandler;
import nextstep.jwp.handler.StaticContentRequestHandler;

public class Application {

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

final var handlers = Set.of(
new BaseRequestHandler(),
new LoginRequestHandler(),
new RegisterRequestHandler()
);
final var defaultHandler = new StaticContentRequestHandler();
final var handlerMapping = new HandlerMapping(handlers, defaultHandler);
Comment on lines +17 to +23
Copy link

Choose a reason for hiding this comment

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

어플리케이션 실행할 때 주입해주는 거 너무 좋은데요 ..
애쉬 코드 모두 훔치고 싶어지네요 🥹


final var tomcat = new Tomcat(handlerMapping);
tomcat.start();
}
}
31 changes: 17 additions & 14 deletions tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
package nextstep.jwp.db;

import nextstep.jwp.model.User;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import nextstep.jwp.model.User;

public class InMemoryUserRepository {

private static final Map<String, User> database = new ConcurrentHashMap<>();
private static final Map<String, User> database = new ConcurrentHashMap<>();
private static final AtomicLong idCounter = new AtomicLong();
Copy link

Choose a reason for hiding this comment

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

AtomicLong 배워갑니다 😯😯


static {
final User user = new User(1L, "gugu", "password", "[email protected]");
database.put(user.getAccount(), user);
}
static {
final User user = new User(idCounter.incrementAndGet(), "gugu", "password", "[email protected]");
database.put(user.getAccount(), user);
}

public static void save(User user) {
database.put(user.getAccount(), user);
}
private InMemoryUserRepository() {
}

public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}
public static void save(User user) {
database.put(user.getAccount(), user.addId(idCounter.incrementAndGet()));
}

private InMemoryUserRepository() {}
public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}
}
21 changes: 21 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/BaseRequestHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nextstep.jwp.handler;

import org.apache.catalina.RequestHandler;
import org.apache.coyote.MimeType;
import org.apache.coyote.request.Request;
import org.apache.coyote.response.Response;

public class BaseRequestHandler extends RequestHandler {

private static final String REQUEST_PATH = "/";
private static final String RESPONSE_BODY = "Hello world!";

public BaseRequestHandler() {
super(REQUEST_PATH);
}

@Override
protected Response doGet(final Request request) {
return Response.ok(RESPONSE_BODY, MimeType.HTML);
}
}
83 changes: 83 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/LoginRequestHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package nextstep.jwp.handler;

import org.apache.catalina.RequestHandler;
import org.apache.catalina.session.Session;
import org.apache.catalina.session.SessionManager;
import org.apache.coyote.Cookie;
import org.apache.coyote.MimeType;
import org.apache.coyote.request.Request;
import org.apache.coyote.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;

public class LoginRequestHandler extends RequestHandler {

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

private static final String REQUEST_PATH = "/login";
private static final String LOGIN_PAGE_PATH = "/login.html";
private static final String REDIRECT_LOCATION = "/index.html";
Comment on lines +20 to +22
Copy link

Choose a reason for hiding this comment

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

url은 상수 대신 직접 쓰는 게 읽기 편하다고 생각하는 편이긴 한데,
여러 번 쓰이니까 상수로 하는 게 더 안전해 보이네요!


public LoginRequestHandler() {
super(LoginRequestHandler.REQUEST_PATH);
}

@Override
protected Response doGet(final Request request) {
if (isSessionExist(request)) {
return Response.redirect(REDIRECT_LOCATION);
}
return Response.ok(ResourceProvider.provide(LOGIN_PAGE_PATH), MimeType.fromPath(LOGIN_PAGE_PATH));
}

private boolean isSessionExist(final Request request) {
final var sessionId = request.findSession();
if (sessionId == null) {
return false;
}
return SessionManager.findById(sessionId) != null;
}

@Override
protected Response doPost(final Request request) {
final var account = request.findBodyField("account");
final var password = request.findBodyField("password");
return login(account, password);
}

private Response login(final String account, final String password) {
if (isInvalidInput(account, password)) {
return Response.badRequest();
}

final var user = InMemoryUserRepository.findByAccount(account);
if (user.isEmpty() || !user.get().checkPassword(password)) {
return Response.unauthorized();
}

final var session = createSession(user.get());
final var cookie = Cookie.session(session.getId());

log.info("[LOGIN SUCCESS] account: {}", account);
return Response.redirect(REDIRECT_LOCATION)
.addCookie(cookie);
}

private boolean isInvalidInput(final String account, final String password) {
return isBlank(account) || isBlank(password);
}

private boolean isBlank(final String value) {
return value == null || value.isBlank();
}

private Session createSession(final User user) {
final var session = Session.create();
session.setAttribute("user", user);
SessionManager.add(session);
return session;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package nextstep.jwp.handler;

import org.apache.catalina.RequestHandler;
import org.apache.coyote.MimeType;
import org.apache.coyote.request.Request;
import org.apache.coyote.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;

public class RegisterRequestHandler extends RequestHandler {

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

private static final String REQUEST_PATH = "/register";
private static final String PAGE_PATH = "/register.html";

public RegisterRequestHandler() {
super(REQUEST_PATH);
}

@Override
protected Response doGet(final Request request) {
return Response.ok(ResourceProvider.provide(PAGE_PATH), MimeType.fromPath(PAGE_PATH));
}

@Override
protected Response doPost(final Request request) {
final var account = request.findBodyField("account");
final var password = request.findBodyField("password");
final var email = request.findBodyField("email");

return register(account, password, email);
}

private Response register(final String account, final String password, final String email) {
if (isInvalidInput(account, password, email) || isDuplicatedAccount(account)) {
return Response.badRequest();
}

InMemoryUserRepository.save(new User(account, password, email));
log.info("[REGISTER SUCCESS] account: {}", account);
return Response.redirect("/index.html");
}

private boolean isInvalidInput(final String account, final String password, final String email) {
return isBlank(account) || isBlank(password) || isBlank(email);
}

private boolean isBlank(final String value) {
return value == null || value.isBlank();
}

private boolean isDuplicatedAccount(final String account) {
return InMemoryUserRepository.findByAccount(account).isPresent();
}
}
31 changes: 31 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/ResourceProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package nextstep.jwp.handler;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class ResourceProvider {

private static final String RESOURCE_ROOT_PATH = "static";

public static String provide(final String path) {
final var resource = ResourceProvider.class.getClassLoader()
.getResource(RESOURCE_ROOT_PATH + path);

if (resource == null) {
throw new IllegalArgumentException("해당하는 자원을 찾을 수 없습니다.");
}

final var file = new File(resource.getPath()).toPath();

if (Files.isRegularFile(file)) {
try {
return new String(Files.readAllBytes(file));
} catch (IOException e) {
throw new IllegalArgumentException("파일을 읽을 수 없습니다.");
}
}

throw new IllegalArgumentException("올바르지 않은 파일입니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package nextstep.jwp.handler;

import org.apache.catalina.RequestHandler;
import org.apache.coyote.MimeType;
import org.apache.coyote.request.Request;
import org.apache.coyote.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StaticContentRequestHandler extends RequestHandler {

private static final Logger log = LoggerFactory.getLogger(StaticContentRequestHandler.class);
private static final String REQUEST_PATH = "/*";

public StaticContentRequestHandler() {
super(REQUEST_PATH);
}

@Override
public Response handle(final Request request) {
final String requestPath = request.getPath();
if (requestPath == null) {
return Response.notFound();
}
try {
final var responseBody = ResourceProvider.provide(requestPath);
final var mimeType = MimeType.fromPath(requestPath);
return Response.ok(responseBody, mimeType);
} catch (IllegalArgumentException e) {
log.warn("{}: {}", request.getPath(), e.getMessage());
return Response.notFound();
}
}
}
14 changes: 9 additions & 5 deletions tomcat/src/main/java/nextstep/jwp/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ public String getAccount() {
return account;
}

public User addId(Long id) {
return new User(id, account, password, email);
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", account='" + account + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
"id=" + id +
", account='" + account + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
}
}
35 changes: 35 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/HandlerMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.apache.catalina;

import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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

public class HandlerMapping {

private final Map<String, RequestHandler> handlers;
private final RequestHandler defaultHandler;

public HandlerMapping(final Set<RequestHandler> handlers, final RequestHandler defaultHandler) {
this.handlers = handlers.stream()
.collect(Collectors.toMap(
RequestHandler::getRequestPath,
handler -> handler
));
this.defaultHandler = defaultHandler;
}
Comment on lines +15 to +22
Copy link

Choose a reason for hiding this comment

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

관련 코멘트 에 답변 달았습니다 !


public Response handle(final Request request) {
final String requestPath = request.getPath();
if (requestPath == null) {
return Response.notFound();
}
final var handler = handlers.get(requestPath);
if (handler == null) {
return defaultHandler.handle(request);
}
return handler.handle(request);
}
}
Loading