-
Notifications
You must be signed in to change notification settings - Fork 4
GSIP 72 Control Flow Module Enhancements
Extend the control flow module to include ip based filtering and throttling of concurrent requests
Juan Marin
The release that this proposal will be implemented trunk, to be included in a future release (tentatively 2.2)
Choose one of: Under Discussion, In Progress, Completed, Rejected, Deferred
Currently the control flow module allows GeoServer administrators to throttle ows services (or ows service calls, such as WMS GetMap request), as well as users. The current implementation of user based throttling relies on identifying them through cookies, which works well but for browser based clients but not for others. This proposal is meant to extend the control flow module to include filtering of ip addresses and throttling of concurrent requests coming from the same ip address.
The use cases that motivate this proposal are as follows:
\1. Establish global limits for number of concurrent requests from a single IP address. A single IP address may only take up to n number of requests in parallel.
Users need to add the following to controlflow.properties to configure this option:
ip=
Where is the maximum number of requests a single IP address can execute in parallel.
Since this is very similar to the current UserControllerFlow implementation, this proposal includes a bit of refactoring to include common code in a parent abstract class that both UserFlowController and IpFlowController extend from:
public abstract class QueueController implements FlowController {
static ThreadLocal<String> QUEUE_ID = new ThreadLocal<String>();
int queueSize;
Map<String, TimedBlockingQueue> queues = new ConcurrentHashMap<String, TimedBlockingQueue>();
@Override
public boolean requestIncoming(Request request, long timeout) {
return false;
}
@Override
public void requestComplete(Request request) {
String queueId = QUEUE_ID.get();
QUEUE_ID.remove();
BlockingQueue<Request> queue = queues.get(queueId);
if (queue != null)
queue.remove(request);
}
@Override
public int getPriority() {
return queueSize;
}
@SuppressWarnings("serial")
protected static class TimedBlockingQueue extends ArrayBlockingQueue<Request> {
long lastModified;
public TimedBlockingQueue(int capacity, boolean fair) {
super(capacity, fair);
}
@Override
public void put(Request o) throws InterruptedException {
super.put(o);
lastModified = System.currentTimeMillis();
}
@Override
public boolean remove(Object o) {
lastModified = System.currentTimeMillis();
return super.remove(o);
}
}
}
The implementation for IpFlowController is as follows:
public class IpFlowController extends QueueController {
/**
* A flow controller that throttles concurrent requests made from the same ip (any ip)
*
* @author Juan Marin, OpenGeo
*/
static final Logger LOGGER = Logging.getLogger(IpFlowController.class);
public IpFlowController(int queueSize) {
this.queueSize = queueSize;
}
protected List<String> ipAddresses = new ArrayList<String>();
@Override
public boolean requestIncoming(Request request, long timeout) {
boolean retval = true;
// check if this client already made other connections
String incomingIp = "";
String ip = request.getHttpRequest().getRemoteAddr();
if (ipAddresses.size() > 0) {
for (String ipAddress : ipAddresses) {
if (ipAddress.equals(ip)) {
incomingIp = ipAddress;
break;
}
}
}
if (incomingIp.equals("")) {
incomingIp = ip;
}
// see if we have that queue already
TimedBlockingQueue queue = null;
if (incomingIp != null && !incomingIp.equals("")) {
queue = queues.get(incomingIp);
}
// generate a unique queue id for this client if none was found
if (queue == null) {
queue = new TimedBlockingQueue(queueSize, true);
queues.put(incomingIp, queue);
}
QUEUE_ID.set(incomingIp);
ipAddresses.add(incomingIp);
// queue token handling
try {
if (timeout > 0) {
retval = queue.offer(request, timeout, TimeUnit.MILLISECONDS);
} else {
queue.put(request);
}
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Unexpected interruption while "
+ "blocking on the request queue");
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("IpFlowController(" + queueSize + "," + incomingIp + ") queue size "
+ queue.size());
LOGGER.fine("IpFlowController(" + queueSize + "," + incomingIp + ") total queues "
+ queues.size());
}
return retval;
}
\2. Specify limits for the number of requests that a particular IP address can take, same as above but specific to a particular IP
Users would add the following to controlflow.properties in order to enable this feature:
ip.address=,<ip_addr>
Where is the maximum number of requests the ip speficied in <ip_addr> will execute in parallel.
The class implementing this functionality extends from IpFlowController and overrides the requestIncoming method as follows:
public class SingleIpFlowController extends IpFlowController {
public SingleIpFlowController(int queueSize) {
super(queueSize);
}
public SingleIpFlowController(int queueSize, String ip) {
super(queueSize);
ipAddresses.add(ip);
}
@Override
public boolean requestIncoming(Request request, long timeout) {
boolean retval = true;
String incomingIp = request.getHttpRequest().getRemoteAddr();
if (incomingIp.equals(ipAddresses.get(0))) {
TimedBlockingQueue queue = null;
if (incomingIp != null && !incomingIp.equals("")) {
queue = queues.get(incomingIp);
}
if (queue == null) {
queue = new TimedBlockingQueue(queueSize, true);
queues.put(incomingIp, queue);
}
QUEUE_ID.set(incomingIp);
try {
if (timeout > 0) {
retval = queue.offer(request, timeout, TimeUnit.MILLISECONDS);
} else {
queue.put(request);
}
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Unexpected interruption while "
+ "blocking on the request queue");
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("SingleIpFlowController(" + queueSize + "," + incomingIp + ") queue size "
+ queue.size());
LOGGER.fine("SingleIpFlowController(" + queueSize + "," + incomingIp + ") total queues "
+ queues.size());
}
}
return retval;
}
}
\3. IP blacklist, which would reject requests coming from specific IP addresses.
To do this, the following would have to be added to controlflow.properties:
ip.blacklist=<ip_addr1>,<ip_addr2>,…
Where <ip_addr1>, <ip_addr2> etc. are the individual IP addresses that need to be blocked
The implementing class will be a filter that will act on blacklisted IP addresses:
public class IpBlacklistFilter implements GeoServerFilter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
isBlocked = false;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String incomingIp = httpRequest.getRemoteAddr();
if (response instanceof HttpServletResponse) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
for (String ipAddress : ipAddresses) {
if (ipAddress.equals(incomingIp)) {
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN,
"This IP has been blocked. Please contact the server administrator");
isBlocked = true;
break;
}
}
if (!isBlocked) {
chain.doFilter(request, response);
}
}
}
}
All new functionality is covered by automated unit tests and will be load tested for proper behavior where relevant (IP throttling).
This section should contain feedback provided by PSC members who may have a problem with the proposal.
No backwards compatibility issues. New implementing classes will integrate with existing logic without affecting it.
Andrea Aime: Alessio Fabiani: Ben Caradoc Davies: Gabriel Roldan: Justin Deoliveira: Jody Garnett: Mark Leslie: Rob Atkinson: Simone Giannecchini:
JIRA Task Email Discussion Wiki Page