欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java使用websocket,并且获取HttpSession 源码分析(推荐)

程序员文章站 2024-02-23 12:33:04
一:本文使用范围 此文不仅仅局限于spring boot,普通的spring工程,甚至是servlet工程,都是一样的,只不过配置一些监听器的方法不同而已。 本文经过作...

一:本文使用范围

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

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

二:spring boot使用websocket

2.1:依赖包

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

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

<dependency>
   <groupid>javax</groupid>
   <artifactid>javaee-api</artifactid>
   <version>7.0</version>
   <scope>provided</scope>
  </dependency>

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

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

    <dependency>
      <groupid>org.springframework.boot</groupid>
      <artifactid>spring-boot-starter-websocket</artifactid>
      <version>1.4.0.release</version>
    </dependency>

2.2:配置websocket

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

@configuration
public class websocketconfig {
  @bean
  public serverendpointexporter serverendpointexporter() {
    return new serverendpointexporter();
  }
}

2.3:websocket的java代码

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

@serverendpoint(value = "/websocket")
@component
public class mywebsocket {

  //与某个客户端的连接会话,需要通过它来给客户端发送数据
  private session session;

  /**
   * 连接成功*/
  @onopen
  public void onopen(session session) {
    this.session = session;
  }

  /**
   * 连接关闭调用的方法
   */
  @onclose
  public void onclose() {
  }

  /**
   * 收到消息
   *
   * @param message 
  */
  @onmessage
  public void onmessage(string message, session session) {
    system.out.println("来自浏览器的消息:" + message);

    //群发消息
    for (mywebsocket item : websocketset) {
      try {
        item.sendmessage(message);
      } catch (ioexception e) {
        e.printstacktrace();
      }
    }
  }

  /**
   * 发生错误时调用
   */
  @onerror
  public void onerror(session session, throwable error) {
    system.out.println("发生错误");
    error.printstacktrace();
  }
  public void sendmessage(string message) throws ioexception {
    this.session.getbasicremote().sendtext(message);//同步
    //this.session.getasyncremote().sendtext(message);//异步
  }
}

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

2.4:浏览器端的代码

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

<!doctype html>
<html>
<head>
</head>
<body>
</body>
<script type="text/javascript">
  var websocket = null;
  //判断当前浏览器是否支持websocket
  if('websocket' in window){
    websocket = new websocket("ws://localhost:9999/websocket");
  }
  else{
    alert('不支持websocket')
  }
  //连接发生错误
  websocket.onerror = function(){
    
  };
  //连接成功
  websocket.onopen = function(event){  
  }
  //接收到消息
  websocket.onmessage = function(event){
    var msg = event.data;
    alert("收到消息:" + msg);
  }
  //连接关闭
  websocket.onclose = function(){    
  }
  //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  window.onbeforeunload = function(){
    websocket.close();
  }
  //发送消息
  function send(message){
    websocket.send(message);
  }
</script>
</html>

 如此就连接成功了。

三:获取httpsession,源码分析

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

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

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

package javax.websocket.server;
import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;
import javax.websocket.decoder;
import javax.websocket.encoder;
@retention(retentionpolicy.runtime)
@target(elementtype.type)
public @interface serverendpoint {
  /**
   * uri or uri-template that the annotated class should be mapped to.
   * @return the uri or uri-template that the annotated class should be mapped
   *     to.
   */
  string value();
  string[] subprotocols() default {};
  class<? extends decoder>[] decoders() default {};
  class<? extends encoder>[] encoders() default {};
  public class<? extends serverendpointconfig.configurator> configurator()
      default serverendpointconfig.configurator.class;
}

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

import javax.servlet.http.httpsession;
import javax.websocket.handshakeresponse;
import javax.websocket.server.handshakerequest;
import javax.websocket.server.serverendpointconfig;
import javax.websocket.server.serverendpointconfig.configurator;
public class httpsessionconfigurator extends configurator {
  @override
  public void modifyhandshake(serverendpointconfig sec, handshakerequest request, handshakeresponse response) {
    //怎么搞?
  }
}

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

package javax.websocket.server;
import java.net.uri;
import java.security.principal;
import java.util.list;
import java.util.map;
/**
 * represents the http request that asked to be upgraded to websocket.
 */
public interface handshakerequest {
  static final string sec_websocket_key = "sec-websocket-key";
  static final string sec_websocket_protocol = "sec-websocket-protocol";
  static final string sec_websocket_version = "sec-websocket-version";
  static final string sec_websocket_extensions= "sec-websocket-extensions";
  map<string,list<string>> getheaders();
  principal getuserprincipal();
  uri getrequesturi();
  boolean isuserinrole(string role);
  /**
   * get the http session object associated with this request. object is used
   * to avoid a direct dependency on the servlet api.
   * @return the javax.servlet.http.httpsession object associated with this
   *     request, if any.
   */
  object gethttpsession();
  map<string, list<string>> getparametermap();
  string getquerystring();
}

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

  /**
   * get the http session object associated with this request. object is used
   * to avoid a direct dependency on the servlet api.
   * @return the javax.servlet.http.httpsession object associated with this
   *     request, if any.
   */
  object gethttpsession();

上面有相应的注释,说明可以从servlet api中获取到相应的httpsession。

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

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

import javax.servlet.http.httpsession;
import javax.websocket.handshakeresponse;
import javax.websocket.server.handshakerequest;
import javax.websocket.server.serverendpointconfig;
import javax.websocket.server.serverendpointconfig.configurator;

/**
 * 从websocket中获取用户session
 *
 *
 */
public class httpsessionconfigurator extends configurator {
  @override
  public void modifyhandshake(serverendpointconfig sec, handshakerequest request, handshakeresponse response) {
     httpsession httpsession = (httpsession) request.gethttpsession();
          sec.getuserproperties().put(httpsession.class.getname(), httpsession);
  }
}

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

 sec.getuserproperties().put(httpsession.class.getname(), httpsession);

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

我们看一下serverenpointconfig的声明

public interface serverendpointconfig extends endpointconfig

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

package javax.websocket;
import java.util.list;
import java.util.map;
public interface endpointconfig {
  list<class<? extends encoder>> getencoders();
  list<class<? extends decoder>> getdecoders();
  map<string,object> getuserproperties();
}

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

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。这时候我们需要写一个监听器。

import javax.servlet.servletrequestevent;
import javax.servlet.servletrequestlistener;
import javax.servlet.http.httpservletrequest;
import org.springframework.stereotype.component;
@component
public class requestlistener implements servletrequestlistener {
  public void requestinitialized(servletrequestevent sre) {
    //将所有request请求都携带上httpsession
    ((httpservletrequest) sre.getservletrequest()).getsession();
  }
  public requestlistener() {
  }
  public void requestdestroyed(servletrequestevent arg0) {
  }
}

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

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

这是使用@bean注解的方式

@autowird
private requestlistener requestlistener;
  @bean
  public servletlistenerregistrationbean<requestlistener> servletlistenerregistrationbean() {
    servletlistenerregistrationbean<requestlistener> servletlistenerregistrationbean = new servletlistenerregistrationbean<>();
    servletlistenerregistrationbean.setlistener(requestlistener);
    return servletlistenerregistrationbean;
  }

或者也可以使用@weblistener注解

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

3.3:在websocket中获取用户的session

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

@serverendpoint(value = "/websocket" , configurator = httpsessionconfigurator.class)

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

@onopen
  public void onopen(session session,endpointconfig config){
    httpsession httpsession= (httpsession) config.getuserproperties().get(httpsession.class.getname());
    user user = (user)httpsession.getattribute(sessionname.user);
    if(user != null){
      this.session = session;
      this.httpsession = httpsession;
    }else{
      //用户未登陆
      try {
        session.close();
      } catch (ioexception e) {
        e.printstacktrace();
      }
    }
  }

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

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助~如果有疑问大家可以留言交流,谢谢大家对的支持!