diff --git a/README.md b/README.md index 86de1aea32..471f032f7f 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,14 @@ # 톰캣 구현하기 -## 1단계 - HTTP 서버 구현하기 -- [x] GET /index.html 응답하기 -- [x] CSS 지원하기 -- [x] Query String 파싱 +## 3단계 - 리팩터링 +- [x] HttpRequest 클래스 구현하기 +- [x] HttpResponse 클래스 구현하기 +- [x] Controller 인터페이스 추가하기 + - [x] AbstractController를 상속한 구현체 만들기 -## 2단계 - 로그인 구현하기 -- [x] HTTP Status Code 302 - - [x] 로그인 성공 시, 응답 헤더에 http status code를 302로 반환하고 `/index.html`로 리다이렉트 - - [x] 로그인 실패 시, `401.html`로 리다이렉트 -- [x] POST 방식으로 회원가입 - - [x] `http://localhost:8080/register` 으로 접속하면 회원가입 페이지(`register.html`)를 보여준다. - - [x] 회원가입 페이지를 보여줄 때는 GET을 사용한다. - - [x] 회원가입을 버튼을 누르면 HTTP method를 GET이 아닌 POST를 사용한다. - - [x] 회원가입을 완료하면 `index.html`로 리다이렉트한다. - - [x] 로그인 페이지도 버튼을 눌렀을 때 GET 방식에서 POST 방식으로 전송하도록 변경하자. -- [x] Cookie에 JSESSIONID 값 저장하기 - - [x] 서버에서 HTTP 응답을 전달할 때 응답 헤더에 `Set-Cookie`를 추가하고 `JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46` 형태로 값을 전달하면 - 클라이언트 요청 헤더의 `Cookie` 필드에 값이 추가된다. - - [x] Cookie 클래스를 추가하고 HTTP Request Header의 Cookie에 `JSESSIONID`가 없으면 HTTP Response Header에 `Set-Cookie`를 반환해주는 기능을 - 구현한다. -- [x] Session 구현하기 - - [x] 쿠키에서 전달 받은 JSESSIONID의 값으로 로그인 여부를 체크할 수 있어야 한다. - - [x] 로그인에 성공하면 Session 객체의 값으로 User 객체를 저장해보자. - - [x] 로그인 상태에서 `/login` 페이지에 HTTP GET method로 접근하면 `index.html` 페이지로 리다이렉트 처리한다 +## 4단계 - 동시성 확장하기 +- [x] Executors로 Thread Pool 적용하기 + - [x] Connector 클래스에서 Executors 클래스를 사용해서 ExecutorService 객체 생성하기 + - [x] maxThreads라는 변수로 스레드 갯수 지정하기 +- [x] 동시성 컬렉션 사용하기 + - [x] SessionManager 클래스에서 Session 컬렉션에 동시성 컬렉션 적용하기 diff --git a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java index 305b1f1e1e..bdfdbab6a2 100644 --- a/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java +++ b/study/src/main/java/cache/com/example/cachecontrol/CacheWebConfig.java @@ -1,13 +1,23 @@ package cache.com.example.cachecontrol; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.WebContentInterceptor; @Configuration public class CacheWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(final InterceptorRegistry registry) { + CacheControl cacheControl = CacheControl + .noCache() + .cachePrivate(); + + WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); + webContentInterceptor.addCacheMapping(cacheControl, "/"); + + registry.addInterceptor(webContentInterceptor); } } diff --git a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java index 41ef7a3d9a..0a50e4e7d8 100644 --- a/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java +++ b/study/src/main/java/cache/com/example/etag/EtagFilterConfiguration.java @@ -1,12 +1,23 @@ package cache.com.example.etag; +import cache.com.example.version.CacheBustingWebConfig; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ShallowEtagHeaderFilter; @Configuration public class EtagFilterConfiguration { -// @Bean -// public FilterRegistrationBean shallowEtagHeaderFilter() { -// return null; -// } + @Bean + public FilterRegistrationBean shallowEtagHeaderFilter() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>( + new ShallowEtagHeaderFilter()); + filterRegistrationBean.addUrlPatterns( + "/etag", + CacheBustingWebConfig.PREFIX_STATIC_RESOURCES + "/*" + ); + + return filterRegistrationBean; + } } diff --git a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java index 6da6d2c795..09520767c5 100644 --- a/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java +++ b/study/src/main/java/cache/com/example/version/CacheBustingWebConfig.java @@ -1,7 +1,9 @@ package cache.com.example.version; +import java.time.Duration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -20,6 +22,13 @@ public CacheBustingWebConfig(ResourceVersion version) { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") - .addResourceLocations("classpath:/static/"); + .addResourceLocations("classpath:/static/") + .setUseLastModified(true) + .setCacheControl( + CacheControl.maxAge(Duration.ofDays(365)) + .cachePublic() + ) + ; } + } diff --git a/study/src/main/resources/application.yml b/study/src/main/resources/application.yml index 4e8655a962..da2f6ae7c9 100644 --- a/study/src/main/resources/application.yml +++ b/study/src/main/resources/application.yml @@ -3,7 +3,11 @@ handlebars: server: tomcat: - accept-count: 1 - max-connections: 1 + accept-count: 8 # 연결된 요청이 max-connections에 도달했을 경우, 추가로 연결 대기할 수 있는 요청의 갯수 + max-connections: 2 # 서버가 동시에 처리할 수 있는 클라이언트 연결의 최대 개수, + #max-connections이 thread의 최대 갯수보다 많으면 추가 연결이 처리 대기열에 배치된다. threads: - max: 2 + max: 2 # 요청을 처리하는 Thread의 최대 갯수, AppTest의 count.intValue()는 해당 값을 의미한다. + compression: + enabled: true + min-response-size: 10 diff --git a/study/src/test/java/thread/stage0/SynchronizationTest.java b/study/src/test/java/thread/stage0/SynchronizationTest.java index 0333c18e3b..b463c2b984 100644 --- a/study/src/test/java/thread/stage0/SynchronizationTest.java +++ b/study/src/test/java/thread/stage0/SynchronizationTest.java @@ -41,7 +41,7 @@ private static final class SynchronizedMethods { private int sum = 0; - public void calculate() { + public synchronized void calculate() { setSum(getSum() + 1); } diff --git a/study/src/test/java/thread/stage0/ThreadPoolsTest.java b/study/src/test/java/thread/stage0/ThreadPoolsTest.java index 238611ebfe..03efdabc8d 100644 --- a/study/src/test/java/thread/stage0/ThreadPoolsTest.java +++ b/study/src/test/java/thread/stage0/ThreadPoolsTest.java @@ -31,8 +31,8 @@ void testNewFixedThreadPool() { executor.submit(logWithSleep("hello fixed thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; - final int expectedQueueSize = 0; + final int expectedPoolSize = 2; + final int expectedQueueSize = 1; assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size()); @@ -46,7 +46,7 @@ void testNewCachedThreadPool() { executor.submit(logWithSleep("hello cached thread pools")); // 올바른 값으로 바꿔서 테스트를 통과시키자. - final int expectedPoolSize = 0; + final int expectedPoolSize = 3; final int expectedQueueSize = 0; assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize()); diff --git a/study/src/test/java/thread/stage0/ThreadTest.java b/study/src/test/java/thread/stage0/ThreadTest.java index 3ffef18869..4537b9fade 100644 --- a/study/src/test/java/thread/stage0/ThreadTest.java +++ b/study/src/test/java/thread/stage0/ThreadTest.java @@ -30,10 +30,10 @@ void testExtendedThread() throws InterruptedException { Thread thread = new ExtendedThread("hello thread"); // 생성한 thread 객체를 시작한다. - thread.start(); + thread.start(); // thread의 작업이 완료될 때까지 기다린다. - thread.join(); + thread.join(); } /** @@ -46,10 +46,10 @@ void testRunnableThread() throws InterruptedException { Thread thread = new Thread(new RunnableThread("hello thread")); // 생성한 thread 객체를 시작한다. - thread.start(); + thread.start(); // thread의 작업이 완료될 때까지 기다린다. - thread.join(); + thread.join(); } private static final class ExtendedThread extends Thread { diff --git a/study/src/test/java/thread/stage2/AppTest.java b/study/src/test/java/thread/stage2/AppTest.java index e253c4a249..14133b29c3 100644 --- a/study/src/test/java/thread/stage2/AppTest.java +++ b/study/src/test/java/thread/stage2/AppTest.java @@ -24,25 +24,30 @@ class AppTest { @Test void test() throws Exception { final var NUMBER_OF_THREAD = 10; - var threads = new Thread[NUMBER_OF_THREAD]; + // Thread 객체 배열 생성 및 10개의 Thread 객체 생성하여 배열에 할당한다. + var threads = new Thread[NUMBER_OF_THREAD]; for (int i = 0; i < NUMBER_OF_THREAD; i++) { threads[i] = new Thread(() -> incrementIfOk(TestHttpUtils.send("/test"))); } + // Thread 객체의 start() 메서드를 호출하면, Thread를 생성하고, 해당 스레드에서 run()메서드를 실행한다. for (final var thread : threads) { thread.start(); Thread.sleep(50); } + // join() 메서드를 호출한 스레드가 join() 메서드를 실행한 스레드의 종료를 기다린다. for (final var thread : threads) { thread.join(); } + // count.intValue()는 실제 생성된 스레드의 갯수이다. assertThat(count.intValue()).isEqualTo(2); } private static void incrementIfOk(final HttpResponse response) { + // 요청이 200일때 AtomicInteger의 수량 증가 if (response.statusCode() == 200) { count.incrementAndGet(); } diff --git a/tomcat/src/main/java/nextstep/jwp/controller/DefaultController.java b/tomcat/src/main/java/nextstep/jwp/controller/DefaultController.java new file mode 100644 index 0000000000..98d9e9f96d --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/DefaultController.java @@ -0,0 +1,16 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.controller.Controller; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public class DefaultController implements Controller { + + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) { + httpResponse.setHttpStatus(HttpStatus.OK) + .setPath("/default"); + } + +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java new file mode 100644 index 0000000000..4fa79be63c --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/LoginController.java @@ -0,0 +1,62 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.model.User; +import nextstep.jwp.service.UserService; +import org.apache.coyote.http11.common.HttpCookie; +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.controller.AbstractController; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.RequestBody; +import org.apache.coyote.http11.request.RequestLine; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.session.HttpSession; + +public class LoginController extends AbstractController { + + public LoginController(UserService userService) { + super(userService); + } + + @Override + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + RequestLine requestLine = httpRequest.getRequestLine(); + HttpSession session = httpRequest.getSession(false); + + if (session != null && session.getAttribute("user") != null) { + httpResponse.setHttpStatus(HttpStatus.FOUND) + .setPath("/index"); + return; + } + + httpResponse.setHttpStatus(HttpStatus.OK) + .setPath(requestLine.getPath()); + } + + @Override + protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) { + RequestBody requestBody = httpRequest.getRequestBody(); + + String account = requestBody.get("account"); + String password = requestBody.get("password"); + + try { + User user = service.login(account, password); + + HttpSession httpSession = httpRequest.getSession(true); + httpSession.setAttribute("user", user); + + httpRequest.addSession(httpSession); + + HttpCookie httpCookie = HttpCookie.create(); + httpCookie.putJSessionId(httpSession.getId()); + + httpResponse.setHttpStatus(HttpStatus.FOUND) + .setPath("/index") + .setCookie(httpCookie); + } catch (IllegalArgumentException e) { + httpResponse.setHttpStatus(HttpStatus.FOUND) + .setPath("/401"); + } + } + +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/NotFoundController.java b/tomcat/src/main/java/nextstep/jwp/controller/NotFoundController.java new file mode 100644 index 0000000000..e4a54d970e --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/NotFoundController.java @@ -0,0 +1,15 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.controller.Controller; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public class NotFoundController implements Controller { + + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) { + httpResponse.setHttpStatus(HttpStatus.NOT_FOUND) + .setPath("/404"); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java new file mode 100644 index 0000000000..b1bc4964e7 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -0,0 +1,35 @@ +package nextstep.jwp.controller; + +import nextstep.jwp.service.UserService; +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.controller.AbstractController; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.RequestBody; +import org.apache.coyote.http11.request.RequestLine; +import org.apache.coyote.http11.response.HttpResponse; + +public class RegisterController extends AbstractController { + + public RegisterController(UserService userService) { + super(userService); + } + + @Override + protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { + RequestLine requestLine = httpRequest.getRequestLine(); + + httpResponse.setHttpStatus(HttpStatus.OK) + .setPath(requestLine.getPath()); + } + + @Override + protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) { + RequestBody requestBody = httpRequest.getRequestBody(); + + String account = requestBody.get("account"); + String password = requestBody.get("password"); + String email = requestBody.get("email"); + + service.save(account, password, email); + } +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/UnauthorizedController.java b/tomcat/src/main/java/nextstep/jwp/controller/UnauthorizedController.java new file mode 100644 index 0000000000..4749c1609e --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/controller/UnauthorizedController.java @@ -0,0 +1,16 @@ +package nextstep.jwp.controller; + +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.controller.Controller; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public class UnauthorizedController implements Controller { + + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) { + httpResponse.setHttpStatus(HttpStatus.UNAUTHORIZED) + .setPath("/401"); + } + +} diff --git a/tomcat/src/main/java/nextstep/jwp/controller/UserController.java b/tomcat/src/main/java/nextstep/jwp/controller/UserController.java deleted file mode 100644 index d57fb043a1..0000000000 --- a/tomcat/src/main/java/nextstep/jwp/controller/UserController.java +++ /dev/null @@ -1,26 +0,0 @@ -package nextstep.jwp.controller; - -import nextstep.jwp.model.User; -import nextstep.jwp.service.UserService; -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.response.ResponseEntity; - -public class UserController { - - private final UserService userService; - - public UserController(UserService userService) { - this.userService = userService; - } - - public User login(String account, String password) { - return userService.login(account, password); - } - - public ResponseEntity signUp(String account, String password, String email) { - userService.save(account, password, email); - - return ResponseEntity.of(HttpStatus.FOUND, "/index"); - } - -} 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..28c31a8a7e 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,16 +16,19 @@ public class Connector implements Runnable { private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_ACCEPT_COUNT = 100; + private static final int MAX_THREADS = 250; private final ServerSocket serverSocket; + private final ExecutorService executorService; private boolean stopped; public Connector() { - this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT); + this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, 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.executorService = Executors.newFixedThreadPool(maxThreads); this.stopped = false; } @@ -67,12 +71,13 @@ 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 { + executorService.shutdown(); serverSocket.close(); } catch (IOException e) { log.error(e.getMessage(), e); 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 bcebee44e3..34c41030a9 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -8,9 +8,8 @@ import java.nio.charset.StandardCharsets; import nextstep.jwp.exception.UncheckedServletException; import org.apache.coyote.Processor; -import org.apache.coyote.http11.handler.HandlerMapping; +import org.apache.coyote.http11.controller.RequestMapping; import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.HttpRequestParser; import org.apache.coyote.http11.response.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,7 +17,7 @@ public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - private static final HandlerMapping HANDLER_MAPPING = new HandlerMapping(); + private static final RequestMapping REQUEST_MAPPING = RequestMapping.INSTANCE; private final Socket connection; @@ -40,7 +39,9 @@ public void process(final Socket connection) { final var reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)) ) { HttpRequest httpRequest = HttpRequestParser.extract(reader); - HttpResponse httpResponse = HANDLER_MAPPING.extractHttpResponse(httpRequest); + HttpResponse httpResponse = HttpResponseParser.extract(httpRequest); + + REQUEST_MAPPING.extractHttpResponse(httpRequest, httpResponse); String response = httpResponse.extractResponse(); outputStream.write(response.getBytes()); diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java similarity index 82% rename from tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java rename to tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java index c82fff2041..9b6944d942 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/HttpRequestParser.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java @@ -1,10 +1,14 @@ -package org.apache.coyote.http11.request; +package org.apache.coyote.http11; import java.io.BufferedReader; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import org.apache.coyote.http11.common.HttpHeaders; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.RequestBody; +import org.apache.coyote.http11.request.RequestLine; public class HttpRequestParser { @@ -15,7 +19,7 @@ private HttpRequestParser() { public static HttpRequest extract(BufferedReader reader) throws IOException { RequestLine requestLine = extractRequestLine(reader); - RequestHeaders requestHeaders = extractRequestHeaders(reader); + HttpHeaders requestHeaders = extractRequestHeaders(reader); RequestBody requestBody = extractRequestBody(reader, requestHeaders.contentLength()); return new HttpRequest(requestLine, requestHeaders, requestBody); @@ -29,7 +33,7 @@ private static RequestLine extractRequestLine(BufferedReader reader) throws IOEx return RequestLine.from(requestLine); } - private static RequestHeaders extractRequestHeaders(BufferedReader reader) throws IOException { + private static HttpHeaders extractRequestHeaders(BufferedReader reader) throws IOException { Map requestHeaders = new HashMap<>(); String line = reader.readLine(); @@ -38,7 +42,7 @@ private static RequestHeaders extractRequestHeaders(BufferedReader reader) throw line = reader.readLine(); } - return RequestHeaders.from(requestHeaders); + return HttpHeaders.createRequestHeaders(requestHeaders); } private static boolean canRead(String line) { diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponseParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponseParser.java new file mode 100644 index 0000000000..1e6e61d109 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponseParser.java @@ -0,0 +1,18 @@ +package org.apache.coyote.http11; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.RequestLine; +import org.apache.coyote.http11.response.HttpResponse; + +public class HttpResponseParser { + + private HttpResponseParser() { + } + + public static HttpResponse extract(HttpRequest httpRequest) { + RequestLine requestLine = httpRequest.getRequestLine(); + + return new HttpResponse(requestLine.getHttpVersion()); + } + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Renderer.java b/tomcat/src/main/java/org/apache/coyote/http11/Renderer.java new file mode 100644 index 0000000000..b326bdd4fd --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/Renderer.java @@ -0,0 +1,83 @@ +package org.apache.coyote.http11; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import org.apache.coyote.http11.common.MimeType; +import org.apache.coyote.http11.response.ResponseBody; + +public class Renderer { + + public static final Renderer EMPTY = Renderer.from("/404"); + + private final String mimeType; + private final String contentLength; + private final ResponseBody body; + + private Renderer(String mimeType, String contentLength, ResponseBody body) { + this.mimeType = mimeType; + this.contentLength = contentLength; + this.body = body; + } + + public static Renderer from(String path) { + URL resource = Renderer.class.getClassLoader() + .getResource(String.format("static/%s", findResourcePath(path))); + + if (resource != null) { + return createRenderer(resource); + } + return EMPTY; + } + + private static String findResourcePath(String path) { + if (path.contains(".")) { + return path; + } + return path + ".html"; + } + + private static Renderer createRenderer(URL resource) { + File file = new File(resource.getFile()); + + String content = readFileContent(file); + int contentLength = content.getBytes().length; + + return new Renderer( + determineMimeType(file), + String.valueOf(contentLength), + new ResponseBody(content) + ); + } + + private static String determineMimeType(File file) { + String fileName = file.getName(); + String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1); + + MimeType mimeType = MimeType.from(fileExtension); + + + return String.format("%s;charset=utf-8 ", mimeType.getContentType()); + } + + public static String readFileContent(File file) { + try { + return Files.readString(file.toPath()); + } catch (IOException | NullPointerException e) { + return ""; + } + } + + public String getMimeType() { + return mimeType; + } + + public String getContentLength() { + return contentLength; + } + + public ResponseBody getResponseBody() { + return body; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/ViewResolver.java b/tomcat/src/main/java/org/apache/coyote/http11/ViewResolver.java deleted file mode 100644 index 0c1f9f0b56..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/ViewResolver.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.apache.coyote.http11; - -import java.io.File; -import java.net.URL; - -public class ViewResolver { - - private ViewResolver() { - } - - public static File findViewFile(String path) { - String resourcePath = findResourcePath(path); - URL resource = ViewResolver.class.getClassLoader() - .getResource(String.format("static/%s", resourcePath)); - - if (resource != null) { - return new File(resource.getFile()); - } - return null; - } - - private static String findResourcePath(String path) { - if (path.contains(".")) { - return path; - } - return path + ".html"; - } - - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookie.java index 5cf02c4deb..d57aa3691c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookie.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpCookie.java @@ -49,6 +49,10 @@ public void put(String key, String value) { cookies.put(key, value); } + public void putAll(HttpCookie httpCookie) { + this.cookies.putAll(httpCookie.cookies); + } + public String find(String key) { return cookies.get(key); } @@ -66,5 +70,4 @@ private String formatCookies(Map.Entry entry) { return String.format("Set-Cookie: %s=%s ", entry.getKey(), entry.getValue()); } - } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpHeaders.java new file mode 100644 index 0000000000..7a21d48ecf --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpHeaders.java @@ -0,0 +1,71 @@ +package org.apache.coyote.http11.common; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class HttpHeaders { + + private final Map headers; + private final HttpCookie httpCookie; + + private HttpHeaders(Map headers, HttpCookie httpCookie) { + this.headers = headers; + this.httpCookie = httpCookie; + } + + public static HttpHeaders createRequestHeaders(Map headers) { + String cookie = headers.remove("Cookie"); + + return new HttpHeaders(headers, HttpCookie.from(cookie)); + } + + public static HttpHeaders createResponseHeaders() { + return new HttpHeaders(new LinkedHashMap<>(), HttpCookie.create()); + } + + public void addHeader(String key, String value) { + headers.put(key, value); + } + + public void addCookie(String key, String value) { + httpCookie.put(key, value); + } + + public void addCookies(HttpCookie httpCookie) { + this.httpCookie.putAll(httpCookie); + } + + public String get(String key) { + return headers.get(key); + } + + public String findCookie(String key) { + return httpCookie.find(key); + } + + public String findJSessionId() { + return httpCookie.findJSessionId(); + } + + public String contentLength() { + return headers.getOrDefault("Content-Length", "0"); + } + + public StringBuilder convertResponseHeaders() { + return new StringBuilder() + .append(httpCookie.getCookies()) + .append(getHeaders()); + } + + private String getHeaders() { + return headers.entrySet().stream() + .map(this::formatHeaders) + .collect(Collectors.joining("\r\n", "", " ")); + } + + private String formatHeaders(Map.Entry entry) { + return String.join(": ", entry.getKey(), entry.getValue()); + } + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpMethod.java b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpMethod.java index e1bc4aad4b..6f345306fc 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpMethod.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpMethod.java @@ -2,9 +2,6 @@ public enum HttpMethod { - GET, POST; + GET, POST - public boolean isEqualTo(HttpMethod httpMethod) { - return this == httpMethod; - } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpStatus.java b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpStatus.java index 27bd705ee4..414f82ed54 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpStatus.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpStatus.java @@ -2,20 +2,24 @@ public enum HttpStatus { - OK(200), - FOUND(302), - UNAUTHORIZED(401), - NOT_FOUND(404), - INTERNAL_SERVER_ERROR(500); + OK("200"), + FOUND("302"), + UNAUTHORIZED("401"), + NOT_FOUND("404"), + INTERNAL_SERVER_ERROR("500"); - private final int statusCode; + private final String statusCode; - HttpStatus(int statusCode) { + HttpStatus(String statusCode) { this.statusCode = statusCode; } public String getHttpStatus() { - return String.format("%d %s", statusCode, name()); + return String.join(" ", statusCode, name()); + } + + public boolean isFound() { + return this == FOUND; } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/HttpVersion.java b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpVersion.java new file mode 100644 index 0000000000..d19026cf3d --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/HttpVersion.java @@ -0,0 +1,25 @@ +package org.apache.coyote.http11.common; + +import java.util.Arrays; + +public enum HttpVersion { + HTTP_1_0("HTTP/1.0"), + HTTP_1_1("HTTP/1.1"); + + private final String version; + + HttpVersion(String name) { + this.version = name; + } + + public static HttpVersion from(String field) { + return Arrays.stream(values()) + .filter(httpVersion -> httpVersion.version.equals(field)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당 Http Version은 지원하지 않습니다.")); + } + + public String getVersion() { + return version; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/common/MimeType.java b/tomcat/src/main/java/org/apache/coyote/http11/common/MimeType.java index 8f9e76e200..8028db1093 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/common/MimeType.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/common/MimeType.java @@ -1,7 +1,5 @@ package org.apache.coyote.http11.common; -import java.io.File; - public enum MimeType { HTML("text/html"), @@ -16,13 +14,7 @@ public enum MimeType { this.contentType = contentType; } - public static MimeType from(File file) { - if (file == null) { - return HTML; - } - String fileName = file.getName(); - String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1); - + public static MimeType from(String fileExtension) { return MimeType.valueOf(fileExtension.toUpperCase()); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/AbstractController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/AbstractController.java new file mode 100644 index 0000000000..637227fb1c --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/AbstractController.java @@ -0,0 +1,34 @@ +package org.apache.coyote.http11.controller; + +import org.apache.coyote.http11.common.HttpMethod; +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public abstract class AbstractController implements Controller { + + protected final T service; + + protected AbstractController(T service) { + this.service = service; + } + + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) { + if (httpRequest.isSameHttpMethod(HttpMethod.GET)) { + doGet(httpRequest, httpResponse); + return; + } + if (httpRequest.isSameHttpMethod(HttpMethod.POST)) { + doPost(httpRequest, httpResponse); + return; + } + httpResponse.setHttpStatus(HttpStatus.NOT_FOUND) + .setPath("/404.html"); + } + + protected abstract void doGet(HttpRequest httpRequest, HttpResponse httpResponse); + + protected abstract void doPost(HttpRequest httpRequest, HttpResponse httpResponse); + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java new file mode 100644 index 0000000000..a7aa3167ec --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/Controller.java @@ -0,0 +1,10 @@ +package org.apache.coyote.http11.controller; + +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.response.HttpResponse; + +public interface Controller { + + void service(HttpRequest httpRequest, HttpResponse httpResponse); + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/RequestMapping.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/RequestMapping.java new file mode 100644 index 0000000000..9863741e39 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/RequestMapping.java @@ -0,0 +1,70 @@ +package org.apache.coyote.http11.controller; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import nextstep.jwp.controller.DefaultController; +import nextstep.jwp.controller.LoginController; +import nextstep.jwp.controller.NotFoundController; +import nextstep.jwp.controller.RegisterController; +import nextstep.jwp.controller.UnauthorizedController; +import nextstep.jwp.service.UserService; +import org.apache.coyote.http11.Renderer; +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.common.MimeType; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.RequestLine; +import org.apache.coyote.http11.response.HttpResponse; +import org.apache.coyote.http11.response.ResponseBody; + +public enum RequestMapping { + + INSTANCE; + + private static final Map controllers = new ConcurrentHashMap<>(); + + static { + controllers.put("/", new DefaultController()); + controllers.put("/index", new StaticResourceController()); + controllers.put("/login", new LoginController(new UserService())); + controllers.put("/register", new RegisterController(new UserService())); + controllers.put("/401", new UnauthorizedController()); + } + + public void extractHttpResponse(HttpRequest request, HttpResponse response) { + RequestLine requestLine = request.getRequestLine(); + String path = requestLine.getPath(); + Controller controller = getController(path); + try { + controller.service(request, response); + } catch (RuntimeException e) { + response.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR) + .setPath("/500"); + } + + render(response); + } + + private Controller getController(String path) { + if (path.contains(".")) { + return new StaticResourceController(); + } + return controllers.getOrDefault(path, new NotFoundController()); + } + + private void render(HttpResponse response) { + String path = response.getPath(); + + if (response.isFound()) { + response.addHeader("Content-Type", MimeType.HTML.getContentType()) + .addHeader("Location", path) + .setResponseBody(ResponseBody.empty()); + return; + } + + Renderer renderer = Renderer.from(path); + response.addHeader("Content-Type", renderer.getMimeType()) + .addHeader("Content-Length", renderer.getContentLength()) + .setResponseBody(renderer.getResponseBody()); + } + +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/controller/StaticResourceController.java b/tomcat/src/main/java/org/apache/coyote/http11/controller/StaticResourceController.java new file mode 100644 index 0000000000..d02067ac80 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/controller/StaticResourceController.java @@ -0,0 +1,17 @@ +package org.apache.coyote.http11.controller; + +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.request.HttpRequest; +import org.apache.coyote.http11.request.RequestLine; +import org.apache.coyote.http11.response.HttpResponse; + +public class StaticResourceController implements Controller { + + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) { + RequestLine requestLine = httpRequest.getRequestLine(); + + httpResponse.setHttpStatus(HttpStatus.OK) + .setPath(requestLine.getPath()); + } +} 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 971d813f9b..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/DefaultHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.apache.coyote.http11.handler; - -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.RequestLine; -import org.apache.coyote.http11.response.ResponseEntity; - -public class DefaultHandler implements HttpHandler { - @Override - public boolean canHandle(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - String path = requestLine.getPath(); - - return "/".equals(path); - } - - @Override - public ResponseEntity handle(HttpRequest httpRequest) { - return ResponseEntity.of(HttpStatus.OK, "/default"); - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapping.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapping.java deleted file mode 100644 index 0c65860998..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HandlerMapping.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.apache.coyote.http11.handler; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; -import org.apache.coyote.http11.ViewResolver; -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.common.MimeType; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.response.HttpResponse; -import org.apache.coyote.http11.response.ResponseBody; -import org.apache.coyote.http11.response.ResponseEntity; -import org.apache.coyote.http11.response.ResponseHeaders; -import org.apache.coyote.http11.response.ResponseLine; - -public class HandlerMapping { - - private static final List HTTP_HANDLERS = List.of( - new ResourceHandler(), - new DefaultHandler(), - new IndexHandler(), - new LoginHandler(), - new RegisterHandler() - ); - - public HttpResponse extractHttpResponse(HttpRequest request) { - ResponseEntity responseEntity = handle(request); - File viewFile = ViewResolver.findViewFile(responseEntity.getPath()); - - return new HttpResponse( - new ResponseLine(responseEntity.getHttpStatus()), - ResponseHeaders.from(responseEntity, MimeType.from(viewFile)), - new ResponseBody(readString(viewFile)) - ); - } - - private ResponseEntity handle(final HttpRequest request) { - try { - return HTTP_HANDLERS.stream() - .filter(httpHandler -> httpHandler.canHandle(request)) - .findFirst() - .orElseGet(NotFoundHandler::new) - .handle(request); - } catch (RuntimeException e) { - return ResponseEntity.of(HttpStatus.INTERNAL_SERVER_ERROR, "/500"); - } - } - - private String readString(File file) { - try { - return Files.readString(file.toPath()); - } catch (IOException e) { - return ""; - } - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java deleted file mode 100644 index 9199f7a2f1..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/HttpHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.apache.coyote.http11.handler; - -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.response.ResponseEntity; - -public interface HttpHandler { - - boolean canHandle(HttpRequest httpRequest); - - ResponseEntity handle(HttpRequest httpRequest); -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/IndexHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/IndexHandler.java deleted file mode 100644 index 21b79a8f6b..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/IndexHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.apache.coyote.http11.handler; - -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.RequestLine; -import org.apache.coyote.http11.response.ResponseEntity; - -public class IndexHandler implements HttpHandler { - @Override - public boolean canHandle(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - - return "/index".equals(requestLine.getPath()); - } - - @Override - public ResponseEntity handle(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - - return ResponseEntity.of(HttpStatus.OK, requestLine.getPath()); - } -} 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 031b4827b1..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/LoginHandler.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.apache.coyote.http11.handler; - -import nextstep.jwp.model.User; -import org.apache.coyote.http11.common.HttpCookie; -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.RequestBody; -import org.apache.coyote.http11.request.RequestLine; -import org.apache.coyote.http11.response.ResponseEntity; -import org.apache.coyote.http11.session.HttpSession; - -public class LoginHandler extends UserHandler { - - @Override - public boolean canHandle(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - - return "/login".equals(requestLine.getPath()); - } - - @Override - protected ResponseEntity doGet(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - HttpSession session = httpRequest.getSession(false); - - if (session != null && session.getAttribute("user") != null) { - return ResponseEntity.of(HttpStatus.FOUND, "/index"); - } - - if (requestLine.isQueryStringExisted()) { - String account = requestLine.findQueryStringValue("account"); - String password = requestLine.findQueryStringValue("password"); - - return getResponseEntity(httpRequest, account, password); - } - - return ResponseEntity.of(HttpStatus.OK, requestLine.getPath()); - } - - @Override - protected ResponseEntity doPost(HttpRequest httpRequest) { - RequestBody requestBody = httpRequest.getRequestBody(); - - String account = requestBody.get("account"); - String password = requestBody.get("password"); - - return getResponseEntity(httpRequest, account, password); - } - - private ResponseEntity getResponseEntity(HttpRequest httpRequest, String account, String password) { - try { - User user = userController.login(account, password); - HttpSession httpSession = httpRequest.getSession(true); - httpSession.setAttribute("user", user); - - httpRequest.addSession(httpSession); - - HttpCookie httpCookie = HttpCookie.create(); - httpCookie.putJSessionId(httpSession.getId()); - - return ResponseEntity.cookie(httpCookie, HttpStatus.FOUND, "/index"); - } catch (IllegalArgumentException e) { - return ResponseEntity.of(HttpStatus.FOUND, "/401"); - } - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/NotFoundHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/NotFoundHandler.java deleted file mode 100644 index f91b9c43d5..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/NotFoundHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.apache.coyote.http11.handler; - -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.response.ResponseEntity; - -public class NotFoundHandler implements HttpHandler { - - @Override - public boolean canHandle(HttpRequest httpRequest) { - return false; - } - - @Override - public ResponseEntity handle(HttpRequest httpRequest) { - return ResponseEntity.of(HttpStatus.NOT_FOUND, "/404"); - } - -} 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 8f6cbd3955..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/RegisterHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.apache.coyote.http11.handler; - -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.RequestBody; -import org.apache.coyote.http11.request.RequestLine; -import org.apache.coyote.http11.response.ResponseEntity; - -public class RegisterHandler extends UserHandler { - - @Override - public boolean canHandle(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - - return "/register".equals(requestLine.getPath()); - } - - @Override - protected ResponseEntity doGet(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - - if (requestLine.isQueryStringExisted()) { - String account = requestLine.findQueryStringValue("account"); - String password = requestLine.findQueryStringValue("password"); - String email = requestLine.findQueryStringValue("email"); - - return userController.signUp(account, password, email); - } - return ResponseEntity.of(HttpStatus.OK, requestLine.getPath()); - } - - @Override - protected ResponseEntity doPost(HttpRequest httpRequest) { - RequestBody requestBody = httpRequest.getRequestBody(); - - String account = requestBody.get("account"); - String password = requestBody.get("password"); - String email = requestBody.get("email"); - - return userController.signUp(account, password, email); - } -} 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 64b8ba87ab..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/ResourceHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.apache.coyote.http11.handler; - -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.request.RequestLine; -import org.apache.coyote.http11.response.ResponseEntity; - -public class ResourceHandler implements HttpHandler { - - @Override - public boolean canHandle(HttpRequest httpRequest) { - return httpRequest.isStaticResource(); - } - - @Override - public ResponseEntity handle(HttpRequest httpRequest) { - RequestLine requestLine = httpRequest.getRequestLine(); - - return ResponseEntity.of(HttpStatus.OK, requestLine.getPath()); - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/handler/UserHandler.java b/tomcat/src/main/java/org/apache/coyote/http11/handler/UserHandler.java deleted file mode 100644 index f6555bf711..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/handler/UserHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.apache.coyote.http11.handler; - -import nextstep.jwp.controller.UserController; -import nextstep.jwp.service.UserService; -import org.apache.coyote.http11.common.HttpMethod; -import org.apache.coyote.http11.request.HttpRequest; -import org.apache.coyote.http11.response.ResponseEntity; - -public abstract class UserHandler implements HttpHandler { - - protected final UserController userController; - - protected UserHandler() { - this.userController = new UserController(new UserService()); - } - - @Override - public ResponseEntity handle(HttpRequest httpRequest) { - if (httpRequest.isSameHttpMethod(HttpMethod.GET)) { - return doGet(httpRequest); - } - return doPost(httpRequest); - } - - protected abstract ResponseEntity doGet(HttpRequest httpRequest); - - protected abstract ResponseEntity doPost(HttpRequest httpRequest); - -} 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 aba7261f44..ccbf4ee12f 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 @@ -3,21 +3,22 @@ import java.io.IOException; import java.util.UUID; import org.apache.catalina.Manager; +import org.apache.coyote.http11.common.HttpHeaders; import org.apache.coyote.http11.common.HttpMethod; import org.apache.coyote.http11.session.HttpSession; import org.apache.coyote.http11.session.SessionManager; public class HttpRequest { - public static final Manager SESSION_MANAGER = new SessionManager(); + private static final Manager SESSION_MANAGER = SessionManager.INSTANCE; private final RequestLine requestLine; - private final RequestHeaders requestHeaders; + private final HttpHeaders requestHeaders; private final RequestBody requestBody; public HttpRequest( RequestLine requestLine, - RequestHeaders requestHeaders, + HttpHeaders requestHeaders, RequestBody requestBody ) { this.requestLine = requestLine; @@ -29,15 +30,11 @@ public boolean isSameHttpMethod(HttpMethod httpMethod) { return requestLine.isSameHttpMethod(httpMethod); } - public boolean isStaticResource() { - return requestLine.isStaticResource(); - } - public RequestLine getRequestLine() { return requestLine; } - public RequestHeaders getRequestHeaders() { + public HttpHeaders getRequestHeaders() { return requestHeaders; } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java index e992bf931c..d9e4aa32cc 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestBody.java @@ -11,11 +11,11 @@ public class RequestBody { private final Map body; - private RequestBody(final Map body) { + private RequestBody(Map body) { this.body = body; } - public static RequestBody from(final String requestBody) { + public static RequestBody from(String requestBody) { if (requestBody == null || requestBody.isEmpty()) { return EMPTY; } @@ -30,7 +30,7 @@ public static RequestBody from(final String requestBody) { return new RequestBody(body); } - public String get(final String key) { + public String get(String key) { return body.get(key); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java deleted file mode 100644 index 56a93bf576..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestHeaders.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.apache.coyote.http11.request; - -import java.util.Map; -import org.apache.coyote.http11.common.HttpCookie; - -public class RequestHeaders { - - private final Map headers; - private final HttpCookie httpCookie; - - private RequestHeaders(Map headers, HttpCookie httpCookie) { - this.headers = headers; - this.httpCookie = httpCookie; - } - - public static RequestHeaders from(Map headers) { - String cookie = headers.remove("Cookie"); - - return new RequestHeaders(headers, HttpCookie.from(cookie)); - } - - public String get(String key) { - return headers.get(key); - } - - public String contentLength() { - return headers.getOrDefault("Content-Length", "0"); - } - - public String findCookie(String key) { - return httpCookie.find(key); - } - - public String findJSessionId() { - return httpCookie.findJSessionId(); - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java index beb873b5a6..48cdedfb38 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/request/RequestLine.java @@ -1,15 +1,16 @@ package org.apache.coyote.http11.request; import org.apache.coyote.http11.common.HttpMethod; +import org.apache.coyote.http11.common.HttpVersion; public class RequestLine { private static final String REQUEST_LINE_DELIMITER = " "; private final HttpMethod httpMethod; private final RequestUri requestUri; - private final String httpVersion; + private final HttpVersion httpVersion; - private RequestLine(HttpMethod httpMethod, RequestUri requestUri, String httpVersion) { + private RequestLine(HttpMethod httpMethod, RequestUri requestUri, HttpVersion httpVersion) { this.httpMethod = httpMethod; this.requestUri = requestUri; this.httpVersion = httpVersion; @@ -20,18 +21,15 @@ public static RequestLine from(String requestLine) { HttpMethod httpMethod = HttpMethod.valueOf(requestLineParts[0].toUpperCase()); RequestUri requestUri = RequestUri.from(requestLineParts[1]); + HttpVersion httpVersion = HttpVersion.from(requestLineParts[2]); - return new RequestLine(httpMethod, requestUri, requestLineParts[2]); + return new RequestLine(httpMethod, requestUri, httpVersion); } public boolean isSameHttpMethod(HttpMethod method) { return httpMethod.equals(method); } - public boolean isStaticResource() { - return requestUri.isStaticResource(); - } - public boolean isQueryStringExisted() { return requestUri.isQueryStringExisted(); } @@ -48,7 +46,7 @@ public HttpMethod getHttpMethod() { return httpMethod; } - public String getHttpVersion() { + public HttpVersion getHttpVersion() { return httpVersion; } 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 a8add1605d..e3f1732b14 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,29 +1,72 @@ package org.apache.coyote.http11.response; +import org.apache.coyote.http11.common.HttpCookie; +import org.apache.coyote.http11.common.HttpHeaders; +import org.apache.coyote.http11.common.HttpStatus; +import org.apache.coyote.http11.common.HttpVersion; + public class HttpResponse { private static final String CRLF = "\r\n"; - private final ResponseLine responseLine; - private final ResponseHeaders responseHeaders; - private final ResponseBody responseBody; - - public HttpResponse( - ResponseLine responseLine, - ResponseHeaders responseHeaders, - ResponseBody responseBody - ) { - this.responseLine = responseLine; - this.responseHeaders = responseHeaders; - this.responseBody = responseBody; + private final HttpVersion httpVersion; + private final HttpHeaders responseHeaders; + private HttpStatus httpStatus; + private ResponseBody responseBody; + private String path; + + public HttpResponse(HttpVersion httpVersion) { + this.httpVersion = httpVersion; + this.responseHeaders = HttpHeaders.createResponseHeaders(); + } + + public boolean isFound() { + return httpStatus.isFound(); } public String extractResponse() { return new StringBuilder() - .append(responseLine.convertStatusLine()).append(CRLF) + .append(convertStatusLine()).append(CRLF) .append(responseHeaders.convertResponseHeaders()).append(CRLF) .append(responseBody.getBody()) .toString(); } + private String convertStatusLine() { + return String.format("%s %s ", httpVersion.getVersion(), httpStatus.getHttpStatus()); + } + + public HttpResponse setHttpStatus(HttpStatus httpStatus) { + this.httpStatus = httpStatus; + return this; + } + + public HttpResponse setPath(String path) { + this.path = path; + return this; + } + + public void setCookie(HttpCookie httpCookie) { + responseHeaders.addCookies(httpCookie); + } + + public String getPath() { + return path; + } + + public HttpResponse addHeader(String key, String value) { + responseHeaders.addHeader(key, value); + return this; + } + + public void setResponseBody(ResponseBody responseBody) { + this.responseBody = responseBody; + } + + @Override + public String toString() { + return "HttpResponse{" + + "responseHeaders=" + responseHeaders + + '}'; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java index 0809bed8ab..fa41063ccd 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseBody.java @@ -5,16 +5,16 @@ public class ResponseBody { private static final String CRLF = "\r\n"; private static final String EMPTY = ""; + public static final ResponseBody RESPONSE_BODY = new ResponseBody(EMPTY); + private final String body; public ResponseBody(String body) { this.body = body; } - private String convertContentLength() { - String contentLengthFormat = "Content-Length: %d "; - - return String.format(contentLengthFormat, body.getBytes().length); + public static ResponseBody empty() { + return RESPONSE_BODY; } public StringBuilder getBody() { @@ -22,7 +22,6 @@ public StringBuilder getBody() { return new StringBuilder(EMPTY); } return new StringBuilder() - .append(convertContentLength()).append(CRLF) .append(CRLF) .append(body); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseEntity.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseEntity.java deleted file mode 100644 index e3204f8d3c..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseEntity.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.apache.coyote.http11.response; - -import org.apache.coyote.http11.common.HttpCookie; -import org.apache.coyote.http11.common.HttpStatus; - -public class ResponseEntity { - - private final HttpStatus httpStatus; - private final String path; - private final HttpCookie httpCookie; - - private ResponseEntity(HttpStatus httpStatus, String path, HttpCookie httpCookie) { - this.httpStatus = httpStatus; - this.path = path; - this.httpCookie = httpCookie; - } - - public static ResponseEntity of(HttpStatus httpStatus, String path) { - return new ResponseEntity(httpStatus, path, HttpCookie.create()); - } - - public static ResponseEntity cookie(HttpCookie httpCookie, HttpStatus httpStatus, String path) { - return new ResponseEntity(httpStatus, path, httpCookie); - } - - public HttpStatus getHttpStatus() { - return httpStatus; - } - - public String getPath() { - return path; - } - - public HttpCookie getHttpCookie() { - return httpCookie; - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java deleted file mode 100644 index c4db985beb..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseHeaders.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.apache.coyote.http11.response; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import org.apache.coyote.http11.common.HttpCookie; -import org.apache.coyote.http11.common.HttpStatus; -import org.apache.coyote.http11.common.MimeType; - -public class ResponseHeaders { - - private final Map headers; - private final HttpCookie httpCookie; - - public ResponseHeaders(Map headers, HttpCookie httpCookie) { - this.headers = headers; - this.httpCookie = httpCookie; - } - - public static ResponseHeaders from(ResponseEntity responseEntity, MimeType mimeType) { - String path = responseEntity.getPath(); - Map headers = makeHeaders(responseEntity.getHttpStatus(), mimeType, path); - return new ResponseHeaders(headers, responseEntity.getHttpCookie()); - } - - private static Map makeHeaders(HttpStatus httpStatus, MimeType mimeType, String path) { - Map headers = new HashMap<>(); - - if (httpStatus.equals(HttpStatus.FOUND)) { - headers.put("Content-Type", convertContentType(MimeType.HTML)); - headers.put("Location", path); - } - - headers.put("Content-Type", convertContentType(mimeType)); - return headers; - } - - private static String convertContentType(MimeType mimeType) { - return String.format("%s;charset=utf-8 ", mimeType.getContentType()); - } - - public StringBuilder convertResponseHeaders() { - return new StringBuilder() - .append(httpCookie.getCookies()) - .append(getHeaders()); - } - - private String getHeaders() { - return headers.entrySet().stream() - .map(this::formatHeaders) - .collect(Collectors.joining("\r\n")); - } - - private String formatHeaders(Map.Entry entry) { - return String.join(": ", entry.getKey(), entry.getValue()); - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseLine.java b/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseLine.java deleted file mode 100644 index ee8b1c7393..0000000000 --- a/tomcat/src/main/java/org/apache/coyote/http11/response/ResponseLine.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.apache.coyote.http11.response; - -import org.apache.coyote.http11.common.HttpStatus; - -public class ResponseLine { - - private final HttpStatus httpStatus; - - public ResponseLine(HttpStatus httpStatus) { - this.httpStatus = httpStatus; - } - - public String convertStatusLine() { - String statusLineFormat = "HTTP/1.1 %s "; - - return String.format(statusLineFormat, httpStatus.getHttpStatus()); - } - -} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/session/HttpSession.java b/tomcat/src/main/java/org/apache/coyote/http11/session/HttpSession.java index 55300b9fb8..2d3d191d46 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/session/HttpSession.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/session/HttpSession.java @@ -12,15 +12,15 @@ public HttpSession(String id) { this.id = id; } - public Object getAttribute(final String name) { + public Object getAttribute(String name) { return attributes.get(name); } - public void setAttribute(final String name, final Object value) { + public void setAttribute(String name, Object value) { attributes.put(name, value); } - public void removeAttribute(final String name) { + public void removeAttribute(String name) { attributes.remove(name); } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java b/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java index 4714747ffc..1b9f04a050 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/session/SessionManager.java @@ -1,26 +1,28 @@ package org.apache.coyote.http11.session; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.catalina.Manager; -public class SessionManager implements Manager { +public enum SessionManager implements Manager { - private static final Map SESSIONS = new HashMap<>(); + INSTANCE; + + private static final Map sessions = new ConcurrentHashMap<>(); @Override public void add(HttpSession httpSession) { - SESSIONS.put(httpSession.getId(), httpSession); + sessions.put(httpSession.getId(), httpSession); } @Override public HttpSession findSession(String id) { - return SESSIONS.get(id); + return sessions.get(id); } @Override public void remove(HttpSession httpSession) { - SESSIONS.remove(httpSession.getId()); + sessions.remove(httpSession.getId()); } }