diff --git a/tomcat/src/main/java/nextstep/jwp/model/User.java b/tomcat/src/main/java/nextstep/jwp/model/User.java index 4c2a2cd184..2eac923382 100644 --- a/tomcat/src/main/java/nextstep/jwp/model/User.java +++ b/tomcat/src/main/java/nextstep/jwp/model/User.java @@ -1,25 +1,35 @@ package nextstep.jwp.model; +import java.util.Map; + public class User { + public static final String ACCOUNT_KEY = "account"; + public static final String PASSWORD_KEY = "password"; + public static final String EMAIL_KEY = "email"; + private final Long id; private final String account; private final String password; private final String email; - public User(Long id, String account, String password, String email) { + public User(final Long id, final String account, final String password, final String email) { this.id = id; this.account = account; this.password = password; this.email = email; } - public User(String account, String password, String email) { + public User(final String account, final String password, final String email) { this(null, account, password, email); } - public boolean checkPassword(String password) { - return this.password.equals(password); + public User(final Map userInfo) { + this(null, userInfo.get(ACCOUNT_KEY), userInfo.get(PASSWORD_KEY), userInfo.get(EMAIL_KEY)); + } + + public boolean checkPassword(final User user) { + return this.password.equals(user.password); } public String getAccount() { diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e69410f6a9..411077661a 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,8 +1,5 @@ package org.apache.catalina; -import jakarta.servlet.http.HttpSession; - -import java.io.IOException; /** * A Manager manages the pool of Sessions that are associated with a @@ -29,7 +26,7 @@ public interface Manager { * * @param session Session to be added */ - void add(HttpSession session); + void add(Session session); /** * Return the active Session, associated with this Manager, with the @@ -39,18 +36,16 @@ public interface Manager { * * @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 */ - HttpSession findSession(String id) throws IOException; + Session findSession(String id); /** * Remove this Session from the active Sessions for this Manager. * * @param session Session to be removed */ - void remove(HttpSession session); + void remove(Session session); } diff --git a/tomcat/src/main/java/org/apache/catalina/RequestMapping.java b/tomcat/src/main/java/org/apache/catalina/RequestMapping.java new file mode 100644 index 0000000000..99cda5d730 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/RequestMapping.java @@ -0,0 +1,38 @@ +package org.apache.catalina; + +import static org.apache.catalina.controller.StaticResourceUri.DEFAULT_PAGE; + +import java.util.HashMap; +import java.util.Map; +import org.apache.catalina.controller.Controller; +import org.apache.catalina.controller.DefaultController; +import org.apache.catalina.controller.LoginController; +import org.apache.catalina.controller.RegisterController; +import org.apache.catalina.controller.RootController; +import org.apache.catalina.controller.StaticResourceController; +import org.apache.coyote.http11.request.HttpRequest; + +public class RequestMapping { + + private static final String ROOT_REQUEST_URL = "/"; + private static final String LOGIN_REQUEST_URL = "/login"; + private static final String REGISTER_REQUEST_URL = "/register"; + private static final Map controllers = new HashMap<>(); + + static { + controllers.put(ROOT_REQUEST_URL, new RootController()); + controllers.put(DEFAULT_PAGE.getUri(), new DefaultController()); + controllers.put(LOGIN_REQUEST_URL, new LoginController()); + controllers.put(REGISTER_REQUEST_URL, new RegisterController()); + } + + public Controller getController(final HttpRequest request) { + final String path = request.getUri().getPath(); + + if (controllers.containsKey(path)) { + return controllers.get(path); + } + + return new StaticResourceController(); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/Session.java b/tomcat/src/main/java/org/apache/catalina/Session.java new file mode 100644 index 0000000000..065ce81279 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/Session.java @@ -0,0 +1,39 @@ +package org.apache.catalina; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class Session { + + private final String id; + private final Map values = new ConcurrentHashMap<>(); + + public Session() { + this(UUID.randomUUID().toString()); + } + + public Session(final String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public Object getAttribute(final String name) { + return this.values.get(name); + } + + public void setAttribute(final String name, final Object value) { + values.put(name, value); + } + + public void removeAttribute(final String name) { + values.remove(name); + } + + public void invalidate() { + values.clear(); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/SessionManger.java b/tomcat/src/main/java/org/apache/catalina/SessionManger.java new file mode 100644 index 0000000000..7976dce614 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/SessionManger.java @@ -0,0 +1,24 @@ +package org.apache.catalina; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionManger implements Manager { + + private static final Map SESSIONS = new ConcurrentHashMap<>(); + + @Override + public void add(final Session session) { + SESSIONS.put(session.getId(), session); + } + + @Override + public Session findSession(final String id) { + return SESSIONS.get(id); + } + + @Override + public void remove(final Session session) { + SESSIONS.remove(session.getId()); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java index 3b2c4dda7c..a26f1d822c 100644 --- a/tomcat/src/main/java/org/apache/catalina/connector/Connector.java +++ b/tomcat/src/main/java/org/apache/catalina/connector/Connector.java @@ -1,13 +1,14 @@ package org.apache.catalina.connector; -import org.apache.coyote.http11.Http11Processor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - 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; +import org.apache.coyote.http11.Http11Processor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Connector implements Runnable { @@ -15,17 +16,20 @@ public class Connector implements Runnable { 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 boolean stopped; + private final ExecutorService executorService; public Connector() { - this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); + this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_MAX_THREADS); } - public Connector(final int port, final int acceptCount) { + public Connector(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) { @@ -67,7 +71,7 @@ private void process(final Socket connection) { return; } var processor = new Http11Processor(connection); - new Thread(processor).start(); + executorService.submit(processor); } public void stop() { diff --git a/tomcat/src/main/java/org/apache/catalina/controller/AbstractController.java b/tomcat/src/main/java/org/apache/catalina/controller/AbstractController.java new file mode 100644 index 0000000000..ce20051f71 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/AbstractController.java @@ -0,0 +1,47 @@ +package org.apache.catalina.controller; + +import static org.apache.coyote.http11.response.ResponseHeaderType.ALLOW; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.HttpRequestMethod; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpStatusCode; + +public abstract class AbstractController implements Controller { + + static final String RESOURCE_DIRECTORY = "static"; + public static final String ALLOW_HEADER_DELIMITER = ", "; + + @Override + public void service(HttpRequest request, HttpResponse response) throws Exception { + HttpRequestMethod requestMethod = request.getMethod(); + + if (requestMethod == HttpRequestMethod.POST) { + doPost(request, response); + return; + } + + if (requestMethod == HttpRequestMethod.GET) { + doGet(request, response); + return; + } + getMethodNotAllowedResponse(response); + } + + protected void doPost(HttpRequest request, HttpResponse response) throws Exception { + getMethodNotAllowedResponse(response); + } + + protected void doGet(HttpRequest request, HttpResponse response) throws Exception { + getMethodNotAllowedResponse(response); + } + + private void getMethodNotAllowedResponse(final HttpResponse response) { + final String allowedHeaders = String.join(ALLOW_HEADER_DELIMITER, + HttpRequestMethod.POST.name(), + HttpRequestMethod.GET.name()); + + response.setStatusCode(HttpStatusCode.METHOD_NOT_ALLOWED) + .addHeader(ALLOW, allowedHeaders); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/Controller.java b/tomcat/src/main/java/org/apache/catalina/controller/Controller.java new file mode 100644 index 0000000000..adbdb7c260 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/Controller.java @@ -0,0 +1,9 @@ +package org.apache.catalina.controller; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public interface Controller { + + void service(HttpRequest request, HttpResponse response) throws Exception; +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/DefaultController.java b/tomcat/src/main/java/org/apache/catalina/controller/DefaultController.java new file mode 100644 index 0000000000..44c5a25e23 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/DefaultController.java @@ -0,0 +1,24 @@ +package org.apache.catalina.controller; + +import static java.util.Objects.requireNonNull; +import static org.apache.catalina.controller.StaticResourceUri.*; +import static org.apache.coyote.http11.response.ResponseContentType.TEXT_HTML; + +import org.apache.catalina.util.FileLoader; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponseBody; +import org.apache.coyote.http11.response.HttpStatusCode; + +public class DefaultController extends AbstractController { + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { + final String resource = FileLoader.load(RESOURCE_DIRECTORY + DEFAULT_PAGE.getUri()); + + response.setStatusCode(HttpStatusCode.OK) + .addContentTypeHeader(TEXT_HTML.getType()) + .addContentLengthHeader(requireNonNull(resource).getBytes().length) + .setResponseBody(new HttpResponseBody(resource)); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/LoginController.java b/tomcat/src/main/java/org/apache/catalina/controller/LoginController.java new file mode 100644 index 0000000000..d2e8fe51dc --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/LoginController.java @@ -0,0 +1,78 @@ +package org.apache.catalina.controller; + +import static java.util.Objects.requireNonNull; +import static org.apache.catalina.controller.StaticResourceUri.DEFAULT_PAGE; +import static org.apache.catalina.controller.StaticResourceUri.LOGIN_PAGE; +import static org.apache.coyote.http11.response.ResponseContentType.TEXT_HTML; + +import java.util.NoSuchElementException; +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.catalina.Session; +import org.apache.catalina.util.Authorizer; +import org.apache.catalina.util.FileLoader; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.HttpRequestBody; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponseBody; +import org.apache.coyote.http11.response.HttpStatusCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoginController extends AbstractController { + + private static final String USER_ATTRIBUTE_KEY = "user"; + private static final Logger log = LoggerFactory.getLogger(LoginController.class); + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + if (Authorizer.hasValidSession(request)) { + redirectToDefaultPage(response); + return; + } + + final HttpRequestBody requestBody = request.getBody(); + final User reqeustUser = new User(requestBody.parse()); + + final User user = InMemoryUserRepository.findByAccount(reqeustUser.getAccount()) + .orElseThrow(() -> new NoSuchElementException("존재하지 않는 사용자입니다.")); + + if (user.checkPassword(reqeustUser)) { + final Session newSession = createSession(user); + + response.setStatusCode(HttpStatusCode.FOUND) + .addContentTypeHeader(TEXT_HTML.getType()) + .addLocationHeader(DEFAULT_PAGE.getUri()) + .addSetCookieHeader("JSESSIONID=" + newSession.getId()); + } + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { + if (Authorizer.hasValidSession(request)) { + redirectToDefaultPage(response); + return; + } + + final String resource = FileLoader.load(RESOURCE_DIRECTORY + LOGIN_PAGE.getUri()); + + response.setStatusCode(HttpStatusCode.OK) + .addContentTypeHeader(TEXT_HTML.getType()) + .addContentLengthHeader(requireNonNull(resource).getBytes().length) + .setResponseBody(new HttpResponseBody(resource)); + } + + private Session createSession(final User user) { + Session newSession = new Session(); + newSession.setAttribute(USER_ATTRIBUTE_KEY, user); + Authorizer.addSession(newSession); + log.info("로그인 성공! 아이디: {}", user.getAccount()); + return newSession; + } + + private void redirectToDefaultPage(final HttpResponse response) { + response.setStatusCode(HttpStatusCode.FOUND) + .addContentTypeHeader(TEXT_HTML.getType()) + .addLocationHeader(DEFAULT_PAGE.getUri()); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/RegisterController.java b/tomcat/src/main/java/org/apache/catalina/controller/RegisterController.java new file mode 100644 index 0000000000..02065c1117 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/RegisterController.java @@ -0,0 +1,56 @@ +package org.apache.catalina.controller; + +import static java.util.Objects.requireNonNull; +import static org.apache.catalina.controller.StaticResourceUri.DEFAULT_PAGE; +import static org.apache.catalina.controller.StaticResourceUri.REGISTER_PAGE; +import static org.apache.coyote.http11.response.ResponseContentType.TEXT_HTML; + +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.model.User; +import org.apache.catalina.util.Authorizer; +import org.apache.catalina.util.FileLoader; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.HttpRequestBody; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponseBody; +import org.apache.coyote.http11.response.HttpStatusCode; + +public class RegisterController extends AbstractController { + + @Override + protected void doPost(final HttpRequest request, final HttpResponse response) { + final HttpRequestBody requestBody = request.getBody(); + final User user = new User(requestBody.parse()); + + if (!Authorizer.hasValidSession(request) && notExistAccount(user)) { + InMemoryUserRepository.save(user); + } + redirectToDefaultPage(response); + } + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { + if (!Authorizer.hasValidSession(request)) { + final String resource = FileLoader.load(RESOURCE_DIRECTORY + REGISTER_PAGE.getUri()); + + response.setStatusCode(HttpStatusCode.OK) + .addContentTypeHeader(TEXT_HTML.getType()) + .addContentLengthHeader(requireNonNull(resource).getBytes().length) + .setResponseBody(new HttpResponseBody(resource)); + return; + } + redirectToDefaultPage(response); + } + + private boolean notExistAccount(final User user) { + return InMemoryUserRepository + .findByAccount(user.getAccount()) + .isEmpty(); + } + + private void redirectToDefaultPage(final HttpResponse response) { + response.setStatusCode(HttpStatusCode.FOUND) + .addContentTypeHeader(TEXT_HTML.getType()) + .addLocationHeader(DEFAULT_PAGE.getUri()); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/RootController.java b/tomcat/src/main/java/org/apache/catalina/controller/RootController.java new file mode 100644 index 0000000000..dd0688d631 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/RootController.java @@ -0,0 +1,21 @@ +package org.apache.catalina.controller; + +import static org.apache.coyote.http11.response.ResponseContentType.TEXT_HTML; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponseBody; +import org.apache.coyote.http11.response.HttpStatusCode; + +public class RootController extends AbstractController { + + private static final String RESPONSE_BODY = "Hello world!"; + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) { + response.setStatusCode(HttpStatusCode.OK) + .addContentTypeHeader(TEXT_HTML.getType()) + .addContentLengthHeader(RESPONSE_BODY.getBytes().length) + .setResponseBody(new HttpResponseBody(RESPONSE_BODY)); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/StaticResourceController.java b/tomcat/src/main/java/org/apache/catalina/controller/StaticResourceController.java new file mode 100644 index 0000000000..17d7cc75d7 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/StaticResourceController.java @@ -0,0 +1,42 @@ +package org.apache.catalina.controller; + +import static java.util.Objects.requireNonNull; +import static org.apache.catalina.controller.StaticResourceUri.NOT_FOUND_PAGE; +import static org.apache.coyote.http11.response.ResponseContentType.TEXT_HTML; + +import java.io.IOException; +import org.apache.catalina.util.FileLoader; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.HttpRequestUri; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.HttpResponseBody; +import org.apache.coyote.http11.response.HttpStatusCode; +import org.apache.coyote.http11.response.ResponseContentType; + +public class StaticResourceController extends AbstractController { + + @Override + protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception { + final HttpRequestUri requestUri = request.getUri(); + final String resource = FileLoader.load(RESOURCE_DIRECTORY + requestUri.getPath()); + + if (resource == null) { + getNotFoundPage(response); + return; + } + + response.setStatusCode(HttpStatusCode.OK) + .addContentTypeHeader(ResponseContentType.from(requestUri.getPath()).getType()) + .addContentLengthHeader(requireNonNull(resource).getBytes().length) + .setResponseBody(new HttpResponseBody(resource)); + } + + private void getNotFoundPage(final HttpResponse response) throws IOException { + final String resource = FileLoader.load(RESOURCE_DIRECTORY + NOT_FOUND_PAGE.getUri()); + + response.setStatusCode(HttpStatusCode.NOT_FOUND) + .addContentTypeHeader(TEXT_HTML.getType()) + .addContentLengthHeader(requireNonNull(resource).getBytes().length) + .setResponseBody(new HttpResponseBody(resource)); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/controller/StaticResourceUri.java b/tomcat/src/main/java/org/apache/catalina/controller/StaticResourceUri.java new file mode 100644 index 0000000000..0d120fdaa1 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/controller/StaticResourceUri.java @@ -0,0 +1,18 @@ +package org.apache.catalina.controller; + +public enum StaticResourceUri { + DEFAULT_PAGE("/index.html"), + NOT_FOUND_PAGE("/404.html"), + LOGIN_PAGE("/login.html"), + REGISTER_PAGE("/register.html"); + + private final String uri; + + StaticResourceUri(final String uri) { + this.uri = uri; + } + + public String getUri() { + return uri; + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/util/Authorizer.java b/tomcat/src/main/java/org/apache/catalina/util/Authorizer.java new file mode 100644 index 0000000000..b71c50b8bd --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/util/Authorizer.java @@ -0,0 +1,39 @@ +package org.apache.catalina.util; + +import org.apache.catalina.Manager; +import org.apache.catalina.Session; +import org.apache.catalina.SessionManger; +import org.apache.coyote.http11.request.HttpRequest; + +public class Authorizer { + + private static final Manager sessionManger = new SessionManger(); + + private Authorizer() { + } + + public static Session findSession(final HttpRequest httpRequest) { + final String sessionId = httpRequest.getSessionId(); + + if (sessionId == null) { + return null; + } + + return sessionManger.findSession(sessionId); + } + + public static void addSession(final Session session) { + final String sessionId = session.getId(); + + if (sessionManger.findSession(sessionId) != null) { + return; + } + + sessionManger.add(session); + } + + public static boolean hasValidSession(final HttpRequest request) { + final Session session = sessionManger.findSession(request.getSessionId()); + return session != null; + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/util/FileLoader.java b/tomcat/src/main/java/org/apache/catalina/util/FileLoader.java new file mode 100644 index 0000000000..8372bce301 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/util/FileLoader.java @@ -0,0 +1,27 @@ +package org.apache.catalina.util; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileLoader { + + private FileLoader() { + } + + public static String load(final String resourcePath) throws IOException { + final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + final URL resource = classLoader.getResource(resourcePath); + + if (resource == null) { + return null; + } + + final File file = new File(resource.getFile()); + final Path path = file.toPath(); + + return new String(Files.readAllBytes(path)); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 4f99a66fe6..b17c440e68 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -2,28 +2,27 @@ import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.util.ArrayList; import java.util.List; -import nextstep.jwp.exception.UncheckedServletException; +import org.apache.catalina.controller.Controller; +import org.apache.catalina.RequestMapping; import org.apache.coyote.Processor; -import org.apache.coyote.http11.handler.HandlerAdapter; -import org.apache.coyote.http11.handler.RequestHandler; import org.apache.coyote.http11.request.HttpRequest; import org.apache.coyote.http11.request.HttpRequestBody; import org.apache.coyote.http11.request.HttpRequestHeaders; import org.apache.coyote.http11.request.HttpRequestStartLine; import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.ResponseConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - public static final String CONTENT_LENGTH_HEADER = "Content-Length"; - public static final String EMPTY_INPUT = ""; + private static final String CONTENT_LENGTH_HEADER = "Content-Length"; + private static final String EMPTY_INPUT = ""; private final Socket connection; @@ -40,42 +39,40 @@ public void run() { @Override public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { - - HttpRequest httpRequest = createHttpRequest(inputStream); - RequestHandler requestHandler = findHandler(httpRequest); - HttpResponse httpResponse = requestHandler.handle(httpRequest); - - outputStream.write(httpResponse.toString().getBytes()); + final var outputStream = connection.getOutputStream(); + final InputStreamReader reader = new InputStreamReader(inputStream); + final BufferedReader bufferedReader = new BufferedReader(reader) + ) { + final HttpRequest httpRequest = createHttpRequest(bufferedReader); + final RequestMapping requestMapping = new RequestMapping(); + final Controller controller = requestMapping.getController(httpRequest); + final HttpResponse response = new HttpResponse(httpRequest.getHttpVersion()); + controller.service(httpRequest, response); + + final ResponseConverter responseConverter = new ResponseConverter(response); + outputStream.write(responseConverter.responseToFormat().getBytes()); outputStream.flush(); - } catch (IOException | UncheckedServletException e) { + } catch (Exception e) { log.error(e.getMessage(), e); } } - private RequestHandler findHandler(final HttpRequest httpRequest) { - HandlerAdapter handlerAdapter = new HandlerAdapter(); - return handlerAdapter.find(httpRequest); - } - - private HttpRequest createHttpRequest(final InputStream inputStream) throws IOException { - InputStreamReader reader = new InputStreamReader(inputStream); - BufferedReader bufferedReader = new BufferedReader(reader); - String startLine = bufferedReader.readLine(); - HttpRequestStartLine requestStartLine = HttpRequestStartLine.from(startLine); - HttpRequestHeaders httpRequestHeaders = getRequestHeader(bufferedReader); + private HttpRequest createHttpRequest(final BufferedReader bufferedReader) throws IOException { + final String startLine = bufferedReader.readLine(); + final HttpRequestStartLine requestStartLine = HttpRequestStartLine.from(startLine); + final HttpRequestHeaders httpRequestHeaders = getRequestHeader(bufferedReader); if (!httpRequestHeaders.contains(CONTENT_LENGTH_HEADER)) { return HttpRequest.of(requestStartLine, httpRequestHeaders); } - HttpRequestBody httpRequestBody = getRequestBody(bufferedReader, httpRequestHeaders); + final HttpRequestBody httpRequestBody = getRequestBody(bufferedReader, httpRequestHeaders); return HttpRequest.of(requestStartLine, httpRequestHeaders, httpRequestBody); } private HttpRequestHeaders getRequestHeader(final BufferedReader bufferedReader) throws IOException { String line; - List headers = new ArrayList<>(); + final List headers = new ArrayList<>(); while (!(line = bufferedReader.readLine()).equals(EMPTY_INPUT)) { headers.add(line); } @@ -85,10 +82,10 @@ private HttpRequestHeaders getRequestHeader(final BufferedReader bufferedReader) private HttpRequestBody getRequestBody(final BufferedReader bufferedReader, final HttpRequestHeaders httpRequestHeaders) throws IOException { - int contentLength = Integer.parseInt(httpRequestHeaders.getValue(CONTENT_LENGTH_HEADER)); - char[] buffer = new char[contentLength]; + final int contentLength = Integer.parseInt(httpRequestHeaders.getValue(CONTENT_LENGTH_HEADER)); + final char[] buffer = new char[contentLength]; bufferedReader.read(buffer, 0, contentLength); - String requestBody = new String(buffer); + final String requestBody = new String(buffer); return new HttpRequestBody(requestBody); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/DefaultHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/DefaultHandler.java deleted file mode 100644 index b06ee0ef09..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/DefaultHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.apache.coyote.http11.handler; - -import static org.apache.coyote.http11.response.HttpStatusCode.OK; -import static org.apache.coyote.http11.response.ResponseHeaderType.CONTENT_LENGTH; -import static org.apache.coyote.http11.response.ResponseHeaderType.CONTENT_TYPE; - -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.response.HttpResponse; -import org.apache.coyote.http11.response.HttpResponseBody; -import org.apache.coyote.http11.response.HttpResponseHeaders; -import org.apache.coyote.http11.response.HttpResponseStatusLine; - -public class DefaultHandler extends RequestHandler { - - public static final String DEFAULT_MESSAGE = "Hello world!"; - - @Override - public HttpResponse handle(final HttpRequest httpRequest) { - String responseBody = DEFAULT_MESSAGE; - HttpResponseStatusLine statusLine = new HttpResponseStatusLine(httpRequest.getHttpVersion(), OK); - - HttpResponseHeaders httpResponseHeaders = new HttpResponseHeaders(); - httpResponseHeaders.add(CONTENT_TYPE, CONTENT_TYPE_HTML); - httpResponseHeaders.add(CONTENT_LENGTH, String.valueOf(responseBody.getBytes().length)); - - HttpResponseBody body = new HttpResponseBody(responseBody); - - return new HttpResponse(statusLine, httpResponseHeaders, body); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerAdapter.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerAdapter.java deleted file mode 100644 index df4c6f6e9d..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerAdapter.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.apache.coyote.http11.handler; - -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.HttpRequestUri; - -public class HandlerAdapter { - - public static final String LOGIN_URI = "/login"; - public static final String DEFAULT_URI = "/"; - public static final String REGISTER_URI = "/register"; - - public RequestHandler find(final HttpRequest request) { - HttpRequestUri uri = request.getUri(); - - if (uri.contains(LOGIN_URI)) { - return new LoginHandler(); - } - if (uri.same(DEFAULT_URI)) { - return new DefaultHandler(); - } - if (uri.contains(REGISTER_URI)) { - return new RegisterHandler(); - } - return new ResourceHandler(); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/LoginHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/LoginHandler.java deleted file mode 100644 index 6c0d854917..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/LoginHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.apache.coyote.http11.handler; - -import static org.apache.coyote.http11.request.HttpRequestMethod.GET; -import static org.apache.coyote.http11.request.HttpRequestMethod.POST; -import static org.apache.coyote.http11.response.HttpStatusCode.FOUND; -import static org.apache.coyote.http11.response.HttpStatusCode.OK; -import static org.apache.coyote.http11.response.HttpStatusCode.UNAUTHORIZED; - -import java.io.IOException; -import java.util.Map; -import java.util.Optional; -import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.model.User; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.HttpRequestBody; -import org.apache.coyote.http11.request.HttpRequestMethod; -import org.apache.coyote.http11.response.HttpResponse; - -public class LoginHandler extends RequestHandler { - - @Override - public HttpResponse handle(final HttpRequest httpRequest) throws IOException { - HttpRequestMethod httpMethod = httpRequest.getMethod(); - if (httpMethod == GET) { - return getPage(httpRequest, DIRECTORY + LOGIN_RESOURCE, OK); - } - - if (httpMethod == POST) { - HttpRequestBody requestBody = httpRequest.getBody(); - Map accountInfo = parseRequestBody(requestBody.getBody()); - - Optional optionalUser = InMemoryUserRepository.findByAccount(accountInfo.get(ACCOUNT_KEY)); - - if (optionalUser.isPresent()) { - User user = optionalUser.get(); - if (user.checkPassword(accountInfo.get(PASSWORD_KEY))) { - return getRedirectPage(httpRequest, DEFAULT_RESOURCE, FOUND); - } - } - return getPage(httpRequest, DIRECTORY + UNAUTHORIZED_RESOURCE, UNAUTHORIZED); - } - return getNotFoundPage(httpRequest); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/RegisterHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/RegisterHandler.java deleted file mode 100644 index 87376f39ba..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/RegisterHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.apache.coyote.http11.handler; - -import static org.apache.coyote.http11.request.HttpRequestMethod.GET; -import static org.apache.coyote.http11.request.HttpRequestMethod.POST; -import static org.apache.coyote.http11.response.HttpStatusCode.FOUND; -import static org.apache.coyote.http11.response.HttpStatusCode.OK; - -import java.io.IOException; -import java.util.Map; -import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.model.User; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.HttpRequestBody; -import org.apache.coyote.http11.request.HttpRequestMethod; -import org.apache.coyote.http11.response.HttpResponse; - -public class RegisterHandler extends RequestHandler { - - @Override - public HttpResponse handle(final HttpRequest httpRequest) throws IOException { - HttpRequestMethod httpMethod = httpRequest.getStartLine().getHttpMethod(); - - if (httpMethod == POST) { - HttpRequestBody requestBody = httpRequest.getBody(); - Map body = parseRequestBody(requestBody.getBody()); - User user = new User(body.get(ACCOUNT_KEY), - body.get(PASSWORD_KEY), - body.get(EMAIL_KEY)); - InMemoryUserRepository.save(user); - return getRedirectPage(httpRequest, DEFAULT_RESOURCE, FOUND); - } - - if (httpMethod == GET) { - return getPage(httpRequest, DIRECTORY + REGISTER_RESOURCE, OK); - } - - return getNotFoundPage(httpRequest); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/RequestHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/RequestHandler.java deleted file mode 100644 index 0c22f1ff6a..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/RequestHandler.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.apache.coyote.http11.handler; - -import static org.apache.coyote.http11.response.HttpStatusCode.NOT_FOUND; -import static org.apache.coyote.http11.response.ResponseHeaderType.CONTENT_LENGTH; -import static org.apache.coyote.http11.response.ResponseHeaderType.CONTENT_TYPE; -import static org.apache.coyote.http11.response.ResponseHeaderType.LOCATION; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.HttpRequestUri; -import org.apache.coyote.http11.response.HttpResponse; -import org.apache.coyote.http11.response.HttpResponseBody; -import org.apache.coyote.http11.response.HttpResponseHeaders; -import org.apache.coyote.http11.response.HttpResponseStatusLine; -import org.apache.coyote.http11.response.HttpStatusCode; - -public abstract class RequestHandler { - - public static final String ACCOUNT_KEY = "account"; - public static final String PASSWORD_KEY = "password"; - public static final String EMAIL_KEY = "email"; - public static final String DIRECTORY = "static"; - public static final String LOGIN_RESOURCE = "/login.html"; - public static final String DEFAULT_RESOURCE = "/index.html"; - public static final String UNAUTHORIZED_RESOURCE = "/401.html"; - public static final String REGISTER_RESOURCE = "/register.html"; - public static final String ENTRY_DELIMITER = "&"; - public static final String KEY_VALUE_DELIMITER = "="; - public static final int KEY_INDEX = 0; - public static final int VALUE_INDEX = 1; - public static final String EMPTY = ""; - public static final String CONTENT_TYPE_HTML = "text/html;charset=utf-8"; - public static final String NOT_FOUND_RESOURCE = "/404.html"; - - - public abstract HttpResponse handle(final HttpRequest httpRequest) throws IOException; - - HttpResponse getPage(HttpRequest httpRequest, String resourcePath, HttpStatusCode statusCode) throws IOException { - final URL resource = getClass().getClassLoader().getResource(resourcePath); - var responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); - - HttpResponseStatusLine statusLine = new HttpResponseStatusLine( - httpRequest.getStartLine().getHttpVersion(), statusCode); - - HttpResponseHeaders httpResponseHeaders = new HttpResponseHeaders(); - httpResponseHeaders.add(CONTENT_TYPE, CONTENT_TYPE_HTML); - httpResponseHeaders.add(CONTENT_LENGTH, String.valueOf(responseBody.getBytes().length)); - - HttpResponseBody body = new HttpResponseBody(responseBody); - - return new HttpResponse(statusLine, httpResponseHeaders, body); - } - - HttpResponse getResource(HttpRequest httpRequest, HttpStatusCode code) throws IOException { - HttpRequestUri requestUri = httpRequest.getUri(); - final URL resource = getClass().getClassLoader().getResource(DIRECTORY + requestUri.getValue()); - - File file = new File(resource.getFile()); - String extension = file.getName().substring(file.getName().lastIndexOf(".") + 1); - - var responseBody = new String(Files.readAllBytes(file.toPath())); - - HttpResponseStatusLine statusLine = new HttpResponseStatusLine( - httpRequest.getStartLine().getHttpVersion(), code); - - HttpResponseHeaders httpResponseHeaders = new HttpResponseHeaders(); - httpResponseHeaders.add(CONTENT_TYPE, String.format("text/%s;charset=utf-8", extension)); - httpResponseHeaders.add(CONTENT_LENGTH, String.valueOf(responseBody.getBytes().length)); - - HttpResponseBody body = new HttpResponseBody(responseBody); - - return new HttpResponse(statusLine, httpResponseHeaders, body); - } - - HttpResponse getNotFoundPage(final HttpRequest httpRequest) throws IOException { - return getPage(httpRequest, DIRECTORY + NOT_FOUND_RESOURCE, NOT_FOUND); - } - - HttpResponse getRedirectPage(HttpRequest httpRequest, String redirectPath, HttpStatusCode statusCode) { - HttpResponseStatusLine statusLine = new HttpResponseStatusLine(httpRequest.getHttpVersion(), statusCode); - - HttpResponseHeaders httpResponseHeaders = new HttpResponseHeaders(); - httpResponseHeaders.add(CONTENT_TYPE, CONTENT_TYPE_HTML); - httpResponseHeaders.add(CONTENT_LENGTH, String.valueOf(EMPTY.getBytes().length)); - httpResponseHeaders.add(LOCATION, redirectPath); - - HttpResponseBody body = new HttpResponseBody(EMPTY); - - return new HttpResponse(statusLine, httpResponseHeaders, body); - } - - Map parseRequestBody(final String body) { - Map requestBody = new HashMap<>(); - Arrays.stream(body.split(ENTRY_DELIMITER)) - .forEach(value -> requestBody.put(value.split(KEY_VALUE_DELIMITER)[KEY_INDEX], - value.split(KEY_VALUE_DELIMITER)[VALUE_INDEX])); - return requestBody; - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/ResourceHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/ResourceHandler.java deleted file mode 100644 index ead87faf72..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/ResourceHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.apache.coyote.http11.handler; - -import static org.apache.coyote.http11.response.HttpStatusCode.OK; - -import java.io.IOException; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.response.HttpResponse; - -public class ResourceHandler extends RequestHandler { - - @Override - public HttpResponse handle(final HttpRequest httpRequest) throws IOException { - return getResource(httpRequest, OK); - } -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpCookie.java new file mode 100644 index 0000000000..f04dd8e14f --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpCookie.java @@ -0,0 +1,31 @@ +package org.apache.coyote.http11.request; + +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class HttpCookie { + + private static final String ENTRY_DELIMITER = ";"; + private static final String KEY_VALUE_DELIMITER = "="; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + + private final Map cookies; + + private HttpCookie(final Map cookies) { + this.cookies = cookies; + } + + public static HttpCookie from(final String cookieHeader) { + final Map cookies = Pattern.compile(ENTRY_DELIMITER) + .splitAsStream(cookieHeader.trim()) + .map(cookie -> cookie.split(KEY_VALUE_DELIMITER)) + .collect(Collectors.toUnmodifiableMap(cookie -> cookie[KEY_INDEX], cookie -> cookie[VALUE_INDEX])); + return new HttpCookie(cookies); + } + + public String getValue(final String key) { + return cookies.get(key); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java index 362722dcda..be0e9a91c1 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequest.java @@ -1,6 +1,7 @@ package org.apache.coyote.http11.request; public class HttpRequest { + private final HttpRequestStartLine startLine; private final HttpRequestHeaders header; private final HttpRequestBody body; @@ -21,10 +22,6 @@ public static HttpRequest of(final HttpRequestStartLine startLine, final HttpReq return new HttpRequest(startLine, header, null); } - public HttpRequestStartLine getStartLine() { - return startLine; - } - public HttpRequestBody getBody() { return body; } @@ -40,4 +37,14 @@ public HttpRequestMethod getMethod() { public HttpRequestUri getUri() { return startLine.getHttpRequestUri(); } + + public String getSessionId() { + final HttpCookie cookie = header.getCookie(); + + if (cookie == null) { + return null; + } + + return cookie.getValue("JSESSIONID"); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestBody.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestBody.java index 2625d6c672..74741d39ff 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestBody.java @@ -1,6 +1,15 @@ package org.apache.coyote.http11.request; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + public class HttpRequestBody { + + public static final String ENTRY_DELIMITER = "&"; + public static final String KEY_VALUE_DELIMITER = "="; + public static final int KEY_INDEX = 0; + public static final int VALUE_INDEX = 1; private final String body; public HttpRequestBody(final String body) { @@ -10,4 +19,11 @@ public HttpRequestBody(final String body) { public String getBody() { return body; } + + public Map parse() { + return Pattern.compile(ENTRY_DELIMITER) + .splitAsStream(body.trim()) + .map(entry -> entry.split(KEY_VALUE_DELIMITER)) + .collect(Collectors.toUnmodifiableMap(entry -> entry[KEY_INDEX], entry -> entry[VALUE_INDEX])); + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestHeaders.java index cd0d73ee50..4093a1e8aa 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestHeaders.java @@ -9,6 +9,7 @@ public class HttpRequestHeaders { public static final String DELIMITER = ": "; public static final int KEY_INDEX = 0; public static final int VALUE_INDEX = 1; + public static final String COOKIE_HEADER = "Cookie"; private final Map headers; private HttpRequestHeaders(final Map headers) { @@ -16,17 +17,25 @@ private HttpRequestHeaders(final Map headers) { } public static HttpRequestHeaders from(final List headers) { - HashMap httpHeaders = new HashMap<>(); - headers.forEach(header -> httpHeaders.put(header.split(DELIMITER)[KEY_INDEX], header.split(DELIMITER)[VALUE_INDEX])); + final HashMap httpHeaders = new HashMap<>(); + headers.forEach( + header -> httpHeaders.put(header.split(DELIMITER)[KEY_INDEX], header.split(DELIMITER)[VALUE_INDEX])); return new HttpRequestHeaders(httpHeaders); } - public String getValue(String header) { + public String getValue(final String header) { return headers.get(header); } - public boolean contains(String header) { + public boolean contains(final String header) { return headers.containsKey(header); } + + public HttpCookie getCookie() { + if (headers.containsKey(COOKIE_HEADER)) { + return HttpCookie.from(headers.get(COOKIE_HEADER)); + } + return null; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestMethod.java index df03727220..e13b28057d 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestMethod.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestMethod.java @@ -3,6 +3,7 @@ import java.util.Arrays; public enum HttpRequestMethod { + POST, PATCH, PUT, diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestStartLine.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestStartLine.java index cebb6ab2ab..52f56caab6 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestStartLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestStartLine.java @@ -2,6 +2,11 @@ public class HttpRequestStartLine { + private static final String START_LINE_DELIMITER = " "; + private static final int METHOD_INDEX = 0; + private static final int URI_INDEX = 1; + private static final int VERSION_INDEX = 2; + private final HttpRequestMethod httpRequestMethod; private final HttpRequestUri httpRequestUri; private final String httpVersion; @@ -14,11 +19,11 @@ private HttpRequestStartLine(final HttpRequestMethod httpRequestMethod, final Ht } public static HttpRequestStartLine from(final String startLine) { - String[] startLineInfo = startLine.split(" "); + final String[] startLineInfo = startLine.split(START_LINE_DELIMITER); - HttpRequestMethod httpRequestMethod = HttpRequestMethod.from(startLineInfo[0]); - HttpRequestUri requestUri = HttpRequestUri.from(startLineInfo[1]); - String httpVersion = startLineInfo[2]; + final HttpRequestMethod httpRequestMethod = HttpRequestMethod.from(startLineInfo[METHOD_INDEX]); + final HttpRequestUri requestUri = HttpRequestUri.from(startLineInfo[URI_INDEX]); + final String httpVersion = startLineInfo[VERSION_INDEX]; return new HttpRequestStartLine(httpRequestMethod, requestUri, httpVersion); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestUri.java b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestUri.java index a27aa1d8f3..dc6aeb0b75 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestUri.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestUri.java @@ -1,26 +1,46 @@ package org.apache.coyote.http11.request; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + public class HttpRequestUri { - private final String value; + private static final String URI_DELIMITER = "\\?"; + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + private static final String QUERY_ENTRY_DELIMITER = "&"; + private static final String KEY_VALUE_DELIMITER = "="; + + private final String path; + private final Map queryString; - private HttpRequestUri(final String value) { - this.value = value; - } - public static HttpRequestUri from(String uri) { - return new HttpRequestUri(uri); + private HttpRequestUri(final String path, final Map queryString) { + this.path = path; + this.queryString = queryString; } - public boolean contains(String uri) { - return value.contains(uri); + public static HttpRequestUri from(final String uri) { + if (!uri.contains(URI_DELIMITER)) { + return new HttpRequestUri(uri, null); + } + return parseQueryString(uri); } - public boolean same(String uri) { - return value.equals(uri); + private static HttpRequestUri parseQueryString(final String uri) { + final String[] uriElements = uri.split(URI_DELIMITER); + final String path = uriElements[KEY_INDEX]; + + final Map queryString = Pattern.compile(QUERY_ENTRY_DELIMITER) + .splitAsStream(uriElements[VALUE_INDEX].trim()) + .map(queryEntry -> queryEntry.split(KEY_VALUE_DELIMITER)) + .collect(Collectors.toUnmodifiableMap(query -> query[KEY_INDEX], query -> query[VALUE_INDEX])); + + return new HttpRequestUri(path, queryString); } - public String getValue() { - return value; + public String getPath() { + return path; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java index a6ad305ec2..81bb7fa96a 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponse.java @@ -1,9 +1,16 @@ package org.apache.coyote.http11.response; public class HttpResponse { + private final HttpResponseStatusLine statusLine; private final HttpResponseHeaders header; - private final HttpResponseBody responseBody; + private HttpResponseBody responseBody; + + public HttpResponse(final String httpVersion) { + this(new HttpResponseStatusLine(httpVersion), + new HttpResponseHeaders(), + new HttpResponseBody()); + } public HttpResponse(final HttpResponseStatusLine statusLine, final HttpResponseHeaders header, final HttpResponseBody responseBody) { @@ -12,12 +19,55 @@ public HttpResponse(final HttpResponseStatusLine statusLine, final HttpResponseH this.responseBody = responseBody; } - @Override - public String toString() { - return String.join("\r\n", - statusLine.toString(), - header.toString(), - "", - responseBody.getBody()); + public HttpResponse setHttpVersion(final String version) { + this.statusLine.setHttpVersion(version); + return this; + } + + public HttpResponse setStatusCode(final HttpStatusCode statusCode) { + this.statusLine.setStatusCode(statusCode); + return this; + } + + public HttpResponse addHeader(final ResponseHeaderType headerType, final Object value) { + this.header.add(headerType, value); + return this; + } + + public HttpResponse addContentTypeHeader(final Object value) { + this.header.addContentType(value); + return this; + } + + public HttpResponse addLocationHeader(final Object value) { + this.header.addLocation(value); + return this; + } + + public HttpResponse addSetCookieHeader(final Object value) { + this.header.addCookie(value); + return this; + } + + public HttpResponse addContentLengthHeader(final Object value) { + this.header.addContentLength(value); + return this; + } + + public HttpResponse setResponseBody(final HttpResponseBody httpResponseBody) { + this.responseBody = httpResponseBody; + return this; + } + + public HttpResponseStatusLine getStatusLine() { + return statusLine; + } + + public HttpResponseHeaders getHeader() { + return header; + } + + public HttpResponseBody getResponseBody() { + return responseBody; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseBody.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseBody.java index d53b8981c4..1af2212889 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseBody.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseBody.java @@ -1,8 +1,13 @@ package org.apache.coyote.http11.response; public class HttpResponseBody { + private final String body; + public HttpResponseBody() { + this(""); + } + public HttpResponseBody(final String body) { this.body = body; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseHeaders.java index b337cf61c7..9befbd3138 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseHeaders.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseHeaders.java @@ -2,27 +2,40 @@ import java.util.LinkedHashMap; import java.util.Map; -import java.util.stream.Collectors; public class HttpResponseHeaders { - private final Map headers; + + private final Map headers; public HttpResponseHeaders() { this(new LinkedHashMap<>()); } - public HttpResponseHeaders(final Map headers) { + public HttpResponseHeaders(final Map headers) { this.headers = headers; } - public void add(ResponseHeaderType headerType, String value) { + public void add(final ResponseHeaderType headerType, final Object value) { headers.put(headerType, value); } - @Override - public String toString() { - return headers.entrySet().stream() - .map(entry -> String.format("%s: %s ", entry.getKey().getType(), entry.getValue())) - .collect(Collectors.joining("\r\n")); + public void addContentType(final Object value) { + headers.put(ResponseHeaderType.CONTENT_TYPE, value); + } + + public void addLocation(final Object value) { + headers.put(ResponseHeaderType.LOCATION, value); + } + + public void addCookie(final Object value) { + headers.put(ResponseHeaderType.SET_COOKIE, value); + } + + public void addContentLength(final Object value) { + headers.put(ResponseHeaderType.CONTENT_LENGTH, value); + } + + public Map getHeaders() { + return headers; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseStatusLine.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseStatusLine.java index b26a2703af..96f87a4cd3 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseStatusLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpResponseStatusLine.java @@ -2,16 +2,32 @@ public class HttpResponseStatusLine { - private final String httpVersion; - private final HttpStatusCode statusCode; + private String httpVersion; + private HttpStatusCode statusCode; + + public HttpResponseStatusLine(final String httpVersion) { + this(httpVersion, null); + } + public HttpResponseStatusLine(final String httpVersion, final HttpStatusCode statusCode) { this.httpVersion = httpVersion; this.statusCode = statusCode; } - @Override - public String toString() { - return String.format("%s %s ", httpVersion, statusCode.toString()); + public void setHttpVersion(final String version) { + this.httpVersion = version; + } + + public void setStatusCode(final HttpStatusCode statusCode) { + this.statusCode = statusCode; + } + + public String getHttpVersion() { + return httpVersion; + } + + public HttpStatusCode getStatusCode() { + return statusCode; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatusCode.java index ac0943f4cd..7711111a11 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatusCode.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/HttpStatusCode.java @@ -4,9 +4,8 @@ public enum HttpStatusCode { OK(200, "OK"), FOUND(302, "FOUND"), - UNAUTHORIZED(401, "UNAUTHORIZED"), - NOT_FOUND(404, "NOT FOUND") - ; + METHOD_NOT_ALLOWED(405, "METHOD NOT ALLOWED"), + NOT_FOUND(404, "NOT_FOUND"); private final int code; private final String type; @@ -16,8 +15,11 @@ public enum HttpStatusCode { this.type = type; } - @Override - public String toString() { - return String.format("%d %s", code, type); + public int getCode() { + return code; + } + + public String getType() { + return type; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseContentType.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseContentType.java new file mode 100644 index 0000000000..32bb9e34f4 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseContentType.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11.response; + +import java.util.Arrays; + +public enum ResponseContentType { + + TEXT_HTML("text/html;charset=utf-8", ".html"), + TEXT_CSS("text/css", ".css"), + TEXT_JAVASCRIPT("text/javascript", ".js"), + ICO("image/x-icon", ".ico"); + + private final String type; + private final String extension; + + ResponseContentType(final String type, final String extension) { + this.type = type; + this.extension = extension; + } + + public static ResponseContentType from(final String resource) { + return Arrays.stream(values()) + .filter(contentType -> resource.endsWith(contentType.extension)) + .findAny() + .orElse(TEXT_HTML); + } + + public String getType() { + return type; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseConverter.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseConverter.java new file mode 100644 index 0000000000..ea645f0f06 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseConverter.java @@ -0,0 +1,43 @@ +package org.apache.coyote.http11.response; + +import java.util.Map; +import java.util.stream.Collectors; + +public class ResponseConverter { + + private final HttpResponse response; + + public ResponseConverter(final HttpResponse response) { + this.response = response; + } + + public String responseToFormat() { + final HttpResponseBody responseBody = response.getResponseBody(); + final String body = responseBody.getBody(); + + return String.join("\r\n", + statusLineToFormat(), + headersToFormat(), + "", + body); + } + + private String statusLineToFormat() { + final HttpResponseStatusLine statusLine = response.getStatusLine(); + final HttpStatusCode statusCode = statusLine.getStatusCode(); + + return String.format("%s %d %s ", + statusLine.getHttpVersion(), + statusCode.getCode(), + statusCode.getType()); + } + + private String headersToFormat() { + final HttpResponseHeaders httpResponseHeaders = response.getHeader(); + final Map headers = httpResponseHeaders.getHeaders(); + + return headers.entrySet().stream() + .map(entry -> String.format("%s: %s ", entry.getKey().getType(), entry.getValue())) + .collect(Collectors.joining("\r\n")); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaderType.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaderType.java index 5dd7cfb633..7d56937c81 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaderType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaderType.java @@ -1,9 +1,12 @@ package org.apache.coyote.http11.response; public enum ResponseHeaderType { + CONTENT_TYPE("Content-Type"), CONTENT_LENGTH("Content-Length"), - LOCATION("Location"); + LOCATION("Location"), + SET_COOKIE("Set-Cookie"), + ALLOW("Allow"); private final String type; diff --git a/tomcat/src/main/resources/static/favicon.ico b/tomcat/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000..04e652f119 Binary files /dev/null and b/tomcat/src/main/resources/static/favicon.ico differ diff --git a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java index 512b919f09..9d6380e6ef 100644 --- a/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java +++ b/tomcat/src/test/java/nextstep/org/apache/coyote/http11/Http11ProcessorTest.java @@ -1,15 +1,14 @@ package nextstep.org.apache.coyote.http11; -import support.StubSocket; -import org.apache.coyote.http11.Http11Processor; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; - -import static org.assertj.core.api.Assertions.assertThat; +import org.apache.coyote.http11.Http11Processor; +import org.junit.jupiter.api.Test; +import support.StubSocket; class Http11ProcessorTest { @@ -36,7 +35,7 @@ void process() { @Test void index() throws IOException { // given - final String httpRequest= String.join("\r\n", + final String httpRequest = String.join("\r\n", "GET /index.html HTTP/1.1 ", "Host: localhost:8080 ", "Connection: keep-alive ", @@ -54,7 +53,7 @@ void index() throws IOException { var expected = "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html;charset=utf-8 \r\n" + "Content-Length: 5564 \r\n" + - "\r\n"+ + "\r\n" + new String(Files.readAllBytes(new File(resource.getFile()).toPath())); assertThat(socket.output()).isEqualTo(expected);