這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
描述
Lhttp是一個基於websocket服務端架構,提供一個類似http的協議去協助開發人員開發長連線應用程式。
使用Lhttp可以大量減少服務端開發的工作量,實現非常好的模組化和業務功能的解耦合。
可以定製任何你想要的功能。
項目地址
特點
使用簡單,功能強大
效能高,使用gnatsd訊息佇列 publish 10000 條訊息耗時0.04s(single-core CPU,1G memory).
支援叢集,橫向擴充,通過增加伺服器來擷取更高的服務能力
非常容器進行定製與擴充
可以非常好的與http服務協同工作,如利用http發送訊息,將訊息轉寄給上遊http伺服器等。所以即便你不會go語言也可以開發一些應用。
聊天室demo
前端sdk
協議棧:
+--------------------+| lhttp |+--------------------+| websocket |+--------------------+| TCP |+--------------------+
系統架構
+---------------------------------------+ | message center cluster (gnatsd) | +---------------------------------------+ ........|.................|...............|..................| +-------------+ +-------------+ +-------------+ | | |lhttp server | |lhttp server | |lhttp server | ... | lhttp 服務叢集| +-------------+ +-------------+ +-------------+ | .....|..........._____| |___.............| |_________...... | | | | | <----使用websocket連結 +--------+ +--------+ +--------+ +--------+ +--------+ | client | | client | | client | | client | | client | +--------+ +--------+ +--------+ +--------+ +--------+
快速入門
go get github.com/nats-io/natsgo get github.com/fanux/lhttp
先啟動gnatsd:
cd bin./gnatsd &./lhttpd
開啟另一個終端,執行用戶端程式,輸入命令碼:
cd bin./lhttpClient
使用docker快速體驗
$ docker build -t lhttp:latest .$ docker run -p 9090:9090 -p 8081:8081 lhttp:latest
開啟瀏覽器,訪問: http://localhost:9090
.
開啟兩個視窗就可以聊起來了。
websocket 連接埠是 8081, 可以使用自己的websocket用戶端去連 ws://localhost:8081
也可以從dockerhub上下載鏡像:
$ docker run -p 9090:9090 -p 8081:8081 fanux/lhttp:latest
協議介紹
LHTTP/1.0 Command\r\n --------起始行,協議名和版本,Command:非常重要,標識這條訊息的命令碼是什麼,服務端也是根據命令碼註冊對應的處理器的。Header1:value\r\n --------首部Header2:value\r\n\r\nbody --------訊息體
案例:
LHTTP/1.0 chat\r\n 命令碼叫`chat`content-type:json\r\n 訊息體使用json編碼publish:channel_jack\r\n 服務端請把這條訊息publish給jack (jack訂閱了channel_jack)\r\n{ to:jack, from:mike, message:hello jack, time:1990-1210 5:30:48}
使用教程,只需三步
定義你的處理器,需要彙總 BaseProcessor
type ChatProcessor struct { *lhttp.BaseProcessor}
實現三個介面,串連開啟時幹嘛,關閉時幹嘛,訊息到來時幹嘛。
type ChatProcessor struct {}func (p ChatProcessor)OnOpen(h *WsHandler) { //your logic}func (p ChatProcessor)OnClose(h *WsHandler) { //your logic}func (p ChatProcessor)OnMessage(h *WsHandler) { //your logic}
註冊你的處理器,這裡的chat
與訊息體中的chat對應
,也就是這個處理器僅會處理LHTTP/1.0 chat\r\n....
這類訊息.
lhttp.Regist("chat",&ChatProcessor{&lhttp.BaseProcessor{}})
如果命令碼是 "chat" ChatProcessor 會處理它。
這裡比如收到訊息就直接將訊息返回:
func (p *ChatProcessor)OnMessage(h *WsHandler) { h.Send(h.GetBody())}
啟動伺服器
http.Handler("/echo",lhttp.Handler(lhttp.StartServer))http.ListenAndServe(":8081")
一個完整的回射例子:
type ChatProcessor struct { *lhttp.BaseProcessor}func (p *ChatProcessor) OnMessage (h *lhttp.WsHandler) { log.Print("on message :", h.GetBody()) h.Send(h.GetBody())}func main(){ lhttp.Regist("chat", &ChatProcessor{&lhttp.BaseProcessor{}}) http.Handle("/echo",lhttp.Handler(lhttp.StartServer)) http.ListenAndServe(":8081",nil)}
訂閱/發布
下面來看用Lhttp開發及時通訊應用有多簡單
假設有兩個用戶端,這裡的用戶端比如瀏覽器應用。
client1:
LHTTP/1.0 command\r\nsubscribe:channelID\r\n\r\nbody optional
client1通過websocket向Lhttp發送如上字串,就訂閱了channelId
client2:
LHTTP/1.0 command\r\npublish:channelID\r\n\r\nbody require
client2通過websocket向Lhttp發送如上字串,就向channelID
發布了一條訊息。 因為client1訂閱了channelID,所以client1會收到這條訊息。
client1不想再收訊息,那麼發如下字串給服務端即可:
LHTTP/1.0 command\r\nunsubscribe:channelID\r\n\r\nbody optional
訂閱/發布 是lhttp內建功能,服務端一行代碼不用寫即可擷取這種服務,只需要使用特定首部subscribe
,publish
和unsubscribe
同時訂閱多個,如同時訂閱多個聊天室。
LHTTP/1.0 chat\r\nsubscribe:channelID1 channelID2 channelID3\r\n\r\n
使用http發布訊息
URL: /publish .
方法: POST .
http body: 整個lhttp訊息
for example I want send a message to who subscribe channel_test by HTTP.
如我想發送一條訊息給訂閱了channel_test的人。
resp,err := http.POST("https://www.yourserver.com/publish", "text/plain", "LHTTP/1.0 chat\r\npublish:channel_test\r\n\r\nhello channel_test guys!")
這裡封裝好了一個更好用的工具 Publish
tools.go
//func Publish(channelID []string, command string, header map[string]string, body string) (err error) {//}//send message to who subscribe mike.Publish("mike", "yourCommand", nil, "hello mike!")
上遊伺服器
upstream首部可以讓lhttp向上遊的http伺服器發送一條訊息。
LHTTP/1.0 command\r\nupstream:POST http://www.xxx.com\r\n\r\nbody
如果是POST方法,lhttp會把整個訊息體當作http的body發送給 http://www.xxx.com
如果是GET,lhttp會忽略訊息體
LHTTP/1.0 command\r\nupstream:GET http://www.xxx.com?user=user_a&age=26\r\n\r\nbody
upstream有什麼用:
如我們不想改動lhttp的代碼,但是想儲存聊天記錄。
通過upstream可以實現很好的解耦:
並且http server可以用其它語言實現.
+----+ +----+ |jack| |mike| +----+ +----+ |_____________ _______| | | +------------+ |lhttp server| +------------+ |(http request with chat record) V +------------+ | http server| upstream server(http://www.xxx.com/record) +------------+ (save chat record)
jack:
LHTTP/1.0 chat\r\nupstream:POST http://www.xxx.com/record\r\npublish:channel_mike\r\n\r\nhello mike,I am jack
mike:
LHTTP/1.0 chat\r\nsubscribe:channel_mike\r\n\r\n
這樣jack publish訊息時不僅mike可以收到,後端的upstream server也可以收到,我們可以在後端伺服器中處理訊息儲存的邏輯,如將訊息
儲存到redis的有序集合中。
分塊訊息
試想一下,一條訊息中既有圖片也有文字還有語音怎麼辦? lhttp的multipart首部解決這個問題
LHTTP/1.0 upload\r\nmultipart:0 56\r\n\r\ncontent-type:text/json\r\n\r\n{filename:file.txt,fileLen:5}content-type:text/plain\r\n\r\nhello
content-type:text/json\r\n\r\n{filename:file.txt,fileLen:5}content-type:text/plain\r\n\r\nhello^ ^|<---------------------first part------------------------->|<---------second part------------>|0 56
http中是使用boundry實現的,lhttp使用位移量標識分塊,這樣效率更高,不需要遍曆整個訊息體。
如何擷取分塊訊息
如用戶端訊息如下:
LHTTP/1.0 upload\r\nmultipart:0 14\r\n\r\nk1:v1\r\n\r\nbody1k2:v2\r\n\r\nbody2
服務端代碼,訊息存在鏈表中:
type UploadProcessor struct { *lhttp.BaseProcessor}func (*UploadProcessor) OnMessage(ws *lhttp.WsHandler) { for m := ws.GetMultipart(); m != nil; m = m.GetNext() { log.Print("multibody:", m.GetBody(), " headers:", m.GetHeaders()) }}//don't forget to tegist your command processorlhttp.Regist("upload", &UploadProcessor{&lhttp.BaseProcessor{}})
首部過濾模組開發