Skip to content

Commit

Permalink
[1,2단계 - Tomcat 구현하기] 몰리(김지민) 미션 제출합니다. (#582)
Browse files Browse the repository at this point in the history
* feat(Http11Processor): 요청 데이터를 HttpRequest 객체로 변경하는 메서드 작성

* feat(Http11Processor): 요청URL과 일치하는 정적 파일을 반환

* feat(Http11Processor): root url로 요청일 경우 특정 문자열 반환

* feat(Http11Processor): html이 아닌 다른 형식의 파일 지원

* refactor(HttpRequestParser): HttpRequest를 처리하는 객체 분리

* feat(RequestHandler): 요청을 처리하는 핸들러 분리

* feat(RequestHandler): login 엔드포인트 접속 시 로직 처리

* fix(RequestHandler): query null인 경우 정적 파일 렌더링하도록

* feat(RequestHandler): 로그인 성공 시 리다이렉트

* refactor(HttpResponseGenerator): response 생성 객체 분리

* fix: remove implementation logback-classic on gradle (#501)

* fix: add threads min-spare configuration on properties (#502)

* feat(HttpHeader): Header 객체 분리

* feat(HttpReqeust): Request 객체 구현

* feat(HttpRequestParser): Get이 아닌 다른 method여도 Request 객체 파싱

* feat(RequestHandler): queryString이 없는 path일 경우 검증 후 로직

* feat(RequestHandler): 회원가입 post 요청 로직 구현

* feat(HttpCookie): Cookie 객체 구현

* fix(RequestHandler): 회원가입 시 비밀번호, 이메일 파싱 로직 수정

* feat(index.html): login 적용 후 코드 활성화

* feat(RequestHandler): login 을 post method로 변경

* fix(RequestHandler): Set-Cookie 응답에 추가하는 로직 수정

* feat(Session): Session, Manger 구현체 생성

* feat(SessionManager): 싱글톤 적용

* feat(RequestHandler): 이미 로그인 되어있는지 세션으로 검증 및 리다이렉트

* chore: 타임리프 의존성 추가

* feat(CacheWebConfig): 휴리스틱 캐싱 제거

* feat(CacheWebConfig): HTTP Compression 설정

* feat(EtagFilterConfiguration): ETag/If-None-Match 적용

* feat(HttpRequest): Cookie 파싱 로직 변경, Session 파싱 제거

* refactor(HttpCookie): 파일 위치 변경

* refactor(/request): request 관련 객체 위치 이동

* refactor(/response): response 관련 객체 위치 이동

* refactor(HttpMethod): http method 상수로 추출

* refactor(handler): handler 분리

* refactor(handler): 요청을 처리하는 최상위 Handler 추상 클래스 생성

* refactor(HttpHeader): toString 구현

* refactor(RequestHandler): 불필요한 예외 전파 제거

* refactor(mapping): 패키지 구조 변경

* feat(EtagFilterConfiguration): etag 추가

* refactor(CacheBustingWebConfig): busting 추가

* test(Http11ProcessorTest): content-length 수정

* refactor(RequestHandlerAdapter): Adapter로 이름 변경

* refactor(/http): 패키지 구조 개선, http 관련 객체 위치 이동

* refactor(Http11Processor): sout문 제거

* refactor(HttpRequestParser): 중복 정의된 조건문 제거

* refactor: 클래스 시작 줄에 개행 추가

* test(SessionManager): 기본 메서드에 대해 테스트 작성

* refactor(/catalina/session): session 관련 객체들 위치 이동

* refactor(Session): 세션에 속성을 추가하는 메서드의 인자 이름 명확하게

* test(SessionTest): 속성값 조회 테스트

* refactor(ResourceHandlerMapping): concat 메서드를 체이닝처럼 개행

* refactor(UrlHandlerMapping): IllegalCallerException 던지지 않고 404 페이지 반환

* refactor(StaticResourceHandler): Handler 상속 및 파일 위치 변경

* test(StaticResourceHandler): 정적 리소스 처리 테스트 작성

* refactor(LoginHandler): Get 요청 처리 로직 개선

* refactor(LoginHandler): 인증되었는지 로직 개선 및 테스트 작성

* refactor(RootEndPointHandler): 루트 엔드포인트 요청을 처리하는 핸들러 분리

* test(RegisterHandlerTest): 회원가입 테스트 작성

* refactor(HandlerMapping): 요청에 대한 핸들러를 반환하도록 역할 부여 및 테스트 작성

* feat(SessionManager): 특정 아이디인 세션이 존재하는지 반환하는 메서드 추가

* refactor(HttpCookie): 필드와 인자가 다른 생성자를 정팩메로 변경

* test(HttpCookie): 테스트 작성 및 필드를 private하게 변경

* refactor(HttpRequestReader): 객체 책임에 맡게 이름 변경

* refactor(StandardHttpHeader): HTTP 일반적인 헤더를 나타내는 이넘 생성

* refactor(/header): header 관련 객체 패키지로 이동

* refactor(StaticResourceHandler): 확장자가 없는 경우 /static 경로에 있는 html 리소스를 반환

* refactor(NotFoundHandler): NotFoundException에 대한 핸들러 분리

* refactor(UnAuthorizationHandler): UnAuthorization 에 대한 핸들러 분리

* refactor(/test): 변경된 응답 메시지를 규격에 맞게 상태 값 형식 변경

* refactor(InternalServerErrorHandler): 최상위 Exception 에 대한 핸들러 분리

* refactor(HandlerMapping): 에러에 대한 핸들링 처리

* refactor(Http11Processor): 핸들링 도중 에러 발생 시 처리

* refactor(HttpResponseGenerator): 500 변경

* refactor(StaticResourceHandler): 정적 파일 조회 로직 변경

* refactor(Http11ProcessorTest): 각 http request에 따른 통합 테스트 추가

* test(HttpRequest): HttpRequest 객체 테스트 작성

* refactor(HttpRequest): application/x-www-form-urlencoded 형식의 요청인 경우 바디를 key, value로 파싱하는 책임 부

* test(HttpRequestReader): HttpRequestReader 테스트 작성

* test(HttpResponseGenerator): host 고

* refacotr(HttpRequest): 특정 헤더인지 StandardHttpHeader 에게 물어보도록 변경

---------

Co-authored-by: Gyeongho Yang <[email protected]>
  • Loading branch information
jminkkk and geoje authored Sep 10, 2024
1 parent 7e91356 commit 654fafe
Show file tree
Hide file tree
Showing 44 changed files with 1,652 additions and 49 deletions.
1 change: 1 addition & 0 deletions study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.assertj:assertj-core:3.26.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
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);
}
}
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 cache.com.example.version.CacheBustingWebConfig;

@Configuration
public class EtagFilterConfiguration {

// @Bean
// public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
// return null;
// }
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> filter = new FilterRegistrationBean<>();
filter.setFilter(new ShallowEtagHeaderFilter());
filter.addUrlPatterns("/etag", CacheBustingWebConfig.PREFIX_STATIC_RESOURCES + "/*");
return filter;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
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;

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 @@ -2,6 +2,9 @@ handlebars:
suffix: .html

server:
compression:
enabled: true
min-response-size: 10
tomcat:
accept-count: 1
max-connections: 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;
package org.apache.catalina.session;

import java.io.IOException;

Expand Down Expand Up @@ -29,7 +27,7 @@ public interface Manager {
*
* @param session Session to be added
*/
void add(HttpSession session);
void add(Session session);

/**
* Return the active Session, associated with this Manager, with the
Expand All @@ -45,12 +43,12 @@ public interface Manager {
* @return the request session or {@code null} if a session with the
* requested ID could not be found
*/
HttpSession findSession(String id) throws IOException;
Session findSession(String id) throws IOException;

/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
*/
void remove(HttpSession session);
void remove(Session session);
}
35 changes: 35 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/session/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.apache.catalina.session;

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

public class Session {

private final String id;
private final Map<String, Object> values = new HashMap<>();

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

public String getId() {
return id;
}

public Object getAttribute(final String name) {
return values.entrySet()
.stream()
.filter(entry -> entry.getKey().equals(name))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
}

public Map<String, Object> getValues() {
return values;
}

public void setAttribute(String name, Object value) {
values.put(name, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.apache.catalina.session;

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

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

private SessionManager() {}

public static SessionManager getInstance() {
return INSTANCE;
}

public boolean existsById(final String id) {
return SESSIONS.containsKey(id);
}

@Override
public void add(Session session) {
SESSIONS.put(session.getId(), session);
}

@Override
public Session findSession(final String id) {
return SESSIONS.get(id);
}

@Override
public void remove(Session session) {
SESSIONS.remove(session.getId());
}
}

59 changes: 59 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/HandlerMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.apache.coyote;

import org.apache.coyote.handler.Handler;
import org.apache.coyote.handler.LoginHandler;
import org.apache.coyote.handler.RegisterHandler;
import org.apache.coyote.handler.RootEndPointHandler;
import org.apache.coyote.handler.StaticResourceHandler;
import org.apache.coyote.handler.exception.InternalServerErrorHandler;
import org.apache.coyote.handler.exception.NotFoundHandler;
import org.apache.coyote.handler.exception.UnAuthorizationHandler;
import org.apache.http.request.HttpRequest;


public class HandlerMapping {

private static final HandlerMapping INSTANCE = new HandlerMapping();
private static final String PATH_DELIMITER = "/";

private HandlerMapping() {
}

public static HandlerMapping getInstance() {
return INSTANCE;
}

public Handler getHandler(final HttpRequest httpRequest) {
return getHandlerByEndPoint(httpRequest);
}

private Handler getHandlerByEndPoint(final HttpRequest httpRequest) {
final String path = httpRequest.getPath();

if (path.equals(PATH_DELIMITER)) {
return RootEndPointHandler.getInstance();
}

if (path.contains("login")) {
return LoginHandler.getInstance();
}

if (path.contains("register")) {
return RegisterHandler.getInstance();
}

return StaticResourceHandler.getInstance();
}

public Handler getHandlerByException(final Exception exception) {
if (exception instanceof NotFoundException) {
return NotFoundHandler.getInstance();
}

if (exception instanceof UnauthorizedException) {
return UnAuthorizationHandler.getInstance();
}

return InternalServerErrorHandler.getInstance();
}
}
7 changes: 7 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/NotFoundException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.apache.coyote;

public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.apache.coyote;

public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}
8 changes: 8 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/handler/Handler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.apache.coyote.handler;

import org.apache.http.request.HttpRequest;

public abstract class Handler {
// TODO: 반환타입 HttpResponse로 변경
public abstract String handle(HttpRequest httpRequest);
}
74 changes: 74 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/handler/LoginHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.apache.coyote.handler;

import java.util.Optional;
import java.util.UUID;

import org.apache.catalina.session.SessionManager;
import org.apache.http.HttpCookie;
import org.apache.http.HttpMethod;
import org.apache.catalina.session.Session;
import org.apache.http.header.StandardHttpHeader;
import org.apache.http.request.HttpRequest;
import org.apache.http.response.HttpResponseGenerator;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;

public class LoginHandler extends Handler {

private static final LoginHandler INSTANCE = new LoginHandler();
private final SessionManager sessionManager = SessionManager.getInstance();

private LoginHandler() {
}

public static LoginHandler getInstance() {
return INSTANCE;
}

@Override
public String handle(final HttpRequest httpRequest) {
if (httpRequest.isSameMethod(HttpMethod.GET)) {
return processLoginGetRequest(httpRequest);
}

if (httpRequest.isSameMethod(HttpMethod.POST)) {
return processLoginPostRequest(httpRequest);
}

return StaticResourceHandler.getInstance().handle(new HttpRequest("GET", "/404.html", "HTTP/1.1", null, null));

}

private String processLoginGetRequest(final HttpRequest httpRequest) {
HttpCookie httpCookie = httpRequest.getHttpCookie();
if (httpCookie == null || ! sessionManager.existsById(httpCookie.getValue("JSESSIONID"))) {
return StaticResourceHandler.getInstance().handle(new HttpRequest("GET", "/login.html", "HTTP/1.1", null, null));
}

return HttpResponseGenerator.getFoundResponse("/index.html");
}

private String processLoginPostRequest(final HttpRequest httpRequest) {
final String account = httpRequest.getFormBody("account");
final String password = httpRequest.getFormBody("password");

final Optional<User> userOptional = InMemoryUserRepository.findByAccount(account);
if (userOptional.isEmpty() || !userOptional.get().checkPassword(password)) {
return StaticResourceHandler.getInstance().handle(new HttpRequest("GET", "/401.html", "HTTP/1.1", null, null));
}

final Session session = new Session(UUID.randomUUID().toString());
session.setAttribute("user", userOptional.get());
sessionManager.add(session);
return addCookie(
HttpResponseGenerator.getFoundResponse("/index.html"),
HttpCookie.of("JSESSIONID=" + session.getId()));
}

private String addCookie(final String response, final HttpCookie cookie) {
return response
.concat("\n")
.concat(StandardHttpHeader.SET_COOKIE.getValue() + ": " + cookie.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.apache.coyote.handler;

import org.apache.http.HttpMethod;
import org.apache.http.request.HttpRequest;
import org.apache.http.response.HttpResponseGenerator;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;

public class RegisterHandler extends Handler {

private static final RegisterHandler INSTANCE = new RegisterHandler();

private RegisterHandler() {
}

public static RegisterHandler getInstance() {
return INSTANCE;
}

public String handle(final HttpRequest httpRequest) {
if (httpRequest.isSameMethod(HttpMethod.GET)) {
return StaticResourceHandler.getInstance().handle(new HttpRequest("GET", "/register.html", "HTTP/1.1", null, null));
}

if (httpRequest.isSameMethod(HttpMethod.POST)) {
return processRegisterPostRequest(httpRequest);
}

return StaticResourceHandler.getInstance().handle(new HttpRequest("GET", "/401.html", "HTTP/1.1", null, null));
}

private String processRegisterPostRequest(final HttpRequest httpRequest) {
String[] body = httpRequest.getBody().split("&");
String account = body[0].split("=")[1];
String email = body[1].split("=")[1];
String password = body[2].split("=")[1];
InMemoryUserRepository.save(new User(account, password, email));
return HttpResponseGenerator.getFoundResponse("/index.html");
}
}
Loading

0 comments on commit 654fafe

Please sign in to comment.