Skip to content

Commit

Permalink
[톰캣 구현하기 - 3, 4 단계] - 제나(위예나) 미션 제출합니다 (#458)
Browse files Browse the repository at this point in the history
* docs: 기능 요구사항 업데이트

* refactor: ContentType svg 추가

* refactor: 문자열 상수화. java convention

* refactor: HttpStatus 추가

* feat: 정적파일 못 읽어올때 404 페이지로 리턴

* feat: controller interface 구현

* feat: uri 에 따른 컨트롤러 생성

* feat: RequestMapping 객체로 분기 처리 위임

* feat: HttpResponse 정적팩터리메소드 추가

* feat: HttpResponse 빌더패턴 삭제. setter 로 구현

* refactor: RequestMapping 분기 처리 제거

* refactor: SessionManager 동시성 컬렉션 사용

* feat: Executors 로 ThreadPool 적용

* fix: LoginController NPE 처리

* fix: Controller 싱글톤 적용

* feat: 404, 500 리다이렉션 처리 수정

* fix: test 깨지는 것 수정

* refactor: Header 상수화

* refactor: sonarlint 분석 반영

* refactor: LoginController if/else 분기 수정

* refactor: 문자열 상수화
  • Loading branch information
yenawee authored Sep 12, 2023
1 parent 89596ca commit ffae834
Show file tree
Hide file tree
Showing 23 changed files with 454 additions and 229 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,14 @@
- [x] Login 도 POST 로 변경
- [x] Cookie에 JSESSIONID 값 저장하기
- [x] Session 구현하기

### 3단계 - 리팩터링

- [x] HttpRequest 클래스 구현하기
- [x] HttpResponse 클래스 구현하기
- [x] Controller 인터페이스 추가하기

### 4단계 - 동시성 확장하기

- [x] Executors로 Thread Pool 적용
- [x] 동시성 컬렉션 사용하기
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nextstep.jwp.controller;

import org.apache.coyote.Controller;
import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.coyote.common.ContentType.HTML;
import static org.apache.coyote.common.Headers.LOCATION;
import static org.apache.coyote.response.HttpStatus.FOUND;

public class AbstractController implements Controller {

protected static final Logger log = LoggerFactory.getLogger(AbstractController.class);

@Override
public void service(HttpRequest request, HttpResponse response) throws Exception {

try {
if (request.getRequestLine().isGetMethod()) {
doGet(request, response);
}
if (request.getRequestLine().isPostMethod()) {
doPost(request, response);
}
} catch (Exception e) {
response.setStatus(FOUND);
response.setContentType(HTML);
response.addHeader(LOCATION, "/500.html");
}
}

protected void doPost(HttpRequest request, HttpResponse response) throws Exception { /* NOOP */ }

protected void doGet(HttpRequest request, HttpResponse response) throws Exception { /* NOOP */ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package nextstep.jwp.controller;

import org.apache.coyote.Controller;
import org.apache.coyote.common.ContentType;
import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.request.RequestUri;
import org.apache.coyote.response.HttpResponse;
import org.apache.coyote.utils.FileUtils;

import static org.apache.coyote.common.ContentType.HTML;
import static org.apache.coyote.common.Headers.LOCATION;
import static org.apache.coyote.response.HttpStatus.FOUND;

public class DefaultController extends AbstractController {

private static final Controller INSTANCE = new DefaultController();

public static Controller getInstance() {
return INSTANCE;
}

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
RequestUri requestUri = request.getRequestLine().getRequestUri();

try {
FileUtils.readFile(requestUri.getPath());
response.setContentType(ContentType.from(requestUri.getExtension()));
response.setBody(FileUtils.readFile(requestUri.getPath()));
} catch (NullPointerException e) {
response.setStatus(FOUND);
response.setContentType(HTML);
response.addHeader(LOCATION, "/404.html");
}
}
}
92 changes: 92 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,92 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.catalina.manager.SessionManager;
import org.apache.coyote.Controller;
import org.apache.coyote.common.HttpCookie;
import org.apache.coyote.common.Session;
import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.request.HttpRequestBody;
import org.apache.coyote.response.HttpResponse;
import org.apache.coyote.utils.FileUtils;

import java.util.Optional;

import static org.apache.coyote.common.ContentType.HTML;
import static org.apache.coyote.common.Headers.JSESSIONID;
import static org.apache.coyote.common.Headers.LOCATION;
import static org.apache.coyote.response.HttpStatus.FOUND;
import static org.apache.coyote.response.HttpStatus.OK;

public class LoginController extends AbstractController {

private static final Controller INSTANCE = new LoginController();

public static Controller getInstance() {
return INSTANCE;
}

@Override
protected void doPost(HttpRequest request, HttpResponse response) throws Exception {
HttpRequestBody httpRequestBody = request.getRequestBody();
String account = httpRequestBody.getValue("account");
String password = httpRequestBody.getValue("password");

Optional<User> user = InMemoryUserRepository.findByAccount(account);

if (user.isPresent()) {
user.filter(userInfo -> userInfo.checkPassword(password))
.ifPresentOrElse(registerUser -> {
log.info(registerUser.toString());
loginSuccess(registerUser, response);
}
, () -> {
log.warn("비밀번호가 틀렸습니다");
loginFail(response);
});
return;
}
log.warn("미가입회원입니다");
loginFail(response);
}

private void loginSuccess(User user, HttpResponse response) {
Session session = new Session();
session.setAttribute("user", user);
SessionManager.add(session);

HttpCookie cookie = HttpCookie.of(JSESSIONID + "=" + session.getId());

response.setStatus(FOUND);
response.setContentType(HTML);
response.addHeader(LOCATION, "/index.html");
response.setCookie(cookie);
}

private void loginFail(HttpResponse response) {
response.setStatus(FOUND);
response.setContentType(HTML);
response.addHeader(LOCATION, "/401.html");
}


@Override
protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
HttpCookie cookie = request.getRequestHeader().getCookie();

String jsessionid = cookie.getValue(JSESSIONID);
Optional<Session> session = SessionManager.findSession(jsessionid);

if (session.isPresent()) {
response.setStatus(FOUND);
response.addHeader(LOCATION, "/index.html");
response.setContentType(HTML);
return;
}

response.setStatus(OK);
response.setContentType(HTML);
response.setBody(FileUtils.readFile("/login.html"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.Controller;
import org.apache.coyote.common.ContentType;
import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.request.HttpRequestBody;
import org.apache.coyote.request.RequestUri;
import org.apache.coyote.response.HttpResponse;
import org.apache.coyote.utils.FileUtils;

import static org.apache.coyote.common.ContentType.HTML;
import static org.apache.coyote.common.Headers.LOCATION;
import static org.apache.coyote.response.HttpStatus.FOUND;

public class RegisterController extends AbstractController {

private static final Controller INSTANCE = new RegisterController();

public static Controller getInstance() {
return INSTANCE;
}

@Override
protected void doPost(HttpRequest request, HttpResponse response) throws Exception {
HttpRequestBody httpRequestBody = request.getRequestBody();

String account = httpRequestBody.getValue("account");
String password = httpRequestBody.getValue("password");
String email = httpRequestBody.getValue("email");

if (InMemoryUserRepository.findByAccount(account).isPresent()) {
throw new IllegalArgumentException("중복 ID 입니다");
}

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

response.setStatus(FOUND);
response.setContentType(HTML);
response.addHeader(LOCATION, "/index.html");
}

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
RequestUri requestUri = request.getRequestLine().getRequestUri();

response.setContentType(ContentType.from(requestUri.getExtension()));
response.setBody(FileUtils.readFile("/register.html"));
}
}
22 changes: 22 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/RootController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.jwp.controller;

import org.apache.coyote.Controller;
import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.response.HttpResponse;

import static org.apache.coyote.common.ContentType.HTML;

public class RootController extends AbstractController {

private static final Controller INSTANCE = new RootController();

public static Controller getInstance() {
return INSTANCE;
}

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
response.setContentType(HTML);
response.setBody("Hello world!");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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 int DEFAULT_MAX_THREADS = 250;

private final ServerSocket serverSocket;
private final ExecutorService executorService;

private boolean stopped;

public Connector() {
Expand All @@ -26,6 +32,13 @@ public Connector() {
public Connector(final int port, final int acceptCount) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.executorService = Executors.newFixedThreadPool(DEFAULT_MAX_THREADS);
}

public Connector(final Container container, 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) {
Expand Down Expand Up @@ -67,13 +80,14 @@ 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 {
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 @@ -2,12 +2,13 @@

import org.apache.coyote.common.Session;

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

public class SessionManager {

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

private SessionManager() {
}
Expand All @@ -16,8 +17,11 @@ public static void add(final Session session) {
SESSIONS.put(session.getId(), session);
}

public static Session findSession(final String id) {
return SESSIONS.get(id);
public static Optional<Session> findSession(final String id) {
if (id == null) {
return Optional.empty();
}
return Optional.ofNullable(SESSIONS.get(id));
}

public void remove(final String id) {
Expand Down
8 changes: 8 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.apache.coyote;

import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.response.HttpResponse;

public interface Controller {
void service(HttpRequest request, HttpResponse response) throws Exception;
}
36 changes: 36 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/RequestMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.apache.coyote;

import nextstep.jwp.controller.DefaultController;
import nextstep.jwp.controller.LoginController;
import nextstep.jwp.controller.RegisterController;
import nextstep.jwp.controller.RootController;
import org.apache.coyote.request.HttpRequest;
import org.apache.coyote.request.RequestUri;

import java.util.Map;

public class RequestMapping {

private static final Map<String, Controller> mappers;

static {
mappers = Map.of(
"/", RootController.getInstance(),
"/login", LoginController.getInstance(),
"/register", RegisterController.getInstance());
}


public Controller getController(HttpRequest request) {
RequestUri requestUri = request.getRequestLine().getRequestUri();

String path = requestUri.getPath();

return mappers.entrySet()
.stream()
.filter(entry -> path.equals(entry.getKey()))
.map(Map.Entry::getValue)
.findFirst()
.orElse(DefaultController.getInstance());
}
}
Loading

0 comments on commit ffae834

Please sign in to comment.