Skip to content
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
23 changes: 17 additions & 6 deletions src/main/java/bootstrap/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
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;

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);
Comment on lines +18 to 21

Choose a reason for hiding this comment

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

AppConfig 싱글톤 미보장: WebServer에서 LOADER = new AppConfig(), SecurityConfig, FilterConfig 등으로 각각 새로운 인스턴스를 생성하고 있습니다. 동시에 FilterConfigSecurityConfig 내부에서도 new AppConfig()를 호출하므로 싱글톤이 보장되지 않습니다. PR 설명에서 "DI가 복잡하게 꼬인 느낌"이라는 피드백과 연결됩니다.\n\n수정안: 싱글톤 패턴을 명확히 하거나, SingletonContainer 기반으로 통합 관리하도록 리팩토링하세요. 예: AppConfig를 정적 싱글톤으로 만들거나, 별도의 초기화 단계에서 통합 관리."


public static void main(String args[]) throws Exception {
Expand All @@ -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)) {
Expand All @@ -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();
}
}
19 changes: 19 additions & 0 deletions src/main/java/config/AppConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
23 changes: 0 additions & 23 deletions src/main/java/config/DependencyLoader.java

This file was deleted.

56 changes: 56 additions & 0 deletions src/main/java/config/FilterConfig.java
Original file line number Diff line number Diff line change
@@ -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<ServletFilter> commonFrontFilter(){
return getOrCreate("commonFrontFilter",
() -> List.of(
appConfig.accessLogFilter()
));
}

private List<ServletFilter> commonBackFilter(){
return getOrCreate("commonBackFilter",
() -> List.of());
Comment on lines 32 to 36

Choose a reason for hiding this comment

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

중복된 key로 인한 캐싱 버그: commonFrontFilter()commonBackFilter() 모두 동일한 key "commonFrontFilter"를 사용하고 있습니다. commonBackFilter()가 실제로는 빈 리스트를 반환해야 하지만, 캐싱으로 인해 앞의 값이 재사용될 수 있습니다.\n\n수정안: commonBackFilter()의 key를 \"commonBackFilter\"로 변경하세요."

}

private List<ServletFilter> getFilterListByAuthorityType(FilterType type) {
List<ServletFilter> servletFilterList = new ArrayList<>();
servletFilterList.addAll(commonFrontFilter());
servletFilterList.addAll(authorizedFilterList(type));
servletFilterList.addAll(commonBackFilter());
return servletFilterList;
}

private List<ServletFilter> 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();
};
}
}
9 changes: 9 additions & 0 deletions src/main/java/config/FilterType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package config;

public enum FilterType {
ALL,
PUBLIC,
AUTHENTICATED,
RESTRICT,
LOG_IN
}
23 changes: 23 additions & 0 deletions src/main/java/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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(){
appConfig.filterChainContainer()
.addPath(FilterType.AUTHENTICATED, "/mypage/**")
.addPath(FilterType.ALL, "/user/**")
.addPath(FilterType.PUBLIC, "/**");
}


}
2 changes: 2 additions & 0 deletions src/main/java/http/HttpStatus.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package http;

public enum HttpStatus {
NONE(0),

OK(200),
CREATED(201),
ACCEPTED(202),
Expand Down
16 changes: 12 additions & 4 deletions src/main/java/http/request/HttpRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,6 +17,7 @@ public class HttpRequest {
private final URI uri;
private String contentType;
private byte[] body;
private UUID rid;

private InetAddress requestAddress;

Expand Down Expand Up @@ -102,4 +100,14 @@ public InetAddress getRequestAddress() {
public void setBody(byte[] body){
this.body = body;
}

public String getOrGenerateRid(){
if(this.rid == null)
this.rid = UUID.randomUUID();
return this.rid.toString();
}

public UUID getRid() {
return rid;
}
}
10 changes: 9 additions & 1 deletion src/main/java/http/response/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import java.util.Map;

public class HttpResponse {
private final HttpStatus status;
private final Map<String, List<String>> headers;
private HttpStatus status;
private byte[] body;

private HttpResponse (HttpStatus status){
Expand All @@ -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;
}
Expand Down
16 changes: 9 additions & 7 deletions src/main/java/web/dispatch/ConnectionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@
import http.request.HttpRequestConverter;
import http.request.HttpRequest;
import http.response.HttpResponse;
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;

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;
Expand All @@ -30,22 +31,23 @@ public ConnectionHandler(Dispatcher dispatcher,
@Override
public void run() {

HttpResponse response = HttpResponse.of();
try {

HttpRequest request = requestConverter.parseRequest(connection);
HttpResponse response = dispatcher.handle(request);
filterChainContainer.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 {
Comment on lines 33 to 49

Choose a reason for hiding this comment

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

HttpResponse 상태 관리 모호성: 예외 발생 시 filterChainContainer.runFilterChain() 호출이 중단되면, response 객체는 HttpStatus.NONE 상태로 유지됩니다. exceptionHandlerMapping.handle(t, connection)이 이 response 객체를 활용하지 못한다면, 예외 처리 응답이 제대로 구성되지 않을 수 있습니다.\n\n수정안: 예외 발생 시 response 객체를 활용하거나, 예외 핸들러가 자체적으로 응답을 생성하는 구조로 명확히 하세요. TODO 주석이 있으므로 추후 리팩토링 시 이를 반영하세요."

try { connection.close(); } catch (Exception ignore) {}
try { connection.close(); } catch (Throwable ignore) {}
}
}
}
10 changes: 5 additions & 5 deletions src/main/java/web/dispatch/Dispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public Dispatcher(List<WebHandler> handlerMapping, List<HandlerAdapter> 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());

Expand All @@ -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);
}
}
21 changes: 21 additions & 0 deletions src/main/java/web/filter/AccessLogFilter.java
Original file line number Diff line number Diff line change
@@ -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.getOrGenerateRid(),
request.getMethod(),
request.getPath(),
request.getRequestAddress());
}
}
Loading