package com.mastfrog.acteur.auth; import com.google.common.collect.Maps; import com.google.inject.Singleton; import com.mastfrog.acteur.HttpEvent; import com.mastfrog.giulius.ShutdownHookRegistry; import com.mastfrog.settings.Settings; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentLinkedDeque; import javax.inject.Inject; import org.joda.time.DateTime; import org.joda.time.Duration; /** * A basic implementation of Tarpit which uses in-memory storage and a five * minute default expiration * * @author Tim Boudreau */ @Singleton final class TarpitImpl implements Tarpit { private final Map<String, Entry> map = Maps.newConcurrentMap(); private final Duration timeToExpiration; private final TarpitCacheKeyFactory keyFactory; private final GarbageCollect gc = new GarbageCollect(); @Inject TarpitImpl(Settings settings, TarpitCacheKeyFactory keyFactory, ShutdownHookRegistry reg) { timeToExpiration = Duration.standardMinutes(settings.getLong(SETTINGS_KEY_TARPIT_EXPIRATION_TIME_MINUTES, 5)); Timer timer = new Timer(true); timer.scheduleAtFixedRate(gc, DateTime.now().plus(timeToExpiration).toDate(), timeToExpiration.getMillis() / 2); reg.add(new Runnable() { @Override public void run() { gc.cancel(); } }); this.keyFactory = keyFactory; } private class GarbageCollect extends TimerTask { @Override public void run() { garbageCollect(); } } @Override public int count(HttpEvent evt) { Entry e = map.get(keyFactory.createKey(evt)); int result = e == null ? 0 : e.size(); return result; } int garbageCollect() { Map<String, Entry> copy = new HashMap<>(map); int result = 0; for (Map.Entry<String, Entry> e : copy.entrySet()) { if (e.getValue().isExpired()) { map.remove(e.getKey()); result++; } } return result; } @Override public int add(HttpEvent evt) { String remoteAddress = keyFactory.createKey(evt); Entry entry = map.get(remoteAddress); if (entry == null) { // still need atomicity for adding synchronized (map) { entry = map.get(remoteAddress); if (entry == null) { entry = new Entry(); map.put(remoteAddress, entry); } else { entry.touch(); } } } else { entry.touch(); } return entry.touch(); } @Override public void remove(HttpEvent evt) { map.remove(keyFactory.createKey(evt)); } private class Entry { ConcurrentLinkedDeque<Long> accesses = new ConcurrentLinkedDeque<>(); int touch() { accesses.offerLast(System.currentTimeMillis()); return accesses.size(); } int size() { return accesses.size(); } boolean isExpired() { DateTime now = DateTime.now(); for (Iterator<Long> iter = accesses.iterator(); iter.hasNext();) { DateTime when = new DateTime(iter.next()); Duration dur = new Duration(when, now); if (dur.isLongerThan(timeToExpiration)) { iter.remove(); } } return accesses.isEmpty(); } @Override public String toString() { return accesses.toString() + " expired " + isExpired(); } } }