1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.filter;
17
18 import java.io.IOException;
19 import java.util.Arrays;
20 import java.util.Set;
21 import java.util.stream.Collectors;
22
23 import org.apache.logging.log4j.LogManager;
24 import org.apache.logging.log4j.Logger;
25 import org.codelibs.fess.mylasta.direction.FessConfig;
26 import org.codelibs.fess.util.ComponentUtil;
27
28 import jakarta.servlet.Filter;
29 import jakarta.servlet.FilterChain;
30 import jakarta.servlet.ServletException;
31 import jakarta.servlet.ServletRequest;
32 import jakarta.servlet.ServletResponse;
33 import jakarta.servlet.http.HttpServletRequest;
34 import jakarta.servlet.http.HttpServletResponse;
35
36
37
38
39
40
41 public class LoadControlFilter implements Filter {
42
43 private static final Logger logger = LogManager.getLogger(LoadControlFilter.class);
44
45 private static final int RETRY_AFTER_SECONDS = 60;
46
47 private static final Set<String> STATIC_EXTENSIONS =
48 Arrays.stream(new String[] { ".css", ".js", ".png", ".jpg", ".gif", ".ico", ".svg", ".woff", ".woff2", ".ttf", ".eot" })
49 .collect(Collectors.toSet());
50
51
52
53
54 public LoadControlFilter() {
55
56 }
57
58 @Override
59 public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
60 throws IOException, ServletException {
61 if (!ComponentUtil.available()) {
62 chain.doFilter(request, response);
63 return;
64 }
65
66 final HttpServletRequest httpRequest = (HttpServletRequest) request;
67 final HttpServletResponse httpResponse = (HttpServletResponse) response;
68 final String path = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
69
70 if (isExcludedPath(path)) {
71 chain.doFilter(request, response);
72 return;
73 }
74
75 final boolean isApiPath = path.startsWith("/api/");
76 final FessConfig fessConfig = ComponentUtil.getFessConfig();
77 final int threshold = isApiPath ? fessConfig.getApiLoadControlAsInteger() : fessConfig.getWebLoadControlAsInteger();
78
79 if (threshold >= 100) {
80 chain.doFilter(request, response);
81 return;
82 }
83
84 final short cpuPercent = ComponentUtil.getSystemHelper().getSearchEngineCpuPercent();
85
86 if (cpuPercent < threshold) {
87 chain.doFilter(request, response);
88 return;
89 }
90
91 if (logger.isInfoEnabled()) {
92 logger.info("Rejecting request due to high CPU load: path={}, cpu={}%, threshold={}%", path, cpuPercent, threshold);
93 }
94
95 if (isApiPath) {
96 sendApiResponse(httpResponse);
97 } else {
98 httpResponse.sendError(429);
99 }
100 }
101
102
103
104
105
106
107 protected boolean isExcludedPath(final String path) {
108 if (path.startsWith("/admin") || path.startsWith("/error") || path.startsWith("/login")) {
109 return true;
110 }
111 final int dotIndex = path.lastIndexOf('.');
112 if (dotIndex >= 0) {
113 final String extension = path.substring(dotIndex);
114 return STATIC_EXTENSIONS.contains(extension);
115 }
116 return false;
117 }
118
119
120
121
122
123
124 protected void sendApiResponse(final HttpServletResponse response) throws IOException {
125 response.setStatus(429);
126 response.setContentType("application/json;charset=UTF-8");
127 response.setHeader("Retry-After", String.valueOf(RETRY_AFTER_SECONDS));
128 response.getWriter()
129 .write("{\"response\":{\"status\":9,\"message\":\"Server is busy. Please retry after " + RETRY_AFTER_SECONDS
130 + " seconds.\",\"retry_after\":" + RETRY_AFTER_SECONDS + "}}");
131 }
132 }