1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server.handler;
20
21 import java.io.Closeable;
22 import java.io.IOException;
23 import java.net.InetAddress;
24 import java.net.InetSocketAddress;
25 import java.util.ArrayDeque;
26 import java.util.Deque;
27 import java.util.concurrent.CompletableFuture;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap;
30 import java.util.concurrent.ExecutionException;
31
32 import javax.servlet.AsyncContext;
33 import javax.servlet.ServletException;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36
37 import org.eclipse.jetty.http.HostPortHttpField;
38 import org.eclipse.jetty.http.HttpField;
39 import org.eclipse.jetty.http.HttpFields;
40 import org.eclipse.jetty.http.HttpHeader;
41 import org.eclipse.jetty.http.QuotedCSV;
42 import org.eclipse.jetty.server.ForwardedRequestCustomizer;
43 import org.eclipse.jetty.server.Request;
44 import org.eclipse.jetty.util.IncludeExcludeSet;
45 import org.eclipse.jetty.util.InetAddressSet;
46 import org.eclipse.jetty.util.StringUtil;
47 import org.eclipse.jetty.util.annotation.ManagedAttribute;
48 import org.eclipse.jetty.util.annotation.ManagedOperation;
49 import org.eclipse.jetty.util.annotation.Name;
50 import org.eclipse.jetty.util.log.Log;
51 import org.eclipse.jetty.util.log.Logger;
52 import org.eclipse.jetty.util.thread.Locker;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 public class ThreadLimitHandler extends HandlerWrapper
76 {
77 private static final Logger LOG = Log.getLogger(ThreadLimitHandler.class);
78
79 private final static String REMOTE = "o.e.j.s.h.TLH.REMOTE";
80 private final static String PERMIT = "o.e.j.s.h.TLH.PASS";
81 private final boolean _rfc7239;
82 private final String _forwardedHeader;
83 private final IncludeExcludeSet<String, InetAddress> _includeExcludeSet = new IncludeExcludeSet<>(InetAddressSet.class);
84 private final ConcurrentMap<String, Remote> _remotes = new ConcurrentHashMap<>();
85 private volatile boolean _enabled;
86 private int _threadLimit=10;
87
88 public ThreadLimitHandler()
89 {
90 this(null,false);
91 }
92
93 public ThreadLimitHandler(@Name("forwardedHeader") String forwardedHeader)
94 {
95 this(forwardedHeader,HttpHeader.FORWARDED.is(forwardedHeader));
96 }
97
98 public ThreadLimitHandler(@Name("forwardedHeader") String forwardedHeader, @Name("rfc7239") boolean rfc7239)
99 {
100 super();
101 _rfc7239 = rfc7239;
102 _forwardedHeader = forwardedHeader;
103 _enabled = true;
104 }
105
106 @Override
107 protected void doStart() throws Exception
108 {
109 super.doStart();
110 LOG.info(String.format("ThreadLimitHandler enable=%b limit=%d include=%s",_enabled,_threadLimit,_includeExcludeSet));
111 }
112
113 @ManagedAttribute("true if this handler is enabled")
114 public boolean isEnabled()
115 {
116 return _enabled;
117 }
118
119 public void setEnabled(boolean enabled)
120 {
121 _enabled = enabled;
122 LOG.info(String.format("ThreadLimitHandler enable=%b limit=%d include=%s",_enabled,_threadLimit,_includeExcludeSet));
123 }
124
125 @ManagedAttribute("The maximum threads that can be dispatched per remote IP")
126 public int getThreadLimit()
127 {
128 return _threadLimit;
129 }
130
131 public void setThreadLimit(int threadLimit)
132 {
133 if (threadLimit<=0)
134 throw new IllegalArgumentException("limit must be >0");
135 _threadLimit = threadLimit;
136 }
137
138 @ManagedOperation("Include IP in thread limits")
139 public void include(String inetAddressPattern)
140 {
141 _includeExcludeSet.include(inetAddressPattern);
142 }
143
144 @ManagedOperation("Exclude IP from thread limits")
145 public void exclude(String inetAddressPattern)
146 {
147 _includeExcludeSet.exclude(inetAddressPattern);
148 }
149
150 @Override
151 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
152 {
153
154 if (!_enabled)
155 {
156
157 super.handle(target,baseRequest,request,response);
158 }
159 else
160 {
161
162 Remote remote = getRemote(baseRequest);
163 if (remote==null)
164 {
165
166 super.handle(target,baseRequest,request,response);
167 }
168 else
169 {
170
171 Closeable permit = (Closeable)baseRequest.getAttribute(PERMIT);
172 try
173 {
174 if (permit!=null)
175 {
176
177 baseRequest.removeAttribute(PERMIT);
178 }
179 else
180 {
181
182 CompletableFuture<Closeable> future_permit=remote.acquire();
183
184
185 if (future_permit.isDone())
186 {
187
188 permit=future_permit.get();
189 }
190 else
191 {
192 if (LOG.isDebugEnabled())
193 LOG.debug("Threadlimited {} {}",remote,target);
194
195 AsyncContext async = baseRequest.startAsync();
196
197
198 async.setTimeout(0);
199
200
201 future_permit.thenAccept(c->
202 {
203 baseRequest.setAttribute(PERMIT,c);
204 async.dispatch();
205 });
206 return;
207 }
208 }
209
210
211 super.handle(target,baseRequest,request,response);
212 }
213 catch (InterruptedException | ExecutionException e)
214 {
215 throw new ServletException(e);
216 }
217 finally
218 {
219 if (permit!=null)
220 permit.close();
221 }
222 }
223 }
224 }
225
226 protected int getThreadLimit(String ip)
227 {
228 if (!_includeExcludeSet.isEmpty())
229 {
230 try
231 {
232 if (!_includeExcludeSet.test(InetAddress.getByName(ip)))
233 {
234 LOG.debug("excluded {}",ip);
235 return 0;
236 }
237 }
238 catch(Exception e)
239 {
240 LOG.ignore(e);
241 }
242 }
243 return _threadLimit;
244 }
245
246 protected Remote getRemote(Request baseRequest)
247 {
248 Remote remote = (Remote)baseRequest.getAttribute(REMOTE);
249 if (remote!=null)
250 return remote;
251
252 String ip=getRemoteIP(baseRequest);
253 LOG.debug("ip={}",ip);
254 if (ip==null)
255 return null;
256
257 int limit = getThreadLimit(ip);
258 if (limit<=0)
259 return null;
260
261 remote = _remotes.get(ip);
262 if (remote==null)
263 {
264 Remote r = new Remote(ip,limit);
265 remote = _remotes.putIfAbsent(ip,r);
266 if (remote==null)
267 remote = r;
268 }
269
270 baseRequest.setAttribute(REMOTE,remote);
271
272 return remote;
273 }
274
275
276 protected String getRemoteIP(Request baseRequest)
277 {
278
279 if (_forwardedHeader!=null && !_forwardedHeader.isEmpty())
280 {
281
282 String remote = _rfc7239?getForwarded(baseRequest):getXForwardedFor(baseRequest);
283 if (remote!=null && !remote.isEmpty())
284 return remote;
285 }
286
287
288
289
290 InetSocketAddress inet_addr = baseRequest.getHttpChannel().getRemoteAddress();
291 if (inet_addr!=null && inet_addr.getAddress()!=null)
292 return inet_addr.getAddress().getHostAddress();
293 return null;
294 }
295
296 private String getForwarded(Request request)
297 {
298
299
300
301 RFC7239 rfc7239 = new RFC7239();
302 HttpFields httpFields = request.getHttpFields();
303 for (HttpField field : httpFields)
304 if (_forwardedHeader.equalsIgnoreCase(field.getName()))
305 rfc7239.addValue(field.getValue());
306
307 if (rfc7239.getFor()!=null)
308 return new HostPortHttpField(rfc7239.getFor()).getHost();
309
310 return null;
311 }
312
313 private String getXForwardedFor(Request request)
314 {
315
316
317
318 String forwarded_for = null;
319 HttpFields httpFields = request.getHttpFields();
320 for (HttpField field : httpFields)
321 if (_forwardedHeader.equalsIgnoreCase(field.getName()))
322 forwarded_for = field.getValue();
323
324 if (forwarded_for==null || forwarded_for.isEmpty())
325 return null;
326
327 int comma = forwarded_for.lastIndexOf(',');
328 return (comma>=0)?forwarded_for.substring(comma+1).trim():forwarded_for;
329 }
330
331
332 private final class Remote implements Closeable
333 {
334 private final String _ip;
335 private final int _limit;
336 private final Locker _locker = new Locker();
337 private int _permits;
338 private Deque<CompletableFuture<Closeable>> _queue = new ArrayDeque<>();
339 private final CompletableFuture<Closeable> _permitted = CompletableFuture.completedFuture(this);
340
341 public Remote(String ip, int limit)
342 {
343 _ip=ip;
344 _limit=limit;
345 }
346
347 public CompletableFuture<Closeable> acquire()
348 {
349 try(Locker.Lock lock = _locker.lock())
350 {
351
352 if (_permits<_limit)
353 {
354
355 _permits++;
356
357 return _permitted;
358 }
359
360
361 CompletableFuture<Closeable> pass = new CompletableFuture<Closeable>();
362 _queue.addLast(pass);
363 return pass;
364 }
365 }
366
367 @Override
368 public void close() throws IOException
369 {
370 try(Locker.Lock lock = _locker.lock())
371 {
372
373 _permits--;
374 while(true)
375 {
376
377 CompletableFuture<Closeable> permit = _queue.pollFirst();
378
379
380 if (permit==null)
381 break;
382
383
384 if (permit.complete(this))
385 {
386 _permits++;
387 break;
388 }
389
390
391
392 }
393 }
394 }
395
396 @Override
397 public String toString()
398 {
399 try(Locker.Lock lock = _locker.lock())
400 {
401 return String.format("R[ip=%s,p=%d,l=%d,q=%d]",_ip,_permits,_limit,_queue.size());
402 }
403 }
404 }
405
406 private final class RFC7239 extends QuotedCSV
407 {
408 String _for;
409
410 private RFC7239()
411 {
412 super(false);
413 }
414
415 String getFor()
416 {
417 return _for;
418 }
419
420 @Override
421 protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
422 {
423 if (valueLength==0 && paramValue>paramName)
424 {
425 String name=StringUtil.asciiToLowerCase(buffer.substring(paramName,paramValue-1));
426 if ("for".equalsIgnoreCase(name))
427 {
428 String value=buffer.substring(paramValue);
429
430
431 if ("unknown".equalsIgnoreCase(value))
432 _for = null;
433
434 else
435 _for=value;
436 }
437 }
438 }
439 }
440 }