-
-
Save t-tera/8803e3d3612ac7dcf7c6e59c587b247c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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