Skip to content

Instantly share code, notes, and snippets.

@t-tera
Last active February 27, 2023 07:26
Show Gist options
  • Save t-tera/8803e3d3612ac7dcf7c6e59c587b247c to your computer and use it in GitHub Desktop.
Save t-tera/8803e3d3612ac7dcf7c6e59c587b247c to your computer and use it in GitHub Desktop.
package filter;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/*
* 本プログラムはパブリックドメインに帰属します(CC0)。
* 本プログラムに関する保証やサポートは一切ありません。
*/
public class SessionSyncFilter implements Filter {
// 重要: 本番運用時は偽にしなければならない。
final private static boolean DEBUG = false;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
int mode = 4;
if (shouldSkipSync((HttpServletRequest)request)) {
mode = 0;
}
if (DEBUG && request.getParameter("filterMode") != null) {
mode = Integer.parseInt(request.getParameter("filterMode"));
}
if (mode == 0) {
debugPrint("skip synchronization");
chain.doFilter(request, response); return;
}
debugPrint(this.getClass().getName() + " mode=" + mode + " start");
HttpSession session = ((HttpServletRequest)request).getSession(false);
if (session != null) {
synchronized(getLockObject(mode, session)) {
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
}
// Mode=1,3は、sessionがリクエストをまたいで同じインスタンスになることを前提としている。
private static Object getLockObject(int mode, HttpSession session) {
// 1: 基本
if (mode == 1) {
return session;
}
// 2: prefix + セッションID文字列
else if (mode == 2) {
return ("my_filter_" + session.getId()).intern();
}
// 3: 独自オブジェクト(MAP)
else if (mode == 3) {
debugPrint("MAP.size()=" + MAP.size());
final Object lock = new Object();
final Object current = MAP.putIfAbsent(session, lock); // atomic
return current == null ? lock : current;
}
// 4: 独自オブジェクト(Springに類似)
else if (mode == 4) {
return SessionLock.get(session);
}
return null;
}
// Mode=3で使う。sessionは弱キー。
final private static Map<HttpSession,Object> MAP = Collections.synchronizedMap(new WeakHashMap<HttpSession,Object>());
// Mode=4で使う。web.xml, annotation(@WebListener)等でListenerとして登録する
// web.xml: <listener><listener-class>filter.SessionSyncFilter$SessionLockListener</listener-class></listener>
public static class SessionLockListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {
// セッション作成時にsetAttribute()させる。
debugPrint("sesionCreated");
SessionLock.get(event.getSession());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
SessionLock.remove(event.getSession());
}
}
// Mode=4で使う。
private static class SessionLock implements Serializable {
final static private long serialVersionUID = 1L;
final static private String ATTR = "MY_FILTER_LOCK";
private static SessionLock get(HttpSession session) {
SessionLock lock = (SessionLock)session.getAttribute(ATTR);
// ここはスレッドセーフではない。
if (lock != null) return lock;
lock = new SessionLock();
session.setAttribute(ATTR, lock);
return lock;
}
private static void remove(HttpSession session) {
session.removeAttribute(ATTR);
}
}
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}
private static boolean shouldSkipSync(HttpServletRequest request) {
final String path = request.getServletPath();
// きちんと判定できなそうなものは偽を返す
if (!path.matches("/[/\\w-]+") || path.contains("//")) return false;
// allowlist
if (path.matches("/aaa|/bbb|/ccc")) return true;
return false;
// denylist
//if (path.matches("(?i)/ddd|/eee")) return false;
//return true;
}
private static void debugPrint(Object o) {
if (DEBUG) System.out.println(o.toString());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment