今天要分享從melody身上學到的許多概念當中,最喜歡的event trigger,筆者在實務開發上獲得很大幫助。
其實要講的部分,包含了許多概念相輔相成做結合,據我所知的名詞大概如下:
type handleMessageFunc func(*Session, []byte)
type handleErrorFunc func(*Session, error)
type handleCloseFunc func(*Session, int, string) error
type handleSessionFunc func(*Session)
type filterFunc func(*Session) bool
// Melody implements a websocket manager.
type Melody struct {
Config *Config
Upgrader *websocket.Upgrader
messageHandler handleMessageFunc
messageHandlerBinary handleMessageFunc
messageSentHandler handleMessageFunc
messageSentHandlerBinary handleMessageFunc
errorHandler handleErrorFunc
closeHandler handleCloseFunc
connectHandler handleSessionFunc
disconnectHandler handleSessionFunc
pongHandler handleSessionFunc
hub *hub
}
我們以其中的messageHandler來說明。
messageHandler是一個method,但這個method為什麼不擺在外面呢,而是放在變數裡面呢?
為什麼不寫成如下面這樣?
func (m *Melody) messageHandler(*Session, []byte) {
...
}
那是因為它並不打算一開始就決定這個method內容是處理了什麼,或者是不在乎內容是做了什麼,到時候交由別人制定。
再說function型態的zero value是nil,所以輕鬆沒負擔。
當然一開始就給它一個空空如也的function實體,也是可以,避免開發者未先判斷nil直接使用,可能發生panic。
到時候重新assign 真正想要的method,替換掉即可。
return &Melody{
Config: newConfig(),
Upgrader: upgrader,
messageHandler: func(*Session, []byte) {},
messageHandlerBinary: func(*Session, []byte) {},
messageSentHandler: func(*Session, []byte) {},
messageSentHandlerBinary: func(*Session, []byte) {},
errorHandler: func(*Session, error) {},
closeHandler: nil,
connectHandler: func(*Session) {},
disconnectHandler: func(*Session) {},
pongHandler: func(*Session) {},
hub: hub,
}
// HandleMessage fires fn when a text message comes in.
func (m *Melody) HandleMessage(fn func(*Session, []byte)) {
m.messageHandler = fn
}
melody再額外提供method,如上面的HandleMessage,以大寫開頭,提供傳入function型態的實體,然後將它assign到當初宣告的變數。
這種寫法在source code 隨處可見,尤其是經典的http server handler,決定什麼時機用什麼handler。
再把實體的method派上去,好用又彈性,而且method的input和output已經先固定。
如前篇的readPump和writePump,身處在某個出不去的迴圈當中(業務持續執行中),要怎麼把訊息傳到外面做溝通或處理?
如果用channel,會有個問題,那是送訊息和接訊息的雙方,要先講好channel的型態和格式,那幾乎是不可能的事情。
再者如果是跨package,外部要知道這個送出來的訊息,該怎麼辦?不可能事先知道外面銜接的method長什麼樣子,好讓我們做呼叫。
我們這個package若是提供給外部做import使用,不可能還import對方的package,然而不import,就不知道method什麼樣子。
在readPump()裡面,for迴圈內部會一直卡在s.conn.ReadMessage(),直到從這個ReadMessage()讀取到訊息,則繼續往下執行。
for {
t, message, err := s.conn.ReadMessage()
if err != nil {
s.melody.errorHandler(s, err)
break
}
if t == websocket.TextMessage {
s.melody.messageHandler(s, message)
}
if t == websocket.BinaryMessage {
s.melody.messageHandlerBinary(s, message)
}
}
讀取到client送來的資訊,這是一個『事件』,這個事情發生了,才需要通知,而且會主動通知,並且把內容送到該通知的對象。
而處理主動送通知的method,是melody的messageHandler()。
messageHandler()在之前,被透過HandleMessage()做assign了什麼實體不知道,但內容和實際怎麼處理就交給了那個實體。
記得melody介紹系列的第一篇,我們其實就是從掛載,melody提供給外部使用的method開始了分析之旅。
Day21 .[心得與討論篇] struct 設計解析 - 以melody package (1)
// HandleWSReadMsg 掛載websocket收到client message的處理
func HandleWSReadMsg() {
// 接收client 傳來的訊息
Serverset.WSServer.HandleMessage(func(s *melody.Session, msg []byte) {
Entry(s, string(msg))
})
}
我自己的程式有個HandleWSReadMsg(),裡面使用了melody提供的HandleMessage()。
HandleMessage(func(s *melody.Session, msg []byte) {
Entry(s, string(msg))
})
一旦client端有送訊息過來,會跑來這個method。
根據Session的結構我可以知道是哪條連線,哪位使用者,以此作後續的處理和作業。
類似的應用,大部分的server framework也是這樣,如gin、echo,掛載了api路徑。
http request送過來,輾轉跑到指定的method做執行。
maybe再稍微研究一下,各位就可以寫一個自己的server framework套件了(誤)。
感謝大家的耐心閱讀,筆者一些對於struct設計的想法,以及melody的理解分享,就在此告一段落。