@ServerEndpoint 内で HttpServletRequest を取得することは可能ですか? 主に、HttpSession オブジェクトにアクセスできるように取得しようとしています。
6 に答える
序文
HttpServletRequest
、HttpSession
、または のプロパティのうち、 が必要かどうかは不明ですHttpSession
。私の答えは、HttpSession
または個々のプロパティを取得する方法を示します。
簡潔にするために、null とインデックスの境界チェックは省略しました。
注意事項
トリッキーです。Martin Andersson からの回答は正しくありません。これはServerEndpointConfig.Configurator
、すべての接続で の同じインスタンスが使用され、競合状態が存在するためです。ドキュメントには「実装により、論理エンドポイントごとにコンフィギュレーターの新しいインスタンスが作成される」と記載されていますが、仕様では「論理エンドポイント」が明確に定義されていません。このフレーズが使用されているすべての場所のコンテキストに基づいて、ServerEndpointConfig
明らかに共有されているクラス、コンフィギュレーター、パス、およびその他のオプション (つまり a ) のバインディングを意味しているようです。とにかく、実装が同じインスタンスを使用しているかどうかは、toString()
内から出力することで簡単に確認できますmodifyHandshake(...)
。
さらに驚くべきことに、Joakim Erdfelt からの回答も確実に機能しません。JSR 356 自体のテキストには が記載されておらずEndpointConfig.getUserProperties()
、JavaDoc にのみ記載されており、 との正確な関係がどこにも明記されていないようですSession.getUserProperties()
。実際には、一部の実装 (Glassfish など)Map
はすべての呼び出しに対して同じインスタンスを返しますが、他の実装 ( ServerEndpointConfig.getUserProperties()
Tomcat 8 など) はそうではありません。で変更する前に、マップの内容を印刷して確認できますmodifyHandshake(...)
。
確認するために、コードを他の回答から直接コピーし、作成したマルチスレッド クライアントに対してテストしました。どちらの場合も、エンドポイント インスタンスに関連付けられているセッションが正しくないことがわかりました。
ソリューションの概要
私は 2 つのソリューションを開発し、マルチスレッド クライアントに対してテストしたときに正しく動作することを確認しました。2 つの重要なトリックがあります。
まず、WebSocket と同じパスを持つフィルターを使用します。HttpServletRequest
これにより、およびにアクセスできるようになりHttpSession
ます。また、セッションがまだ存在しない場合は、セッションを作成する機会も与えられます (ただし、その場合、HTTP セッションを使用することはまったく疑わしく思えます)。
次に、WebSocketSession
とHttpServletRequest
またはの両方に存在するいくつかのプロパティを見つけますHttpSession
。getUserPrincipal()
との 2 つの候補があることがわかりgetRequestParameterMap()
ます。両方を悪用する方法を紹介します:)
ユーザープリンシパルを使用したソリューション
最も簡単な方法は、 と を利用することSession.getUserPrincipal()
ですHttpServletRequest.getUserPrincipal()
。欠点は、これがこのプロパティの他の正当な使用を妨げる可能性があることです。そのため、それらの影響を覚悟している場合にのみ使用してください。
ユーザー ID などの文字列を 1 つだけ保存したい場合、これは実際にはあまり乱用されませんが、これから説明するように、ラッパーをオーバーライドするのではなく、コンテナーで管理される方法で設定する必要があると思われます。とにかく、オーバーライドするだけでそれを行うことができますPrincipal.getName()
。次に、でキャストする必要さえありませんEndpoint
。しかし、我慢できるなら、次のHttpSession
ようにオブジェクト全体を渡すこともできます。
PrincipalWithSession.java
package example1;
import java.security.Principal;
import javax.servlet.http.HttpSession;
public class PrincipalWithSession implements Principal {
private final HttpSession session;
public PrincipalWithSession(HttpSession session) {
this.session = session;
}
public HttpSession getSession() {
return session;
}
@Override
public String getName() {
return ""; // whatever is appropriate for your app, e.g., user ID
}
}
WebSocketFilter.java
package example1;
import java.io.IOException;
import java.security.Principal;
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.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@WebFilter("/example1")
public class WebSocketFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
final PrincipalWithSession p = new PrincipalWithSession(httpRequest.getSession());
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
@Override
public Principal getUserPrincipal() {
return p;
}
};
chain.doFilter(wrappedRequest, response);
}
public void init(FilterConfig config) throws ServletException { }
public void destroy() { }
}
WebSocketEndpoint.java
package example1;
import javax.servlet.http.HttpSession;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/example1")
public class WebSocketEndpoint {
private HttpSession httpSession;
@OnOpen
public void onOpen(Session webSocketSession) {
httpSession = ((PrincipalWithSession) webSocketSession.getUserPrincipal()).getSession();
}
@OnMessage
public String demo(String msg) {
return msg + "; (example 1) session ID " + httpSession.getId();
}
}
リクエスト パラメータを使用したソリューション
2 番目のオプションでは、 と を使用Session.getRequestParameterMap()
しHttpServletRequest.getParameterMap()
ます。ServerEndpointConfig.getUserProperties()
を使用していますが、常に同じオブジェクトをマップに配置しているため、この場合は安全であることに注意してください。したがって、共有されているかどうかに違いはありません。一意のセッション識別子は、ユーザー パラメータではなく、リクエストごとに一意のリクエスト パラメータを介して渡されます。
このソリューションは、ユーザー プリンシパルのプロパティに干渉しないため、ハッキングがやや少なくなります。挿入されたものに加えて実際のリクエスト パラメータを渡す必要がある場合は、簡単に行うことができます。ここに示すように、新しい空のリクエスト パラメータ マップではなく、既存のリクエスト パラメータ マップから開始するだけです。ただし、ユーザーが実際の HTTP 要求で同じ名前で独自の要求パラメーターを指定して、フィルターに追加された特別なパラメーターを偽装できないように注意してください。
SessionTracker.java
/* A simple, typical, general-purpose servlet session tracker */
package example2;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class SessionTracker implements ServletContextListener, HttpSessionListener {
private final ConcurrentMap<String, HttpSession> sessions = new ConcurrentHashMap<>();
@Override
public void contextInitialized(ServletContextEvent event) {
event.getServletContext().setAttribute(getClass().getName(), this);
}
@Override
public void contextDestroyed(ServletContextEvent event) {
}
@Override
public void sessionCreated(HttpSessionEvent event) {
sessions.put(event.getSession().getId(), event.getSession());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
sessions.remove(event.getSession().getId());
}
public HttpSession getSessionById(String id) {
return sessions.get(id);
}
}
WebSocketFilter.java
package example2;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
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.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@WebFilter("/example2")
public class WebSocketFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
final Map<String, String[]> fakedParams = Collections.singletonMap("sessionId",
new String[] { httpRequest.getSession().getId() });
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
@Override
public Map<String, String[]> getParameterMap() {
return fakedParams;
}
};
chain.doFilter(wrappedRequest, response);
}
@Override
public void init(FilterConfig config) throws ServletException { }
@Override
public void destroy() { }
}
WebSocketEndpoint.java
package example2;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.HandshakeResponse;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
@ServerEndpoint(value = "/example2", configurator = WebSocketEndpoint.Configurator.class)
public class WebSocketEndpoint {
private HttpSession httpSession;
@OnOpen
public void onOpen(Session webSocketSession, EndpointConfig config) {
String sessionId = webSocketSession.getRequestParameterMap().get("sessionId").get(0);
SessionTracker tracker =
(SessionTracker) config.getUserProperties().get(SessionTracker.class.getName());
httpSession = tracker.getSessionById(sessionId);
}
@OnMessage
public String demo(String msg) {
return msg + "; (example 2) session ID " + httpSession.getId();
}
public static class Configurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request,
HandshakeResponse response) {
Object tracker = ((HttpSession) request.getHttpSession()).getServletContext().getAttribute(
SessionTracker.class.getName());
// This is safe to do because it's the same instance of SessionTracker all the time
sec.getUserProperties().put(SessionTracker.class.getName(), tracker);
super.modifyHandshake(sec, request, response);
}
}
}
単一物件のソリューション
の特定のプロパティのみが必要で、ユーザー ID など、それ自体HttpSession
全体が必要ない場合は、ビジネス全体を廃止して、オーバーライドから返されるマップに必要なパラメーターを配置するだけで済みます。次に、カスタムを取り除くこともできます。プロパティは、エンドポイントから簡単にアクセスできます。HttpSession
SessionTracker
HttpServletRequestWrapper.getParameterMap()
Configurator
Session.getRequestParameterMap()
WebSocketFilter.java
package example5;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
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.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@WebFilter("/example5")
public class WebSocketFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
final Map<String, String[]> props = new HashMap<>();
// Add properties of interest from session; session ID
// is just for example
props.put("sessionId", new String[] { httpRequest.getSession().getId() });
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
@Override
public Map<String, String[]> getParameterMap() {
return props;
}
};
chain.doFilter(wrappedRequest, response);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
WebSocketEndpoint.java
package example5;
import java.util.List;
import java.util.Map;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/example5")
public class WebSocketEndpoint {
private Map<String, List<String>> params;
@OnOpen
public void onOpen(Session session) {
params = session.getRequestParameterMap();
}
@OnMessage
public String demo(String msg) {
return msg + "; (example 5) session ID " + params.get("sessionId").get(0);
}
}
考えられるすべてのソリューションは、以下に基づいています。
A. クライアント ブラウザーの実装は、HTTP ヘッダーとして渡される Cookie 値を介してセッション ID を維持します。または (Cookie が無効になっている場合)、生成された URL のセッション ID ポストフィックスを生成するサーブレット コンテナーによって管理されます。
B. HTTP ハンドシェイク中にのみ HTTP リクエスト ヘッダーにアクセスできます。その後、それはWebsocket Protocolです
となることによって...
解決策 1: 「ハンドシェイク」を使用して HTTP にアクセスする
解決策 2: クライアント側の JavaScript で、HTTP セッション ID パラメーターを動的に生成し、このセッション ID を含む最初のメッセージを (Websocket 経由で) 送信します。「エンドポイント」をキャッシュ/ユーティリティ クラスに接続し、セッション ID -> セッション マッピングを維持します。メモリ リークを回避します。たとえば、セッション リスナーを使用してキャッシュからセッションを削除できます。
PS Martin Andersson と Joakim Erdfelt からの回答に感謝します。残念ながら、マーティンの解決策はスレッドセーフではありません...