@ServerEndpoint内でHttpServletRequestを取得することは可能ですか?主に、HttpSessionオブジェクトにアクセスできるように取得しようとしています。
HttpServletRequest
、HttpSession
、またはHttpSession
のプロパティのどれが必要かは不明です。私の答えは、HttpSession
または個々のプロパティを取得する方法を示します。
簡潔にするために、nullとインデックスの境界チェックを省略しました。
トリッキーです。 _ServerEndpointConfig.Configurator
_の同じインスタンスがすべての接続に使用されるため、Martin Anderssonからの答えは正しくありません。したがって、競合状態が存在します。ドキュメントでは「実装は論理エンドポイントごとにコンフィグレータの新しいインスタンスを作成します」と述べていますが、仕様は「論理エンドポイント」を明確に定義していません。フレーズが使用されるすべての場所のコンテキストに基づいて、クラス、コンフィギュレーター、パス、および他のオプション、つまり、明らかに共有されるServerEndpointConfig
のバインディングを意味するように見えます。とにかく、toString()
内からmodifyHandshake(...)
を出力することで、実装が同じインスタンスを使用しているかどうかを簡単に確認できます。
さらに驚くべきことに、Joakim Erdfeltからの回答も確実に機能しません。 JSR 356のテキスト自体はEndpointConfig.getUserProperties()
に言及しておらず、JavaDocにのみ記載されており、Session.getUserProperties()
との正確な関係がどこにも指定されていないようです。実際には、一部の実装(Glassfishなど)は、ServerEndpointConfig.getUserProperties()
へのすべての呼び出しに対して同じMap
インスタンスを返しますが、他の実装(Tomcat 8など)は返しません。 modifyHandshake(...)
内で変更する前に、マップの内容を印刷して確認できます。
確認するために、他の回答からコードを直接コピーし、作成したマルチスレッドクライアントに対してテストしました。どちらの場合も、エンドポイントインスタンスに関連付けられている誤ったセッションを観察しました。
私は2つのソリューションを開発しましたが、マルチスレッドクライアントに対してテストしたときに正しく動作することを確認しました。 2つの重要なトリックがあります。
まず、WebSocketと同じパスを持つフィルターを使用します。これにより、HttpServletRequest
およびHttpSession
にアクセスできます。また、セッションがまだ存在しない場合は、セッションを作成する機会も与えられます(ただし、その場合、HTTPセッションの使用はまったく疑わしいようです)。
次に、WebSocket Session
とHttpServletRequest
またはHttpSession
の両方に存在するプロパティを見つけます。 getUserPrincipal()
とgetRequestParameterMap()
の2つの候補があることがわかりました。両方を悪用する方法を紹介します:)
最も簡単な方法は、Session.getUserPrincipal()
とHttpServletRequest.getUserPrincipal()
を利用することです。欠点は、このプロパティの他の正当な使用を妨げる可能性があるため、これらの影響に備えている場合にのみ使用してください。
ユーザーIDなどの文字列を1つだけ保存する場合、これは実際にはあまり悪用されませんが、おそらく、後で説明するようにラッパーをオーバーライドするのではなく、コンテナー管理の方法で設定する必要があります。とにかくPrincipal.getName()
をオーバーライドするだけでそれを行うことができます。その後、Endpoint
にキャストする必要さえありません。しかし、それを使いこなすことができれば、次のようにHttpSession
オブジェクト全体を渡すこともできます。
_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
}
}
_
_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() { }
}
_
_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()
を使用しますが、この場合は常に同じオブジェクトをマップに配置するので安全であり、共有されているかどうかは関係ありません。一意のセッション識別子は、ユーザーパラメータではなく、リクエストパラメータを介して渡されます。リクエストパラメータisリクエストごとに一意です。
このソリューションは、ユーザープリンシパルプロパティに干渉しないため、ややハッキングが少なくなります。挿入されたものに加えてactualリクエストパラメータを渡す必要がある場合は、簡単に行うことができます。ここに示すように、新しい空のリクエストマップではなく、既存のリクエストパラメータマップから開始するだけです。 。ただし、ユーザーが実際のHTTPリクエストと同じ名前で独自のリクエストパラメータを指定することで、フィルタに追加された特別なパラメータを偽装できないように注意してください。
_/* 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);
}
}
_
_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() { }
}
_
_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()
からプロパティに簡単にアクセスできます。
_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 {
}
}
_
_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);
}
}
_
WebSocketのJava API仕様を確認して、HttpSession
オブジェクトを取得できるかどうかを確認しましょう。 specification は29ページに記載しています:
Webソケット接続はhttpリクエストで開始されるため、クライアントが動作しているHttpSessionと、そのHttpSession内で確立されたすべてのwebsocketの間には関連付けがあります。このAPIを使用すると、オープニングハンドシェイクで、同じクライアントに対応する一意のHttpSessionにアクセスできます。
はい、可能です。
ただし、HttpServletRequest
オブジェクトへの参照を取得することはできないと思います。 allServletRequestListener
を使用して新しいサーブレットリクエストをリッスンできますが、どのリクエストがどのサーバーエンドポイントに属しているかを把握する必要があります。解決策を見つけたら教えてください!
How-toは、仕様の13ページと14ページに大まかに説明されており、次の見出しの下のコードで例示されています。
英語では、HttpSession
オブジェクトを取得するためにハンドシェイクプロセスをインターセプトする必要があります。その後、HttpSession参照をサーバーエンドポイントに転送するには、コンテナーがサーバーエンドポイントインスタンスを作成するときにインターセプトし、参照を手動で挿入する必要もあります。独自の_ServerEndpointConfig.Configurator
_を提供し、メソッドmodifyHandshake()
およびgetEndpointInstance()
をオーバーライドすることにより、これらすべてを行います。
カスタムコンフィギュレーターは、論理ServerEndpoint
ごとに1回インスタンス化されます( JavaDoc を参照)。
これはサーバーエンドポイントクラスです(このコードスニペットの後にCustomConfiguratorクラスの実装を提供します)。
_@ServerEndpoint(value = "/myserverendpoint", configurator = CustomConfigurator.class)
public class MyServerEndpoint
{
private HttpSession httpSession;
public void setHttpSession(HttpSession httpSession) {
if (this.httpSession != null) {
throw new IllegalStateException("HttpSession has already been set!");
}
this.httpSession = httpSession;
}
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
System.out.println("My Session Id: " + httpSession.getId());
}
}
_
そして、これはカスタム設定です:
_public class CustomConfigurator extends ServerEndpointConfig.Configurator
{
private HttpSession httpSession;
// modifyHandshake() is called before getEndpointInstance()!
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
httpSession = (HttpSession) request.getHttpSession();
super.modifyHandshake(sec, request, response);
}
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
T endpoint = super.getEndpointInstance(endpointClass);
if (endpoint instanceof MyServerEndpoint) {
// The injection point:
((MyServerEndpoint) endpoint).setHttpSession(httpSession);
}
else {
throw new InstantiationException(
MessageFormat.format("Expected instanceof \"{0}\". Got instanceof \"{1}\".",
MyServerEndpoint.class, endpoint.getClass()));
}
return endpoint;
}
}
_
上記のすべての回答は読む価値がありますが、OP(および私の)の問題を解決できるものはありません。
WSエンドポイントが開いているときにHttpSessionにアクセスして、新しく作成されたエンドポイントインスタンスに渡すことができますが、HttpSessionインスタンスが存在することを保証するものはありません!
したがって、このハッキングの前にステップ0が必要です(WebSocketのJSR 365実装が嫌いです)。 Websocket-httpSessionはnullを返します
すべてのアプリケーションサーバーで機能する唯一の方法は、ThreadLocalを使用することです。見る:
可能なソリューションはすべて次のものに基づいています。
A.クライアントブラウザーの実装は、HTTPヘッダーとして渡されるCookie値を介してセッションIDを維持します。または(Cookieが無効な場合)生成されたURLのセッションID後置を生成するサーブレットコンテナーによって管理されます。
B. HTTPハンドシェイク中にのみHTTP要求ヘッダーにアクセスできます。その後、それはWebsocketプロトコルです
そのため...
解決策1:「ハンドシェイク」を使用してHTTPにアクセスする
解決策2:クライアント側のJavaScriptで、HTTPセッションIDパラメーターを動的に生成し、このセッションIDを含む最初のメッセージを(Websocket経由で)送信します。 「エンドポイント」をセッションIDを維持するキャッシュ/ユーティリティクラスに接続します->セッションマッピング。メモリリークを回避するには、インスタンスのセッションリスナーを使用して、キャッシュからセッションを削除します。
追伸Martin AnderssonとJoakim Erdfeltからの回答に感謝します。残念ながら、Martinのソリューションはスレッドセーフではありません...