当前位置:   article > 正文

nginx代理情况下获取websocket客户端真实ip的方法_websocket ippoint

websocket ippoint

文章转自:http://www.cnblogs.com/zhuxiaojie/p/6238826.html

一:本文使用范围

此文不仅仅局限于spring boot,普通的spring工程,甚至是servlet工程,都是一样的,只不过配置一些监听器的方法不同而已。

 

本文经过作者实践,确认完美运行。

 

二:Spring boot使用websocket

2.1:依赖包

websocket本身是servlet容器所提供的服务,所以需要在web容器中运行,像我们所使用的tomcat,当然,spring boot中已经内嵌了tomcat。

websocket遵循了javaee规范,所以需要引入javaee的包 

复制代码

  1. <dependency>
  2. <groupId>javax</groupId>
  3. <artifactId>javaee-api</artifactId>
  4. <version>7.0</version>
  5. <scope>provided</scope>
  6. </dependency>

复制代码

当然,其实tomcat中已经自带了这个包。

如果是在spring boot中,还需要加入websocket的starter

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-websocket</artifactId>
  4. <version>1.4.0.RELEASE</version>
  5. </dependency>

 

 

 

2.2:配置websocket

如果不是spring boot项目,那就不需要进行这样的配置,因为如果在tomcat中运行的话,tomcat会扫描带有@ServerEndpoint的注解成为websocket,而spring boot项目中需要由这个bean来提供注册管理。

复制代码

  1. @Configuration
  2. public class WebSocketConfig {
  3. @Bean
  4. public ServerEndpointExporter serverEndpointExporter() {
  5. return new ServerEndpointExporter();
  6. }
  7. }

复制代码

 

 

 

 

2.3:websocket的java代码

使用websocket的核心,就是一系列的websocket注解,@ServerEndpoint是注册在类上面开启。

 

复制代码

  1. @ServerEndpoint(value = "/websocket")
  2. @Component
  3. public class MyWebSocket {
  4. //与某个客户端的连接会话,需要通过它来给客户端发送数据
  5. private Session session;
  6. /**
  7. * 连接成功*/
  8. @OnOpen
  9. public void onOpen(Session session) {
  10. this.session = session;
  11. }
  12. /**
  13. * 连接关闭调用的方法
  14. */
  15. @OnClose
  16. public void onClose() {
  17. }
  18. /**
  19. * 收到消息
  20. *
  21. * @param message
  22. */
  23. @OnMessage
  24. public void onMessage(String message, Session session) {
  25. System.out.println("来自浏览器的消息:" + message);
  26. //群发消息
  27. for (MyWebSocket item : webSocketSet) {
  28. try {
  29. item.sendMessage(message);
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. /**
  36. * 发生错误时调用
  37. */
  38. @OnError
  39. public void onError(Session session, Throwable error) {
  40. System.out.println("发生错误");
  41. error.printStackTrace();
  42. }
  43. public void sendMessage(String message) throws IOException {
  44. this.session.getBasicRemote().sendText(message);//同步
  45. //this.session.getAsyncRemote().sendText(message);//异步
  46. }
  47. }

复制代码

 

其实我也感觉很奇怪,为什么不使用接口来规范。即使是因为@ServerEndpoint注解中其它属性中可以定义出一些额外的参数,但相信也是可以抽象出来的,不过想必javaee这样做,应该是有它的用意吧。

 

 

 

 

2.4:浏览器端的代码

浏览器端的代码需要浏览器支持websocket,当然,也有socket.js可以支持到ie7,但是这个我没用过。毕竟ie基本上没人用的,市面上的浏览器基本上全部都支持websocket。

复制代码

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. </head>
  5. <body>
  6. </body>
  7. <script type="text/javascript">
  8. var websocket = null;
  9. //判断当前浏览器是否支持WebSocket
  10. if('WebSocket' in window){
  11. websocket = new WebSocket("ws://localhost:9999/websocket");
  12. }
  13. else{
  14. alert('不支持websocket')
  15. }
  16. //连接发生错误
  17. websocket.onerror = function(){
  18. };
  19. //连接成功
  20. websocket.onopen = function(event){
  21. }
  22. //接收到消息
  23. websocket.onmessage = function(event){
  24. var msg = event.data;
  25. alert("收到消息:" + msg);
  26. }
  27. //连接关闭
  28. websocket.onclose = function(){
  29. }
  30. //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  31. window.onbeforeunload = function(){
  32. websocket.close();
  33. }
  34. //发送消息
  35. function send(message){
  36. websocket.send(message);
  37. }
  38. </script>
  39. </html>

复制代码

 

如此就连接成功了。

 

 

 

 

 

三:获取HttpSession,源码分析

获取HttpSession是一个很有必要讨论的问题,因为java后台需要知道当前是哪个用户,用以处理该用户的业务逻辑,或者是对该用户进行授权之类的,但是由于websocket的协议与Http协议是不同的,所以造成了无法直接拿到session。但是问题总是要解决的,不然这个websocket协议所用的场景也就没了。

 

3.1:获取HttpSession的工具类,源码详细分析

我们先来看一下@ServerEndpoint注解的源码

复制代码

  1. package javax.websocket.server;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. import javax.websocket.Decoder;
  7. import javax.websocket.Encoder;
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Target(ElementType.TYPE)
  10. public @interface ServerEndpoint {
  11. /**
  12. * URI or URI-template that the annotated class should be mapped to.
  13. * @return The URI or URI-template that the annotated class should be mapped
  14. * to.
  15. */
  16. String value();
  17. String[] subprotocols() default {};
  18. Class<? extends Decoder>[] decoders() default {};
  19. Class<? extends Encoder>[] encoders() default {};
  20. public Class<? extends ServerEndpointConfig.Configurator> configurator()
  21. default ServerEndpointConfig.Configurator.class;
  22. }

复制代码

 

我们看到最后的一个方法,也就是加粗的方法。可以看到,它要求返回一个ServerEndpointConfig.Configurator的子类,我们写一个类去继承它。

 

按 Ctrl+C 复制代码

 

按 Ctrl+C 复制代码

当我们覆盖modifyHandshake方法时,可以看到三个参数,其中后面两个参数让我们感觉有点见过的感觉,我们查看一HandshakeRequest的源码

 

复制代码

  1. package javax.websocket.server;
  2. import java.net.URI;
  3. import java.security.Principal;
  4. import java.util.List;
  5. import java.util.Map;
  6. /**
  7. * Represents the HTTP request that asked to be upgraded to WebSocket.
  8. */
  9. public interface HandshakeRequest {
  10. static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
  11. static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
  12. static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
  13. static final String SEC_WEBSOCKET_EXTENSIONS= "Sec-WebSocket-Extensions";
  14. Map<String,List<String>> getHeaders();
  15. Principal getUserPrincipal();
  16. URI getRequestURI();
  17. boolean isUserInRole(String role);
  18. /**
  19. * Get the HTTP Session object associated with this request. Object is used
  20. * to avoid a direct dependency on the Servlet API.
  21. * @return The javax.servlet.http.HttpSession object associated with this
  22. * request, if any.
  23. */
  24. Object getHttpSession();
  25. Map<String, List<String>> getParameterMap();
  26. String getQueryString();
  27. }

复制代码

我们发现它是一个接口,接口中规范了这样的一个方法

复制代码

  1. /**
  2. * Get the HTTP Session object associated with this request. Object is used
  3. * to avoid a direct dependency on the Servlet API.
  4. * @return The javax.servlet.http.HttpSession object associated with this
  5. * request, if any.
  6. */
  7. Object getHttpSession();

复制代码

上面有相应的注释,说明可以从Servlet API中获取到相应的HttpSession。

 

当我们发现这个方法的时候,其实已经松了一口气了。

那么我们就可以补全未完成的代码

 

复制代码

  1. import javax.servlet.http.HttpSession;
  2. import javax.websocket.HandshakeResponse;
  3. import javax.websocket.server.HandshakeRequest;
  4. import javax.websocket.server.ServerEndpointConfig;
  5. import javax.websocket.server.ServerEndpointConfig.Configurator;
  6. /**
  7. * 从websocket中获取用户session
  8. *
  9. *
  10. */
  11. public class HttpSessionConfigurator extends Configurator {
  12. @Override
  13. public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
  14. HttpSession httpSession = (HttpSession) request.getHttpSession();
  15.       
  16. sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
  17. }
  18. }

复制代码

 

其实通过上面的源码分析,你们应该知道了HttpSession的获取。但是下面又多了一行代码

 sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

这行代码又是什么意思呢?

我们看一下ServerEnpointConfig的声明

public interface ServerEndpointConfig extends EndpointConfig

我们发现这个接口继承了EndpointConfig的接口,好,我们看一下EndpointConfig的源码:

 

复制代码

  1. package javax.websocket;
  2. import java.util.List;
  3. import java.util.Map;
  4. public interface EndpointConfig {
  5. List<Class<? extends Encoder>> getEncoders();
  6. List<Class<? extends Decoder>> getDecoders();
  7. Map<String,Object> getUserProperties();
  8. }

复制代码

我们发现了这样的一个方法定义

Map<String,Object> getUserProperties();

可以看到,它是一个map,从方法名也可以理解到,它是用户的一些属性的存储,那既然它提供了get方法,那么也就意味着我们可以拿到这个map,并且对这里面的值进行操作,

所以就有了上面的

sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

 

 

那么到此,获取HttpSession的源码分析,就完成了。

 

 

 

 

3.2:设置HttpSession的类

我们之前有说过,由于HTTP协议与websocket协议的不同,导致没法直接从websocket中获取协议,然后在3.1中我们已经写了获取HttpSession的代码,但是如果真的放出去执行,那么会报空指值异常,因为这个HttpSession并没有设置进去。

好,这一步,我们来设置HttpSession。这时候我们需要写一个监听器。

 

复制代码

  1. import javax.servlet.ServletRequestEvent;
  2. import javax.servlet.ServletRequestListener;
  3. import javax.servlet.http.HttpServletRequest;
  4. import org.springframework.stereotype.Component;
  5. @Component
  6. public class RequestListener implements ServletRequestListener {
  7. public void requestInitialized(ServletRequestEvent sre) {
  8. //将所有request请求都携带上httpSession
  9. ((HttpServletRequest) sre.getServletRequest()).getSession();
  10. }
  11. public RequestListener() {
  12. }
  13. public void requestDestroyed(ServletRequestEvent arg0) {
  14. }
  15. }

复制代码

 

然后我们需要把这个类注册为监听器,如果是普通的Spring工程,或者是servlet工程,那么要么在web.xml中配置,要么使用@WebListener注解。

因为本文是以Spring boot工程来演示,所以这里只写Spring boot配置Listener的代码,其它的配置方式,请自行百度。

 

这是使用@Bean注解的方式

复制代码

  1. @Autowird
  2. private RequestListener requestListener;
  1.   
  2. @Bean
  3. public ServletListenerRegistrationBean<RequestListener> servletListenerRegistrationBean() {
  4. ServletListenerRegistrationBean<RequestListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>();
  5. servletListenerRegistrationBean.setListener(requestListener);
  6. return servletListenerRegistrationBean;
  7. }

复制代码

或者也可以使用@WebListener注解

然后使用@ServletComponentScan注解,配置在启动方法上面。

 

 

 

 

3.3:在websocket中获取用户的session

然后刚才我们通过源码分析,是知道@ServerEndpoint注解中是有一个参数可以配置我们刚才继承的类。好的,我们现在进行配置。

@ServerEndpoint(value = "/websocket" , configurator = HttpSessionConfigurator.class)

 

接下来就可以在@OnOpen注解中所修饰的方法中,拿到EndpointConfig对象,并且通过这个对象,拿到之前我们设置进去的map

复制代码

  1. @OnOpen
  2. public void onOpen(Session session,EndpointConfig config){
  3. HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
  4. User user = (User)httpSession.getAttribute(SessionName.USER);
  5. if(user != null){
  6. this.session = session;
  7. this.httpSession = httpSession;
  8. }else{
  9. //用户未登陆
  10. try {
  11. session.close();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. }

复制代码

 

 

这下我们就从java的webscoket中拿到了用户的session。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/405778
推荐阅读
相关标签
  

闽ICP备14008679号