Lhttp是一个基于websocket服务端框架,提供一个相似http的协议去帮助开发者开发长链接的应用。javascript
使用Lhttp能够大量减小服务端开发的工做量,实现很是好的模块化和业务功能的解耦合。前端
能够定制任何你想要的功能。java
项目地址git
使用简单,功能强大github
性能高,使用gnatsd消息队列 publish 10000 条消息耗时0.04s(single-core CPU,1G memory).web
支持集群,横向扩展,经过增长服务器来获取更高的服务能力redis
很是容器进行定制与扩展docker
能够很是好的与http服务协同工做,如利用http发送消息,将消息转发给上游http服务器等。因此即使你不会go语言也能够开发一些应用。json
前端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/nats go get github.com/fanux/lhttp
先启动gnatsd:
cd bin ./gnatsd & ./lhttpd
打开另外一个终端,执行客户端程序,输入命令码:
cd bin ./lhttpClient
$ 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\n body --------消息体
事例:
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\n subscribe:channelID\r\n \r\n body optional
client1经过websocket向Lhttp发送如上字符串,就订阅了channelId
client2:
LHTTP/1.0 command\r\n publish:channelID\r\n \r\n body require
client2经过websocket向Lhttp发送如上字符串,就向channelID
发布了一条消息。 由于client1订阅了channelID,因此client1会收到这条消息。
client1不想再收消息,那么发以下字符串给服务端便可:
LHTTP/1.0 command\r\n unsubscribe:channelID\r\n \r\n body optional
订阅/发布 是lhttp内置功能,服务端一行代码不用写便可获取这种服务,只须要使用特定首部subscribe
,publish
和unsubscribe
同时订阅多个,如同时订阅多个聊天室。
LHTTP/1.0 chat\r\n subscribe:channelID1 channelID2 channelID3\r\n \r\n
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\n upstream:POST http://www.xxx.com\r\n \r\n body
若是是POST方法,lhttp会把整个消息体看成http的body发送给 http://www.xxx.com
若是是GET,lhttp会忽略消息体
LHTTP/1.0 command\r\n upstream:GET http://www.xxx.com?user=user_a&age=26\r\n \r\n body
如咱们不想改动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\n upstream:POST http://www.xxx.com/record\r\n publish:channel_mike\r\n \r\n hello mike,I am jack
mike:
LHTTP/1.0 chat\r\n subscribe:channel_mike\r\n \r\n
这样jack publish消息时不只mike能够收到,后端的upstream server也能够收到,咱们能够在后端服务器中处理消息存储的逻辑,如将消息
存储到redis的有序集合中。
试想一下,一条消息中既有图片也有文字还有语音怎么办? lhttp的multipart首部解决这个问题
LHTTP/1.0 upload\r\n multipart:0 56\r\n \r\n content-type:text/json\r\n \r\n {filename:file.txt,fileLen:5} content-type:text/plain\r\n \r\n hello
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 processor lhttp.Regist("upload", &UploadProcessor{&lhttp.BaseProcessor{}})