From 43d474ec0c2862b2836d6412e2a4e12d9a1ac01a Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Fri, 9 Jan 2026 18:07:41 +0900 Subject: [PATCH 1/8] =?UTF-8?q?refactor(http):=20HttpResponse.status=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20-=20HttpResponse=EB=A5=BC=20?= =?UTF-8?q?=EB=AF=B8=EB=A6=AC=20=EC=83=9D=EC=84=B1=ED=95=9C=20=EB=92=A4=20?= =?UTF-8?q?=ED=9D=90=EB=A6=84=EC=97=90=20=EB=94=B0=EB=9D=BC=20status?= =?UTF-8?q?=EB=A5=BC=20=EC=84=A0=ED=83=9D=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/http/HttpStatus.java | 2 ++ src/main/java/http/response/HttpResponse.java | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/http/HttpStatus.java b/src/main/java/http/HttpStatus.java index af8b95af0..78836466f 100644 --- a/src/main/java/http/HttpStatus.java +++ b/src/main/java/http/HttpStatus.java @@ -1,6 +1,8 @@ package http; public enum HttpStatus { + NONE(0), + OK(200), CREATED(201), ACCEPTED(202), diff --git a/src/main/java/http/response/HttpResponse.java b/src/main/java/http/response/HttpResponse.java index 9e11f9620..cd088ab0f 100644 --- a/src/main/java/http/response/HttpResponse.java +++ b/src/main/java/http/response/HttpResponse.java @@ -12,8 +12,8 @@ import java.util.Map; public class HttpResponse { - private final HttpStatus status; private final Map> headers; + private HttpStatus status; private byte[] body; private HttpResponse (HttpStatus status){ @@ -28,6 +28,14 @@ public static HttpResponse of (HttpStatus status){ return new HttpResponse(status); } + public static HttpResponse of (){ + return new HttpResponse(HttpStatus.NONE); + } + + public void setStatus(HttpStatus status) { + this.status = status; + } + public HttpStatus getStatus() { return status; } From 559e1b57823cc8dfd4dc1152802d954da5decad5 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Fri, 9 Jan 2026 18:21:01 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat(web-filter):=20FilterChain=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20=ED=95=84=ED=84=B0=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20ServletFilter=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20-=20ServletFilter=EB=A5=BC=20chain=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=97=AE=EC=96=B4=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8A=94=20FilterChain=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/web/filter/FilterChain.java | 112 ++++++++++++++++++++ src/main/java/web/filter/ServletFilter.java | 8 ++ 2 files changed, 120 insertions(+) create mode 100644 src/main/java/web/filter/FilterChain.java create mode 100644 src/main/java/web/filter/ServletFilter.java diff --git a/src/main/java/web/filter/FilterChain.java b/src/main/java/web/filter/FilterChain.java new file mode 100644 index 000000000..ccd9cb427 --- /dev/null +++ b/src/main/java/web/filter/FilterChain.java @@ -0,0 +1,112 @@ +package web.filter; + +import http.request.HttpRequest; +import http.response.HttpResponse; +import web.dispatch.Dispatcher; + +import java.util.ArrayList; +import java.util.List; + +public class FilterChain { + private final List filterChainList; + private final Dispatcher dispatcher; + + public FilterChain(Dispatcher dispatcher) { + this.filterChainList = new ArrayList<>(); + this.dispatcher = dispatcher; + } + + public FilterChain addChain(String path, List filterList) { + filterChainList.add(Pair.of(path, filterList)); + return this; + } + + public void runFilterChain(HttpRequest request, HttpResponse response) { + String requestedPath = request.getPath(); + + Pair matched = findMatchedChain(requestedPath); + List filters = (matched == null) ? List.of() : matched.filterList; + + FilterEngine engine = new FilterEngine(dispatcher, request, response, filters); + engine.doFilter(); + } + + private Pair findMatchedChain(String requestedPath) { + for (Pair pair : filterChainList) { + if (isMatched(requestedPath, pair.path)) { + return pair; + } + } + return null; + } + + private boolean isMatched(String requestedPath, String filterPath) { + if (filterPath == null || filterPath.isBlank()) return false; + if (requestedPath == null) return false; + + if (!requestedPath.startsWith("/")) requestedPath = "/" + requestedPath; + if (!filterPath.startsWith("/")) filterPath = "/" + filterPath; + + if (!filterPath.contains("*")) { + return requestedPath.equals(filterPath); + } + + if (filterPath.endsWith("/**")) { + String prefix = filterPath.substring(0, filterPath.length() - 3); + if (prefix.isEmpty()) return true; + return requestedPath.equals(prefix) || requestedPath.startsWith(prefix + "/"); + } + + if (filterPath.endsWith("/*")) { + String prefix = filterPath.substring(0, filterPath.length() - 2); + if (!(requestedPath.equals(prefix) || requestedPath.startsWith(prefix + "/"))) return false; + + String rest = requestedPath.substring(prefix.length()); + if (rest.isEmpty()) return false; + if (!rest.startsWith("/")) return false; + + String afterSlash = rest.substring(1); + return !afterSlash.isEmpty() && !afterSlash.contains("/"); + } + + return false; + } + + private static class Pair { + private final String path; + private final List filterList; + + private Pair(String path, List filterList) { + this.path = path; + this.filterList = filterList; + } + + public static Pair of(String path, List filterList) { + return new Pair(path, filterList); + } + } + + public static class FilterEngine { + private final Dispatcher dispatcher; + private final HttpRequest request; + private final HttpResponse response; + private final List filterList; + private int position = 0; + + public FilterEngine(Dispatcher dispatcher, HttpRequest request, HttpResponse response, List filterList) { + this.dispatcher = dispatcher; + this.request = request; + this.response = response; + this.filterList = filterList; + } + + public void doFilter() { + if (position >= filterList.size()) { + dispatcher.handle(request, response); + return; + } + ServletFilter next = filterList.get(position++); + next.runFilter(request, response, this); + } + } +} diff --git a/src/main/java/web/filter/ServletFilter.java b/src/main/java/web/filter/ServletFilter.java new file mode 100644 index 000000000..46704b3f0 --- /dev/null +++ b/src/main/java/web/filter/ServletFilter.java @@ -0,0 +1,8 @@ +package web.filter; + +import http.request.HttpRequest; +import http.response.HttpResponse; + +public interface ServletFilter { + void runFilter(HttpRequest request, HttpResponse response, FilterChain.FilterEngine chain); +} From 7157d40b3d890e583f6c885a6644d7c84ae99f3e Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Fri, 9 Jan 2026 18:22:56 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor(web):=20Filter=EC=97=90=EC=84=9C?= =?UTF-8?q?=20HttpResponse=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95(=ED=97=A4=EB=8D=94,=20=EC=BF=A0=ED=82=A4)=EC=9D=B4=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20web=20=EA=B3=84?= =?UTF-8?q?=EC=B8=B5=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/web/dispatch/ConnectionHandler.java | 11 +++++++---- src/main/java/web/dispatch/Dispatcher.java | 10 +++++----- src/main/java/web/renderer/HttpResponseRenderer.java | 2 +- src/main/java/web/renderer/StaticViewRenderer.java | 5 +++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/web/dispatch/ConnectionHandler.java b/src/main/java/web/dispatch/ConnectionHandler.java index 0ea2ae576..33ab6266c 100644 --- a/src/main/java/web/dispatch/ConnectionHandler.java +++ b/src/main/java/web/dispatch/ConnectionHandler.java @@ -5,6 +5,7 @@ import http.request.HttpRequestConverter; import http.request.HttpRequest; import http.response.HttpResponse; +import web.filter.FilterChain; import java.net.Socket; @@ -14,6 +15,7 @@ public class ConnectionHandler implements Runnable{ private final HttpResponseConverter responseConverter; private final ExceptionHandlerMapping exceptionHandlerMapping; private final Dispatcher dispatcher; + private FilterChain filterChain; public ConnectionHandler(Dispatcher dispatcher, ExceptionHandlerMapping exceptionHandlerMapping, @@ -30,22 +32,23 @@ public ConnectionHandler(Dispatcher dispatcher, @Override public void run() { + HttpResponse response = HttpResponse.of(); try { HttpRequest request = requestConverter.parseRequest(connection); - HttpResponse response = dispatcher.handle(request); + filterChain.runFilterChain(request,response); responseConverter.sendResponse(response, connection); - } catch (Exception e){ + } catch (Throwable t){ /** * TODO: * ExceptionHandler 또한 HttpResponse를 반환하게 하고 * finally에 `responseConverter.sendResponse(response, connection);` 를 넣어 * socket에 write를 하는 포인트를 단일 포인트로 관리 */ - exceptionHandlerMapping.handle(e, connection); + exceptionHandlerMapping.handle(t, connection); } finally { - try { connection.close(); } catch (Exception ignore) {} + try { connection.close(); } catch (Throwable ignore) {} } } } diff --git a/src/main/java/web/dispatch/Dispatcher.java b/src/main/java/web/dispatch/Dispatcher.java index 0d55440b0..1a6c136ae 100644 --- a/src/main/java/web/dispatch/Dispatcher.java +++ b/src/main/java/web/dispatch/Dispatcher.java @@ -28,7 +28,7 @@ public Dispatcher(List handlerMapping, List adapterL handlerMapping.forEach(hm -> this.handlerMapping.get(hm.getMethod()).add(hm)); } - public HttpResponse handle(HttpRequest request){ + public HttpResponse handle(HttpRequest request, HttpResponse response){ logger.debug("{}: {} - {} from {}", request.getMethod(), request.getPath(), request.getQueryString(), request.getRequestAddress()); @@ -41,11 +41,11 @@ public HttpResponse handle(HttpRequest request){ HandlerAdapter adapter = adapterList.stream().filter(ha -> ha.support(handler)) .findFirst().orElseThrow(() -> new ErrorException("DispatcherError: No adapter matched")); - HandlerResponse response = adapter.handle(request, handler); + HandlerResponse handlerResponse = adapter.handle(request, handler); HttpResponseRenderer responseHandler = responseHandlerList.stream() - .filter(rh -> rh.supports(response)) - .findFirst().orElseThrow(()-> new ErrorException("Post handler not exists")); - return responseHandler.handle(response); + .filter(rh -> rh.supports(handlerResponse)) + .findFirst().orElseThrow(()-> new ErrorException("Renderer not exists")); + return responseHandler.handle(response, handlerResponse); } } diff --git a/src/main/java/web/renderer/HttpResponseRenderer.java b/src/main/java/web/renderer/HttpResponseRenderer.java index 5ff61fe80..dd6926827 100644 --- a/src/main/java/web/renderer/HttpResponseRenderer.java +++ b/src/main/java/web/renderer/HttpResponseRenderer.java @@ -5,5 +5,5 @@ public interface HttpResponseRenderer { boolean supports(HandlerResponse response); - HttpResponse handle(HandlerResponse response); + HttpResponse handle(HttpResponse httpResponse, HandlerResponse handlerResponse); } diff --git a/src/main/java/web/renderer/StaticViewRenderer.java b/src/main/java/web/renderer/StaticViewRenderer.java index d61dc0d01..299dba15b 100644 --- a/src/main/java/web/renderer/StaticViewRenderer.java +++ b/src/main/java/web/renderer/StaticViewRenderer.java @@ -2,6 +2,7 @@ import config.VariableConfig; import exception.ErrorException; +import http.HttpStatus; import http.response.HttpResponse; import web.response.HandlerResponse; import web.response.StaticViewResponse; @@ -18,7 +19,7 @@ public boolean supports(HandlerResponse response) { } @Override - public HttpResponse handle(HandlerResponse handlerResponse) { + public HttpResponse handle(HttpResponse httpResponse, HandlerResponse handlerResponse) { StaticViewResponse staticResponse = (StaticViewResponse) handlerResponse; String path = staticResponse.getPath(); @@ -28,7 +29,7 @@ public HttpResponse handle(HandlerResponse handlerResponse) { try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) { byte[] body = in.readAllBytes(); - HttpResponse httpResponse = HttpResponse.of(handlerResponse.getStatus()); + httpResponse.setStatus(handlerResponse.getStatus()); httpResponse.setBody(file, body); handlerResponse.getCookies().forEach(cookie->httpResponse.addHeader("Set-Cookie", cookie)); return httpResponse; From 23d010d0ec08135e8bfb05bcb6c461dc8473b03b Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Fri, 9 Jan 2026 18:24:59 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat(web-filter):=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EA=B5=AC=ED=98=84=20-=20Request=EC=97=90?= =?UTF-8?q?=20=EB=8C=80=ED=95=9C=20=EB=A1=9C=EA=B7=B8=EB=A5=BC=20=EB=82=A8?= =?UTF-8?q?=EA=B8=B0=EA=B3=A0=20=EB=A1=9C=EA=B9=85=EC=9A=A9=20request=20id?= =?UTF-8?q?(rid)=EB=A5=BC=20=EB=82=A8=EA=B8=B0=EB=8A=94=20AccessLogFilter?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/http/request/HttpRequest.java | 15 +++++++++---- src/main/java/web/filter/AccessLogFilter.java | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/main/java/web/filter/AccessLogFilter.java diff --git a/src/main/java/http/request/HttpRequest.java b/src/main/java/http/request/HttpRequest.java index 824551de3..9964ff927 100644 --- a/src/main/java/http/request/HttpRequest.java +++ b/src/main/java/http/request/HttpRequest.java @@ -7,10 +7,7 @@ import java.net.InetAddress; import java.net.URI; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; public class HttpRequest { private final HttpMethod method; @@ -20,6 +17,7 @@ public class HttpRequest { private final URI uri; private String contentType; private byte[] body; + private UUID rid; private InetAddress requestAddress; @@ -102,4 +100,13 @@ public InetAddress getRequestAddress() { public void setBody(byte[] body){ this.body = body; } + + public String generateRid(){ + this.rid = UUID.randomUUID(); + return this.rid.toString(); + } + + public UUID getRid() { + return rid; + } } diff --git a/src/main/java/web/filter/AccessLogFilter.java b/src/main/java/web/filter/AccessLogFilter.java new file mode 100644 index 000000000..b59b1988b --- /dev/null +++ b/src/main/java/web/filter/AccessLogFilter.java @@ -0,0 +1,21 @@ +package web.filter; + +import http.request.HttpRequest; +import http.response.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AccessLogFilter implements ServletFilter { + + private static final Logger log = LoggerFactory.getLogger(AccessLogFilter.class); + + @Override + public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { + chain.doFilter(); + log.info("rid-{}: {} {} from {}", + request.generateRid(), + request.getMethod(), + request.getPath(), + request.getRequestAddress()); + } +} From a2fa1cb12abcf56a459655465dcc78c4b1454776 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Sun, 11 Jan 2026 15:38:14 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat(web-filter):=20RestrictedFilter=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20-=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=20path=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EB=B0=A9=EC=96=B4=20=ED=95=84=ED=84=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/web/filter/RestrictedFilter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/web/filter/RestrictedFilter.java diff --git a/src/main/java/web/filter/RestrictedFilter.java b/src/main/java/web/filter/RestrictedFilter.java new file mode 100644 index 000000000..ec5b07420 --- /dev/null +++ b/src/main/java/web/filter/RestrictedFilter.java @@ -0,0 +1,18 @@ +package web.filter; + +import exception.ErrorCode; +import exception.ServiceException; +import http.request.HttpRequest; +import http.response.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RestrictedFilter implements ServletFilter{ + private static final Logger log = LoggerFactory.getLogger(RestrictedFilter.class); + + @Override + public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { + log.info("rid:{} - Request to restricted path:{}", request.getRid(), request.getPath()); + throw new ServiceException(ErrorCode.FORBIDDEN); + } +} From f5654698c5911db922321f1f25121faf2ab24d2a Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Sun, 11 Jan 2026 17:07:08 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat(web-filter):=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B0=8F=20DI=20=EA=B0=9C=EB=B0=9C=20-=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=ED=83=80=EC=9E=85=20=EB=B3=84=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=EA=B5=AC=EC=84=B1=EC=9D=84=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8A=94=20FilterConfig=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EA=B0=9C=EB=B0=9C=20-=20Path=20=EB=B3=84=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=ED=83=80=EC=9E=85=EC=9D=84=20=EC=A7=80=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8A=94=20SecurityConfig=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/config/AppConfig.java | 19 +++++ src/main/java/config/FilterConfig.java | 56 ++++++++++++++ src/main/java/config/FilterType.java | 9 +++ src/main/java/config/SecurityConfig.java | 25 ++++++ ...erChain.java => FilterChainContainer.java} | 77 ++++++++++++------- src/main/java/web/filter/ServletFilter.java | 2 +- 6 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 src/main/java/config/FilterConfig.java create mode 100644 src/main/java/config/FilterType.java create mode 100644 src/main/java/config/SecurityConfig.java rename src/main/java/web/filter/{FilterChain.java => FilterChainContainer.java} (51%) diff --git a/src/main/java/config/AppConfig.java b/src/main/java/config/AppConfig.java index 1541f42b7..540260975 100644 --- a/src/main/java/config/AppConfig.java +++ b/src/main/java/config/AppConfig.java @@ -18,6 +18,9 @@ import web.dispatch.argument.ArgumentResolver; import web.dispatch.argument.resolver.HttpRequestResolver; import web.dispatch.argument.resolver.QueryParamsResolver; +import web.filter.AccessLogFilter; +import web.filter.FilterChainContainer; +import web.filter.RestrictedFilter; import web.handler.StaticContentHandler; import web.handler.WebHandler; import web.renderer.HttpResponseRenderer; @@ -209,4 +212,20 @@ public ErrorExceptionHandler errorExceptionHandler() { ErrorExceptionHandler::new ); } + + /** + * ===== Filter ===== + */ + public FilterChainContainer filterChainContainer(){ + return getOrCreate("filterChainContainer", + () -> new FilterChainContainer(dispatcher())); + } + + public AccessLogFilter accessLogFilter(){ + return getOrCreate("accessLogFilter", AccessLogFilter::new); + } + + public RestrictedFilter restrictedFilter(){ + return getOrCreate("restrictedFilter", RestrictedFilter::new); + } } diff --git a/src/main/java/config/FilterConfig.java b/src/main/java/config/FilterConfig.java new file mode 100644 index 000000000..d940e4246 --- /dev/null +++ b/src/main/java/config/FilterConfig.java @@ -0,0 +1,56 @@ +package config; + +import exception.ErrorException; +import web.filter.ServletFilter; + +import java.util.ArrayList; +import java.util.List; + +public class FilterConfig extends SingletonContainer { + private final AppConfig appConfig = new AppConfig(); + private int callCount = 0; + + public void config(){ + if(callCount>0) throw new ErrorException("FilterConfig::set: Duplicated call"); + setFilterChains(); + callCount++; + } + + private void setFilterChains(){ + appConfig.filterChainContainer() + .addFilterList(FilterType.ALL, getFilterListByAuthorityType(FilterType.ALL)) + .addFilterList(FilterType.PUBLIC, getFilterListByAuthorityType(FilterType.PUBLIC)) + .addFilterList(FilterType.AUTHENTICATED, getFilterListByAuthorityType(FilterType.AUTHENTICATED)) + .addFilterList(FilterType.RESTRICT, getFilterListByAuthorityType(FilterType.RESTRICT)); + } + + private List commonFrontFilter(){ + return getOrCreate("commonFrontFilter", + () -> List.of( + appConfig.accessLogFilter() + )); + } + + private List commonBackFilter(){ + return getOrCreate("commonFrontFilter", + () -> List.of()); + } + + private List getFilterListByAuthorityType(FilterType type) { + List servletFilterList = new ArrayList<>(); + servletFilterList.addAll(commonFrontFilter()); + servletFilterList.addAll(authorizedFilterList(type)); + servletFilterList.addAll(commonBackFilter()); + return servletFilterList; + } + + private List authorizedFilterList(FilterType type) { + return switch (type) { + case ALL -> List.of(); + case PUBLIC -> List.of(); + case AUTHENTICATED -> List.of(); + case RESTRICT -> List.of(appConfig.restrictedFilter()); + case LOG_IN -> List.of(); + }; + } +} diff --git a/src/main/java/config/FilterType.java b/src/main/java/config/FilterType.java new file mode 100644 index 000000000..49c433b25 --- /dev/null +++ b/src/main/java/config/FilterType.java @@ -0,0 +1,9 @@ +package config; + +public enum FilterType { + ALL, + PUBLIC, + AUTHENTICATED, + RESTRICT, + LOG_IN +} diff --git a/src/main/java/config/SecurityConfig.java b/src/main/java/config/SecurityConfig.java new file mode 100644 index 000000000..4dea6523c --- /dev/null +++ b/src/main/java/config/SecurityConfig.java @@ -0,0 +1,25 @@ +package config; + +import exception.ErrorException; + +public class SecurityConfig extends SingletonContainer { + private final AppConfig appConfig = new AppConfig(); + private int callCount; + + public void config(){ + if(callCount>0) throw new ErrorException("SecurityConfig::setPaths: Duplicated call"); + setPaths(); + callCount++; + } + + public void setPaths(){ + if(callCount>0) throw new ErrorException("SecurityConfig::setPaths: Duplicated call"); + appConfig.filterChainContainer() + .addPath(FilterType.AUTHENTICATED, "/mypage/**") + .addPath(FilterType.ALL, "/user/**") + .addPath(FilterType.PUBLIC, "/**"); + callCount++; + } + + +} diff --git a/src/main/java/web/filter/FilterChain.java b/src/main/java/web/filter/FilterChainContainer.java similarity index 51% rename from src/main/java/web/filter/FilterChain.java rename to src/main/java/web/filter/FilterChainContainer.java index ccd9cb427..826a9fc3c 100644 --- a/src/main/java/web/filter/FilterChain.java +++ b/src/main/java/web/filter/FilterChainContainer.java @@ -1,43 +1,62 @@ package web.filter; +import config.FilterType; +import exception.ErrorCode; +import exception.ErrorException; +import exception.ServiceException; import http.request.HttpRequest; import http.response.HttpResponse; import web.dispatch.Dispatcher; -import java.util.ArrayList; -import java.util.List; +import java.util.*; -public class FilterChain { - private final List filterChainList; +public class FilterChainContainer { + private final List registeredPaths; + private final Map> filterChainMap; private final Dispatcher dispatcher; - public FilterChain(Dispatcher dispatcher) { - this.filterChainList = new ArrayList<>(); + public FilterChainContainer(Dispatcher dispatcher) { + this.registeredPaths = new ArrayList<>(); + this.filterChainMap = new HashMap<>(); this.dispatcher = dispatcher; } - public FilterChain addChain(String path, List filterList) { - filterChainList.add(Pair.of(path, filterList)); - return this; - } - public void runFilterChain(HttpRequest request, HttpResponse response) { String requestedPath = request.getPath(); - Pair matched = findMatchedChain(requestedPath); - List filters = (matched == null) ? List.of() : matched.filterList; + FilterTypePaths matched = findMatchedChain(requestedPath).orElseThrow(()-> new ServiceException(ErrorCode.FORBIDDEN)); + List filters = filterChainMap.get(matched.type); - FilterEngine engine = new FilterEngine(dispatcher, request, response, filters); + FilterChainEngine engine = new FilterChainEngine(dispatcher, request, response, filters); engine.doFilter(); } - private Pair findMatchedChain(String requestedPath) { - for (Pair pair : filterChainList) { - if (isMatched(requestedPath, pair.path)) { - return pair; + public FilterChainContainer addFilterList(FilterType type, List filterList) { + if(filterChainMap.containsKey(type)) + throw new ErrorException("FilterChain Construction: Duplicate filter list per type"); + filterChainMap.put(type, filterList); + return this; + } + + public FilterChainContainer addPaths(FilterType type, List paths) { + registeredPaths.add(FilterTypePaths.of(type, paths)); + return this; + } + + public FilterChainContainer addPath(FilterType type, String path) { + registeredPaths.add(FilterTypePaths.of(type, List.of(path))); + return this; + } + + private Optional findMatchedChain(String requestedPath) { + for (FilterTypePaths filterTypePaths : registeredPaths) { + for (String path : filterTypePaths.paths) { + if (isMatched(requestedPath, path)) { + return Optional.of(filterTypePaths); + } } } - return null; + return Optional.empty(); } private boolean isMatched(String requestedPath, String filterPath) { @@ -72,28 +91,28 @@ private boolean isMatched(String requestedPath, String filterPath) { return false; } - private static class Pair { - private final String path; - private final List filterList; + private static class FilterTypePaths { + private final FilterType type; + private final List paths; - private Pair(String path, List filterList) { - this.path = path; - this.filterList = filterList; + public FilterTypePaths(FilterType type, List paths) { + this.type = type; + this.paths = paths; } - public static Pair of(String path, List filterList) { - return new Pair(path, filterList); + public static FilterTypePaths of(FilterType type, List paths) { + return new FilterTypePaths(type, paths); } } - public static class FilterEngine { + public static class FilterChainEngine { private final Dispatcher dispatcher; private final HttpRequest request; private final HttpResponse response; private final List filterList; private int position = 0; - public FilterEngine(Dispatcher dispatcher, HttpRequest request, HttpResponse response, List filterList) { + public FilterChainEngine(Dispatcher dispatcher, HttpRequest request, HttpResponse response, List filterList) { this.dispatcher = dispatcher; this.request = request; this.response = response; diff --git a/src/main/java/web/filter/ServletFilter.java b/src/main/java/web/filter/ServletFilter.java index 46704b3f0..0b5ba3641 100644 --- a/src/main/java/web/filter/ServletFilter.java +++ b/src/main/java/web/filter/ServletFilter.java @@ -4,5 +4,5 @@ import http.response.HttpResponse; public interface ServletFilter { - void runFilter(HttpRequest request, HttpResponse response, FilterChain.FilterEngine chain); + void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain); } From e6750822d500f51df8fd316dea278b9fbb28ac91 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Sun, 11 Jan 2026 17:07:23 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat(web-filter):=20Bootstrap=EC=97=90=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/bootstrap/WebServer.java | 23 ++++++++++++++----- src/main/java/config/DependencyLoader.java | 23 ------------------- .../java/web/dispatch/ConnectionHandler.java | 11 ++++----- 3 files changed, 22 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/config/DependencyLoader.java diff --git a/src/main/java/bootstrap/WebServer.java b/src/main/java/bootstrap/WebServer.java index 9c9926c84..95f524e1f 100644 --- a/src/main/java/bootstrap/WebServer.java +++ b/src/main/java/bootstrap/WebServer.java @@ -5,7 +5,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import config.DependencyLoader; +import config.AppConfig; +import config.FilterConfig; +import config.SecurityConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.dispatch.ConnectionHandler; @@ -13,7 +15,9 @@ public class WebServer { private static final Logger logger = LoggerFactory.getLogger(WebServer.class); private static final int DEFAULT_PORT = 8080; - private static final DependencyLoader LOADER = new DependencyLoader(); + private static final AppConfig LOADER = new AppConfig(); + private static final SecurityConfig securityConfig = new SecurityConfig(); + private static final FilterConfig filterConfig = new FilterConfig(); private static final ExecutorService executor = Executors.newFixedThreadPool(32); public static void main(String args[]) throws Exception { @@ -23,6 +27,7 @@ public static void main(String args[]) throws Exception { } else { port = Integer.parseInt(args[0]); } + config(); // 서버소켓을 생성한다. 웹서버는 기본적으로 8080번 포트를 사용한다. try (ServerSocket listenSocket = new ServerSocket(port)) { @@ -33,14 +38,20 @@ public static void main(String args[]) throws Exception { while ((connection = listenSocket.accept()) != null) { Socket singleConnection = connection; executor.submit(() -> { - ConnectionHandler connectionHandler = new ConnectionHandler(LOADER.dispatcher, - LOADER.exceptionHandlerMapping, - LOADER.httpResponseConverter, - LOADER.httpRequestConverter, + ConnectionHandler connectionHandler = new ConnectionHandler( + LOADER.filterChainContainer(), + LOADER.exceptionHandlerMapping(), + LOADER.httpResponseConverter(), + LOADER.httpRequestConverter(), singleConnection); connectionHandler.run(); }); } } } + + private static void config(){ + securityConfig.config(); + filterConfig.config(); + } } diff --git a/src/main/java/config/DependencyLoader.java b/src/main/java/config/DependencyLoader.java deleted file mode 100644 index 5db9b30bf..000000000 --- a/src/main/java/config/DependencyLoader.java +++ /dev/null @@ -1,23 +0,0 @@ -package config; - -import exception.ExceptionHandlerMapping; -import http.request.HttpRequestConverter; -import http.response.HttpResponseConverter; -import web.dispatch.Dispatcher; - -public class DependencyLoader { - private final AppConfig appConfig; - - public final HttpRequestConverter httpRequestConverter; - public final HttpResponseConverter httpResponseConverter; - public final ExceptionHandlerMapping exceptionHandlerMapping; - public final Dispatcher dispatcher; - - public DependencyLoader(){ - this.appConfig = new AppConfig(); - this.httpRequestConverter = appConfig.httpRequestConverter(); - this.httpResponseConverter = appConfig.httpResponseConverter(); - this.exceptionHandlerMapping = appConfig.exceptionHandlerMapping(); - this.dispatcher = appConfig.dispatcher(); - } -} diff --git a/src/main/java/web/dispatch/ConnectionHandler.java b/src/main/java/web/dispatch/ConnectionHandler.java index 33ab6266c..c29558c8b 100644 --- a/src/main/java/web/dispatch/ConnectionHandler.java +++ b/src/main/java/web/dispatch/ConnectionHandler.java @@ -5,24 +5,23 @@ import http.request.HttpRequestConverter; import http.request.HttpRequest; import http.response.HttpResponse; -import web.filter.FilterChain; +import web.filter.FilterChainContainer; import java.net.Socket; public class ConnectionHandler implements Runnable{ private final Socket connection; + private final FilterChainContainer filterChainContainer; private final HttpRequestConverter requestConverter; private final HttpResponseConverter responseConverter; private final ExceptionHandlerMapping exceptionHandlerMapping; - private final Dispatcher dispatcher; - private FilterChain filterChain; - public ConnectionHandler(Dispatcher dispatcher, + public ConnectionHandler(FilterChainContainer filterChainContainer, ExceptionHandlerMapping exceptionHandlerMapping, HttpResponseConverter responseConverter, HttpRequestConverter requestConverter, Socket connection) { - this.dispatcher = dispatcher; + this.filterChainContainer = filterChainContainer; this.exceptionHandlerMapping = exceptionHandlerMapping; this.responseConverter = responseConverter; this.requestConverter = requestConverter; @@ -36,7 +35,7 @@ public void run() { try { HttpRequest request = requestConverter.parseRequest(connection); - filterChain.runFilterChain(request,response); + filterChainContainer.runFilterChain(request,response); responseConverter.sendResponse(response, connection); } catch (Throwable t){ From f29192b9b8e5c13ace234026122bca133bd30a76 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Sun, 11 Jan 2026 17:25:31 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20PR=20=EB=A6=AC=EB=B7=B0=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81=20(https://github.com/codingbara?= =?UTF-8?q?Go/be-was/pull/47#pullrequestreview-3647505089)=20-=20FilterCon?= =?UTF-8?q?fig=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20SecurityConfig=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=A4=91=EB=B3=B5=20=ED=98=B8=EC=B6=9C=20=EB=B0=A9?= =?UTF-8?q?=EC=96=B4=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20-=20HttpR?= =?UTF-8?q?equest.rid=20=EC=A4=91=EB=B3=B5=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/config/FilterConfig.java | 2 +- src/main/java/config/SecurityConfig.java | 2 -- src/main/java/http/request/HttpRequest.java | 5 +++-- src/main/java/web/filter/AccessLogFilter.java | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/config/FilterConfig.java b/src/main/java/config/FilterConfig.java index d940e4246..4f7005056 100644 --- a/src/main/java/config/FilterConfig.java +++ b/src/main/java/config/FilterConfig.java @@ -32,7 +32,7 @@ private List commonFrontFilter(){ } private List commonBackFilter(){ - return getOrCreate("commonFrontFilter", + return getOrCreate("commonBackFilter", () -> List.of()); } diff --git a/src/main/java/config/SecurityConfig.java b/src/main/java/config/SecurityConfig.java index 4dea6523c..514218be2 100644 --- a/src/main/java/config/SecurityConfig.java +++ b/src/main/java/config/SecurityConfig.java @@ -13,12 +13,10 @@ public void config(){ } public void setPaths(){ - if(callCount>0) throw new ErrorException("SecurityConfig::setPaths: Duplicated call"); appConfig.filterChainContainer() .addPath(FilterType.AUTHENTICATED, "/mypage/**") .addPath(FilterType.ALL, "/user/**") .addPath(FilterType.PUBLIC, "/**"); - callCount++; } diff --git a/src/main/java/http/request/HttpRequest.java b/src/main/java/http/request/HttpRequest.java index 9964ff927..cf0c025bf 100644 --- a/src/main/java/http/request/HttpRequest.java +++ b/src/main/java/http/request/HttpRequest.java @@ -101,8 +101,9 @@ public void setBody(byte[] body){ this.body = body; } - public String generateRid(){ - this.rid = UUID.randomUUID(); + public String getOrGenerateRid(){ + if(this.rid == null) + this.rid = UUID.randomUUID(); return this.rid.toString(); } diff --git a/src/main/java/web/filter/AccessLogFilter.java b/src/main/java/web/filter/AccessLogFilter.java index b59b1988b..2cd4e2922 100644 --- a/src/main/java/web/filter/AccessLogFilter.java +++ b/src/main/java/web/filter/AccessLogFilter.java @@ -13,7 +13,7 @@ public class AccessLogFilter implements ServletFilter { public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) { chain.doFilter(); log.info("rid-{}: {} {} from {}", - request.generateRid(), + request.getOrGenerateRid(), request.getMethod(), request.getPath(), request.getRequestAddress());