Skip to main content

🚀 Request Dispatching System

Overview​

This document provides an in-depth technical analysis of the Sprout Framework's request dispatching system. It examines the entire processing pipeline, from the moment an HTTP request is parsed to the execution of a controller method and the generation of a response. We will detail the operational mechanisms of Filters and Interceptors, highlight differences with the Spring Framework, and analyze Sprout's unique design decisions.

Dispatching Pipeline Architecture​

Overall Request Handling Flow​


HttpRequest → RequestDispatcher → Controller Method → HttpResponse
↓
┌─────────────────────────────────────────────────────────────────────┐
│ DispatchHook → FilterChain → InterceptorChain → HandlerInvoker │
│ ↓ │
│ ResponseResolver ← ResponseAdvice ← Return Value │
└─────────────────────────────────────────────────────────────────────┘

Step-by-Step Execution Order​

Request Phase:

  1. DispatchHook.beforeDispatch() - Pre-processing hook
  2. FilterChain.doFilter() - Filter chain execution
  3. InterceptorChain.applyPreHandle() - Interceptor pre-processing
  4. HandlerMethodInvoker.invoke() - Controller method execution
  5. InterceptorChain.applyPostHandle() - Interceptor post-processing

Response Phase:

  1. ResponseAdvice.beforeBodyWrite() - Response advice processing
  2. ResponseResolver.resolve() - Response resolution
  3. InterceptorChain.applyAfterCompletion() - Interceptor completion processing
  4. DispatchHook.afterDispatch() - Post-processing hook

Core Component Analysis​

1. RequestDispatcher: The Central Coordinator​

Dependency Injection Structure

@Component
public class RequestDispatcher {
private final HandlerMapping mapping;
private final HandlerMethodInvoker invoker;
private final List<ResponseResolver> responseResolvers;
private final List<ResponseAdvice> responseAdvices;
private final List<Filter> filters;
private final List<Interceptor> interceptors;
private final List<ExceptionResolver> exceptionResolvers;
private final List<DispatchHook> dispatchHooks;
}

Design Principles

  1. Dependency Inversion: Depends on interfaces, not concrete implementations.
  2. Composition Pattern: Composes complex logic by combining multiple strategy objects.
  3. Single Responsibility: Focuses solely on coordinating the dispatching process, delegating actual processing to dedicated components.
  4. Extensibility: Supports multiple implementations for each component through List-based injection.

2. Main Dispatch Logic Analysis​

dispatch() Method: The Top-Level Entry Point

public void dispatch(HttpRequest<?> req, HttpResponse res) throws IOException {
try {
// 1. Execute pre-processing hooks
for (DispatchHook hook : dispatchHooks) {
hook.beforeDispatch(req, res);
}

// 2. Connect the filter chain with the actual dispatch logic
new FilterChain(filters, this::doDispatch).doFilter(req, res);
} finally {
// 3. Execute post-processing hooks (always runs)
for (DispatchHook hook : dispatchHooks) {
hook.afterDispatch(req, res);
}
}
}

Key Design Features

  1. try-finally Pattern: Guarantees the execution of post-processing hooks, regardless of whether an exception occurs.
  2. Functional Interface Usage: Passes a method reference with this::doDispatch.
  3. Layered Execution: Follows a strict order: Hooks → Filters → Actual Dispatch.

doDispatch() Method: Core Business Logic

private void doDispatch(HttpRequest<?> req, HttpResponse res) {
HandlerMethod hm = null;
Exception caughtException = null;
InterceptorChain interceptorChain = new InterceptorChain(interceptors);

try {
// 1. Handler Mapping
hm = mapping.findHandler(req.getPath(), req.getMethod());
if (hm == null) {
// Directly create a 404 response
res.setResponseEntity(
new ResponseEntity<>("Not Found", null, ResponseCode.NOT_FOUND)
);
return;
}

// 2. Interceptor pre-processing
if (!interceptorChain.applyPreHandle(req, res, hm)) {
return; // Request handling stopped by an interceptor
}

// 3. Execute the controller method
Object returnValue = invoker.invoke(hm.requestMappingInfo(), req);

// 4. Interceptor post-processing
interceptorChain.applyPostHandle(req, res, hm, returnValue);

// 5. Response handling
setResponseResolvers(returnValue, req, res);

} catch (Exception e) {
caughtException = e;
// 6. Delegate exception handling
handleException(req, res, hm, e);
} finally {
// 7. Interceptor completion processing
if (hm != null) {
interceptorChain.applyAfterCompletion(req, res, hm, caughtException);
}
}
}

3. Exception Handling Strategy​

Layered Exception Resolution Mechanism

// Exception handling via the ExceptionResolver chain
Object handledReturnValue = null;
for (ExceptionResolver resolver : exceptionResolvers) {
handledReturnValue = resolver.resolveException(req, res, hm, caughtException);
if (handledReturnValue != null) {
// If handled successfully, set the response
if (handledReturnValue instanceof ResponseEntity) {
res.setResponseEntity((ResponseEntity<?>) handledReturnValue);
} else {
setResponseResolvers(handledReturnValue, req, res);
}
return;
}
}

Exception Handling Design Features

  1. Chain of Responsibility: Multiple resolvers attempt to handle the exception sequentially.
  2. Early Exit: The chain stops as soon as one resolver successfully handles the exception.
  3. Flexible Return Value: Can return a ResponseEntity directly or another object for further processing.
  4. Type Safety: Uses instanceof for runtime type checking.

4. Response Handling Pipeline​

Collaboration between ResponseResolver and ResponseAdvice

private void setResponseResolvers(Object returnValue, HttpRequest<?> req, HttpResponse res) {
if (res.isCommitted()) return; // Prevent duplicate processing

// 1. Apply the ResponseAdvice chain
Object processed = applyResponseAdvices(returnValue, req);

// 2. Find and apply the appropriate ResponseResolver
for (ResponseResolver resolver : responseResolvers) {
if (resolver.supports(processed)) {
ResponseEntity<?> responseEntity = resolver.resolve(processed, req);
res.setResponseEntity(responseEntity);
return;
}
}

throw new IllegalStateException("No suitable ResponseResolver found");
}

ResponseAdvice Chain Processing

private Object applyResponseAdvices(Object returnValue, HttpRequest<?> req) {
Object current = returnValue;
for (ResponseAdvice advice : responseAdvices) {
if (advice.supports(current, req)) {
current = advice.beforeBodyWrite(current, req);
}
}
return current;
}

Filter System Analysis​

FilterChain Implementation​

Chain of Responsibility Pattern

public class FilterChain {
private final List<Filter> filters;
private final Dispatcher dispatcher;
private int currentFilterIndex = 0;

public void doFilter(HttpRequest<?> request, HttpResponse response) throws IOException {
if (currentFilterIndex < filters.size()) {
// Execute the next filter
filters.get(currentFilterIndex++).doFilter(request, response, this);
return;
}
// After all filters are done, invoke the actual dispatcher
dispatcher.dispatch(request, response);
}
}

FilterChain Features

  1. State-Based Progression: Tracks the current execution position with currentFilterIndex.
  2. Recursive Invocation: Each filter invokes the next element in the chain.
  3. Functional Interface: Delegates final processing via the Dispatcher functional interface.
  4. Linear Execution: Filters are executed sequentially.

Filter Interface​

public interface Filter extends InfrastructureBean {
void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws IOException;
}

Sprout Filter vs. Servlet Filter

FeatureSprout FilterServlet Filter
ParametersHttpRequest, HttpResponse, FilterChainServletRequest, ServletResponse, FilterChain
Checked ExceptionsIOExceptionIOException, ServletException
LifecycleSpring Bean LifecycleManaged by Servlet Container
Configuration@Component + DIweb.xml or @WebFilter

CorsFilter: A Practical Example​

Configuration-Based CORS Handling

@Component
public class CorsFilter implements Filter {
private final AppConfig appConfig;

@Override
public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws IOException {
String origin = Optional.ofNullable(request.getHeaders().get("Origin"))
.map(Object::toString)
.orElse(null);

if (origin == null || origin.isEmpty()) {
chain.doFilter(request, response); // Skip CORS if no Origin header
return;
}

// Set CORS headers
applyCorsHeaders(response, origin);

// Handle OPTIONS preflight requests
if (request.getMethod().equals(HttpMethod.OPTIONS)) {
handlePreflightRequest(request, response);
return; // Stop chain progression
}

chain.doFilter(request, response); // Continue to the next in the chain
}
}

Interceptor System Analysis​

InterceptorChain Implementation​

Sequential Execution and Reverse-Order Cleanup

public class InterceptorChain {
private final List<Interceptor> interceptors;

public boolean applyPreHandle(HttpRequest request, HttpResponse response, Object handler) {
for (Interceptor interceptor : interceptors) {
if (!interceptor.preHandle(request, response, handler)) {
return false; // Stop if any interceptor returns false
}
}
return true;
}

public void applyPostHandle(HttpRequest request, HttpResponse response, Object handler, Object result) {
// Execute in reverse order (LIFO)
for (int i = interceptors.size() - 1; i >= 0; i--) {
interceptors.get(i).postHandle(request, response, handler, result);
}
}

public void applyAfterCompletion(HttpRequest request, HttpResponse response, Object handler, Exception ex) {
// Execute in reverse order (LIFO)
for (int i = interceptors.size() - 1; i >= 0; i--) {
interceptors.get(i).afterCompletion(request, response, handler, ex);
}
}
}

Interceptor Execution Pattern

  1. preHandle: Executes in forward order. If any returns false, the entire chain (including the controller) is aborted.
  2. postHandle: Executes in reverse (LIFO) order, but only if the controller method executes successfully.
  3. afterCompletion: Executes in reverse (LIFO) order, always, regardless of whether an exception occurred.

Interceptor vs. Filter Comparison​

FeatureInterceptorFilter
Execution PointAfter handler mappingBefore handler mapping
Accessible InfoCan access handler infoCannot access handler info
Execution Phases3 phases (pre/post/after)1 phase (doFilter)
Processing ScopeCentered on controller methodsThe entire HTTP request
Execution OrderPost-processing is LIFOAlways sequential
Interruptionboolean return valueNot calling the chain

HandlerMethodInvoker Analysis​

Method Execution Strategy​

@Component
public class HandlerMethodInvoker {
private final CompositeArgumentResolver resolvers;

public Object invoke(RequestMappingInfo requestMappingInfo, HttpRequest<?> request) throws Exception {
PathPattern pattern = requestMappingInfo.pattern();

// 1. Extract path variables from the URL pattern
Map<String, String> pathVariables = pattern.extractPathVariables(request.getPath());

// 2. Resolve controller method parameters
Object[] args = resolvers.resolveArguments(requestMappingInfo.handlerMethod(), request, pathVariables);

// 3. Invoke the method via reflection
return requestMappingInfo.handlerMethod().invoke(requestMappingInfo.controller(), args);
}
}

Core Processing Steps

  1. Path Variable Extraction: Uses PathPattern to extract variables like {id} from the URL.
  2. Argument Resolution: Employs CompositeArgumentResolver to determine values for method parameters.
  3. Method Invocation: Executes the actual controller method using the Java Reflection API.

ControllerAdvice-Based Exception Handling System​

Sprout provides an exception handling system that mimics Spring's @ControllerAdvice and @ExceptionHandler annotations.

ControllerAdviceRegistry: The Exception Handler Registry​

Scanning and Registering Exception Handlers

@Component
public class ControllerAdviceRegistry {
private final List<ExceptionHandlerObject> allExceptionHandlers = new ArrayList<>();
private final Map<Class<? extends Throwable>, Optional<ExceptionHandlerObject>> cachedHandlers = new ConcurrentHashMap<>();

public void scanControllerAdvices(BeanFactory context) {
Collection<Object> allBeans = context.getAllBeans();
for (Object bean : allBeans) {
if (bean.getClass().isAnnotationPresent(ControllerAdvice.class)) {
// Found a @ControllerAdvice class
for (Method method : bean.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(ExceptionHandler.class)) {
method.setAccessible(true);
allExceptionHandlers.add(new ExceptionHandlerObject(method, bean));
}
}
}
}
}
}

Key Design Features

  1. Reflection-Based Scanning: Discovers @ControllerAdvice beans at runtime.
  2. Method Accessibility: setAccessible(true) allows invocation of private handler methods.
  3. Caching Mechanism: Caches handlers by exception type using ConcurrentHashMap.

Exception Handler Matching Algorithm​

Best-Match Handler Selection Strategy

private Optional<ExceptionHandlerObject> lookupBestMatchHandler(Class<? extends Throwable> exceptionClass) {
ExceptionHandlerObject bestMatch = null;
int bestMatchDistance = Integer.MAX_VALUE;

for (ExceptionHandlerObject handler : allExceptionHandlers) {
Method handlerMethod = handler.getMethod();
for (Class<? extends Throwable> handledExceptionType : handlerMethod.getAnnotation(ExceptionHandler.class).value()) {
if (handledExceptionType.isAssignableFrom(exceptionClass)) {
// Calculate distance in the exception hierarchy
int distance = getExceptionDistance(handledExceptionType, exceptionClass);

if (distance < bestMatchDistance) {
bestMatch = handler;
bestMatchDistance = distance;
}
}
}
}
return Optional.ofNullable(bestMatch);
}

private int getExceptionDistance(Class<?> fromClass, Class<?> toClass) {
if (fromClass.equals(toClass)) return 0; // Exact type match

int distance = 0;
Class<?> current = toClass;
while (current != null && !current.equals(fromClass)) {
current = current.getSuperclass();
distance++;
}
return (current != null) ? distance : Integer.MAX_VALUE;
}

Matching Algorithm Features

  1. Distance-Based Matching: Selects the closest (most specific) handler in the exception type hierarchy.
  2. Multi-Exception Support: @ExceptionHandler({Exception1.class, Exception2.class}).
  3. Inheritance Awareness: A parent exception handler can process child exceptions.
  4. Exact Match Priority: An identical type match has a distance of 0 and is prioritized.

ControllerAdviceExceptionResolver Implementation​

Exception Resolution and Method Invocation

@Component
@Order(0) // Highest priority
public class ControllerAdviceExceptionResolver implements ExceptionResolver {

@Override
public Object resolveException(HttpRequest<?> request, HttpResponse response,
Object handlerMethod, Exception exception) {
Optional<ExceptionHandlerObject> handlerOptional =
controllerAdviceRegistry.getExceptionHandler(exception.getClass());

if (handlerOptional.isPresent()) {
ExceptionHandlerObject exceptionHandler = handlerOptional.get();
Method handlerMethodRef = exceptionHandler.getMethod();
Object handlerInstance = exceptionHandler.getBean();

// Support for various method signatures
Object handlerReturnValue = invokeExceptionHandler(handlerMethodRef, handlerInstance, exception, request);

// Response processing via ResponseResolver
return processHandlerReturnValue(handlerReturnValue, request, response);
}
return null; // Could not handle
}
}

Supported Method Signature Patterns

// 1. No parameters
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument() { }

// 2. Exception only
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) { }

// 3. Exception + Request
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e, HttpRequest request) { }

// 4. Request + Exception (reversed order)
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(HttpRequest request, IllegalArgumentException e) { }

Method Invocation Strategy

private Object invokeExceptionHandler(Method handlerMethodRef, Object handlerInstance,
Exception exception, HttpRequest request) {
int paramCount = handlerMethodRef.getParameterCount();
Class<?>[] paramTypes = handlerMethodRef.getParameterTypes();

if (paramCount == 0) {
return handlerMethodRef.invoke(handlerInstance);
} else if (paramCount == 1 && paramTypes[0].isAssignableFrom(exception.getClass())) {
return handlerMethodRef.invoke(handlerInstance, exception);
} else if (paramCount == 2) {
// Determine by type, regardless of parameter order
if (paramTypes[0].isAssignableFrom(exception.getClass()) &&
paramTypes[1].isAssignableFrom(HttpRequest.class)) {
return handlerMethodRef.invoke(handlerInstance, exception, request);
} else if (paramTypes[0].isAssignableFrom(HttpRequest.class) &&
paramTypes[1].isAssignableFrom(exception.getClass())) {
return handlerMethodRef.invoke(handlerInstance, request, exception);
}
}
throw new UnsupportedOperationException("Unsupported method signature");
}

ExceptionHandlerObject: Handler Metadata​

A Simple Metadata Holder

public class ExceptionHandlerObject {
private final Method method;
private final Object bean;

public ExceptionHandlerObject(Method method, Object bean) {
this.method = method;
this.bean = bean;
method.setAccessible(true); // Allow invocation of private methods
}
}

Annotation Definitions​

@ControllerAdvice Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ControllerAdvice {
// Applicable only at the class level
}

@ExceptionHandler Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ExceptionHandler {
Class<? extends Throwable>[] value(); // Array of exception types to handle
}

Overall Exception Handling Flow​

Integration within RequestDispatcher

// Exception handling block within the doDispatch() method
catch (Exception e) {
caughtException = e;

Object handledReturnValue = null;
for (ExceptionResolver resolver : exceptionResolvers) {
// ControllerAdviceExceptionResolver runs first due to @Order(0)
handledReturnValue = resolver.resolveException(req, res, hm, caughtException);
if (handledReturnValue != null) {
// Handled by an @ExceptionHandler
setResponseResolvers(handledReturnValue, req, res);
return;
}
}
// Default handling for unhandled exceptions
}

Execution Order

  1. Exception is thrown in a controller.
  2. ControllerAdviceExceptionResolver executes (highest priority).
  3. The resolver searches for a handler in ControllerAdviceRegistry.
  4. The most specific handler is selected.
  5. The handler method is invoked.
  6. The response is converted by a ResponseResolver.
  7. If not handled, control passes to the next ExceptionResolver.

Comparison with Spring​

@ControllerAdvice Feature Comparison

FeatureSpring @ControllerAdviceSprout @ControllerAdvice
ApplicabilityGlobal or limited by package/classGlobal only
Annotation TargetClass levelClass level
ScanningAutomatic via component scanningManual scan from BeanFactory
Method SignatureVery flexible (Model, WebRequest, etc.)Limited (Exception, HttpRequest)
Response HandlingViewResolver, HttpMessageConverterResponseResolver
CachingBuilt-inManual (ConcurrentHashMap)

@ExceptionHandler Feature Comparison

FeatureSpring @ExceptionHandlerSprout @ExceptionHandler
Parameter Support20+ types (Model, HttpSession, etc.)4 patterns only
Return Value SupportResponseEntity, Model, View, etc.ResponseResolver dependent
Exception MatchingRuntime resolutionCompile-time + Runtime
Order ControlSupports @OrderDistance-based algorithm
Async SupportDeferredResult, CallableNot supported

Performance and Memory Characteristics​

Caching Strategy

// Caching handlers by exception type for performance optimization
private final Map<Class<? extends Throwable>, Optional<ExceptionHandlerObject>> cachedHandlers = new ConcurrentHashMap<>();

public Optional<ExceptionHandlerObject> getExceptionHandler(Class<? extends Throwable> exceptionClass) {
return cachedHandlers.computeIfAbsent(exceptionClass, this::lookupBestMatchHandler);
}

Time Complexity

  • First Lookup: O(n × m) (where n = # of handlers, m = # of exception types per handler)
  • Cache Hit: O(1)
  • Exception Distance Calculation: O(d) (where d = depth of inheritance hierarchy)

Memory Usage

  • Handler Storage: One ExceptionHandlerObject instance per @ExceptionHandler method.
  • Cache Storage: One Optional<ExceptionHandlerObject> per looked-up exception type.
  • Method Accessibility: setAccessible(true) call saves security check overhead.

Comparative Analysis with Spring Framework​

Architectural Differences​

Spring DispatcherServlet vs. Sprout RequestDispatcher

AspectSpring DispatcherServletSprout RequestDispatcher
Base TechnologyServlet API-basedPure Java-based
LifecycleManaged by Servlet ContainerSpring Bean Lifecycle
Initializationinit(), destroy()Constructor Injection
Exception HandlingHandlerExceptionResolverExceptionResolver
View ResolutionViewResolverResponseResolver

Filter System Differences​

Execution Point and Scope

// Spring: Servlet Container Level
public class SpringFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Executes in the Servlet Container
// Runs before the DispatcherServlet
}
}

// Sprout: Application Level
public class SproutFilter implements Filter {
public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) {
// Executed directly by the RequestDispatcher
// Runs within the application context
}
}

Lifecycle Management

  • Spring: The servlet container manages the filter lifecycle; configured via @WebFilter or web.xml.
  • Sprout: The Spring IoC container manages the lifecycle; automatically registered with @Component.

Interceptor Implementation Differences​

HandlerInterceptor vs. Interceptor

// Spring HandlerInterceptor
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { return true; }
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}
}

// Sprout Interceptor
public interface Interceptor extends InfrastructureBean {
boolean preHandle(HttpRequest request, HttpResponse response, Object handler);
void postHandle(HttpRequest request, HttpResponse response, Object handler, Object result);
void afterCompletion(HttpRequest request, HttpResponse response, Object handler, Exception ex);
}

Key Differences

  1. Parameter Types: Servlet API vs. custom HTTP abstraction.
  2. ModelAndView: Spring passes a view model; Sprout passes the raw return value.
  3. Default Implementation: Spring uses default methods; Sprout requires all methods to be implemented.

Performance Analysis​

Time Complexity​

Complexity per Request Processing Stage

  • DispatchHook Execution: O(h) (h = number of hooks)
  • Filter Chain: O(f) (f = number of filters)
  • Handler Mapping: O(log n) (n = number of registered handlers, assuming tree-based lookup)
  • Interceptor Chain: O(i) (i = number of interceptors)
  • Argument Resolution: O(p) (p = number of method parameters)
  • Method Execution: O(1) (reflection call)
  • Response Handling: O(r + a) (r = number of resolvers, a = number of advices)

Overall Time Complexity: O(h + f + log n + i + p + r + a)

Memory Usage Patterns​

Object Creation and GC Pressure

// Objects created on every request
InterceptorChain interceptorChain = new InterceptorChain(interceptors); // Chain object
Map<String, String> pathVariables = pattern.extractPathVariables(request.getPath()); // Path variable map
Object[] args = resolvers.resolveArguments(handlerMethod, request, pathVariables); // Argument array

Optimization Strategies

  1. Chain Object Pooling: Reuse InterceptorChain instances.
  2. Argument Array Caching: Reuse arrays for identical method signatures.
  3. Path Variable Optimization: Use a singleton for empty maps.

Parallel Processing Considerations​

Thread Safety

  • RequestDispatcher: Stateless component, thread-safe.
  • FilterChain: Holds state (currentFilterIndex), requires a new instance per request.
  • InterceptorChain: Stateless, thread-safe.

Concurrency Optimization

// Current: new chain per request
new FilterChain(filters, this::doDispatch).doFilter(req, res);

// Optimization: ThreadLocal-based chain reuse is possible
private final ThreadLocal<FilterChain> chainCache = ThreadLocal.withInitial(() -> new FilterChain(filters, this::doDispatch));

Extensibility and Maintainability​

Adding New Processing Steps​

Extension Points

  1. DispatchHook: Add logic before/after the entire request cycle.
  2. Filter: Pre-process at the HTTP level (auth, logging, CORS).
  3. Interceptor: Pre/post-process at the controller level (permissions, logging).
  4. ExceptionResolver: Add new exception handling strategies.
  5. ResponseResolver: Support new response formats.

Configuration and Auto-Configuration​

Bean Registration and Order Control

@Configuration
public class WebConfig {
@Bean
@Order(1) // Controls execution order
public Filter corsFilter() {
return new CorsFilter();
}

@Bean
@Order(2)
public Filter authenticationFilter() {
return new AuthenticationFilter();
}
}

Testability​

Unit Testing Strategy

@Test
void testRequestDispatching() {
// Given
HttpRequest request = mockRequest("/api/users/123");
HttpResponse response = mockResponse();
HandlerMapping mapping = mockMapping();

RequestDispatcher dispatcher = new RequestDispatcher(
mapping, invoker, resolvers, advices,
List.of(), List.of(), List.of(), List.of()
);

// When
dispatcher.dispatch(request, response);

// Then
assertEquals(ResponseCode.OK, response.getStatusCode());
}

Security Considerations​

Current Security Mechanisms​

1. Handler Mapping Security

  • Returns a 404 response if no handler is found.
  • Prevents direct exposure of internal exceptions.

2. Exception Handling Security

  • Stack traces are printed to the console (inappropriate for production).
  • ExceptionResolver enables safe transformation of exceptions.

Security Improvement Suggestions​

1. Prevent Information Exposure

// Current: Exposes debugging info
System.err.println("Exception caught in doDispatch: " + e.getMessage());
e.printStackTrace();

// Improved: Control based on logging level
if (logger.isDebugEnabled()) {
logger.debug("Exception in doDispatch", e);
} else {
logger.error("Request processing failed: {}", request.getPath());
}

2. Strengthen Input Validation

// Recommended: Limit request size
public void dispatch(HttpRequest<?> req, HttpResponse res) {
validateRequest(req); // Validate size, header count, etc.
// ... existing logic
}

3. CSRF/XSS Prevention

@Component
public class SecurityFilter implements Filter {
public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) {
applyCsrfProtection(request, response);
applyXssProtection(response);
chain.doFilter(request, response);
}
}

We welcome new opinions, suggestions for vulnerabilities and improvements, and pull requests!