java使用websocket,並且擷取HttpSession,源碼分析

來源:互聯網
上載者:User

標籤:oca   web.xml   extends   cal   3.3   run   對象   注釋   should   

一:本文使用範圍

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

 

本文經過作者實踐,確認完美運行。

 

二:Spring boot使用websocket2.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來提供註冊管理。

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

 

 

 

 

2.3:websocket的java代碼

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

 

@ServerEndpoint(value = "/websocket")@Componentpublic 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;@Componentpublic 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。

出處
: http://www.cnblogs.com/zhuxiaojie/p/6238826.html

java使用websocket,並且擷取HttpSession,源碼分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.