国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

REST 用SSE構(gòu)建實(shí)時(shí)Web應(yīng)用

2018-08-08 14:57 更新

在講Server-Sent Events (SSE) 之前,我們先來(lái)看看 HTTP 請(qǐng)求- 響應(yīng)。一個(gè)標(biāo)準(zhǔn)的 HTTP 請(qǐng)求- 響應(yīng),需要客戶端打開(kāi)一個(gè)連接,將一個(gè) HTTP 請(qǐng)求(如 HTTP GET 請(qǐng)求)發(fā)送到服務(wù)端,然后接收到 HTTP 回來(lái)的響應(yīng),如果該響應(yīng)被完全發(fā)送或者接收,服務(wù)端就會(huì)把連接關(guān)閉。通常是由某個(gè)客戶發(fā)起,客戶端才會(huì)需要請(qǐng)求所有數(shù)據(jù)。

sse-real-time-web-00

然而, Server-Sent Events (SSE) 與 HTTP 請(qǐng)求- 響應(yīng)背道而馳,它是一種機(jī)制,客戶端一旦建立起客戶機(jī)-服務(wù)器的連接,就能讓服務(wù)端將數(shù)據(jù)以異步的方式從服務(wù)器推到客戶端。當(dāng)連接由客戶端建立完成,服務(wù)端就提供數(shù)據(jù),并決定新數(shù)據(jù)“塊"可用時(shí)將其發(fā)送到客戶端。當(dāng)一個(gè)新的數(shù)據(jù)事件發(fā)生在服務(wù)端時(shí),這個(gè)事件被服務(wù)端發(fā)送到客戶端。因此,名稱被稱為 Server-Sent Events(服務(wù)器推送事件)。下面是支持服務(wù)端到客戶端交互的技術(shù)總覽:

  • 插件提供 socket 方式:比如利用 Flash XMLSocket,Java Applet 套接口,Activex 包裝的 socket。

    • 優(yōu)點(diǎn):原生 socket 的支持,與 PC 端的實(shí)現(xiàn)方式相似;
    • 缺點(diǎn):瀏覽器端需要裝相應(yīng)的插件;與 js 進(jìn)行交互時(shí)復(fù)雜
  • Polling:輪詢,重復(fù)發(fā)送新的請(qǐng)求到服務(wù)端。如果服務(wù)端沒(méi)有新的數(shù)據(jù),就發(fā)送適當(dāng)?shù)闹甘静㈥P(guān)閉連接。然后客戶端等待一段時(shí)間后,發(fā)送另一個(gè)請(qǐng)求(例如,一秒后)

    • 優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,無(wú)需做過(guò)多的更改
    • 缺點(diǎn):輪詢的間隔過(guò)長(zhǎng),會(huì)導(dǎo)致用戶不能及時(shí)接收到更新的數(shù)據(jù);輪詢的間隔過(guò)短,會(huì)導(dǎo)致查詢請(qǐng)求過(guò)多,增加服務(wù)器端的負(fù)擔(dān)。

sse-real-time-web-01

  • Long-polling:長(zhǎng)輪詢,客戶端發(fā)送一個(gè)請(qǐng)求到服務(wù)端,如果服務(wù)端沒(méi)有新的數(shù)據(jù),就保持住這個(gè)連接直到有數(shù)據(jù)。一旦服務(wù)端有了數(shù)據(jù)(消息)給客戶端,它就使用這個(gè)連接發(fā)送數(shù)據(jù)給客戶端。接著連接關(guān)閉。
    • 優(yōu)點(diǎn):比 Polling 做了優(yōu)化,有較好的時(shí)效性
    • 缺點(diǎn):需第三方庫(kù)支持,實(shí)現(xiàn)較為復(fù)雜;每次連接只能發(fā)送一個(gè)數(shù)據(jù),多個(gè)數(shù)據(jù)發(fā)送時(shí)耗費(fèi)服務(wù)器性能

sse-real-time-web-02

  • 基于 iframe 及 htmlfile 的流(streaming)方式:iframe 流方式是在頁(yè)面中插入一個(gè)隱藏的 iframe,利用其src屬性在服務(wù)器和客戶端之間創(chuàng)建一條長(zhǎng)鏈接,服務(wù)器向 iframe 傳輸數(shù)據(jù)(通常是 HTML,內(nèi)有負(fù)責(zé)插入信息的 javascript),來(lái)實(shí)時(shí)更新頁(yè)面。
    • 優(yōu)點(diǎn):消息能夠?qū)崟r(shí)到達(dá);
    • 缺點(diǎn):服務(wù)器維持著長(zhǎng)連接期會(huì)消耗資源;iframe 不規(guī)范的用法;數(shù)據(jù)推送過(guò)程會(huì)有加載進(jìn)度條顯示,界面體驗(yàn)不好

sse-real-time-web-09

  • Server-Sent events:SSE 與 長(zhǎng)輪詢機(jī)制類似,區(qū)別是每個(gè)連接不只發(fā)送一個(gè)消息??蛻舳税l(fā)送一個(gè)請(qǐng)求,服務(wù)端就保持這個(gè)連接直到有一個(gè)新的消息已經(jīng)準(zhǔn)備好了,那么它將消息發(fā)送回客戶端,同時(shí)仍然保持這個(gè)連接是打開(kāi),這樣這個(gè)連接就可以用于另一個(gè)可用消息的發(fā)送。一旦準(zhǔn)備好了一個(gè)新消息,通過(guò)同一初始連接發(fā)送回客戶端??蛻舳藛为?dú)處理來(lái)自服務(wù)端傳回的消息后不關(guān)閉連接。所以,SSE 通常重用一個(gè)連接處理多個(gè)消息(稱為事件)。SSE 還定義了一個(gè)專門(mén)的媒體類型 text/event-stream,描述一個(gè)從服務(wù)端發(fā)送到客戶端的簡(jiǎn)單格式。SSE 還提供在大多數(shù)現(xiàn)代瀏覽器里的標(biāo)準(zhǔn) javascript 客戶端 API 實(shí)現(xiàn)。關(guān)于 SSE 的更多信息,請(qǐng)參見(jiàn) SSE API 規(guī)范。
    • 優(yōu)點(diǎn):HTML5 標(biāo)準(zhǔn);實(shí)現(xiàn)較為簡(jiǎn)單;一個(gè)連接可以發(fā)送多個(gè)數(shù)據(jù)
    • 缺點(diǎn):IE 不支持 EventSource(可以使用第三方的 js 庫(kù)來(lái)解決,具體可以本章中的源碼) ;服務(wù)器只能單向推送數(shù)據(jù)到客戶端

sse-real-time-web-09

sse-real-time-web-05

  • WebSocket: WebSocket 與上述技術(shù)都不同,因?yàn)樗峁┝艘粋€(gè)真正的全雙工連接。發(fā)起者是一個(gè)客戶端,發(fā)送一個(gè)帶特殊 HTTP 頭的請(qǐng)求到服務(wù)端,通知服務(wù)器, HTTP 連接可能“升級(jí)”到一個(gè)全雙工的 TCP/IP WebSocket 連接。如果服務(wù)端支持 WebSocket,它可能會(huì)選擇升級(jí)到 WebSocket。一旦建立 WebSocket 連接,它可用于客戶機(jī)和服務(wù)器之間的雙向通信。客戶端和服務(wù)器可以隨意向?qū)Ψ桨l(fā)送數(shù)據(jù)。此時(shí),新的 WebSocket 連接上的交互不再是基于 HTTP 協(xié)議了。 WebSocket 可以用于需要快速在兩個(gè)方向上交換小塊數(shù)據(jù)的在線游戲或任何其他應(yīng)用程序。(示例可以參考http://www.waylau.com/netty-websocket-chat/)
    • 優(yōu)點(diǎn):HTML5 標(biāo)準(zhǔn);大多數(shù)瀏覽器支持;真正全雙工;性能強(qiáng)
    • 缺點(diǎn):實(shí)現(xiàn)相對(duì)復(fù)雜;ws 協(xié)議

sse-real-time-web-04

sse-real-time-web-06

SSE vs. WebSocket

用比較籠統(tǒng)的一個(gè)說(shuō)法,就是WebSocket能做的,SSE也能做,反之亦然,但是它們還是有差別的,特別是在完成某些任務(wù)方面。

WebSocket 是一種更為復(fù)雜的服務(wù)端實(shí)現(xiàn)技術(shù),但它是真正的雙向傳輸技術(shù),既能從服務(wù)端向客戶端推送數(shù)據(jù),也能從客戶端向服務(wù)端推送數(shù)據(jù)。

WebSocket 和 SSE 的瀏覽器支持率差不多,除了IE。IE是個(gè)例外,即便IE11都還不支持原生 SSE,IE10 添加了WebSocket 支持,可見(jiàn)上圖。

與 WebSocket 相比,SSE 有一些顯著的優(yōu)勢(shì)。我認(rèn)為它最大的優(yōu)勢(shì)就是便利:不需要添加任何新組件,用任何你習(xí)慣的后端語(yǔ)言和框架就能繼續(xù)使用。你不用為新建虛擬機(jī)、弄一個(gè)新的IP或新的端口號(hào)而勞神,就像在現(xiàn)有網(wǎng)站中新增一個(gè)頁(yè)面那樣簡(jiǎn)單。我喜歡把這稱為既存基礎(chǔ)設(shè)施優(yōu)勢(shì)。

SSE 的第二個(gè)優(yōu)勢(shì)是服務(wù)端的簡(jiǎn)潔。我們將在下節(jié)中看到,服務(wù)端代碼只需幾行。相對(duì)而言,WebSocket 則很復(fù)雜,不借助輔助類庫(kù)基本搞不定。

因?yàn)?SSE 能在現(xiàn)有的 HTTP/HTTPS 協(xié)議上運(yùn)作,所以它能直接運(yùn)行于現(xiàn)有的代理服務(wù)器和認(rèn)證技術(shù)。而對(duì) WebSocket 而言,代理服務(wù)器需要做一些開(kāi)發(fā)(或其他工作)才能支持,在寫(xiě)這本書(shū)時(shí),很多服務(wù)器還沒(méi)有(雖然這種狀況會(huì)改善)。SSE還有一個(gè)優(yōu)勢(shì):它是一種文本協(xié)議,腳本調(diào)試非常容易。事實(shí)上,在本書(shū)中,我們會(huì)在開(kāi)發(fā)和測(cè)試時(shí)用 curl,甚至直接在命令行中運(yùn)行后端腳本。

不過(guò),這就引出了 WebSocket 相較 SSE 的一個(gè)潛在優(yōu)勢(shì):WebSocket 是二進(jìn)制協(xié)議,而 SSE 是文本協(xié)議(通常使用UTF-8編碼)。當(dāng)然,我們可以通過(guò)SSE連接傳輸二進(jìn)制數(shù)據(jù):在 SSE 中,只有兩個(gè)具有特殊意義的字符,它們是 CR 和LF,而對(duì)它們進(jìn)行轉(zhuǎn)碼并不難。但用 SSE 傳輸二進(jìn)制數(shù)據(jù)時(shí)數(shù)據(jù)會(huì)變大,如果需要從服務(wù)端到客戶端傳輸大量的二進(jìn)制數(shù)據(jù),最好還是用 WebSocket。

WebSocket 相較 SSE 最大的優(yōu)勢(shì)在于它是雙向交流的,這意味向服務(wù)端發(fā)送數(shù)據(jù)就像從服務(wù)端接收數(shù)據(jù)一樣簡(jiǎn)單。用 SSE時(shí),一般通過(guò)一個(gè)獨(dú)立的 Ajax 請(qǐng)求從客戶端向服務(wù)端傳送數(shù)據(jù)。相對(duì)于 WebSocket,這樣使用 Ajax 會(huì)增加開(kāi)銷,但也就多一點(diǎn)點(diǎn)而已。如此一來(lái),問(wèn)題就變成了“什么時(shí)候需要關(guān)心這個(gè)差異?”如果需要以1次/秒或者更快的頻率向服務(wù)端傳輸數(shù)據(jù),那應(yīng)該用 WebSocket。0.2次/秒到1次/秒的頻率是一個(gè)灰色地帶,用 WebSocket 和用 SSE 差別不大;但如果你期望重負(fù)載,那就有必要確定基準(zhǔn)點(diǎn)。頻率低于0.2次/秒左右時(shí),兩者差別不大。

從服務(wù)端向客戶端傳輸數(shù)據(jù)的性能如何?如果是文本數(shù)據(jù)而非二進(jìn)制數(shù)據(jù)(如前文所提到的),SSE和WebSocket沒(méi)什么區(qū)別。它們都用TCP/IP套接字,都是輕量級(jí)協(xié)議。延遲、帶寬、服務(wù)器負(fù)載等都沒(méi)有區(qū)別。

在舊版本瀏覽器上的兼容,WebSocket 難兼容,SSE 易兼容。

SSE 的應(yīng)用場(chǎng)景

看了上述的定義,可以知道 SSE 適合應(yīng)用于服務(wù)端單向推送信息到客戶端的場(chǎng)景。 Jersey 的 SSE 大致可以分為發(fā)布-訂閱模式和廣播模式。

為使用 Jersey SSE, 添加如下依賴:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-sse</artifactId>
</dependency> 

發(fā)布-訂閱模式

服務(wù)端代碼:

@Path("see-events")
public class SseResource {

    private EventOutput eventOutput = new EventOutput();
    private OutboundEvent.Builder eventBuilder;
    private OutboundEvent event ;

    /**
     * 提供 SSE 事件輸出通道的資源方法
     * @return eventOutput
     */
     @GET
    @Produces(SseFeature.SERVER_SENT_EVENTS)
    public EventOutput getServerSentEvents() {

         // 不斷循環(huán)執(zhí)行
        while (true) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設(shè)置日期格式
            String now =  df.format(new Date()); //獲取當(dāng)前系統(tǒng)時(shí)間
            String message = "Server Time:" + now;
            System.out.println( message );

            eventBuilder = new OutboundEvent.Builder();
            eventBuilder.id(now);
            eventBuilder.name("message");
            eventBuilder.data(String.class,
                    message );  // 推送服務(wù)器時(shí)間的信息給客戶端
            event = eventBuilder.build();
            try {
                eventOutput.write(event);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    eventOutput.close();
                    return eventOutput;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

上面的代碼定義了資源部署在 URI "see-events"。這個(gè)資源有一個(gè) @GET 資源方法返回作為一個(gè)實(shí)體 EventOutput ——通用 Jersey ChunkedOutput API 的擴(kuò)展用于輸出分塊消息處理。

客戶端代碼:

//判斷瀏覽器是否支持 EventSource
if (typeof (EventSource) !== "undefined") {
    var source = new EventSource("webapi/see-events");

    // 當(dāng)通往服務(wù)器的連接被打開(kāi)
    source.onopen = function(event) {
        console.log("連接開(kāi)啟!");

    };

    // 當(dāng)接收到消息。只能是事件名稱是 message
    source.onmessage = function(event) {
        console.log(event.data);
        var data = event.data;
        var lastEventId = event.lastEventId;
        document.getElementById("x").innerHTML += "\n" + 'lastEventId:'+lastEventId+';data:'+data;
    };

    //可以是任意命名的事件名稱
    /*
    source.addEventListener('message', function(event) {
        console.log(event.data);
        var data = event.data;
        var lastEventId = event.lastEventId;
        document.getElementById("x").innerHTML += "\n" + 'lastEventId:'+lastEventId+';data:'+data;
    });
    */

    // 當(dāng)錯(cuò)誤發(fā)生
    source.onerror = function(event) {
        console.log("連接錯(cuò)誤!");

    };
} else {
    document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events..."
}

首先要判斷瀏覽器是否支持 EventSource,而后,EventSource 對(duì)象分別監(jiān)聽(tīng) onopen、onmessage、onerror 事件。其中, source.onmessage = function(event) {} 和 source.addEventListener('message', function(event) {} 是一樣的,區(qū)別是,后者可以支持監(jiān)聽(tīng)不同名稱的事件,而 onmessage 屬性只支持一個(gè)事件處理方法。。

效果

運(yùn)行項(xiàng)目

mvn jetty:run

瀏覽器訪問(wèn) http://localhost:8080

sse-real-time-web-07

廣播模式

服務(wù)端代碼:

@Singleton
@Path("sse-chat")
public class SseChatResource {

    private SseBroadcaster broadcaster = new SseBroadcaster();

    /**
     * 提供 SSE 事件輸出通道的資源方法
     * @return eventOutput
     */
    @GET
    @Produces(SseFeature.SERVER_SENT_EVENTS)
    public EventOutput listenToBroadcast() {
        EventOutput eventOutput = new EventOutput();
        this.broadcaster.add(eventOutput);
        return eventOutput;
    }

    /**
     * 提供 寫(xiě)入 SSE 事件通道的資源方法
     * @param message
     * @param name
     */
    @POST
    @Produces(MediaType.TEXT_PLAIN)
    public void broadcastMessage(@DefaultValue("waylau.com") @QueryParam("message")  String message,
            @DefaultValue("waylau") @QueryParam("name")  String name) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設(shè)置日期格式
        String now =  df.format(new Date()); //獲取當(dāng)前系統(tǒng)時(shí)間
        message = now +":"+ name +":"+ message;  // 發(fā)送的消息帶上當(dāng)前的時(shí)間

        OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder();
        OutboundEvent event = eventBuilder.name("message")
            .mediaType(MediaType.TEXT_PLAIN_TYPE)
            .data(String.class, message)
            .build();

        // 發(fā)送廣播
        broadcaster.broadcast(event);
     }
}

其中,SseChatResource 資源類用 @Singleton 注解,告訴 Jersey 運(yùn)行時(shí),資源類只有一個(gè)實(shí)例,用于所有傳入/sse-chat路徑的請(qǐng)求。應(yīng)用程序引用私有的 broadcaster 字段,這樣我們?yōu)樗姓?qǐng)求可以使用相同的實(shí)例。客戶端想監(jiān)聽(tīng) SSE 事件,先發(fā)送 GET 請(qǐng)求到sse-chat的 listenToBroadcast() 資源方法處理。方法創(chuàng)建一個(gè)新的 EventOutput 用于展示請(qǐng)求的客戶端的連接,并通過(guò) add(EventOutput) 注冊(cè) eventOutput 實(shí)例到單例 broadcaster。方法返回 eventOutput 導(dǎo)致 Jersey 使請(qǐng)求的客戶端事件與 eventOutput 實(shí)例綁定,向客戶機(jī)發(fā)送響應(yīng) HTTP 頭??蛻舳诉B接保持開(kāi)放,客戶端等待準(zhǔn)備接收新的 SSE 事件。所有的事件通過(guò) broadcaster 寫(xiě)入 eventOutput。這樣開(kāi)發(fā)人員可以方便地處理發(fā)送新的事件到所有訂閱的客戶端。

當(dāng)客戶端想要廣播新消息給所有的已經(jīng)監(jiān)聽(tīng) SSE 連接的客戶端時(shí),它先發(fā)送一個(gè) POST 請(qǐng)求將消息內(nèi)容發(fā)到 SseChatResource 資源。 SseChatResource 資源調(diào)用方法 broadcastMessage,消息內(nèi)容作為輸入?yún)?shù)。一個(gè)新的 SSE 出站事件是建立在標(biāo)準(zhǔn)方法上并傳遞給 broadcaster。broadcaster 內(nèi)部在所有注冊(cè)了的 EventOutput 上調(diào)用 write(OutboundEvent) 。當(dāng)該方法只返回一個(gè)標(biāo)準(zhǔn)文本響應(yīng)給客戶端,來(lái)通知客戶端已經(jīng)成功廣播了消息。正如您可以看到的, broadcastMessage 資源方法只是一個(gè)簡(jiǎn)單的 JAX-RS 資源的方法。

您可能已經(jīng)注意到,Jersey SseBroadcaster 完成該用例不是強(qiáng)制性的。每個(gè) EventOutput 可以只是存儲(chǔ)在收集器里,在 broadcastMessage 方法里面迭代。然而,SseBroadcaster 內(nèi)部會(huì)識(shí)別和處理客戶端斷開(kāi)連接。當(dāng)客戶端關(guān)閉了連接,broadcaster 可檢測(cè)并刪除過(guò)期的在內(nèi)部收集器里面注冊(cè)了 EventOutput 的連接,以及釋放所有服務(wù)器端關(guān)聯(lián)了陳舊連接的資源。此外,SseBroadcaster 的實(shí)現(xiàn)是線程安全的,這樣客戶端可以在任何時(shí)間連接和斷開(kāi), SseBroadcaster 總是廣播消息給最近收集的注冊(cè)和活躍的客戶端。

客戶端代碼:

//判斷瀏覽器是否支持 EventSource
if (typeof (EventSource) !== "undefined") {
    var source = new EventSource("webapi/sse-chat");

    // 當(dāng)通往服務(wù)器的連接被打開(kāi)
    source.onopen = function(event) {
        var ta = document.getElementById('response_text');
        ta.value = '連接開(kāi)啟!';
    };

    // 當(dāng)接收到消息。只能是事件名稱是 message
    source.onmessage = function(event) {
        var ta = document.getElementById('response_text');
        ta.value = ta.value + '\n' + event.data;
    };

    //可以是任意命名的事件名稱
    /*
    source.addEventListener('message', function(event) {
         var ta = document.getElementById('response_text');
         ta.value = ta.value + '\n' + event.data;
    });
    */

    // 當(dāng)錯(cuò)誤發(fā)生
    source.onerror = function(event) {
        var ta = document.getElementById('response_text');
        ta.value = ta.value + '\n' + "連接出錯(cuò)!";

    };
} else {
    alert("Sorry, your browser does not support server-sent events");
}

function send(message) {
    var xmlhttp;
    var name = document.getElementById('name_id').value;

    if (window.XMLHttpRequest)
    {// code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp=new XMLHttpRequest();
    }
    else
    {// code for IE6, IE5
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }

    xmlhttp.open("POST","webapi/sse-chat?message=" + message +'&name=' + name ,true);
    xmlhttp.send();
}

EventSource 的用法與發(fā)布-訂閱模式類似。而 send(message) 方法是將消息以 POST 請(qǐng)求發(fā)送給服務(wù)端,而后將該消息進(jìn)行廣播,從而達(dá)到了聊天室的效果。

效果

sse-real-time-web-08

相關(guān)問(wèn)題

異步請(qǐng)求

報(bào)如下錯(cuò)誤:

八月 18, 2015 7:48:28 下午 org.glassfish.jersey.servlet.internal.ResponseWriter suspend
WARNING: Attempt to put servlet request into asynchronous mode has failed. Please check your servlet configuration - all Servlet instances and Servlet filters involved in the request processing must explicitly declare support for asynchronous request processing.
java.lang.IllegalStateException: !asyncSupported
    at org.eclipse.jetty.server.Request.startAsync(Request.java:2072)
    at org.glassfish.jersey.servlet.async.AsyncContextDelegateProviderImpl$ExtensionImpl.getAsyncContext(AsyncContextDelegateProviderImpl.java:112)
    at org.glassfish.jersey.servlet.async.AsyncContextDelegateProviderImpl$ExtensionImpl.suspend(AsyncContextDelegateProviderImpl.java:96)
    at org.glassfish.jersey.servlet.internal.ResponseWriter.suspend(ResponseWriter.java:121)
    at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:747)
    at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:424)
    at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:414)
    at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:312)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:292)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1139)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:460)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:386)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:334)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:587)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:497)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
    at java.lang.Thread.run(Thread.java:722)

是指服務(wù)器不支持異步請(qǐng)求。解決方法是在 web.xml 中添加

<async-supported>true</async-supported>

最后的 web.xml 為:

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    id="WebApp_ID" version="3.1">

    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.waylau.rest.RestApplication</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/webapi/*</url-pattern>
    </servlet-mapping>
</web-app>

跨域請(qǐng)求

由于瀏覽器同源策略,凡是發(fā)送請(qǐng)求url的協(xié)議、域名、端口三者之間任意一與當(dāng)前頁(yè)面地址不同即為跨域。

URL說(shuō)明是否允許通信
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下允許
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同文件夾允許
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口不允許
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同協(xié)議不允許
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名對(duì)應(yīng)ip不允許
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同不允許
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二級(jí)域名(同上)不允許(cookie這種情況下也不允許訪問(wèn))
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名不允許

出于安全考慮,默認(rèn)是不允許跨域訪問(wèn)的,會(huì)報(bào)如下異常:

sse-real-time-web-10

解決是服務(wù)器啟動(dòng) CORS。

先是做一個(gè)過(guò)濾器 CrossDomainFilter.java,將響應(yīng)頭“Access-Control-Allow-Origin”設(shè)置為“*”

@Override
public void filter(ContainerRequestContext requestContext,
        ContainerResponseContext responseContext) throws IOException {

    // 響應(yīng)頭添加了對(duì)允許訪問(wèn)的域,* 代表是全部域
    responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); 

}

在 RestApplication 里,注冊(cè)該過(guò)濾器即可。

public class RestApplication extends ResourceConfig {

    public RestApplication() {
        // 資源類所在的包路徑  
        packages("com.waylau.rest.resource");

        // 注冊(cè) MultiPart
        register(MultiPartFeature.class);

        // 注冊(cè)CORS過(guò)濾器
        register(CrossDomainFilter.class);
    }
}

這樣,就能跨域訪問(wèn)了,如下,192.168.11.103 可以訪問(wèn) 192.168.11.125 站下的資源

sse-real-time-web-11

源碼

見(jiàn) sse-real-time-web 項(xiàng)目

參考:


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)