java使用websocket,而且獲取HttpSession,源碼分析

轉載請在頁首註明做者與出處javascript

 http://www.cnblogs.com/zhuxiaojie/p/6238826.htmlhtml

一:本文使用範圍

此文不只僅侷限於spring boot,普通的spring工程,甚至是servlet工程,都是同樣的,只不過配置一些監聽器的方法不一樣而已。java

 

本文通過做者實踐,確認完美運行。web

 

二:Spring boot使用websocket

2.1:依賴包

websocket自己是servlet容器所提供的服務,因此須要在web容器中運行,像咱們所使用的tomcat,固然,spring boot中已經內嵌了tomcat。spring

websocket遵循了javaee規範,因此須要引入javaee的包 api

<dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>7.0</version>
      <scope>provided</scope>
    </dependency>

固然,其實tomcat中已經自帶了這個包。瀏覽器

若是是在spring boot中,還須要加入websocket的startertomcat

        <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來提供註冊管理。websocket

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

 

 

 

 

2.3:websocket的java代碼

使用websocket的核心,就是一系列的websocket註解,@ServerEndpoint是註冊在類上面開啓。session

 

@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。

相關文章
相關標籤/搜索