使用springboot来实现WebLog
使用websocket技術(shù)實(shí)時(shí)輸出系統(tǒng)日志到瀏覽器端,實(shí)現(xiàn)WebLog?
?
?
boot-websocket-log:
spring boot系統(tǒng)中使用websocket技術(shù)實(shí)時(shí)輸出系統(tǒng)日志到瀏覽器端,因?yàn)槭菍?shí)時(shí)輸出,所有第一時(shí)間就想到了使用webSocket,而且在spring boot中,使用websocket超級方便,閱讀本文,你會接觸到以下關(guān)鍵詞相關(guān)技術(shù):
WebSocket(stopmp服務(wù)端)
stomp協(xié)議
sockjs.min.js
stomp.min.js(stomp客戶端),
本文使用到的其實(shí)就是
使用spring boot自帶的webSocket模塊提供stomp的服務(wù)端,
前端使用stomp.min.js做stomp的客戶端,
使用sockjs來鏈接,
前端訂閱后端日志端點(diǎn)的消息,后端實(shí)時(shí)推送,達(dá)到日志實(shí)時(shí)輸出到web頁面的目的
?
此項(xiàng)目使用場景
集成到已有的項(xiàng)目中,實(shí)現(xiàn)項(xiàng)目日志文件web端瀏覽(第一個(gè)功能)
單獨(dú)使用,指定已有項(xiàng)目的日志文件位置,實(shí)現(xiàn)項(xiàng)目日志web端瀏覽(第二個(gè)功能)
使用boot-websocket-log的stopmp服務(wù)推送日志其他的非web端消費(fèi)
?
?
參考博文:https://gitee.com/kailing/boot-websocket-log
?
知識補(bǔ)充1:stomp協(xié)議:
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,面向消息的簡單文本協(xié)議,它提供了一個(gè)可互操作的連接格式,允許STOMP客戶端與任意STOMP消息代理(Broker)進(jìn)行交互。STOMP協(xié)議由于設(shè)計(jì)簡單,易于開發(fā)客戶端,因此在多種語言和多種平臺上得到廣泛地應(yīng)用。
?
STOMP協(xié)議的前身是TTMP協(xié)議(一個(gè)簡單的基于文本的協(xié)議),專為消息中間件設(shè)計(jì)。
STOMP是一個(gè)非常簡單和容易實(shí)現(xiàn)的協(xié)議,其設(shè)計(jì)靈感源自于HTTP的簡單性。盡管STOMP協(xié)議在服務(wù)器端的實(shí)現(xiàn)可能有一定的難度,但客戶端的實(shí)現(xiàn)卻很容易。例如,可以使用Telnet登錄到任何的STOMP代理,并與STOMP代理進(jìn)行交互。
STOMP協(xié)議與HTTP協(xié)議很相似,它基于TCP協(xié)議,使用了以下命令:
CONNECT
SEND
SUBSCRIBE
UNSUBSCRIBE
BEGIN
COMMIT
ABORT
ACK
NACK
DISCONNECT
STOMP的客戶端和服務(wù)器之間的通信是通過“幀”(Frame)實(shí)現(xiàn)的,每個(gè)幀由多“行”(Line)組成。
第一行包含了命令,然后緊跟鍵值對形式的Header內(nèi)容。
第二行必須是空行。
第三行開始就是Body內(nèi)容,末尾都以空字符結(jié)尾。
例:?
發(fā)送消息
SEND?
destination:/queue/trade?
content-type:application/json?
content-length:44?
{“action”:”BUY”,”ticker”:”MMM”,”shares”,44}^@
?
訂閱消息
SUBSCRIBE?
id:sub-1?
destination:/topic/price.stock.*?
^@
?
服務(wù)器進(jìn)行廣播消息
MESSAGE?
message-id:nxahklf6-1?
subscription:sub-1?
destination:/topic/price.stock.MMM?
{“ticker”:”MMM”,”price”:129.45}^@
?
?
客戶端 API
引入stomp.js
<script type="application/javascript" src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>- ?
發(fā)起連接
客戶端可以通過使用Stomp.js和sockjs-client連接
// 建立連接對象(還未發(fā)起連接)
var socket=new SockJS("/spring-websocket-portfolio/portfolio");
// 獲取 STOMP 子協(xié)議的客戶端對象
var stompClient = Stomp.over(socket);
// 向服務(wù)器發(fā)起websocket連接并發(fā)送CONNECT幀
stompClient.connect(
? ? {},
function connectCallback (frame) {
? ? // 連接成功時(shí)(服務(wù)器響應(yīng) CONNECTED 幀)的回調(diào)方法
? ? ? ? document.getElementById("state-info").innerHTML = "連接成功";
? ? ? ? console.log('已連接【' + frame + '】');
? ? ? ? stompClient.subscribe('/topic/getResponse', function (response) {
? ? ? ? ? ? showResponse(response.body);
? ? ? ? });
? ? },
function errorCallBack (error) {
? ? // 連接失敗時(shí)(服務(wù)器響應(yīng) ERROR 幀)的回調(diào)方法
? ? ? ? document.getElementById("state-info").innerHTML = "連接失敗";
? ? ? ? console.log('連接失敗【' + error + '】');
? ? }
);
stompClient.connect()方法簽名:
client.connect(headers, connectCallback, errorCallback);?
其中?
headers表示客戶端的認(rèn)證信息,如:
var headers = {
? login: 'mylogin',
? passcode: 'mypasscode',
? // additional header
? 'client-id': 'my-client-id'
};
若無需認(rèn)證,直接使用空對象 “{}” 即可;
connectCallback 表示連接成功時(shí)(服務(wù)器響應(yīng) CONNECTED 幀)的回調(diào)方法;?
errorCallback 表示連接失敗時(shí)(服務(wù)器響應(yīng) ERROR 幀)的回調(diào)方法,非必須;
?
斷開連接
若要從客戶端主動斷開連接,可調(diào)用 disconnect() 方法
client.disconnect(function () {alert("See you next time!"); };該方法為異步進(jìn)行,因此包含了回調(diào)參數(shù),操作完成時(shí)自動回調(diào);
?
心跳機(jī)制
若使用STOMP 1.1 版本,默認(rèn)開啟了心跳檢測機(jī)制,可通過client對象的heartbeat field進(jìn)行配置(默認(rèn)值都是10000 ms):
client.heartbeat.outgoing = 20000; ?// client will send heartbeats every 20000ms
client.heartbeat.incoming = 0; ? ? ?// client does not want to receive heartbeats from the server
// The heart-beating is using window.setInterval() to regularly send heart-beats and/or check server heart-beats
發(fā)送信息
連接成功后,客戶端可使用 send() 方法向服務(wù)器發(fā)送信息:
client.send(destination url[, headers[, body]]);
其中?
destination url 為服務(wù)器 controller中 @MessageMapping 中匹配的URL,字符串,必須參數(shù);?
headers 為發(fā)送信息的header,JavaScript 對象,可選參數(shù);?
body 為發(fā)送信息的 body,字符串,可選參數(shù);
例:
client.send("/queue/test", {priority: 9}, "Hello, STOMP");
client.send("/queue/test", {}, "Hello, STOMP");
?
訂閱、接收信息
STOMP 客戶端要想接收來自服務(wù)器推送的消息,必須先訂閱相應(yīng)的URL,即發(fā)送一個(gè) SUBSCRIBE 幀,然后才能不斷接收來自服務(wù)器的推送消息;?
訂閱和接收消息通過 subscribe() 方法實(shí)現(xiàn):
subscribe(destination url, callback[, headers])
其中?
destination url 為服務(wù)器 @SendTo 匹配的 URL,字符串;?
callback 為每次收到服務(wù)器推送的消息時(shí)的回調(diào)方法,該方法包含參數(shù) message;?
headers 為附加的headers,JavaScript 對象;什么作用??
該方法返回一個(gè)包含了id屬性的 JavaScript 對象,可作為 unsubscribe() 方法的參數(shù);
例:var headers = {ack: 'client', 'selector': "location = 'Europe'"};
var ?callback = function(message) {
? if (message.body) {
? ? alert("got message with body " + message.body)
? } else {
? ? alert("got empty message");
? }
});
var subscription = client.subscribe("/queue/test", callback, headers);
取消訂閱
var subscription = client.subscribe(...);
subscription.unsubscribe();
JSON 支持
STOMP 幀的 body 必須是 string 類型,若希望接收/發(fā)送 json 對象,可通過 JSON.stringify() and JSON.parse() 實(shí)現(xiàn);?
例:
var quote = {symbol: 'APPL', value: 195.46};
client.send("/topic/stocks", {}, JSON.stringify(quote));
client.subcribe("/topic/stocks", function(message) {
var quote = JSON.parse(message.body);
alert(quote.symbol + " is at " + quote.value);
});
事務(wù)支持
STOMP 客戶端支持在發(fā)送消息時(shí)用事務(wù)進(jìn)行處理:?
舉例說明:
// start the transaction
// 該方法返回一個(gè)包含了事務(wù) id、commit()、abort() 的JavaScript 對象
var tx = client.begin();
// send the message in a transaction
// 最關(guān)鍵的在于要在 headers 對象中加入事務(wù) id,若沒有添加,則會直接發(fā)送消息,不會以事務(wù)進(jìn)行處理
client.send("/queue/test", {transaction: tx.id}, "message in a transaction");
// commit the transaction to effectively send the message
tx.commit();
// tx.abort();
Debug 信息
STOMP 客戶端默認(rèn)將傳輸過程中的所有 debug 信息以 console.log() 形式輸出到客戶端瀏覽器中,也可通過以下方式輸出到 DOM 中:
client.debug = function(str) {
? ? // str 參數(shù)即為 debug 信息
// append the debug log to a #debug div somewhere in the page using JQuery:
$("#debug").append(str + "\n");
};
認(rèn)證
這一部分內(nèi)容看的不是很理解,因此直接將原文放在這里了,待補(bǔ)充。?
By default, STOMP messages will be automatically acknowledged by the server before the message is delivered to the client.
The client can chose instead to handle message acknowledgement by subscribing to a destination and specify a ack header set to client or client-individual.
In that case, the client must use the message.ack() method to inform the server that it has acknowledge the message.
var subscription = client.subscribe("/queue/test",
? function(message) {
? ? // do something with the message
? ? ...
? ? // and acknowledge it
? ? message.ack();
? },
? {ack: 'client'}
);
The ack() method accepts a headers argument for additional headers to acknowledge the message. For example, it is possible to acknowledge a message as part of a transaction and ask for a receipt when the ACK STOMP frame has effectively be processed by the broker:?
var tx = client.begin();?
message.ack({ transaction: tx.id, receipt: ‘my-receipt’ });?
tx.commit();?
The nack() method can also be used to inform STOMP 1.1 brokers that the client did not consume the message. It takes the same arguments than the ack() method.
參考博文:https://blog.csdn.net/jqsad/article/details/77745379
?
同 HTTP 在 TCP 套接字上添加請求-響應(yīng)模型層一樣,STOMP 在 WebSocket 之上提供了一個(gè)基于幀的線路格式層,用來定義消息語義;
?
?
?
補(bǔ)充知識2:websocket:
首先HTTP有1.1和1.0之說,也就是所謂的keep-alive,把多個(gè)HTTP請求合并為一個(gè),但是Websocket其實(shí)是一個(gè)新協(xié)議,跟HTTP協(xié)議基本沒有關(guān)系,只是為了兼容現(xiàn)有瀏覽器的握手規(guī)范而已,也就是說它是HTTP協(xié)議上的一種補(bǔ)充可以通過這樣一張圖理解:
重點(diǎn):兩個(gè)關(guān)鍵(主動推送信息,持久連接)
二、Websocket是什么樣的協(xié)議,具體有什么優(yōu)點(diǎn)
首先,Websocket是一個(gè)持久化的協(xié)議,相對于HTTP這種非持久的協(xié)議來說。
簡單的舉個(gè)例子吧,用目前應(yīng)用比較廣泛的PHP生命周期來解釋。
1) HTTP的生命周期通過Request來界定,也就是一個(gè)Request 一個(gè)Response,那么在HTTP1.0中,這次HTTP請求就結(jié)束了。
在HTTP1.1中進(jìn)行了改進(jìn),使得有一個(gè)keep-alive,也就是說,在一個(gè)HTTP連接中,可以發(fā)送多個(gè)Request,接收多個(gè)Response。
但是請記住 Request = Response , 在HTTP中永遠(yuǎn)是這樣,也就是說一個(gè)request只能有一個(gè)response。而且這個(gè)response也是被動的,不能主動發(fā)起。
教練,你BB了這么多,跟Websocket有什么關(guān)系呢?
_(:з」∠)_好吧,我正準(zhǔn)備說Websocket呢。。
首先Websocket是基于HTTP協(xié)議的,或者說借用了HTTP的協(xié)議來完成一部分握手。
在握手階段是一樣的
-------以下涉及專業(yè)技術(shù)內(nèi)容,不想看的可以跳過lol:,或者只看加黑內(nèi)容--------
首先我們來看個(gè)典型的Websocket握手(借用Wikipedia的。。)
熟悉HTTP的童鞋可能發(fā)現(xiàn)了,這段類似HTTP協(xié)議的握手請求中,多了幾個(gè)東西。
我會順便講解下作用。
這個(gè)就是Websocket的核心了,告訴Apache、Nginx等服務(wù)器:注意啦,窩發(fā)起的是Websocket協(xié)議,快點(diǎn)幫我找到對應(yīng)的助理處理~不是那個(gè)老土的HTTP。
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13首先,Sec-WebSocket-Key 是一個(gè)Base64 encode的值,這個(gè)是瀏覽器隨機(jī)生成的,告訴服務(wù)器:泥煤,不要忽悠窩,我要驗(yàn)證尼是不是真的是Websocket助理。
然后,Sec_WebSocket-Protocol 是一個(gè)用戶定義的字符串,用來區(qū)分同URL下,不同的服務(wù)所需要的協(xié)議。簡單理解:今晚我要服務(wù)A,別搞錯(cuò)啦~
最后,Sec-WebSocket-Version 是告訴服務(wù)器所使用的Websocket Draft(協(xié)議版本),在最初的時(shí)候,Websocket協(xié)議還在 Draft 階段,各種奇奇怪怪的協(xié)議都有,而且還有很多期奇奇怪怪不同的東西,什么Firefox和Chrome用的不是一個(gè)版本之類的,當(dāng)初Websocket協(xié)議太多可是一個(gè)大難題。。不過現(xiàn)在還好,已經(jīng)定下來啦~大家都使用的一個(gè)東西~ 脫水:服務(wù)員,我要的是13歲的噢→_→
然后服務(wù)器會返回下列東西,表示已經(jīng)接受到請求, 成功建立Websocket啦!
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat這里開始就是HTTP最后負(fù)責(zé)的區(qū)域了,告訴客戶,我已經(jīng)成功切換協(xié)議啦~
Upgrade: websocket Connection: Upgrade依然是固定的,告訴客戶端即將升級的是Websocket協(xié)議,而不是mozillasocket,lurnarsocket或者shitsocket。
然后,Sec-WebSocket-Accept 這個(gè)則是經(jīng)過服務(wù)器確認(rèn),并且加密過后的 Sec-WebSocket-Key。服務(wù)器:好啦好啦,知道啦,給你看我的ID CARD來證明行了吧。。
后面的,Sec-WebSocket-Protocol 則是表示最終使用的協(xié)議。
至此,HTTP已經(jīng)完成它所有工作了,接下來就是完全按照Websocket協(xié)議進(jìn)行了。
具體的協(xié)議就不在這闡述了。
你TMD又BBB了這么久,那到底Websocket有什么鬼用,http long poll,或者ajax輪詢不都可以實(shí)現(xiàn)實(shí)時(shí)信息傳遞么。
好好好,年輕人,那我們來講一講Websocket有什么用。
來給你吃點(diǎn)胡(蘇)蘿(丹)卜(紅)
三、Websocket的作用
在講Websocket之前,我就順帶著講下 long poll 和 ajax輪詢 的原理。
首先是 ajax輪詢 ,ajax輪詢 的原理非常簡單,讓瀏覽器隔個(gè)幾秒就發(fā)送一次請求,詢問服務(wù)器是否有新信息。
場景再現(xiàn):
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:沒有(Response)
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:沒有。。(Response)
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:你好煩啊,沒有啊。。(Response)
客戶端:啦啦啦,有沒有新消息(Request)
服務(wù)端:好啦好啦,有啦給你。(Response)
客戶端:啦啦啦,有沒有新消息(Request)
服務(wù)端:。。。。。沒。。。。沒。。。沒有(Response) ---- loop(一直循環(huán))
?
long poll
long poll 其實(shí)原理跟 ajax輪詢 差不多,都是采用輪詢的方式,不過采取的是阻塞模型(一直打電話,沒收到就不掛電話),也就是說,客戶端發(fā)起連接后,如果沒消息,就一直不返回Response給客戶端。直到有消息才返回,返回完之后,客戶端再次建立連接,周而復(fù)始。
場景再現(xiàn)
客戶端:啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request)
服務(wù)端:額。。 等待到有消息的時(shí)候。。來 給你(Response)
客戶端:啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request) -loop
從上面可以看出其實(shí)這兩種方式,都是在不斷地建立HTTP連接,然后等待服務(wù)端處理,可以體現(xiàn)HTTP協(xié)議的另外一個(gè)特點(diǎn),被動性。
何為被動性呢,其實(shí)就是,服務(wù)端不能主動聯(lián)系客戶端,只能有客戶端發(fā)起。
簡單地說就是,服務(wù)器是一個(gè)很懶的冰箱(這是個(gè)梗)(不會、不能主動發(fā)起連接),但是上司有命令,如果有客戶來,不管多么累都要好好接待。說完這個(gè),我們再來說一說上面的缺陷(原諒我廢話這么多吧OAQ)
從上面很容易看出來,不管怎么樣,上面這兩種都是非常消耗資源的。
ajax輪詢 需要服務(wù)器有很快的處理速度和資源。(速度)
long poll 需要有很高的并發(fā),也就是說同時(shí)接待客戶的能力。(場地大小)
所以ajax輪詢 和long poll 都有可能發(fā)生這種情況。
客戶端:啦啦啦啦,有新信息么?
服務(wù)端:月線正忙,請稍后再試(503 Server Unavailable)
客戶端:。。。。好吧,啦啦啦,有新信息么?
服務(wù)端:月線正忙,請稍后再試(503 Server Unavailable)
然后服務(wù)端在一旁忙的要死:冰箱,我要更多的冰箱!更多。。更多。。(我錯(cuò)了。。這又是梗。。)
?
言歸正傳,我們來說Websocket吧
通過上面這個(gè)例子,我們可以看出,這兩種方式都不是最好的方式,需要很多資源。
一種需要更快的速度,一種需要更多的'電話'。這兩種都會導(dǎo)致'電話'的需求越來越高。
哦對了,忘記說了HTTP還是一個(gè)無狀態(tài)協(xié)議。(感謝評論區(qū)的各位指出OAQ)
通俗的說就是,服務(wù)器因?yàn)槊刻煲哟嗫蛻袅?#xff0c;是個(gè)健忘鬼,你一掛電話,他就把你的東西全忘光了,把你的東西全丟掉了。你第二次還得再告訴服務(wù)器一遍。
所以在這種情況下出現(xiàn)了,Websocket出現(xiàn)了。
他解決了HTTP的這幾個(gè)難題。
首先,被動性,當(dāng)服務(wù)器完成協(xié)議升級后(HTTP->Websocket),服務(wù)端就可以主動推送信息給客戶端啦。
所以上面的情景可以做如下修改。
客戶端:啦啦啦,我要建立Websocket協(xié)議,需要的服務(wù):chat,Websocket協(xié)議版本:17(HTTP Request)
服務(wù)端:ok,確認(rèn),已升級為Websocket協(xié)議(HTTP Protocols Switched)
客戶端:麻煩你有信息的時(shí)候推送給我噢。。
服務(wù)端:ok,有的時(shí)候會告訴你的。
服務(wù)端:balabalabalabala
服務(wù)端:balabalabalabala
服務(wù)端:哈哈哈哈哈啊哈哈哈哈
服務(wù)端:笑死我了哈哈哈哈哈哈哈
就變成了這樣,只需要經(jīng)過一次HTTP請求,就可以做到源源不斷的信息傳送了。(在程序設(shè)計(jì)中,這種設(shè)計(jì)叫做回調(diào),即:你有信息了再來通知我,而不是我傻乎乎的每次跑來問你)
這樣的協(xié)議解決了上面同步有延遲,而且還非常消耗資源的這種情況。
?
?
那么為什么他會解決服務(wù)器上消耗資源的問題呢?
其實(shí)我們所用的程序是要經(jīng)過兩層代理的,即HTTP協(xié)議在Nginx等服務(wù)器的解析下,然后再傳送給相應(yīng)的Handler(PHP等)來處理。
簡單地說,我們有一個(gè)非常快速的接線員(Nginx),他負(fù)責(zé)把問題轉(zhuǎn)交給相應(yīng)的客服(Handler)處理器。
本身接線員基本上速度是足夠的,但是每次都卡在客服(Handler)了,老有客服處理速度太慢。,導(dǎo)致客服不夠。
Websocket就解決了這樣一個(gè)難題,建立后,可以直接跟接線員建立持久連接,有信息的時(shí)候客服想辦法通知接線員,然后接線員在統(tǒng)一轉(zhuǎn)交給客戶。
這樣就可以解決客服處理速度過慢的問題了。
同時(shí),在傳統(tǒng)的方式上,要不斷的建立,關(guān)閉HTTP協(xié)議,由于HTTP是非狀態(tài)性的,每次都要重新傳輸identity info(鑒別信息),來告訴服務(wù)端你是誰。
雖然接線員很快速,但是每次都要聽這么一堆,效率也會有所下降的,同時(shí)還得不斷把這些信息轉(zhuǎn)交給客服,不但浪費(fèi)客服的處理時(shí)間,而且還會在網(wǎng)路傳輸中消耗過多的流量/時(shí)間。
但是Websocket只需要一次HTTP握手,所以說整個(gè)通訊過程是建立在一次連接/狀態(tài)中,也就避免了HTTP的非狀態(tài)性,服務(wù)端會一直知道你的信息,直到你關(guān)閉請求,這樣就解決了接線員要反復(fù)解析HTTP協(xié)議,還要查看identity info的信息。
同時(shí)由客戶主動詢問,轉(zhuǎn)換為服務(wù)器(推送)有信息的時(shí)候就發(fā)送(當(dāng)然客戶端還是等主動發(fā)送信息過來的。。),沒有信息的時(shí)候就交給接線員(Nginx),不需要占用本身速度就慢的客服(Handler)了
參考:https://www.zhihu.com/question/20215561
?
?
補(bǔ)充3:stomp.js客戶端(Stomp Over Webscoket)
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的使用springboot来实现WebLog的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: altium导出钻孔文件_[Altium
- 下一篇: 发生在“注解”@的那些事儿