☕ #middleware - Middleware Directive (Java)
#middleware - Middleware Directive (Java)
The #middleware
directive provides enterprise-grade middleware capabilities for Java applications, enabling request/response processing, logging, validation, and custom business logic with Spring Boot integration.
Basic Syntax
Basic middleware
#middleware {
#api /endpoint {
return @process_request()
}
}Middleware with custom logic
#middleware {
before: @log_request(@request)
after: @log_response(@response)
} {
#api /logged-endpoint {
return @process_request()
}
}Conditional middleware
#middleware if: @request.method == "POST" {
#api /conditional-endpoint {
return @process_post_request()
}
}
Java Implementation
import org.tusklang.java.TuskLang;
import org.tusklang.java.directives.MiddlewareDirective;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Controller
public class MiddlewareController {
private final TuskLang tuskLang;
private final MiddlewareDirective middlewareDirective;
private final LoggingService loggingService;
public MiddlewareController(TuskLang tuskLang, LoggingService loggingService) {
this.tuskLang = tuskLang;
this.middlewareDirective = new MiddlewareDirective();
this.loggingService = loggingService;
}
// Basic middleware with Spring interceptors
@GetMapping("/api/endpoint")
public ResponseEntity<DataResponse> getData() {
return ResponseEntity.ok(dataService.getData());
}
// Middleware with custom logic
@PostMapping("/api/logged-endpoint")
public ResponseEntity<DataResponse> getLoggedData(HttpServletRequest request,
HttpServletResponse response) {
// Before middleware
loggingService.logRequest(request);
DataResponse data = dataService.getData();
// After middleware
loggingService.logResponse(response, data);
return ResponseEntity.ok(data);
}
// Conditional middleware
@RequestMapping(value = "/api/conditional-endpoint", method = {RequestMethod.GET, RequestMethod.POST})
public ResponseEntity<DataResponse> getConditionalData(HttpServletRequest request) {
if ("POST".equals(request.getMethod())) {
// Apply POST-specific middleware
validationService.validatePostRequest(request);
}
return ResponseEntity.ok(dataService.getData());
}
}
Middleware Configuration
Detailed middleware configuration
#middleware {
before: [
@log_request(),
@validate_request(),
@rate_limit_check()
]
after: [
@log_response(),
@add_headers(),
@cache_response()
]
error: [
@log_error(),
@format_error_response()
]
} {
#api /configured-endpoint {
return @process_request()
}
}Middleware with dependencies
#middleware {
requires: ["auth", "logging"]
optional: ["caching", "metrics"]
order: ["auth", "logging", "caching", "metrics"]
} {
#api /dependency-endpoint {
return @process_request()
}
}
Java Middleware Configuration
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import java.util.Map;@Component
@ConfigurationProperties(prefix = "tusk.middleware")
public class MiddlewareConfig {
private List<String> globalBefore = List.of("logging", "validation");
private List<String> globalAfter = List.of("logging", "headers");
private List<String> globalError = List.of("error-logging", "error-formatting");
private Map<String, MiddlewareDefinition> middlewares;
private List<String> requiredMiddlewares;
private List<String> optionalMiddlewares;
private List<String> middlewareOrder;
// Getters and setters
public List<String> getGlobalBefore() { return globalBefore; }
public void setGlobalBefore(List<String> globalBefore) { this.globalBefore = globalBefore; }
public List<String> getGlobalAfter() { return globalAfter; }
public void setGlobalAfter(List<String> globalAfter) { this.globalAfter = globalAfter; }
public List<String> getGlobalError() { return globalError; }
public void setGlobalError(List<String> globalError) { this.globalError = globalError; }
public Map<String, MiddlewareDefinition> getMiddlewares() { return middlewares; }
public void setMiddlewares(Map<String, MiddlewareDefinition> middlewares) { this.middlewares = middlewares; }
public List<String> getRequiredMiddlewares() { return requiredMiddlewares; }
public void setRequiredMiddlewares(List<String> requiredMiddlewares) { this.requiredMiddlewares = requiredMiddlewares; }
public List<String> getOptionalMiddlewares() { return optionalMiddlewares; }
public void setOptionalMiddlewares(List<String> optionalMiddlewares) { this.optionalMiddlewares = optionalMiddlewares; }
public List<String> getMiddlewareOrder() { return middlewareOrder; }
public void setMiddlewareOrder(List<String> middlewareOrder) { this.middlewareOrder = middlewareOrder; }
public static class MiddlewareDefinition {
private boolean enabled = true;
private int order = 0;
private List<String> dependencies;
private Map<String, Object> config;
// Getters and setters
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public int getOrder() { return order; }
public void setOrder(int order) { this.order = order; }
public List<String> getDependencies() { return dependencies; }
public void setDependencies(List<String> dependencies) { this.dependencies = dependencies; }
public Map<String, Object> getConfig() { return config; }
public void setConfig(Map<String, Object> config) { this.config = config; }
}
}
@Configuration
public class MiddlewareConfiguration implements WebMvcConfigurer {
private final MiddlewareConfig config;
private final List<HandlerInterceptor> interceptors;
public MiddlewareConfiguration(MiddlewareConfig config, List<HandlerInterceptor> interceptors) {
this.config = config;
this.interceptors = interceptors;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Add global interceptors
for (HandlerInterceptor interceptor : interceptors) {
registry.addInterceptor(interceptor);
}
// Add configured interceptors
if (config.getMiddlewares() != null) {
config.getMiddlewares().entrySet().stream()
.filter(entry -> entry.getValue().isEnabled())
.sorted(Map.Entry.comparingByValue(Comparator.comparing(MiddlewareConfig.MiddlewareDefinition::getOrder)))
.forEach(entry -> {
HandlerInterceptor interceptor = createInterceptor(entry.getKey(), entry.getValue());
registry.addInterceptor(interceptor);
});
}
}
private HandlerInterceptor createInterceptor(String name, MiddlewareConfig.MiddlewareDefinition definition) {
// Create interceptor based on middleware type
switch (name) {
case "logging":
return new LoggingInterceptor();
case "validation":
return new ValidationInterceptor();
case "auth":
return new AuthInterceptor();
case "caching":
return new CachingInterceptor();
case "metrics":
return new MetricsInterceptor();
default:
return new CustomInterceptor(name, definition);
}
}
}
Logging Middleware
Request/response logging
#middleware {
before: @log_request(@request.method, @request.path, @request.ip)
after: @log_response(@response.status, @response.time)
} {
#api /logged-endpoint {
return @process_request()
}
}Structured logging
#middleware {
before: @log_structured({
event: "request_start",
method: @request.method,
path: @request.path,
user_id: @auth.user?.id,
timestamp: @date.now()
})
after: @log_structured({
event: "request_end",
status: @response.status,
duration: @response.duration,
timestamp: @date.now()
})
} {
#api /structured-logged {
return @process_request()
}
}
Java Logging Middleware
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Instant;
import java.util.Map;
import java.util.HashMap;@Component
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
private final ObjectMapper objectMapper;
public LoggingInterceptor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
Map<String, Object> logData = new HashMap<>();
logData.put("event", "request_start");
logData.put("method", request.getMethod());
logData.put("path", request.getRequestURI());
logData.put("ip", getClientIpAddress(request));
logData.put("user_agent", request.getHeader("User-Agent"));
logData.put("timestamp", Instant.now().toString());
// Add user ID if authenticated
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof User) {
User user = (User) auth.getPrincipal();
logData.put("user_id", user.getId());
}
try {
logger.info("Request: {}", objectMapper.writeValueAsString(logData));
} catch (Exception e) {
logger.warn("Failed to log request", e);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
Map<String, Object> logData = new HashMap<>();
logData.put("event", "request_end");
logData.put("method", request.getMethod());
logData.put("path", request.getRequestURI());
logData.put("status", response.getStatus());
logData.put("duration", duration);
logData.put("timestamp", Instant.now().toString());
if (ex != null) {
logData.put("error", ex.getMessage());
}
try {
logger.info("Response: {}", objectMapper.writeValueAsString(logData));
} catch (Exception e) {
logger.warn("Failed to log response", e);
}
}
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddr();
}
}
@Service
public class LoggingService {
private final Logger logger = LoggerFactory.getLogger(LoggingService.class);
private final ObjectMapper objectMapper;
public LoggingService(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public void logRequest(HttpServletRequest request) {
Map<String, Object> logData = new HashMap<>();
logData.put("event", "request");
logData.put("method", request.getMethod());
logData.put("path", request.getRequestURI());
logData.put("ip", getClientIpAddress(request));
logData.put("timestamp", Instant.now().toString());
logger.info("Request: {}", logData);
}
public void logResponse(HttpServletResponse response, Object data) {
Map<String, Object> logData = new HashMap<>();
logData.put("event", "response");
logData.put("status", response.getStatus());
logData.put("timestamp", Instant.now().toString());
logger.info("Response: {}", logData);
}
public void logStructured(Map<String, Object> data) {
try {
logger.info("Structured log: {}", objectMapper.writeValueAsString(data));
} catch (Exception e) {
logger.warn("Failed to log structured data", e);
}
}
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddr();
}
}
Validation Middleware
Request validation
#middleware {
before: @validate_request(@request.body, @request.headers)
} {
#api /validated-endpoint {
return @process_validated_request()
}
}Schema validation
#middleware {
before: @validate_schema(@request.body, "user_schema")
} {
#api /schema-validated {
return @process_schema_validated_request()
}
}Custom validation
#middleware {
before: @custom_validation(@request.body, @auth.user)
} {
#api /custom-validated {
return @process_custom_validated_request()
}
}
Java Validation Middleware
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.validation.Validator;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import javax.validation.ConstraintViolation;
import javax.validation.Validator as JavaxValidator;
import java.util.Set;@Component
public class ValidationInterceptor implements HandlerInterceptor {
private final Validator springValidator;
private final JavaxValidator javaxValidator;
private final SchemaValidator schemaValidator;
public ValidationInterceptor(Validator springValidator,
JavaxValidator javaxValidator,
SchemaValidator schemaValidator) {
this.springValidator = springValidator;
this.javaxValidator = javaxValidator;
this.schemaValidator = schemaValidator;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// Validate request body
if (request.getContentType() != null && request.getContentType().contains("application/json")) {
try {
String body = getRequestBody(request);
if (body != null && !body.isEmpty()) {
validateRequestBody(body, request);
}
} catch (Exception e) {
response.setStatus(400);
response.getWriter().write("{\"error\": \"Invalid request body\"}");
return false;
}
}
// Validate headers
validateHeaders(request);
return true;
}
private void validateRequestBody(String body, HttpServletRequest request) {
// JSON schema validation
if (request.getRequestURI().contains("/schema-validated")) {
schemaValidator.validate(body, "user_schema");
}
// Bean validation
try {
Object requestObject = objectMapper.readValue(body, Object.class);
if (requestObject instanceof Validatable) {
Validatable validatable = (Validatable) requestObject;
Errors errors = new BeanPropertyBindingResult(validatable, "request");
springValidator.validate(validatable, errors);
if (errors.hasErrors()) {
throw new ValidationException("Validation failed", errors);
}
}
} catch (Exception e) {
throw new ValidationException("Failed to validate request body", e);
}
}
private void validateHeaders(HttpServletRequest request) {
// Validate required headers
String authHeader = request.getHeader("Authorization");
if (request.getRequestURI().contains("/protected") && authHeader == null) {
throw new ValidationException("Authorization header required");
}
// Validate content type for POST requests
if ("POST".equals(request.getMethod()) &&
request.getContentType() == null) {
throw new ValidationException("Content-Type header required for POST requests");
}
}
private String getRequestBody(HttpServletRequest request) {
try {
return request.getReader().lines().collect(Collectors.joining());
} catch (Exception e) {
return null;
}
}
}
@Service
public class ValidationService {
private final JavaxValidator javaxValidator;
private final SchemaValidator schemaValidator;
public ValidationService(JavaxValidator javaxValidator, SchemaValidator schemaValidator) {
this.javaxValidator = javaxValidator;
this.schemaValidator = schemaValidator;
}
public void validateRequest(Object request, Map<String, String> headers) {
// Bean validation
Set<ConstraintViolation<Object>> violations = javaxValidator.validate(request);
if (!violations.isEmpty()) {
throw new ValidationException("Validation failed", violations);
}
// Header validation
validateHeaders(headers);
}
public void validateSchema(Object request, String schemaName) {
schemaValidator.validate(request, schemaName);
}
public void customValidation(Object request, User user) {
// Custom business logic validation
if (request instanceof UserUpdateRequest) {
UserUpdateRequest updateRequest = (UserUpdateRequest) request;
// Check if user can update the target user
if (!user.getId().equals(updateRequest.getUserId()) && !user.hasRole("ADMIN")) {
throw new ValidationException("Insufficient permissions to update user");
}
}
}
private void validateHeaders(Map<String, String> headers) {
// Validate required headers
if (!headers.containsKey("Content-Type")) {
throw new ValidationException("Content-Type header required");
}
}
}
@Component
public class SchemaValidator {
private final ObjectMapper objectMapper;
private final Map<String, JsonNode> schemas;
public SchemaValidator(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.schemas = loadSchemas();
}
public void validate(Object data, String schemaName) {
JsonNode schema = schemas.get(schemaName);
if (schema == null) {
throw new ValidationException("Schema not found: " + schemaName);
}
JsonNode dataNode = objectMapper.valueToTree(data);
// Use JSON Schema validator
JsonSchema jsonSchema = JsonSchemaFactory.byDefault().getJsonSchema(schema);
ProcessingReport report = jsonSchema.validate(dataNode);
if (!report.isSuccess()) {
throw new ValidationException("Schema validation failed", report);
}
}
private Map<String, JsonNode> loadSchemas() {
Map<String, JsonNode> schemas = new HashMap<>();
// Load schemas from resources
try {
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath:schemas/*.json");
for (Resource resource : resources) {
String filename = resource.getFilename();
String schemaName = filename.substring(0, filename.lastIndexOf('.'));
JsonNode schema = objectMapper.readTree(resource.getInputStream());
schemas.put(schemaName, schema);
}
} catch (Exception e) {
throw new RuntimeException("Failed to load schemas", e);
}
return schemas;
}
}
Authentication Middleware
Authentication check
#middleware {
before: @check_auth(@request.headers.Authorization)
} {
#api /auth-protected {
return @get_protected_data()
}
}Role-based middleware
#middleware {
before: @check_role(@auth.user.role, ["admin", "user"])
} {
#api /role-protected {
return @get_role_protected_data()
}
}Permission-based middleware
#middleware {
before: @check_permission(@auth.user.id, "read:data")
} {
#api /permission-protected {
return @get_permission_protected_data()
}
}
Java Authentication Middleware
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;@Component
public class AuthInterceptor implements HandlerInterceptor {
private final JwtTokenProvider tokenProvider;
private final UserService userService;
private final PermissionService permissionService;
public AuthInterceptor(JwtTokenProvider tokenProvider,
UserService userService,
PermissionService permissionService) {
this.tokenProvider = tokenProvider;
this.userService = userService;
this.permissionService = permissionService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(401);
response.getWriter().write("{\"error\": \"Authorization header required\"}");
return false;
}
try {
String token = authHeader.substring(7);
Claims claims = tokenProvider.validateToken(token);
// Set authentication in context
User user = userService.findById(claims.get("userId", Long.class));
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
// Check role-based access
if (request.getRequestURI().contains("/role-protected")) {
if (!hasRequiredRole(user, List.of("ADMIN", "USER"))) {
response.setStatus(403);
response.getWriter().write("{\"error\": \"Insufficient role\"}");
return false;
}
}
// Check permission-based access
if (request.getRequestURI().contains("/permission-protected")) {
if (!permissionService.hasPermission(user.getId(), "read:data")) {
response.setStatus(403);
response.getWriter().write("{\"error\": \"Insufficient permissions\"}");
return false;
}
}
return true;
} catch (Exception e) {
response.setStatus(401);
response.getWriter().write("{\"error\": \"Invalid token\"}");
return false;
}
}
private boolean hasRequiredRole(User user, List<String> requiredRoles) {
return user.getAuthorities().stream()
.anyMatch(authority -> requiredRoles.contains(authority.getAuthority().replace("ROLE_", "")));
}
}
@Service
public class AuthService {
private final JwtTokenProvider tokenProvider;
private final UserService userService;
public AuthService(JwtTokenProvider tokenProvider, UserService userService) {
this.tokenProvider = tokenProvider;
this.userService = userService;
}
public User checkAuth(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new AuthenticationException("Authorization header required");
}
String token = authHeader.substring(7);
Claims claims = tokenProvider.validateToken(token);
return userService.findById(claims.get("userId", Long.class));
}
public boolean checkRole(User user, List<String> requiredRoles) {
return user.getAuthorities().stream()
.anyMatch(authority -> requiredRoles.contains(authority.getAuthority().replace("ROLE_", "")));
}
public boolean checkPermission(Long userId, String permission) {
return permissionService.hasPermission(userId, permission);
}
}
Caching Middleware
Response caching
#middleware {
before: @check_cache(@request.path, @request.query)
after: @cache_response(@response, "5m")
} {
#api /cached-endpoint {
return @get_cached_data()
}
}Conditional caching
#middleware {
before: @check_cache_if(@request.method == "GET")
after: @cache_response_if(@response.status == 200)
} {
#api /conditional-cached {
return @get_conditional_cached_data()
}
}
Java Caching Middleware
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.cache.CacheManager;
import org.springframework.cache.Cache;@Component
public class CachingInterceptor implements HandlerInterceptor {
private final CacheManager cacheManager;
private final CacheKeyGenerator keyGenerator;
public CachingInterceptor(CacheManager cacheManager, CacheKeyGenerator keyGenerator) {
this.cacheManager = cacheManager;
this.keyGenerator = keyGenerator;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// Only cache GET requests
if (!"GET".equals(request.getMethod())) {
return true;
}
String cacheKey = keyGenerator.generateKey(request);
Cache cache = cacheManager.getCache("api-cache");
if (cache != null) {
Cache.ValueWrapper cached = cache.get(cacheKey);
if (cached != null) {
try {
response.setContentType("application/json");
response.getWriter().write(objectMapper.writeValueAsString(cached.get()));
return false; // Stop processing, return cached response
} catch (Exception e) {
// Continue with normal processing if cache serialization fails
}
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// Only cache successful GET responses
if (!"GET".equals(request.getMethod()) || response.getStatus() != 200) {
return;
}
String cacheKey = keyGenerator.generateKey(request);
Cache cache = cacheManager.getCache("api-cache");
if (cache != null) {
// Note: In a real implementation, you'd need to capture the response body
// This is a simplified example
cache.put(cacheKey, "cached-response");
}
}
}
@Service
public class CachingService {
private final CacheManager cacheManager;
private final CacheKeyGenerator keyGenerator;
public CachingService(CacheManager cacheManager, CacheKeyGenerator keyGenerator) {
this.cacheManager = cacheManager;
this.keyGenerator = keyGenerator;
}
public Object checkCache(String path, Map<String, String> queryParams) {
String cacheKey = keyGenerator.generateKey(path, queryParams);
Cache cache = cacheManager.getCache("api-cache");
if (cache != null) {
Cache.ValueWrapper cached = cache.get(cacheKey);
return cached != null ? cached.get() : null;
}
return null;
}
public void cacheResponse(Object response, String ttl) {
// Implementation would depend on the specific caching strategy
// This is a simplified example
}
public Object checkCacheIf(boolean condition) {
return condition ? checkCache() : null;
}
public void cacheResponseIf(Object response, boolean condition) {
if (condition) {
cacheResponse(response, "5m");
}
}
}
Metrics Middleware
Request metrics
#middleware {
before: @start_metrics(@request.path, @request.method)
after: @end_metrics(@response.status, @response.duration)
} {
#api /metrics-tracked {
return @get_metrics_data()
}
}Custom metrics
#middleware {
before: @increment_counter("api_requests", @request.path)
after: @record_histogram("response_time", @response.duration)
} {
#api /custom-metrics {
return @get_custom_metrics_data()
}
}
Java Metrics Middleware
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;@Component
public class MetricsInterceptor implements HandlerInterceptor {
private final MeterRegistry meterRegistry;
private final Map<String, Counter> counters;
private final Map<String, Timer> timers;
public MetricsInterceptor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.counters = new ConcurrentHashMap<>();
this.timers = new ConcurrentHashMap<>();
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
// Increment request counter
String path = request.getRequestURI();
String method = request.getMethod();
Counter counter = counters.computeIfAbsent(
"api_requests",
k -> Counter.builder("api_requests")
.tag("path", path)
.tag("method", method)
.register(meterRegistry)
);
counter.increment();
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
// Record response time
String path = request.getRequestURI();
String method = request.getMethod();
int status = response.getStatus();
Timer timer = timers.computeIfAbsent(
"response_time",
k -> Timer.builder("response_time")
.tag("path", path)
.tag("method", method)
.tag("status", String.valueOf(status))
.register(meterRegistry)
);
timer.record(duration, TimeUnit.MILLISECONDS);
// Record error counter if there was an exception
if (ex != null) {
Counter errorCounter = counters.computeIfAbsent(
"api_errors",
k -> Counter.builder("api_errors")
.tag("path", path)
.tag("method", method)
.register(meterRegistry)
);
errorCounter.increment();
}
}
}
@Service
public class MetricsService {
private final MeterRegistry meterRegistry;
public MetricsService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
public void startMetrics(String path, String method) {
// Start timing
Timer.Sample sample = Timer.start(meterRegistry);
// Store sample in request attributes for later use
}
public void endMetrics(int status, long duration) {
// End timing and record metrics
Timer timer = Timer.builder("response_time")
.tag("status", String.valueOf(status))
.register(meterRegistry);
timer.record(duration, TimeUnit.MILLISECONDS);
}
public void incrementCounter(String name, String... tags) {
Counter counter = Counter.builder(name)
.tags(tags)
.register(meterRegistry);
counter.increment();
}
public void recordHistogram(String name, double value, String... tags) {
Timer timer = Timer.builder(name)
.tags(tags)
.register(meterRegistry);
timer.record((long) value, TimeUnit.MILLISECONDS);
}
}
Custom Middleware
Custom middleware
#middleware {
before: @custom_before_logic(@request)
after: @custom_after_logic(@response)
error: @custom_error_logic(@error)
} {
#api /custom-middleware {
return @process_with_custom_logic()
}
}Middleware with configuration
#middleware {
config: {
feature_flag: "new_feature"
timeout: 5000
retries: 3
}
before: @configured_logic(@request, @config)
} {
#api /configured-middleware {
return @process_with_config()
}
}
Java Custom Middleware
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;@Component
public class CustomInterceptor implements HandlerInterceptor {
private final String name;
private final MiddlewareConfig.MiddlewareDefinition config;
private final CustomLogicService customLogicService;
public CustomInterceptor(String name, MiddlewareConfig.MiddlewareDefinition config,
CustomLogicService customLogicService) {
this.name = name;
this.config = config;
this.customLogicService = customLogicService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
try {
// Execute custom before logic
customLogicService.executeBeforeLogic(request, config);
return true;
} catch (Exception e) {
// Execute custom error logic
customLogicService.executeErrorLogic(e, request, response);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
// Execute custom after logic
customLogicService.executeAfterLogic(response, config);
} catch (Exception e) {
// Log error but don't fail the request
logger.warn("Error in custom after logic", e);
}
}
}
@Service
public class CustomLogicService {
private final FeatureFlagService featureFlagService;
private final RetryService retryService;
public CustomLogicService(FeatureFlagService featureFlagService, RetryService retryService) {
this.featureFlagService = featureFlagService;
this.retryService = retryService;
}
public void executeBeforeLogic(HttpServletRequest request,
MiddlewareConfig.MiddlewareDefinition config) {
// Check feature flags
if (config.getConfig() != null) {
String featureFlag = (String) config.getConfig().get("feature_flag");
if (featureFlag != null && !featureFlagService.isEnabled(featureFlag)) {
throw new FeatureDisabledException("Feature " + featureFlag + " is disabled");
}
}
// Apply custom logic based on configuration
if (config.getConfig() != null) {
Integer timeout = (Integer) config.getConfig().get("timeout");
if (timeout != null) {
// Set request timeout
request.setAttribute("timeout", timeout);
}
}
}
public void executeAfterLogic(HttpServletResponse response,
MiddlewareConfig.MiddlewareDefinition config) {
// Apply custom response logic
if (config.getConfig() != null) {
Integer retries = (Integer) config.getConfig().get("retries");
if (retries != null) {
response.setHeader("X-Retry-Count", String.valueOf(retries));
}
}
}
public void executeErrorLogic(Exception error, HttpServletRequest request,
HttpServletResponse response) {
// Handle custom error logic
if (error instanceof FeatureDisabledException) {
response.setStatus(503);
response.setHeader("X-Feature-Disabled", "true");
} else {
response.setStatus(500);
}
}
}
Middleware Testing
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.beans.factory.annotation.Autowired;@SpringBootTest
@TestPropertySource(properties = {
"tusk.middleware.global-before=logging,validation",
"tusk.middleware.global-after=logging"
})
public class MiddlewareTest {
@Autowired
private MiddlewareController controller;
@MockBean
private LoggingService loggingService;
@MockBean
private ValidationService validationService;
@Test
public void testLoggingMiddleware() {
// Test that logging middleware is applied
ResponseEntity<DataResponse> response = controller.getLoggedData(mockRequest(), mockResponse());
verify(loggingService).logRequest(any(HttpServletRequest.class));
verify(loggingService).logResponse(any(HttpServletResponse.class), any(DataResponse.class));
}
@Test
public void testValidationMiddleware() {
// Test that validation middleware is applied
HttpServletRequest request = mockRequest();
request.setContentType("application/json");
ResponseEntity<DataResponse> response = controller.getData();
verify(validationService).validateRequest(any(), any());
}
@Test
public void testConditionalMiddleware() {
// Test conditional middleware
HttpServletRequest getRequest = mockRequest();
getRequest.setMethod("GET");
HttpServletRequest postRequest = mockRequest();
postRequest.setMethod("POST");
// GET request should not trigger validation
controller.getConditionalData(getRequest);
verify(validationService, never()).validatePostRequest(any());
// POST request should trigger validation
controller.getConditionalData(postRequest);
verify(validationService).validatePostRequest(postRequest);
}
private HttpServletRequest mockRequest() {
return mock(HttpServletRequest.class);
}
private HttpServletResponse mockResponse() {
return mock(HttpServletResponse.class);
}
}
Configuration Properties
application.yml
tusk:
middleware:
global-before: ["logging", "validation"]
global-after: ["logging", "headers"]
global-error: ["error-logging", "error-formatting"]
middlewares:
logging:
enabled: true
order: 1
config:
level: "INFO"
include-headers: true
validation:
enabled: true
order: 2
dependencies: ["logging"]
config:
strict-mode: true
schema-validation: true
auth:
enabled: true
order: 3
dependencies: ["logging"]
config:
jwt-secret: "${JWT_SECRET}"
token-expiration: 3600
caching:
enabled: true
order: 4
optional: true
config:
ttl: 300
max-size: 1000
metrics:
enabled: true
order: 5
optional: true
config:
enabled-metrics: ["requests", "response-time", "errors"]
required-middlewares: ["logging", "validation", "auth"]
optional-middlewares: ["caching", "metrics"]
middleware-order: ["logging", "validation", "auth", "caching", "metrics"]spring:
cache:
type: redis
redis:
time-to-live: 300000
cache-null-values: false
Summary
The #middleware
directive in TuskLang provides comprehensive middleware capabilities for Java applications. With Spring Boot integration, flexible configuration, and support for various middleware types, you can implement sophisticated request/response processing that enhances your application's functionality.
Key features include: - Multiple middleware types: Logging, validation, authentication, caching, and metrics - Spring Boot integration: Seamless integration with Spring Boot interceptors - Flexible configuration: Configurable middleware with dependencies and ordering - Conditional middleware: Apply middleware based on request conditions - Custom middleware: Extensible middleware system for custom logic - Performance monitoring: Built-in metrics and monitoring capabilities - Testing support: Comprehensive testing utilities
The Java implementation provides enterprise-grade middleware that integrates seamlessly with Spring Boot applications while maintaining the simplicity and power of TuskLang's declarative syntax.