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 단계] - 제나(위예나) 미션 제출합니다 #458

Merged
merged 21 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
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