Skip to content

Commit

Permalink
[톰캣 구현하기 - 3, 4단계] 망고(고재철) 미션 제출합니다. (#460)
Browse files Browse the repository at this point in the history
* docs: 3, 4단계 요구사항 정리

* refactor: RequestLine 객체 생성

* refactor: RequestHeader 객체 생성

* refactor: HttpRequest 객체 분리

* refactor: StatusLine 객체 생성

* refactor: ResponseHeader 객체 생성

* refactor: HttpResponse 객체 분리

* feat: ResponseBody 객체 추가

* test: HttpResponse toString 테스트 추가

* feat: HttpResponse 빈 객체 생성자 추가

* feat: Controller 인터페이스 추가

* feat: HomeController 구현

* feat: ResourceController 구현

* feat: RegisterController 구현

* feat: LoginController 구현

* refactor: RequestMapping 객체 추가

* test: 캐시 학습 테스트 완료

* test: Thread 학습 테스트 완료

* feat: Thread Pool 적용

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

* refactor: 405 Method Not Allowed 반영

* refactor: HttpMethod enum 생성

* refactor: Request Parameter 구하는 책임 변경

* refactor: HttpRequest에서 Cookie 값 반환하도록 책임 변경

* refactor: httpRequest, httpResponse 변수명 통일
  • Loading branch information
Go-Jaecheol authored Sep 14, 2023
1 parent ce909aa commit 82e4db7
Show file tree
Hide file tree
Showing 40 changed files with 1,649 additions and 214 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@
- [x] POST 방식으로 회원가입
- [x] Cookie에 JSESSIONID 값 저장하기
- [x] Session 구현하기

- 3단계 - 리팩터링
- [x] HttpRequest 클래스 구현하기
- [x] HttpResponse 클래스 구현하기
- [x] Controller 인터페이스 추가하기

- 4단계 - 동시성 확장하기
- [x] Executors로 Thread Pool 적용
- [x] 동시성 컬렉션 사용하기
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
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) {
final var cacheControl = CacheControl.noCache().cachePrivate();
final var webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.addCacheMapping(cacheControl, "/**");
registry.addInterceptor(webContentInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package cache.com.example.etag;

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;

import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES;

@Configuration
public class EtagFilterConfiguration {

// @Bean
// public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
// return null;
// }
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
final var filterRegistrationBean = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/etag", PREFIX_STATIC_RESOURCES + "/*");
return filterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

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;

import java.time.Duration;

@Configuration
public class CacheBustingWebConfig implements WebMvcConfigurer {

Expand All @@ -20,6 +23,7 @@ public CacheBustingWebConfig(ResourceVersion version) {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
.addResourceLocations("classpath:/static/");
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic());
}
}
3 changes: 3 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ server:
max-connections: 1
threads:
max: 2
compression:
enabled: true
min-response-size: 10
2 changes: 1 addition & 1 deletion study/src/test/java/thread/stage0/SynchronizationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private static final class SynchronizedMethods {

private int sum = 0;

public void calculate() {
public synchronized void calculate() {
setSum(getSum() + 1);
}

Expand Down
6 changes: 3 additions & 3 deletions study/src/test/java/thread/stage0/ThreadPoolsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nextstep.jwp.controller;

import nextstep.jwp.exception.HttpRequestException;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

import java.io.IOException;

public abstract class AbstractController implements Controller {

protected static final String TEXT_HTML = "text/html;charset=utf-8";
protected static final String TEXT_CSS = "text/css;";
protected static final String INDEX_PAGE = "/index.html";
protected static final String UNAUTHORIZED_PAGE = "/401.html";
protected static final String HEADER_LOCATION = "Location";
protected static final String HEADER_SET_COOKIE = "Set-Cookie";
protected static final String HEADER_CONTENT_TYPE = "Content-Type";
protected static final String HEADER_CONTENT_LENGTH = "Content-Length";
protected static final String HTTP_METHOD_EXCEPTION_MESSAGE = "올바르지 않은 HTTP Method 입니다.";

@Override
public void service(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException {
if (httpRequest.getRequestLine().getMethod().equals("POST")) {
doPost(httpRequest, httpResponse);
return;
}
if (httpRequest.getRequestLine().getMethod().equals("GET")) {
doGet(httpRequest, httpResponse);
return;
}
throw new HttpRequestException(HTTP_METHOD_EXCEPTION_MESSAGE);
}

protected abstract void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException;

protected abstract void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException;
}
11 changes: 11 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.jwp.controller;

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

import java.io.IOException;

public interface Controller {

void service(final HttpRequest request, final HttpResponse httpResponse) throws IOException;
}
26 changes: 26 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/HomeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.jwp.controller;

import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpStatus;
import org.apache.coyote.http11.response.ResponseBody;
import org.apache.coyote.http11.response.StatusLine;

public class HomeController extends AbstractController {

@Override
protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) {
final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED);
httpResponse.setStatusLine(statusLine);
}

@Override
protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) {
final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK);
final var responseBody = ResponseBody.fromText("Hello world!");
httpResponse.setStatusLine(statusLine);
httpResponse.addResponseHeader("Content-Type", TEXT_HTML);
httpResponse.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length));
httpResponse.setResponseBody(responseBody);
}
}
78 changes: 78 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.http11.HttpCookie;
import org.apache.coyote.http11.Session;
import org.apache.coyote.http11.SessionManager;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpStatus;
import org.apache.coyote.http11.response.ResponseBody;
import org.apache.coyote.http11.response.StatusLine;

import java.io.IOException;
import java.util.Map;
import java.util.Optional;

public class LoginController extends AbstractController {

private static final SessionManager SESSION_MANAGER = new SessionManager();

@Override
protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) {
final HttpCookie cookie = httpRequest.getCookie();
final User user = findUserBySessionId(cookie.getJSessionId());
final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.FOUND);
httpResponse.setStatusLine(statusLine);
if (user == null) {
handleFirstLogin(httpRequest, httpResponse);
return;
}
httpResponse.addResponseHeader(HEADER_LOCATION, INDEX_PAGE);
}

private void handleFirstLogin(final HttpRequest httpRequest, final HttpResponse httpResponse) {
final Map<String, String> requestBodyValues = httpRequest.getRequestParameters();
final Optional<User> user = InMemoryUserRepository.findByAccount(requestBodyValues.get("account"));
if (user.isEmpty() || !user.get().checkPassword(requestBodyValues.get("password"))) {
httpResponse.addResponseHeader(HEADER_LOCATION, UNAUTHORIZED_PAGE);
return;
}
final String sessionId = addSession(user.get());
httpResponse.addResponseHeader(HEADER_LOCATION, INDEX_PAGE);
httpResponse.addResponseHeader(HEADER_SET_COOKIE, "JSESSIONID=" + sessionId);
}

@Override
protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException {
final HttpCookie cookie = httpRequest.getCookie();
final User user = findUserBySessionId(cookie.getJSessionId());
if (user != null) {
doPost(httpRequest, httpResponse);
return;
}
final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK);
final var responseBody = ResponseBody.fromUri("/login.html");
httpResponse.setStatusLine(statusLine);
httpResponse.addResponseHeader(HEADER_CONTENT_TYPE, TEXT_HTML);
httpResponse.addResponseHeader(HEADER_CONTENT_LENGTH, String.valueOf(responseBody.getBody().getBytes().length));
httpResponse.setResponseBody(responseBody);
}

private User findUserBySessionId(final String sessionId) {
if (sessionId == null) {
return null;
}
final Session session = SESSION_MANAGER.findSession(sessionId)
.orElseGet(Session::create);
return (User) session.getAttribute("user");
}

private String addSession(final User user) {
final var session = Session.create();
session.setAttribute("user", user);
SESSION_MANAGER.add(session);
return session.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpStatus;
import org.apache.coyote.http11.response.ResponseBody;
import org.apache.coyote.http11.response.StatusLine;

import java.io.IOException;
import java.util.Map;

public class RegisterController extends AbstractController {

@Override
protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) {
final Map<String, String> requestBodyValues = httpRequest.getRequestParameters();
final var user = new User(requestBodyValues.get("account"), requestBodyValues.get("password"),
requestBodyValues.get("email"));
InMemoryUserRepository.save(user);

final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.FOUND);
httpResponse.setStatusLine(statusLine);
httpResponse.addResponseHeader("Location", INDEX_PAGE);
}

@Override
protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException {
final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK);
final var responseBody = ResponseBody.fromUri("/register.html");
httpResponse.setStatusLine(statusLine);
httpResponse.addResponseHeader("Content-Type", TEXT_HTML);
httpResponse.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length));
httpResponse.setResponseBody(responseBody);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package nextstep.jwp.controller;

import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpStatus;
import org.apache.coyote.http11.response.ResponseBody;
import org.apache.coyote.http11.response.StatusLine;

import java.io.IOException;

public class ResourceController extends AbstractController {

@Override
protected void doPost(final HttpRequest httpRequest, final HttpResponse httpResponse) {
final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.METHOD_NOT_ALLOWED);
httpResponse.setStatusLine(statusLine);
}

@Override
protected void doGet(final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException {
final var statusLine = StatusLine.of(httpRequest.getRequestLine().getProtocol(), HttpStatus.OK);
final String uri = httpRequest.getRequestLine().getPath().split("\\?")[0];
final var responseBody = ResponseBody.fromUri(uri);
httpResponse.setStatusLine(statusLine);
httpResponse.addResponseHeader("Content-Type", getContentType(uri));
httpResponse.addResponseHeader("Content-Length", String.valueOf(responseBody.getBody().getBytes().length));
httpResponse.setResponseBody(responseBody);
}

private String getContentType(final String uri) {
if (uri.endsWith(".css")) {
return TEXT_CSS;
}
return TEXT_HTML;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nextstep.jwp.exception;

public class HttpRequestException extends RuntimeException {

public HttpRequestException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,29 @@
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Connector implements Runnable {

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

private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int MAX_THREAD_POOL_SIZE = 250;

private final ExecutorService executorService;
private final ServerSocket serverSocket;
private boolean stopped;

public Connector() {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, MAX_THREAD_POOL_SIZE);
}

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) {
Expand Down Expand Up @@ -67,7 +72,7 @@ private void process(final Socket connection) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
executorService.execute(processor);
}

public void stop() {
Expand Down
Loading

0 comments on commit 82e4db7

Please sign in to comment.