从 Web Socket @ServerEndpoint 中的 HttpServletRequest 访问 HttpSession
Posted
技术标签:
【中文标题】从 Web Socket @ServerEndpoint 中的 HttpServletRequest 访问 HttpSession【英文标题】:Accessing HttpSession from HttpServletRequest in a Web Socket @ServerEndpoint 【发布时间】:2013-07-29 23:24:12 【问题描述】:是否可以在 @ServerEndpoint 中获取 HttpServletRequest?主要是我想得到它,以便我可以访问 HttpSession 对象。
【问题讨论】:
【参考方案1】:更新(2016 年 11 月):此答案中提供的信息适用于 JSR356 规范,该规范的各个实现可能在此信息之外有所不同。在 cmets 和其他答案中找到的其他建议都是 JSR356 规范之外的特定于实现的行为。
如果此处的建议给您带来问题,请升级您的各种 Jetty、Tomcat、Wildfly 或 Glassfish/Tyrus 安装。据报道,这些实现的所有当前版本都可以按以下概述的方式工作。
现在回到 2013 年 8 月...
的原始答案Martin Andersson 的回答存在并发缺陷。 Configurator 可以被多个线程同时调用,在modifyHandshake()
和getEndpointInstance()
的调用之间,您可能无法访问正确的 HttpSession 对象。
或者换个说法……
请求 A 修改握手A 请求 B 修改握手 B 获取端点实例 A 获取端点实例 B这是对 Martin 代码的修改,它使用 ServerEndpointConfig.getUserProperties()
映射使 HttpSession
在 @OnOpen
方法调用期间可用于您的套接字实例
GetHttpSessionConfigurator.java
package examples;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
HttpSession httpSession = (HttpSession)request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(),httpSession);
GetHttpSessionSocket.java
package examples;
import java.io.IOException;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value = "/example",
configurator = GetHttpSessionConfigurator.class)
public class GetHttpSessionSocket
private Session wsSession;
private HttpSession httpSession;
@OnOpen
public void open(Session session, EndpointConfig config)
this.wsSession = session;
this.httpSession = (HttpSession) config.getUserProperties()
.get(HttpSession.class.getName());
@OnMessage
public void echo(String msg) throws IOException
wsSession.getBasicRemote().sendText(msg);
额外功能:无需 instanceof
或强制转换。
一些 EndpointConfig 知识
EndpointConfig
对象确实存在于每个“端点实例”中。
但是,“端点实例”在规范中有 2 个含义。
-
JSR 的默认行为,其中每个传入的升级请求都会导致端点类的新对象实例
javax.websocket.Session
将对象端点实例及其配置与特定的逻辑连接联系在一起。
可以将单个 Endpoint 实例用于多个 javax.websocket.Session
实例(这是 ServerEndpointConfig.Configurator
支持的功能之一)
ServerContainer 实现将跟踪一组 ServerEndpointConfig,它们代表服务器可以响应 websocket 升级请求的所有已部署端点。
这些 ServerEndpointConfig 对象实例可以来自几个不同的来源。
-
由
javax.websocket.server.ServerContainer.addEndpoint(ServerEndpointConfig)
手动提供
通常在javax.servlet.ServletContextInitializer.contextInitialized(ServletContextEvent sce)
调用中完成
来自javax.websocket.server.ServerApplicationConfig.getEndpointConfigs(Set)
电话。
通过扫描网络应用程序自动创建 @ServerEndpoint
注释类。
这些ServerEndpointConfig
对象实例作为javax.websocket.Session
最终创建时的默认值存在。
ServerEndpointConfig.Configurator 实例
在接收或处理任何升级请求之前,所有 ServerEndpointConfig.Configurator
对象现在都存在并准备好执行其主要和唯一目的,以允许自定义升级过程的 websocket 连接到最终的 javax.websocket.Session
访问特定于会话的 EndpointConfig
注意,您不能从端点实例中访问 ServerEndpointConfig
对象实例。您只能访问 EndpointConfig
实例。
这意味着如果您在部署期间提供了ServerContainer.addEndpoint(new MyCustomServerEndpointConfig())
,然后尝试通过注释访问它,它将无法正常工作。
以下所有内容均无效。
@OnOpen
public void onOpen(Session session, EndpointConfig config)
MyCustomServerEndpointConfig myconfig = (MyCustomServerEndpointConfig) config;
/* this would fail as the config is cannot be cast around like that */
// --- or ---
@OnOpen
public void onOpen(Session session, ServerEndpointConfig config)
/* For @OnOpen, the websocket implementation would assume
that the ServerEndpointConfig to be a declared PathParam
*/
// --- or ---
@OnOpen
public void onOpen(Session session, MyCustomServerEndpointConfig config)
/* Again, for @OnOpen, the websocket implementation would assume
that the MyCustomServerEndpointConfig to be a declared PathParam
*/
您可以在 Endpoint 对象实例的生命周期内访问 EndpointConfig,但时间有限。 javax.websocket.Endpoint.onOpen(Session,Endpoint)
,注释 @OnOpen
方法,或通过使用 CDI。 EndpointConfig 不能以任何其他方式或在任何其他时间使用。
但是,您始终可以通过 Session.getUserProperties()
调用访问 UserProperties,该调用始终可用。此用户属性映射始终可用,无论是通过注释技术(例如 @OnOpen
、@OnClose
、@OnError
或 @OnMessage
调用期间的会话参数),通过会话的 CDI 注入,甚至使用使用从 javax.websocket.Endpoint
扩展的非注释 websocket。
升级的工作原理
如前所述,每个已定义的端点都有一个与之关联的ServerEndpointConfig
。
那些ServerEndpointConfigs
是代表EndpointConfig
的默认状态的单个实例,最终可供可能并最终创建的端点实例使用。
当一个传入的升级请求到达时,它在 JSR 上经过了以下处理。
-
路径是否与任何 ServerEndpointConfig.getPath() 条目匹配
如果不匹配,返回 404 进行升级
将升级请求传递到 ServerEndpointConfig.Configurator.checkOrigin()
如果无效,返回错误升级响应
创建握手响应
将升级请求传递到 ServerEndpointConfig.Configurator.getNegotiatedSubprotocol()
在 HandshakeResponse 中存储答案
将升级请求传递到 ServerEndpointConfig.Configurator.getNegotiatedExtensions()
在 HandshakeResponse 中存储答案
创建新的端点特定的 ServerEndpointConfig 对象。复制编码器、解码器和用户属性。这个新的 ServerEndpointConfig 包装了路径、扩展、端点类、子协议、配置器的默认值。
将升级请求、响应和新的 ServerEndpointConfig 传递到 ServerEndpointConfig.Configurator.modifyHandshake()
调用ServerEndpointConfig.getEndpointClass()
在 ServerEndpointConfig.Configurator.getEndpointInstance(Class) 上使用类
创建 Session,关联端点实例和 EndpointConfig 对象。
通知连接的端点实例
需要 EndpointConfig 的注释方法获取与此 Session 关联的方法。
对 Session.getUserProperties() 的调用返回 EndpointConfig.getUserProperties()
请注意,ServerEndpointConfig.Configurator 是一个单例,每个映射的 ServerContainer 端点。
这是有意的,也是期望的,允许实现者使用多个功能。
为多个对等点返回相同的端点实例(如果他们愿意)。所谓的 websocket 编写的无状态方法。 对所有 Endpoint 实例的昂贵资源进行单点管理如果实现为每次握手都创建一个新的配置器,那么这种技术是不可能的。
(披露:我为 Jetty 9 编写和维护 JSR-356 实现)
【讨论】:
1. 您知道吗,如果您使用的是 Tyrus,传递给您的@OnOpen
方法的 EndpointConfig
对象不是您的自定义配置对象,但它是默认?这让我不信任用户属性映射。此外,您不必使用 instanceof 运算符。如果未来的程序员不恰当地使用自定义类,我用它来确保我们获得正确的异常类型。 2. 它可能看起来像一个并发缺陷,但事实并非如此。我已经在我的回答中强调了这一点。 JavaDoc 说:“实现为每个逻辑端点创建了一个新的配置器实例。”
自从阿基米德特拉哈诺接受了您的回答以及我的回答包含“并发缺陷”的声明。如果你们中的任何人能详细说明为什么你会这样想,我会很高兴。也许我误解了 WebSocket API 的规范和/或 JavaDocs。
@studro 它实际上指向了 javax.websocket
的糟糕规范,而不是“WebSocket 标准”。
@Joakim 查看 JsrCreator.java 中 Jetty 9.1.x 的源代码,很明显 modifyHandshake
接收原始的 ServerEndpointConfig
而不是每次握手的副本。与您描述的流程相比,modifyHandshake
(第 6 点)甚至发生在 checkOrigin
(第 2 点)之前。它与 GlassFish 4 完全相同,所以我看不出使用 userProperties() 比使用配置器的实例变量更线程安全。事实上,我看不出如何使用当前的 JSR 356 实现在配置器和端点实例之间可靠地传递任何东西。
为了完整起见,Tomcat 确实将每个会话的端点配置传递给modifyHanshake
,因此答案对于 Tomcat 是正确的,但对于 Jetty 和 GlassFish 则不正确。该规范似乎没有指定任何特定的行为。【参考方案2】:
前言
目前尚不清楚您是否想要HttpServletRequest
、HttpSession
或HttpSession
之外的属性。我的回答将展示如何获取HttpSession
或个别属性。
为简洁起见,我省略了 null 和索引边界检查。
注意事项
这很棘手。 Martin Andersson 的答案不正确,因为每个连接都使用相同的 ServerEndpointConfig.Configurator
实例,因此存在竞争条件。虽然文档声明“实现为每个逻辑端点创建了一个新的配置器实例”,但规范并未明确定义“逻辑端点”。根据使用该短语的所有地方的上下文,它似乎意味着类、配置器、路径和其他选项的绑定,即ServerEndpointConfig
,它显然是共享的。无论如何,您可以通过从modifyHandshake(...)
中打印出toString()
来轻松查看实现是否使用相同的实例。
更令人惊讶的是,Joakim Erdfelt 的回答也不能可靠地工作。 JSR 356 的文本本身并没有提到EndpointConfig.getUserProperties()
,它只在JavaDoc 中,而且似乎没有指定它与Session.getUserProperties()
的确切关系。在实践中,一些实现(例如 Glassfish)为所有对 ServerEndpointConfig.getUserProperties()
的调用返回相同的 Map
实例,而其他实现(例如 Tomcat 8)则不会。您可以在modifyHandshake(...)
内修改之前打印出地图内容进行检查。
为了验证,我直接从其他答案中复制了代码,然后针对我编写的多线程客户端对其进行了测试。在这两种情况下,我都观察到与端点实例关联的会话不正确。
解决方案概要
我开发了两种解决方案,在针对多线程客户端进行测试时,我已经验证它们可以正常工作。有两个关键技巧。
首先,使用与 WebSocket 路径相同的过滤器。这将使您可以访问HttpServletRequest
和HttpSession
。如果会话尚不存在,它还为您提供了创建会话的机会(尽管在这种情况下使用 HTTP 会话似乎很可疑)。
其次,找到 WebSocket Session
和 HttpServletRequest
或 HttpSession
中都存在的一些属性。原来有两个候选者:getUserPrincipal()
和 getRequestParameterMap()
。我会告诉你如何滥用它们:)
使用用户主体的解决方案
最简单的方法是利用Session.getUserPrincipal()
和HttpServletRequest.getUserPrincipal()
。缺点是这可能会干扰此属性的其他合法用途,因此请仅在您准备好应对这些影响时才使用它。
如果您只想存储一个字符串,例如用户 ID,这实际上并不算滥用,尽管它可能应该以某种容器管理的方式设置,而不是像我将向您展示的那样覆盖包装器.无论如何,您只需覆盖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();
使用请求参数的解决方案
第二个选项使用Session.getRequestParameterMap()
和HttpServletRequest.getParameterMap()
。请注意,它使用ServerEndpointConfig.getUserProperties()
,但在这种情况下它是安全的,因为我们总是将相同的对象放入地图中,因此它是否共享没有区别。唯一会话标识符不是通过用户参数传递,而是通过请求参数传递,每个请求都是唯一的。
这个解决方案稍微不那么hacky,因为它不会干扰用户主体属性。请注意,如果除了插入的请求参数之外,您还需要传递 实际 请求参数,您可以轻松地这样做:只需从现有请求参数映射开始,而不是像此处所示的新的空请求参数映射.但请注意,用户不能通过在实际 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);
单一属性的解决方案
如果您只需要HttpSession
之外的某些属性而不是整个HttpSession
本身,例如用户ID,那么您可以取消整个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);
【讨论】:
这应该是公认的答案。干得好乍得。但我有一个疑问:我认为不可能为每个实现都放置一个外部过滤器 我在 Wildfly/Undertow 上试过这个,但即使正确应用了过滤器,session.getUserPrincipal()
总是返回 null
。 Undertow WS 是否可能不使用来自过滤器的 servlet 请求?
“我开发了两种解决方案,在针对多线程客户端进行测试时,我已经验证了它们可以正常工作。”
你不能像那样转换 WebSocket 会话(JSR356 规范不支持它),你的解决方案将是特定于实现的。
另外,JSR356 规范在 WebSocket 升级之前没有过滤器的支持要求。实现者可以选择在 Servlet 链执行之前处理升级。 (许多实现都这样做)【参考方案3】:
有可能吗?
让我们回顾一下Java API for WebSocket 规范,看看是否可以获取HttpSession
对象。 specification 在第 29 页上说:
因为 websocket 连接是通过 http 请求启动的, 客户端下的 HttpSession 之间存在关联 正在运行,并且在其中建立的任何 websocket HttpSession。 API 允许在打开握手时访问 对应同一个客户端的唯一 HttpSession。
所以是的,这是可能的。
但是,我认为您不可能获得对HttpServletRequest
对象的引用。您可以使用ServletRequestListener
侦听所有 个新的 servlet 请求,但您仍然需要确定哪个请求属于哪个服务器端点。如果您找到解决方案,请告诉我!
摘要操作方法
如何在规范的第 13 页和第 14 页上进行了粗略的描述,并由我在下一个标题下的代码中举例说明。
在英语中,我们需要拦截握手过程以获取HttpSession
对象。为了然后将 HttpSession 引用传输到我们的服务器端点,我们还需要在容器创建服务器端点实例时进行拦截并手动注入引用。我们通过提供自己的ServerEndpointConfig.Configurator
并覆盖modifyHandshake()
和getEndpointInstance()
方法来完成所有这些工作。
自定义配置器将根据逻辑ServerEndpoint
实例化一次(请参阅JavaDoc)。
代码示例
这是服务器端点类(我在这段代码sn-p之后提供了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;
【讨论】:
什么是ConnectionWebSocket
?
哦该死。对不起!从我已经实施的解决方案中复制粘贴该部分。在我的情况下,端点被称为 ConnectionWebSocket =) 我编辑了答案。谢谢!如果对你有用,别忘了点赞。
有一种更简单(更正确)的方法,也请参阅我对这个问题的回答。
对于由 Undertow 提供支持的 JBoss/WildFly 8.1,根本不会调用 getEndpointInstance。对我来说,唯一的解决方法是通过 ThreadLocal 传递 HttpSession,因为 modifyHandshake 和 onOpen 实际上是在同一个过滤器/请求处理链中执行的。
我错了, modifyHandshake 和 onOpen 总是在不同的线程中执行,所以我不得不通过静态映射传递 servlet 数据,并将键作为客户端连接的主机/端口对。然而,这些信息不是直接可用的,而是通过一些反射魔法。【参考方案4】:
以上所有答案都值得一读,但没有一个能解决 OP(和我)的问题。
您可以在 WS 端点打开时访问 HttpSession 并将其传递给新创建的端点实例,但没有人保证存在 HttpSession 实例!
所以我们需要在这次黑客攻击之前第 0 步(我讨厌 WebSocket 的 JSR 365 实现)。 Websocket - httpSession returns null
【讨论】:
这是一个很好的观点,但这不是一个答案,应该是一个评论。【参考方案5】:所有可能的解决方案都基于:
A.客户端浏览器实现通过作为 HTTP 标头传递的 Cookie 值维护会话 ID,或者(如果禁用 cookie)由 Servlet 容器管理,该容器将为生成的 URL 生成会话 ID 后缀
B.您只能在 HTTP 握手期间访问 HTTP 请求标头;之后就是Websocket协议
所以……
方案一:使用“握手”访问HTTP
解决方案 2:在客户端的 javascript 中,动态生成 HTTP 会话 ID 参数并发送包含此会话 ID 的第一条消息(通过 Websocket)。将“端点”连接到维护 Session ID -> Session 映射的缓存/实用程序类;避免内存泄漏,您可以使用 Session Listener 例如从缓存中删除会话。
附:感谢 Martin Andersson 和 Joakim Erdfelt 的回答。 不幸的是,Martin 的解决方案不是线程安全的......
【讨论】:
您可以直接为解决方案 1 once you have 15 rep 投票。这看起来更像评论,您可以获得分享once you have 50 rep的特权。请阅读"How do I write a good answer?" 我无法发表评论,因此我将其发布为“答案”。谢谢!【参考方案6】:适用于所有应用服务器的唯一方法是使用 ThreadLocal。见:
https://java.net/jira/browse/WEBSOCKET_SPEC-235
【讨论】:
链接已失效。这就是为什么最好写例子。不是被oracle.com/technical-resources/articles/java/jsr356.html取代了吗?以上是关于从 Web Socket @ServerEndpoint 中的 HttpServletRequest 访问 HttpSession的主要内容,如果未能解决你的问题,请参考以下文章
Spring Web Socket - 从 MQ 监听器通知客户端
将服务器从 2012 升级到 2019 后,Web Socket 不适用于实际设备