Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[톰캣 구현하기 3 & 4단계] 이레(이다형) 미션 제출합니다 #484

Merged
merged 13 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
# 톰캣 구현하기

## 3단계 리팩터링
1차 구조
- coyote -> catalina -> jwp 의존 관계

### 기능구현 목록
- [x] HttpRequest에 세션 추가
- [x] HttpResponse 변경 및 수정
- [x] Servlet, DispatcherServlet 구현
- [x] HttpRequest와 세션 생명 주기 수정
- [x] RequestMapping 클래스 생성
- [x] 패키지 분리 및 핸들러 인터페이스와 컨트롤러 추상클래스 생성
- [x] 패키지 분리 및 컨트롤러 클래스 수정
Original file line number Diff line number Diff line change
@@ -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.ResourceHandlerRegistry;
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);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
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() {
FilterRegistrationBean<ShallowEtagHeaderFilter> registrationBean = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
registrationBean.addUrlPatterns("/etag");
registrationBean.addUrlPatterns("/resources/*");
return registrationBean;
}
}
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());
}
}
5 changes: 5 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ server:
max-connections: 1
threads:
max: 2

compression:
enabled: true
mime-types: text/html,text/plain,text/css,application/javascript,application/json
min-response-size: 10
10 changes: 9 additions & 1 deletion tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package nextstep;

import nextstep.jwp.controller.LoginController;
import nextstep.jwp.controller.RegisterController;
import nextstep.jwp.controller.RootController;
import org.apache.catalina.DispatcherServlet;
import org.apache.catalina.RequestMapping;
import org.apache.catalina.startup.Tomcat;

public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();
tomcat.start();
RequestMapping requestMapping = new RequestMapping().addHandler("/", new RootController())
.addHandler("/login", new LoginController())
.addHandler("/register", new RegisterController());
tomcat.start(new DispatcherServlet(requestMapping));
Copy link
Member

@e-astsea e-astsea Sep 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

톰캣의 인자로 DispatcherServlet을 전달한 이유가 궁금합니다!

톰캣은 서블릿을 관리하고 실행한다고 저는 생각하는데 내부에서 처리가 되어야 하는게 아닌가 싶어서요!

여기서 받은 값을 커넥터에 servlet을 인자로 가지고 가는 것에대한 이유가 궁금합니당

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequestMapping 정보는 애플리케이션 실행시에 넘겨주는 xml파일 같은 거라고 생각했어요.웹 애플리케이션이 시작될 때 서블릿 컨테이너(톰캣)은 web.xml 파일 또는 자바 기반 설정을 읽어 웹 애플리케이션의 구성을 초기화하는데, 이때 톰캣에 관련 정보를 넘겨주는 것이 맞다고 생각해서 이와 같이 구성하게 되었습니다.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nextstep.jwp.controller;

import org.apache.catalina.Handler;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.RequestMethod;
import org.apache.coyote.http11.response.HttpResponse;

import static org.apache.coyote.http11.request.RequestMethod.GET;
import static org.apache.coyote.http11.request.RequestMethod.POST;

public abstract class AbstractController implements Handler {
@Override
public void handle(final HttpRequest request, final HttpResponse response) throws Exception {
RequestMethod requestMethod = request.getRequestLine().getRequestMethod();
if (requestMethod == GET) {
doGet(request, response);
return;
}
if (requestMethod == POST) {
doPost(request, response);
return;
}
}

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

protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
}
}
53 changes: 53 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,53 @@
package nextstep.jwp.controller;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.catalina.Session;
import org.apache.catalina.SessionManager;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.RequestBody;
import org.apache.coyote.http11.response.HttpResponse;

import static org.apache.coyote.http11.common.HttpStatus.*;

public class LoginController extends AbstractController {

private static final String USER_ATTRIBUTE = "user";

@Override
protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception {
Session session = request.getSession();
if (session != null) {
Session savedSession = SessionManager.findSession(session.getId());
Object sessionUserAttribute = session.getAttribute(USER_ATTRIBUTE);
if (savedSession != null && sessionUserAttribute !=null && sessionUserAttribute.equals(savedSession.getAttribute(USER_ATTRIBUTE))) {
response.httpStatus(FOUND)
.header("Location", "/index.html")
.redirectUri("/index.html");
return;
}
}
response.httpStatus(OK)
.redirectUri("/login.html");
}

@Override
protected void doPost(final HttpRequest request, final HttpResponse response) throws Exception {
RequestBody requestBody = request.getRequestBody();
String account = requestBody.getContentValue("account");
String password = requestBody.getContentValue("password");

User user = InMemoryUserRepository.findByAccount(account)
.orElseThrow();
if (user.checkPassword(password)) {
request.getSession().setAttribute(USER_ATTRIBUTE, user);
response.httpStatus(FOUND)
.header("Location", "/index.html")
.redirectUri("/index.html");
return;
}
response.httpStatus(UNAUTHORIZED)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

401, 404, 500 등등 에러가 발생할 경우 현재는 각각 처리를 하고 있는데 이를 한곳에서 에러 핸들링을 하면 더 처리가 편하지 않을까요 ?!

Copy link
Author

@zillionme zillionme Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오션의 의견이 맞는것 같습니다. 예외 처리를 한다면 상위 객체로 예외를 몰아 처리를 해야할 것 같습니다.

.header("Location", "/401.html")
.redirectUri("/401.html");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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.request.RequestBody;
import org.apache.coyote.http11.response.HttpResponse;

import static org.apache.coyote.http11.common.HttpStatus.FOUND;
import static org.apache.coyote.http11.common.HttpStatus.OK;

public class RegisterController extends AbstractController {

@Override
protected void doGet(final HttpRequest request, final HttpResponse response) throws Exception {
response.httpStatus(OK)
.redirectUri("/register.html");
}

@Override
protected void doPost(final HttpRequest request, final HttpResponse response) throws Exception {
RequestBody requestBody = request.getRequestBody();
String account = requestBody.getContentValue("account");
String email = requestBody.getContentValue("email");
String password = requestBody.getContentValue("password");
InMemoryUserRepository.save(new User(account, password, email));
response.httpStatus(FOUND)
.header("Location", "/index.html");
}
}
15 changes: 15 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/RootController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package nextstep.jwp.controller;

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

import static org.apache.coyote.http11.common.HttpStatus.OK;

public class RootController extends AbstractController {

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
response.httpStatus(OK)
.redirectUri("/root.html");
}
}
47 changes: 47 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/DispatcherServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.apache.catalina;

import org.apache.coyote.http11.Servlet;
import org.apache.coyote.http11.common.HttpCookie;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.RequestHeader;
import org.apache.coyote.http11.response.HttpResponse;

public class DispatcherServlet implements Servlet {

private final RequestMapping requestMapping;

public DispatcherServlet(final RequestMapping requestMapping) {
this.requestMapping = requestMapping;
}

@Override
public void service(final HttpRequest httpRequest, final HttpResponse httpResponse) throws Exception {
Handler handler = requestMapping.getHandler(httpRequest);
setSessionOnHttpRequest(httpRequest);
handler.handle(httpRequest, httpResponse);
addSessionToHttpResponse(httpRequest, httpResponse);
}

private void setSessionOnHttpRequest(final HttpRequest httpRequest) {
RequestHeader requestHeader = httpRequest.getRequestHeader();
HttpCookie cookie = requestHeader.getCookie();
String jsessionid = cookie.getCookieValue("JSESSIONID");
Session session = SessionManager.findSession(jsessionid);
if (session == null) {
return;
}
session.setFirstSent(false);
httpRequest.setSession(session);
}

private void addSessionToHttpResponse(final HttpRequest httpRequest, final HttpResponse httpResponse) {
Session session = httpRequest.getSession();
if (session == null) {
Session newSession = new Session();
SessionManager.addSession(newSession);
httpResponse.setSession(newSession);
return;
}
httpResponse.setSession(session);
}
}
9 changes: 9 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/Handler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.apache.catalina;

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

public interface Handler {

void handle(HttpRequest request, HttpResponse response) throws Exception;
}
23 changes: 23 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/RequestMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.apache.catalina;

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

import java.util.HashMap;
import java.util.Map;

public class RequestMapping {

private final Handler staticHandler = new StaticFileHandler();

private final Map<String, Handler> handlers = new HashMap<>();

public RequestMapping addHandler(String uri, Handler handler) {
handlers.put(uri, handler);
return this;
}

public Handler getHandler(final HttpRequest httpRequest) {
String requestURI = httpRequest.getRequestLine().getRequestURI();
return handlers.getOrDefault(requestURI, staticHandler);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.apache.coyote.http11.common;
package org.apache.catalina;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -9,28 +9,38 @@ public class Session {

private final String id;
private final Map<String, Object> attributes = new HashMap<>();
private boolean isFirstSent;


public Session() {
this(UUID.randomUUID().toString());
}

public Session(final String id) {
private Session(final String id) {
this.id = id;
this.isFirstSent = true;
}

public boolean isFirstSent() {
return isFirstSent;
}

public String getId() {
return id;
}

public Object getAttribute(String name) {
return attributes.get(name);
return attributes.getOrDefault(name, null);
}

public void setAttribute(String name, Object value) {
attributes.put(name, value);
}

public void setFirstSent(final boolean isFirstSent) {
this.isFirstSent = isFirstSent;
}

public void removeAttribute(String name) {
attributes.remove(name);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.apache.coyote.http11.common;
package org.apache.catalina;

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

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

private SessionManager() {
}
Expand Down
Loading