Skip to content
20 changes: 20 additions & 0 deletions src/main/java/app/handler/HomeHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package app.handler;

import http.HttpMethod;
import http.HttpStatus;
import http.request.HttpRequest;
import web.handler.SingleArgHandler;
import web.response.DynamicViewResponse;
import web.response.HandlerResponse;

public class HomeHandler extends SingleArgHandler<HttpRequest> {

public HomeHandler() {
super(HttpMethod.GET, "/");
}

@Override
public HandlerResponse handle(HttpRequest request) {
return DynamicViewResponse.of(HttpStatus.OK, "/index.html");
}
}
3 changes: 2 additions & 1 deletion src/main/java/app/handler/LoginWithPost.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public HandlerResponse handle(QueryParameters params) {

SessionEntity session = sessionManager.create(
user.getUserId(),
user.getUserRole());
user.getUserRole(),
user.getNickname());

RedirectResponse res = RedirectResponse.to("/");
res.setCookie(
Expand Down
35 changes: 31 additions & 4 deletions src/main/java/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package config;

import app.handler.HomeHandler;
import app.handler.LoginWithPost;
import app.handler.RegisterWithGet;
import app.handler.RegisterWithPost;
Expand All @@ -22,10 +23,14 @@
import web.filter.*;
import web.handler.StaticContentHandler;
import web.handler.WebHandler;
import web.renderer.DynamicViewRenderer;
import web.renderer.HttpResponseRenderer;
import web.renderer.RedirectRenderer;
import web.renderer.StaticViewRenderer;
import web.renderer.view.ExpressionResolver;
import web.session.SessionStorage;
import web.renderer.view.TemplateEngine;
import web.renderer.view.TemplateLoader;

import java.util.List;

Expand Down Expand Up @@ -84,8 +89,8 @@ public List<WebHandler> webHandlerList() {
staticContentHandler(),
registerWithGet(),
registerWithPost(),
loginWithPost()
)
loginWithPost(),
homeHandler())
);
}

Expand Down Expand Up @@ -115,13 +120,18 @@ public LoginWithPost loginWithPost() {
() -> new LoginWithPost(sessionStorage()));
}

public HomeHandler homeHandler(){
return getOrCreate("homeHandler", HomeHandler::new);
}

// ===== Renderer =====
public List<HttpResponseRenderer> httpResponseRendererList() {
return getOrCreate(
"httpResponseRendererList",
() -> List.of(
staticViewRenderer(),
redirectRenderer()
redirectRenderer(),
dynamicViewRenderer()
)
);
}
Expand All @@ -137,6 +147,24 @@ public RedirectRenderer redirectRenderer() {
return getOrCreate("redirectRenderer", RedirectRenderer::new);
}

public DynamicViewRenderer dynamicViewRenderer() {
return getOrCreate("dynamicViewRenderer",
() -> new DynamicViewRenderer(templateEngine()));
}

// ===== ViewEngine =====
public TemplateEngine templateEngine() {
return getOrCreate("templateEngine",
() -> new TemplateEngine(templateLoader(), expressionResolver()));
}

public ExpressionResolver expressionResolver(){
return getOrCreate("expressResolver", ExpressionResolver::new);
}

public TemplateLoader templateLoader() {
return getOrCreate("templateLoader", TemplateLoader::new);
}

// ===== Adapter =====
public List<HandlerAdapter> handlerAdapterList() {
Expand Down Expand Up @@ -164,7 +192,6 @@ public DefaultHandlerAdapter defaultHandlerAdapter() {
DefaultHandlerAdapter::new
);
}

// ===== Resolver =====
public List<ArgumentResolver<?>> argumentResolverList() {
return getOrCreate(
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import exception.ErrorException;

import java.util.List;

public class SecurityConfig extends SingletonContainer {
private final AppConfig appConfig = new AppConfig();
private int callCount;
Expand All @@ -15,9 +17,9 @@ public void config(){
public void setPaths(){
appConfig.filterChainContainer()
.addPath(FilterType.AUTHENTICATED, "/mypage/**")
.addPath(FilterType.LOG_IN, "/user/login")
.addPath(FilterType.ALL, "/user/**")
.addPath(FilterType.PUBLIC, "/**");
.addPaths(FilterType.LOG_IN, List.of("/user/login", "/login"))
.addPaths(FilterType.PUBLIC, List.of("/", "/home/*"))
.addPath(FilterType.ALL, "/**");
}


Expand Down
3 changes: 3 additions & 0 deletions src/main/java/config/VariableConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ public class VariableConfig {
public static final List<String> STATIC_RESOURCE_ROOTS = List.of(
"./src/main/resources",
"./src/main/resources/static");
public static final List<String> DYNAMIC_RESOURCE_ROOTS = List.of(
"./src/main/resources/templates"
);

public static final long IDLE_MS = 30*60*100;
public static final long ABSOLUTE_MS = 180*60*100;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/exception/handler/ErrorExceptionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.time.format.DateTimeFormatter;

public class ErrorExceptionHandler implements ExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(ErrorException.class);
private final Logger logger = LoggerFactory.getLogger(ErrorExceptionHandler.class);
@Override
public boolean support(Throwable e) {
return e instanceof ErrorException;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/web/dispatch/Dispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public HttpResponse handle(HttpRequest request, HttpResponse response){
HandlerAdapter adapter = adapterList.stream().filter(ha -> ha.support(handler))
.findFirst().orElseThrow(() -> new ErrorException("DispatcherError: No adapter matched"));

HandlerResponse handlerResponse = adapter.handle(request, handler);
HandlerResponse handlerResponse = adapter.handle(request, handler).postHandling(request, response);

Choose a reason for hiding this comment

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

postHandling()을 모든 응답에 적용하는 것은 좋은 설계이지만, 응답 타입마다 필요한 처리가 다를 수 있습니다. 현재는 DynamicViewResponse만 구현했지만, 다른 응답 타입에서 필요하지 않은 작업이 있을 경우 성능이나 로직 복잡도가 증가할 수 있습니다. 필요시 선택적 적용 인터페이스를 고려하세요."


HttpResponseRenderer responseHandler = responseHandlerList.stream()
.filter(rh -> rh.supports(handlerResponse))
Expand Down
1 change: 1 addition & 0 deletions src/main/java/web/filter/AccessLogFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class AccessLogFilter implements ServletFilter {

@Override
public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) {
request.getOrGenerateRid();
chain.doFilter();
if(request.getAuthenticationInfo()!=null &&
request.getAuthenticationInfo().getRole().equals(UserRole.MEMBER)) {
Expand Down
15 changes: 8 additions & 7 deletions src/main/java/web/filter/AuthenticationFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import http.HttpStatus;
import http.request.HttpRequest;
import http.response.HttpResponse;
import web.filter.authentication.AuthenticationInfo;
import web.filter.authentication.UnanimousAuthentication;
import web.filter.authentication.UserAuthentication;
import web.filter.authentication.UserRole;
Expand All @@ -20,17 +21,17 @@ public AuthenticationFilter(SessionStorage sessionManager) {
public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) {
String sid = request.getCookieValue("SID").orElse(null);
SessionEntity session = sessionManager.getValid(sid);
AuthenticationInfo authInfo;

if (session == null) {
request.setAuthenticationInfo(
UnanimousAuthentication.of());
authInfo = UnanimousAuthentication.of();
} else{
request.setAuthenticationInfo(
UserAuthentication.of(
session.getUserId(),
UserRole.valueOf(session.getUserRole())));
authInfo = UserAuthentication.of(
session.getUserId(),
UserRole.valueOf(session.getUserRole()));
authInfo.addAttribute("nickname",session.getNickname());
}

request.setAuthenticationInfo(authInfo);
chain.doFilter();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@

public interface AuthenticationInfo {
Optional<Long> getUserId();
Optional<Object> getAttribute(String key);
Comment on lines 4 to +7

Choose a reason for hiding this comment

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

getAttribute() 메서드가 Optional<Object>를 반환하는데, 구현체(UnanimousAuthentication, UserAuthentication)에서 항상 값을 감싼 Optional을 반환합니다. 만약 키가 존재하지 않으면 null을 감싼 Optional이 반환되어 실제 부재를 표현하지 못합니다.

개선 방안: Optional.ofNullable()을 사용하여 null 값을 빈 Optional로 처리하거나, Optional.empty()를 명시적으로 반환하도록 수정하세요."

void addAttribute(String key, Object value);
UserRole getRole();
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
package web.filter.authentication;

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

public class UnanimousAuthentication implements AuthenticationInfo {
private final UserRole USER_ROLE = UserRole.UNANIMOUS;

public UnanimousAuthentication() {
this.attributes = new HashMap<>();
}

private final Map<String, Object> attributes;

public static UnanimousAuthentication of(){
return new UnanimousAuthentication();
}

@Override
public void addAttribute(String key, Object value) {
attributes.put(key, value);
}

@Override
public Optional<Object> getAttribute(String key) {
return Optional.of(attributes.get(key));
}

@Override
public Optional<Long> getUserId() {
return Optional.empty();
}


@Override
public UserRole getRole() {
return this.USER_ROLE;
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/web/filter/authentication/UserAuthentication.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package web.filter.authentication;

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

public class UserAuthentication implements AuthenticationInfo {
private final Long userId;
private final UserRole userRole;
private final Map<String, Object> attributes;

private UserAuthentication(Long userId, UserRole userRole) {
this.userId = userId;
this.userRole = userRole;
this.attributes = new HashMap<>();
}

public static UserAuthentication of(Long userId, UserRole userRole){
return new UserAuthentication(userId, userRole);
}

@Override
public void addAttribute(String key, Object value) {
attributes.put(key, value);
}

@Override
public Optional<Object> getAttribute(String key) {
return Optional.of(attributes.get(key));
}

@Override
public Optional<Long> getUserId() {
return Optional.of(userId);
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/web/renderer/DynamicViewRenderer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package web.renderer;

import http.response.HttpResponse;
import web.response.DynamicViewResponse;
import web.response.HandlerResponse;
import web.renderer.view.TemplateEngine;

import java.nio.charset.StandardCharsets;

public class DynamicViewRenderer implements HttpResponseRenderer {
private final TemplateEngine templateEngine;

public DynamicViewRenderer(TemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}

@Override
public boolean supports(HandlerResponse response) {
return response instanceof DynamicViewResponse;
}

@Override
public HttpResponse handle(HttpResponse httpResponse, HandlerResponse handlerResponse) {
DynamicViewResponse dynamicViewResponse = (DynamicViewResponse) handlerResponse;
String path = dynamicViewResponse.getPath();
templateEngine.clearCache();
String render = templateEngine.render(path, dynamicViewResponse.getModel());

httpResponse.setStatus(handlerResponse.getStatus());
httpResponse.setBody(render.getBytes(StandardCharsets.UTF_8));
httpResponse.addHeader("Content-Type", "text/html; charset=utf-8");
handlerResponse.getCookies().forEach(cookie->httpResponse.addHeader("Set-Cookie", cookie));
return httpResponse;
}
}
1 change: 0 additions & 1 deletion src/main/java/web/renderer/StaticViewRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import config.VariableConfig;
import exception.ErrorException;
import http.HttpStatus;
import http.response.HttpResponse;
import web.response.HandlerResponse;
import web.response.StaticViewResponse;
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/web/renderer/view/ExpressionResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package web.renderer.view;

import java.lang.reflect.Method;
import java.util.Map;

public class ExpressionResolver {

public Object resolve(String expr, Map<String, Object> model) {
if (expr == null || expr.isBlank()) return null;

String[] parts = expr.trim().split("\\.");
Object cur = model.get(parts[0]);

for (int i = 1; i < parts.length; i++) {
if (cur == null) return null;
cur = getProperty(cur, parts[i]);
}
return cur;
}

private Object getProperty(Object target, String name) {
if (target instanceof Map<?, ?> m) {
return m.get(name);
}

Class<?> c = target.getClass();

// getXxx()
String getter = "get" + capitalize(name);
try {
Method method = c.getMethod(getter);
return method.invoke(target);
} catch (Exception ignore) {}

return null;
}

private String capitalize(String s) {
if (s == null || s.isEmpty()) return s;
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
}
14 changes: 14 additions & 0 deletions src/main/java/web/renderer/view/HtmlEscaper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package web.renderer.view;

public final class HtmlEscaper {
private HtmlEscaper() {}

public static String escape(String s) {
if (s == null || s.isEmpty()) return "";
return s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"","&quot;")
.replace("'", "&#39;");
}
}
Loading